// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchlib.rankingexpression.rule; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import com.yahoo.searchlib.rankingexpression.RankingExpression; import com.yahoo.searchlib.rankingexpression.Reference; import com.yahoo.searchlib.rankingexpression.evaluation.Context; import com.yahoo.searchlib.rankingexpression.evaluation.StringValue; import com.yahoo.searchlib.rankingexpression.evaluation.Value; import com.yahoo.tensor.TensorType; import com.yahoo.tensor.evaluation.TypeContext; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Optional; /** * A node referring either to a value in the context or to a named ranking expression function. * * @author bratseth */ public final class ReferenceNode extends CompositeNode { private final Reference reference; /* Creates a node with a simple identifier reference */ public ReferenceNode(String name) { this.reference = Reference.fromIdentifier(name); } public ReferenceNode(String name, List arguments, String output) { this.reference = new Reference(name, arguments != null ? new Arguments(arguments) : new Arguments(), output); } public ReferenceNode(Reference reference) { this.reference = reference; } public String getName() { return reference.name(); } public int hashCode() { return reference.hashCode(); } /** Returns the arguments, never null */ public Arguments getArguments() { return reference.arguments(); } /** Returns a copy of this where the arguments are replaced by the given arguments */ public ReferenceNode setArguments(List arguments) { return new ReferenceNode(reference.withArguments(new Arguments(arguments))); } /** Returns the specific output this references, or null if none specified */ public String getOutput() { return reference.output(); } /** Returns a copy of this node with a modified output */ public ReferenceNode setOutput(String output) { return new ReferenceNode(reference.withOutput(output)); } /** Returns an empty list as this has no children */ @Override public List children() { return reference.arguments().expressions(); } @Override public StringBuilder toString(StringBuilder string, SerializationContext context, Deque path, CompositeNode parent) { // A reference to an identifier (function argument or bound variable)? if (reference.isIdentifier() && context.getBinding(getName()) != null) { // a bound identifier: replace by the value it is bound to return string.append(context.getBinding(getName())); } String name = getName(); // A reference to a function? ExpressionFunction function = context.getFunction(getName()); if (function != null && function.arguments().size() == getArguments().size() && getOutput() == null) { // a function reference: replace by the referenced function wrapped in rankingExpression // if (path == null) // path = new ArrayDeque<>(); // String myPath = getName() + getArguments().expressions(); // if (path.contains(myPath)) // throw new IllegalStateException("Cycle in ranking expression function: " + path); // path.addLast(myPath); // ExpressionFunction.Instance instance = function.expand(context, getArguments().expressions(), path); // path.removeLast(); // context.addFunctionSerialization(RankingExpression.propertyName(instance.getName()), instance.getExpressionString()); // return string.append("rankingExpression(").append(instance.getName()).append(')'); // return new Instance(toSymbol(argumentBindings), body.getRoot().toString(new StringBuilder(), context.withBindings(argumentBindings), path, null).toString()); // hack for testing: // So, this worked. Meaning that when expanding we could probably cut down on the context tree? // String expression = function.getBody().toString(); // context.addFunctionSerialization(RankingExpression.propertyName(getName()), expression); // <- actually set by deriveFunctionProperties - this will only overwrite String prefix = string.toString(); // incredibly ugly hack - for testing this // so problem here with input values if (prefix.startsWith("attribute")) { if (name.equals("segment_ids") || name.equals("input_mask") || name.equals("input_ids")) { return string.append(getName()); // TODO: divine this! } } // so, in one case // rankprofile[2].fef.property[35].name "rankingExpression(imported_ml_function_bertsquad8_input_ids).rankingScript" // rankprofile[2].fef.property[35].value "input_ids" // vs // rankprofile[2].fef.property[2].name "rankingExpression(input_ids).rankingScript" // rankprofile[2].fef.property[2].value "attribute(input_ids)" // uppermost is wrong, then we need the below return string.append("rankingExpression(").append(getName()).append(')'); } // Not resolved in this context: output as-is return reference.toString(string, context, path, parent); } /** Returns the reference of this node */ public Reference reference() { return reference; } @Override public TensorType type(TypeContext context) { TensorType type = null; try { type = context.getType(reference); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(reference + " is invalid", e); } if (type == null) throw new IllegalArgumentException("Unknown feature '" + toString() + "'"); return type; } @Override public Value evaluate(Context context) { // TODO: Context should accept a Reference instead. if (reference.isIdentifier()) return context.get(reference.name()); else return context.get(getName(), getArguments(), getOutput()); } @Override public CompositeNode setChildren(List newChildren) { return setArguments(newChildren); } }