diff options
author | Jon Bratseth <bratseth@oath.com> | 2018-02-14 09:42:46 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@oath.com> | 2018-02-14 09:42:46 +0100 |
commit | fa0bc59e2313aa6b6249ad88f7c1892a3a29553d (patch) | |
tree | f426c5657280a25c4599deecc5cca3be9378cab7 | |
parent | c17b1582face7c7f31fea7e151a5855908fe04f5 (diff) |
Handle argument bindings
16 files changed, 277 insertions, 86 deletions
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java b/config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java index a55ce0982dd..649d7bddcc2 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java @@ -5,6 +5,8 @@ */ package com.yahoo.searchdefinition; +import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; + import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -69,7 +71,7 @@ public class FeatureNames { feature.substring(endParenthesis)); } - /** Canomicalizes a single argument */ + /** Canonicalizes a single argument */ private static String canonicalizeArgument(String argument) { if (argument.startsWith("'")) { if ( ! argument.endsWith("'")) @@ -89,20 +91,20 @@ public class FeatureNames { return "\"" + argument + "\""; } - public static String asConstantFeature(String constantName) { - return canonicalize("constant(\"" + constantName + "\")"); + public static ReferenceNode.Reference asConstantFeature(String constantName) { + return ReferenceNode.Reference.simple("constant", constantName); } - public static String asAttributeFeature(String attributeName) { - return canonicalize("attribute(\"" + attributeName + "\")"); + public static ReferenceNode.Reference asAttributeFeature(String attributeName) { + return ReferenceNode.Reference.simple("attribute", attributeName); } - public static String asQueryFeature(String propertyName) { - return canonicalize("query(\"" + propertyName + "\")"); + public static ReferenceNode.Reference asQueryFeature(String propertyName) { + return ReferenceNode.Reference.simple("query", propertyName); } - /** Returns true if this is a cpomstant, attribute, or query feature */ - public static boolean isFeature(String feature) { + /** Returns true if this is a constant, attribute, or query feature */ + public static boolean isSimpleFeature(String feature) { return FeatureNames.isConstantFeature(feature) || FeatureNames.isAttributeFeature(feature) || FeatureNames.isQueryFeature(feature); diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/MapEvaluationTypeContext.java b/config-model/src/main/java/com/yahoo/searchdefinition/MapEvaluationTypeContext.java index 38a0a23f48a..bf6a95f10c1 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/MapEvaluationTypeContext.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/MapEvaluationTypeContext.java @@ -3,12 +3,10 @@ package com.yahoo.searchdefinition; import com.yahoo.searchlib.rankingexpression.ExpressionFunction; import com.yahoo.searchlib.rankingexpression.rule.Arguments; -import com.yahoo.searchlib.rankingexpression.rule.EvaluationTypeContext; import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode; import com.yahoo.searchlib.rankingexpression.rule.FunctionReferenceContext; import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; import com.yahoo.tensor.TensorType; -import com.yahoo.tensor.evaluation.MapEvaluationContext; import com.yahoo.tensor.evaluation.TypeContext; import java.util.Collection; @@ -26,9 +24,9 @@ import java.util.Optional; * * @author bratseth */ -public class MapEvaluationTypeContext extends FunctionReferenceContext implements EvaluationTypeContext { +public class MapEvaluationTypeContext extends FunctionReferenceContext implements TypeContext { - private final Map<String, TensorType> featureTypes = new HashMap<>(); + private final Map<ReferenceNode.Reference, TensorType> featureTypes = new HashMap<>(); public MapEvaluationTypeContext(Collection<ExpressionFunction> functions) { super(functions); @@ -36,33 +34,34 @@ public class MapEvaluationTypeContext extends FunctionReferenceContext implement public MapEvaluationTypeContext(Map<String, ExpressionFunction> functions, Map<String, String> bindings, - Map<String, TensorType> featureTypes) { + Map<ReferenceNode.Reference, TensorType> featureTypes) { super(functions, bindings); this.featureTypes.putAll(featureTypes); } - public void setType(String name, TensorType type) { - featureTypes.put(FeatureNames.canonicalize(name), type); + public void setType(Name name, TensorType type) { + // TODO: Use a type parameter if we do this both here and in getType ... + if ( ! (name instanceof ReferenceNode.Reference)) + throw new IllegalArgumentException("Not expecting unstructured names here"); + featureTypes.put((ReferenceNode.Reference)name, type); } - // TODO: Remove? @Override - public TensorType getType(String name) { - if (FeatureNames.isFeature(name)) - return featureTypes.get(FeatureNames.canonicalize(name)); - else - return TensorType.empty; // we do not have type information for these. Correct would be either empty or null - } - - @Override - public TensorType getType(String name, Arguments arguments, String output) { - Optional<String> simpleFeature = simpleFeature(name, arguments); // (all simple feature outputs return the same type) - if (simpleFeature.isPresent()) - return featureTypes.get(simpleFeature.get()); - - Optional<ExpressionFunction> function = functionInvocation(name, output); + public TensorType getType(Name name) { + if ( ! (name instanceof ReferenceNode.Reference)) + throw new IllegalArgumentException("Not expecting unstructured names here"); + ReferenceNode.Reference reference = (ReferenceNode.Reference)name; + + if (isSimpleFeature(reference)) { + // The argument may be a local identifier bound to the actual value + String argument = simpleArgument(reference.arguments()).get(); + reference = ReferenceNode.Reference.simple(reference.name(), bindings.getOrDefault(argument, argument)); + return featureTypes.get(reference); + } + + Optional<ExpressionFunction> function = functionInvocation(reference); if (function.isPresent()) - return function.get().getBody().type(this.withBindings(bind(function.get().arguments()))); + return function.get().getBody().type(this.withBindings(bind(function.get().arguments(), reference.arguments()))); // We do not know what this is - since we do not have complete knowledge abut the match features // in Java we must assume this is a match feature and return the double type - which is the type of all @@ -71,18 +70,33 @@ public class MapEvaluationTypeContext extends FunctionReferenceContext implement } /** - * If the arguments makes a simple feature ("attribute(name)", "constant(name)" or "query(name)", + * Return whether the reference (discarding the output) is a simple feature + * ("attribute(name)", "constant(name)" or "query(name)"). + * We disregard the output because all outputs under a simple feature have the same type. + */ + private boolean isSimpleFeature(ReferenceNode.Reference reference) { + Optional<String> argument = simpleArgument(reference.arguments()); + if ( ! argument.isPresent()) return false; + return reference.name().equals("attribute") || + reference.name().equals("constant") || + reference.name().equals("query"); + } + + /** + * If the reference (discarding the output) is a simple feature + * ("attribute(name)", "constant(name)" or "query(name)"), * it is returned. Otherwise empty is returned. + * We disregard the output because all outputs under a simple feature have the same type. */ - private Optional<String> simpleFeature(String name, Arguments arguments) { - Optional<String> argument = simpleArgument(arguments); + private Optional<String> simpleFeature(ReferenceNode.Reference reference) { + Optional<String> argument = simpleArgument(reference.arguments()); if ( ! argument.isPresent()) return Optional.empty(); // The argument may be a "local value" bound to another value, or else it is the "global" argument of the feature String actualArgument = bindings.getOrDefault(argument.get(), argument.get()); - String feature = asFeatureString(name, actualArgument); - if (FeatureNames.isFeature(feature)) + String feature = asFeatureString(reference.name(), actualArgument); + if (FeatureNames.isSimpleFeature(feature)) return Optional.of(feature); else return Optional.empty(); @@ -108,20 +122,24 @@ public class MapEvaluationTypeContext extends FunctionReferenceContext implement return Optional.of(refArgument.getName()); } - private Optional<ExpressionFunction> functionInvocation(String name, String output) { - if (output != null) return Optional.empty(); - return Optional.ofNullable(functions().get(name)); + private Optional<ExpressionFunction> functionInvocation(ReferenceNode.Reference reference) { + if (reference.output() != null) return Optional.empty(); + return Optional.ofNullable(functions().get(reference.name())); } /** Binds the given list of formal arguments to their actual values */ - private Map<String, String> bind(List<String> arguments) { - Map<String, String> bindings = new HashMap<>(arguments.size()); - for (String formalArgument : arguments) - bindings.put(formalArgument, null); // TODO + private Map<String, String> bind(List<String> formalArguments, + Arguments invocationArguments) { + // TODO: What is our position on argument overloading/argument count differences? + Map<String, String> bindings = new HashMap<>(formalArguments.size()); + for (int i = 0; i < formalArguments.size(); i++) + bindings.put(formalArguments.get(i), invocationArguments.expressions().get(i).toString()); // TODO: toString does not work generally return bindings; } - public Map<String, TensorType> featureTypes() { return Collections.unmodifiableMap(featureTypes); } + public Map<ReferenceNode.Reference, TensorType> featureTypes() { + return Collections.unmodifiableMap(featureTypes); + } @Override public MapEvaluationTypeContext withBindings(Map<String, String> bindings) { diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java index 51c7618acff..0b0c0c9e0ca 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java @@ -32,6 +32,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -357,14 +358,14 @@ public class RankProfile implements Serializable, Cloneable { /** Returns a read-only view of the summary features to use in this profile. This is never null */ public Set<ReferenceNode> getSummaryFeatures() { - if (summaryFeatures!=null) return Collections.unmodifiableSet(summaryFeatures); - if (getInherited()!=null) return getInherited().getSummaryFeatures(); + if (summaryFeatures != null) return Collections.unmodifiableSet(summaryFeatures); + if (getInherited() != null) return getInherited().getSummaryFeatures(); return Collections.emptySet(); } public void addSummaryFeature(ReferenceNode feature) { - if (summaryFeatures==null) - summaryFeatures=new LinkedHashSet<>(); + if (summaryFeatures == null) + summaryFeatures = new LinkedHashSet<>(); summaryFeatures.add(feature); } @@ -766,16 +767,18 @@ public class RankProfile implements Serializable, Cloneable { for (QueryProfileType queryProfileType : queryProfiles.getTypeRegistry().allComponents()) { for (FieldDescription field : queryProfileType.declaredFields().values()) { TensorType type = field.getType().asTensorType(); - if ( ! FeatureNames.isQueryFeature(field.getName())) continue; - String feature = FeatureNames.canonicalize(field.getName()); - TensorType existingType = context.getType(feature); + Optional<ReferenceNode.Reference> feature = ReferenceNode.Reference.simple(field.getName()); + if ( ! feature.isPresent() || ! feature.get().name().equals("query")) continue; + + TensorType existingType = context.getType(feature.get()); if (existingType != null) type = existingType.dimensionwiseGeneralizationWith(type).orElseThrow( () -> new IllegalArgumentException(queryProfileType + " contains query feature " + feature + " with type " + field.getType().asTensorType() + ", but this is already defined " + - "in another query profile with type " + context.getType(feature))); - context.setType(feature, type); + "in another query profile with type " + + context.getType(feature.get()))); + context.setType(feature.get(), type); } } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java index 469f29098ad..e7cd21ac834 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java @@ -18,6 +18,7 @@ import com.yahoo.searchdefinition.parser.TokenMgrError; import com.yahoo.searchdefinition.processing.Processing; import com.yahoo.vespa.documentmodel.DocumentModel; import com.yahoo.vespa.model.container.search.QueryProfiles; +import com.yahoo.yolean.Exceptions; import java.io.File; import java.io.IOException; @@ -153,7 +154,7 @@ public class SearchBuilder { } catch (TokenMgrError e) { throw new ParseException("Unknown symbol: " + e.getMessage()); } catch (ParseException pe) { - throw new ParseException(stream.formatException(pe.getMessage())); + throw new ParseException(stream.formatException(Exceptions.toMessageString(pe))); } return importRawSearch(search); } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java index 1f60ad870ec..182aaba5be8 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java @@ -42,17 +42,20 @@ public class FeatureNamesTestCase { @Test public void testConstantFeature() { - assertEquals("constant(\"foo/bar\")", FeatureNames.asConstantFeature("foo/bar")); + assertEquals("constant(\"foo/bar\")", + FeatureNames.asConstantFeature("foo/bar").toString()); } @Test public void testAttributeFeature() { - assertEquals("attribute(foo)", FeatureNames.asAttributeFeature("foo")); + assertEquals("attribute(foo)", + FeatureNames.asAttributeFeature("foo").toString()); } @Test public void testQueryFeature() { - assertEquals("query(\"foo.bar\")", FeatureNames.asQueryFeature("foo.bar")); + assertEquals("query(\"foo.bar\")", + FeatureNames.asQueryFeature("foo.bar").toString()); } } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeValidatorTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeValidatorTestCase.java index db3b12db1bf..b8117178c74 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeValidatorTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionTypeValidatorTestCase.java @@ -1,22 +1,33 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.searchdefinition.processing; +import com.yahoo.searchdefinition.RankProfile; import com.yahoo.searchdefinition.RankProfileRegistry; +import com.yahoo.searchdefinition.Search; import com.yahoo.searchdefinition.SearchBuilder; +import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode; +import com.yahoo.tensor.TensorType; import com.yahoo.yolean.Exceptions; import org.junit.Test; + +import java.util.Map; +import java.util.stream.Collectors; + import static com.yahoo.config.model.test.TestUtil.joinLines; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +/** + * @author bratseth + */ public class RankingExpressionTypeValidatorTestCase { @Test public void tensorFirstPhaseMustProduceDouble() throws Exception { try { RankProfileRegistry rankProfileRegistry = new RankProfileRegistry(); - SearchBuilder searchBuilder = new SearchBuilder(rankProfileRegistry); - searchBuilder.importString(joinLines( + SearchBuilder builder = new SearchBuilder(rankProfileRegistry); + builder.importString(joinLines( "search test {", " document test { ", " field a type tensor(x[],y[]) {", @@ -30,7 +41,7 @@ public class RankingExpressionTypeValidatorTestCase { " }", "}" )); - searchBuilder.build(); + builder.build(); fail("Expected exception"); } catch (IllegalArgumentException expected) { @@ -43,8 +54,8 @@ public class RankingExpressionTypeValidatorTestCase { public void tensorSecondPhaseMustProduceDouble() throws Exception { try { RankProfileRegistry rankProfileRegistry = new RankProfileRegistry(); - SearchBuilder searchBuilder = new SearchBuilder(rankProfileRegistry); - searchBuilder.importString(joinLines( + SearchBuilder builder = new SearchBuilder(rankProfileRegistry); + builder.importString(joinLines( "search test {", " document test { ", " field a type tensor(x[],y[]) {", @@ -61,7 +72,7 @@ public class RankingExpressionTypeValidatorTestCase { " }", "}" )); - searchBuilder.build(); + builder.build(); fail("Expected exception"); } catch (IllegalArgumentException expected) { @@ -101,4 +112,42 @@ public class RankingExpressionTypeValidatorTestCase { } } + @Test + public void testMacroInvocationTypes() throws Exception { + RankProfileRegistry rankProfileRegistry = new RankProfileRegistry(); + SearchBuilder builder = new SearchBuilder(rankProfileRegistry); + builder.importString(joinLines( + "search test {", + " document test { ", + " field a type tensor(x[],y[]) {", + " indexing: attribute", + " }", + " field b type tensor(z[10]) {", + " indexing: attribute", + " }", + " }", + " rank-profile my_rank_profile {", + " macro macro1(attribute_to_use) {", + " expression: attribute(attribute_to_use)", + " }", + " summary-features {", + " macro1(a)", + " macro1(b)", + " }", + " }", + "}" + )); + builder.build(); + RankProfile profile = + builder.getRankProfileRegistry().getRankProfile(builder.getSearch(), "my_rank_profile"); + assertEquals(TensorType.fromSpec("tensor(x[],y[])"), + summaryFeatures(profile).get("macro1(a)").type(profile.typeContext(builder.getQueryProfileRegistry()))); + assertEquals(TensorType.fromSpec("tensor(z[10])"), + summaryFeatures(profile).get("macro1(b)").type(profile.typeContext(builder.getQueryProfileRegistry()))); + } + + private Map<String, ReferenceNode> summaryFeatures(RankProfile profile) { + return profile.getSummaryFeatures().stream().collect(Collectors.toMap(f -> f.toString(), f -> f)); + } + } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.java index 49466f1974d..f0532d9d433 100755 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.java @@ -91,8 +91,8 @@ public class FeatureList implements Iterable<ReferenceNode> { /** * Returns the feature at the given index. * - * @param i The index of the feature to return. - * @return The featuer at the given index. + * @param i the index of the feature to return. + * @return the feature at the given index. */ public ReferenceNode get(int i) { return features.get(i); @@ -137,4 +137,5 @@ public class FeatureList implements Iterable<ReferenceNode> { public Iterator<ReferenceNode> iterator() { return features.iterator(); } + } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java index 5f8daa69ecf..486affe9371 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java @@ -82,8 +82,8 @@ public class ArrayContext extends AbstractArrayContext implements Cloneable { } @Override - public TensorType getType(String name) { - Integer index = nameToIndex().get(name); + public TensorType getType(Name name) { + Integer index = nameToIndex().get(name.toString()); if (index == null) return null; return values[index].type(); } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java index 0625e8506cc..01b8bffe995 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java @@ -68,7 +68,7 @@ public class DoubleOnlyArrayContext extends AbstractArrayContext { } @Override - public TensorType getType(String name) { return TensorType.empty; } + public TensorType getType(Name name) { return TensorType.empty; } /** Perform a slow lookup by name */ @Override diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java index a81d0c89f8f..c7679ea9e55 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java @@ -42,7 +42,7 @@ public class MapContext extends Context { /** Returns the type of the given value key, or null if it is not bound. */ @Override - public TensorType getType(String key) { + public TensorType getType(Name key) { Value value = bindings.get(key); if (value == null) return null; return value.type(); diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapTypeContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapTypeContext.java index d461ae52cbe..2ddc8213d94 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapTypeContext.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapTypeContext.java @@ -15,18 +15,18 @@ import java.util.Map; */ public class MapTypeContext implements TypeContext { - private final Map<String, TensorType> featureTypes = new HashMap<>(); + private final Map<Name, TensorType> featureTypes = new HashMap<>(); public void setType(String name, TensorType type) { - featureTypes.put(name, type); + featureTypes.put(new Name(name), type); } @Override - public TensorType getType(String name) { + public TensorType getType(Name name) { return featureTypes.get(name); } /** Returns an unmodifiable map of the bindings in this */ - public Map<String, TensorType> bindings() { return Collections.unmodifiableMap(featureTypes); } + public Map<Name, TensorType> bindings() { return Collections.unmodifiableMap(featureTypes); } } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.java index fb9a7cb9ad7..d7163fe9166 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.java @@ -13,7 +13,8 @@ import java.util.List; /** * A set of argument expressions to a function or feature. - * This is immutable. + * This is a value object. + *. * * @author bratseth */ @@ -22,7 +23,11 @@ public final class Arguments implements Serializable { private final ImmutableList<ExpressionNode> expressions; public Arguments() { - this(null); + this(ImmutableList.of()); + } + + public Arguments(ExpressionNode singleArgument) { + this(ImmutableList.of(singleArgument)); } public Arguments(List<? extends ExpressionNode> expressions) { @@ -62,8 +67,9 @@ public final class Arguments implements Serializable { } @Override - public boolean equals(Object rhs) { - return rhs instanceof Arguments && expressions.equals(((Arguments)rhs).expressions); + public boolean equals(Object other) { + if (other == this) return true; + return other instanceof Arguments && expressions.equals(((Arguments)other).expressions); } @Override diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java index ebfef21a815..e121fa12b5f 100755 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java @@ -9,8 +9,13 @@ import com.yahoo.tensor.TensorType; import com.yahoo.tensor.evaluation.TypeContext; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Deque; import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * A node referring either to a value in the context or to a named ranking expression (function aka macro). @@ -31,6 +36,7 @@ public final class ReferenceNode extends CompositeNode { } public ReferenceNode(String name, List<? extends ExpressionNode> arguments, String output) { + Objects.requireNonNull(name, "name cannot be null"); this.name = name; this.arguments = arguments != null ? new Arguments(arguments) : new Arguments(); this.output = output; @@ -119,11 +125,10 @@ public final class ReferenceNode extends CompositeNode { @Override public TensorType type(TypeContext context) { - String feature = toString(new SerializationContext(), null, false); - TensorType type = context.getType(feature); - // TensorType type = context.getType(name, arguments, output); TODO + TensorType type = context.getType(new Reference(name, arguments, output, + toString(new SerializationContext(), null, true))); if (type == null) - throw new IllegalArgumentException("Unknown feature '" + feature + "'"); + throw new IllegalArgumentException("Unknown feature '" + toString() + "'"); return type; } @@ -139,4 +144,82 @@ public final class ReferenceNode extends CompositeNode { return new ReferenceNode(name, newChildren, output); } + /** Wraps the content of this in a form which can be passed to a type context */ + // TODO: Extract to top level? + public static class Reference extends TypeContext.Name { + + private static final Pattern identifierRegexp = Pattern.compile("[A-Za-z0-9_][A-Za-z0-9_-]*"); + + private final String name; + private final Arguments arguments; + + /** The output, or null if none */ + private final String output; + + public Reference(String name, Arguments arguments, String output, String stringForm) { + super(stringForm); + Objects.requireNonNull(name, "name cannot be null"); + Objects.requireNonNull(arguments, "arguments cannot be null"); + Objects.requireNonNull(stringForm, "stringForm cannot be null"); + this.name = name; + this.arguments = arguments; + this.output = output; + } + + public String name() { return name; } + public Arguments arguments() { return arguments; } + public String output() { return output; } + + /** Creates a reference to a simple feature consisting of a name and a single argument */ + public static Reference simple(String name, String argumentValue) { + return new Reference(name, + new Arguments(new ReferenceNode(argumentValue)), + null, + name + "(" + quoteIfNecessary(argumentValue) + ")"); + } + + /** + * Returns the given simple feature as a reference, or empty if it is not a valid simple + * feature string on the form name(argument). + */ + public static Optional<Reference> simple(String feature) { + int startParenthesis = feature.indexOf('('); + if (startParenthesis < 0) + return Optional.empty(); + int endParenthesis = feature.lastIndexOf(')'); + String featureName = feature.substring(0, startParenthesis); + if (startParenthesis < 1 || endParenthesis < startParenthesis) return Optional.empty(); + String argument = feature.substring(startParenthesis + 1, endParenthesis); + if (argument.startsWith("'") || argument.startsWith("\"")) + argument = argument.substring(1); + if (argument.endsWith("'") || argument.endsWith("\"")) + argument = argument.substring(0, argument.length() - 1); + return Optional.of(simple(featureName, argument)); + } + + private static String quoteIfNecessary(String s) { + if (identifierRegexp.matcher(s).matches()) + return s; + else + return "\"" + s + "\""; + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if ( ! (o instanceof Reference)) return false; + Reference other = (Reference)o; + if ( ! Objects.equals(other.name, this.name)) return false; + if ( ! Objects.equals(other.arguments, this.arguments)) return false; + if ( ! Objects.equals(other.output, this.output)) return false; + return true; + } + + @Override + public int hashCode() { + return Objects.hash(name, arguments, output); + } + + } + } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/evaluation/MapEvaluationContext.java b/vespajlib/src/main/java/com/yahoo/tensor/evaluation/MapEvaluationContext.java index 9fe6b7d053f..078f2e39815 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/evaluation/MapEvaluationContext.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/evaluation/MapEvaluationContext.java @@ -20,8 +20,8 @@ public class MapEvaluationContext implements EvaluationContext { public void put(String name, Tensor tensor) { bindings.put(name, tensor); } @Override - public TensorType getType(String name) { - Tensor tensor = bindings.get(name); + public TensorType getType(Name name) { + Tensor tensor = bindings.get(name.toString()); if (tensor == null) return null; return tensor.type(); } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/evaluation/TypeContext.java b/vespajlib/src/main/java/com/yahoo/tensor/evaluation/TypeContext.java index 760a225efdf..ecb3a801324 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/evaluation/TypeContext.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/evaluation/TypeContext.java @@ -16,6 +16,31 @@ public interface TypeContext { * @return returns the type of the tensor which will be returned by calling getTensor(name) * or null if getTensor will return null. */ - TensorType getType(String name); + TensorType getType(Name name); + + /** A name which is just a string. Names are value objects. */ + class Name { + + private final String name; + + public Name(String name) { + this.name = name; + } + + @Override + public String toString() { return name; } + + @Override + public int hashCode() { return name.hashCode(); } + + @Override + public boolean equals(Object other) { + if (other == this) return true; + if ( ! (other instanceof Name)) return false; + return ((Name)other).name.equals(this.name); + } + + } + } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/evaluation/VariableTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/evaluation/VariableTensor.java index 34beb465d4c..5f809a3d2b1 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/evaluation/VariableTensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/evaluation/VariableTensor.java @@ -45,7 +45,7 @@ public class VariableTensor extends PrimitiveTensorFunction { @Override public TensorType type(TypeContext context) { - TensorType givenType = context.getType(name); + TensorType givenType = context.getType(new TypeContext.Name(name)); if (givenType == null) return null; verifyType(givenType); return givenType; |