diff options
author | Jon Bratseth <bratseth@gmail.com> | 2022-02-12 18:56:04 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@gmail.com> | 2022-02-12 18:56:04 +0100 |
commit | d4bf9c688f38b3bbb4a6aa881f54dba96a12071a (patch) | |
tree | 499b193e224a19eb67bc8f23526523ea0e4acd00 /integration | |
parent | c4f7ab5658060eb9700276c646c237d433a17708 (diff) |
Resolve inherited profiles
Diffstat (limited to 'integration')
6 files changed, 177 insertions, 112 deletions
diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/SdUtil.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/SdUtil.java index cc214f7320c..546f2030002 100644 --- a/integration/intellij/src/main/java/ai/vespa/intellij/schema/SdUtil.java +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/SdUtil.java @@ -225,83 +225,4 @@ public class SdUtil { return new ArrayList<>(PsiTreeUtil.collectElementsOfType(element, SdSummaryDefinition.class)); } - /** A collection of tokens with a current index. */ - public static class Tokens { - - private final ASTNode[] nodes; - private int i = 0; - - private Tokens(PsiElement element) { - nodes = element.getNode().getChildren(null); - } - - /** - * Advances to the next token, if it is of the given type. - * - * @return true if the current token was of the given type and we advanced it, false - * if it was not and nothing was changed - */ - public boolean skip(IElementType ... tokenTypes) { - boolean is = is(tokenTypes); - if (is) - i++; - return is; - } - - /** - * Advances beyond the next token, if it is whitespace. - * - * @return true if the current token was of the given type and we advanced it, false - * if it was not and nothing was changed - */ - public boolean skipWhitespace() { - boolean is = isWhitespace(); - if (is) - i++; - return is; - } - - /** Returns whether the current token is of the given type */ - public boolean is(IElementType ... tokenTypes) { - for (IElementType type : tokenTypes) - if (current().getElementType() == type) return true; - return false; - } - - /** Returns whether the current token is whitespace */ - public boolean isWhitespace() { - return current().getPsi() instanceof PsiWhiteSpace; - } - - /** Returns the current token if it is of the required type and throws otherwise. */ - public ASTNode require(IElementType ... tokenTypes) { - if ( ! is(tokenTypes) ) - throw new IllegalArgumentException("Expected " + toString(tokenTypes) + " but got " + current()); - ASTNode current = current(); - i++; - return current; - } - - public void requireWhitespace() { - if ( ! isWhitespace() ) - throw new IllegalArgumentException("Expected whitespace, but got " + current()); - i++; - } - - /** Returns the current token (AST node), or null if we have reached the end. */ - public ASTNode current() { - if (i >= nodes.length) return null; - return nodes[i]; - } - - private String toString(IElementType[] tokens) { - return Arrays.stream(tokens).map(token -> token.getDebugName()).collect(Collectors.joining(", ")); - } - - public static Tokens of(PsiElement element) { - return new Tokens(element); - } - - } - } diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/RankProfile.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/RankProfile.java index 3b559cecd27..c66c9d4ca99 100644 --- a/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/RankProfile.java +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/RankProfile.java @@ -4,6 +4,7 @@ package ai.vespa.intellij.schema.model; import ai.vespa.intellij.schema.SdUtil; import ai.vespa.intellij.schema.psi.SdRankProfileDefinition; import ai.vespa.intellij.schema.psi.SdTypes; +import ai.vespa.intellij.schema.utils.AST; import com.intellij.lang.ASTNode; import java.util.ArrayList; @@ -37,33 +38,12 @@ public class RankProfile { * @return the profiles this inherits from, empty if none */ public List<RankProfile> findInherited() { - ASTNode inheritsNode = definition.getNode().findChildByType(SdTypes.INHERITS); - if (inheritsNode == null) return List.of(); - return inherits().stream() - .map(parentIdentifierAST -> parentIdentifierAST.getPsi().getReference()) - .filter(reference -> reference != null) - .map(reference -> owner.rankProfile(reference.getCanonicalText())) - .flatMap(r -> r.stream()) - .collect(Collectors.toList()); - } - - /** Returns the nodes following "inherits" in the definition of this */ - private List<ASTNode> inherits() { - SdUtil.Tokens tokens = SdUtil.Tokens.of(definition); - tokens.require(SdTypes.RANK_PROFILE); - tokens.requireWhitespace(); - tokens.require(SdTypes.IDENTIFIER_VAL, SdTypes.IDENTIFIER_WITH_DASH_VAL); - if ( ! tokens.skipWhitespace()) return List.of(); - if ( ! tokens.skip(SdTypes.INHERITS)) return List.of(); - tokens.requireWhitespace(); - List<ASTNode> inherited = new ArrayList<>(); - do { - inherited.add(tokens.require(SdTypes.IDENTIFIER_VAL, SdTypes.IDENTIFIER_WITH_DASH_VAL)); - tokens.skipWhitespace(); - if ( ! tokens.skip(SdTypes.COMMA)) break; - tokens.skipWhitespace(); - } while (true); - return inherited; + return AST.inherits(definition).stream() + .map(parentIdentifierAST -> parentIdentifierAST.getPsi().getReference()) + .filter(reference -> reference != null) + .map(reference -> owner.rankProfile(reference.getCanonicalText())) + .flatMap(r -> r.stream()) + .collect(Collectors.toList()); } } diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java index 3670d095be2..7ad91907b22 100644 --- a/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java @@ -3,13 +3,12 @@ package ai.vespa.intellij.schema.model; import ai.vespa.intellij.schema.psi.SdFile; import ai.vespa.intellij.schema.psi.SdRankProfileDefinition; +import ai.vespa.intellij.schema.psi.SdSchemaDefinition; +import ai.vespa.intellij.schema.utils.AST; import ai.vespa.intellij.schema.utils.Files; import ai.vespa.intellij.schema.utils.Path; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiFileSystemItem; -import com.intellij.psi.search.FilenameIndex; -import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import java.util.Optional; @@ -29,6 +28,9 @@ public class Schema { /** The project this is part of */ private final Project project; + /** The schema this inherits, or empty if none. Resolved lazily. */ + private Optional<Schema> inherited = null; + public Schema(SdFile definition, Path path, Project project) { this.definition = definition; this.path = path; @@ -39,14 +41,26 @@ public class Schema { public SdFile definition() { return definition; } + public Optional<Schema> inherited() { + if (inherited != null) return inherited; + System.out.println("----------- starting schema inherits"); + return inherited = AST.inherits(PsiTreeUtil.collectElementsOfType(definition, SdSchemaDefinition.class).stream().findFirst().get()) + .stream() + .findFirst() // Only one schema can be inherited; ignore any following + .map(inheritedNode -> fromProjectFile(project, path.getParentPath().append(inheritedNode.getText() + ".sd"))); + } + /** Returns a rank profile belonging to this, defined either inside it or in a separate .profile file */ public Optional<RankProfile> rankProfile(String name) { - var definition = findProfileElement(name, this.definition); - if (definition.isEmpty()) { // Defined in a separate file schema-name/profile-name.profile + var definition = findProfileElement(name, this.definition); // Look up in this + if (definition.isEmpty()) { // Look up in a separate file schema-name/profile-name.profile Optional<PsiFile> file = Files.open(path.getParentPath().append(name()).append(name + ".profile"), project); if (file.isPresent()) definition = findProfileElement(name, file.get()); } + if (definition.isEmpty() && inherited().isPresent()) { // Look up in parent + return inherited().get().rankProfile(name); + } return definition.map(d -> new RankProfile(d, this)); } diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/utils/AST.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/utils/AST.java new file mode 100644 index 00000000000..0edf7d1f93c --- /dev/null +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/utils/AST.java @@ -0,0 +1,38 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.intellij.schema.utils; + +import ai.vespa.intellij.schema.psi.SdTypes; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; + +import java.util.ArrayList; +import java.util.List; + +/** + * AST navigation tools. + * + * @author bratseth + */ +public class AST { + + /** Returns the nodes following "inherits" in the given element. */ + public static List<ASTNode> inherits(PsiElement element) { + Tokens tokens = Tokens.of(element); + tokens.dump(); + tokens.requireElement(); + tokens.requireWhitespace(); + tokens.require(SdTypes.IDENTIFIER_VAL, SdTypes.IDENTIFIER_WITH_DASH_VAL); + if ( ! tokens.skipWhitespace()) return List.of(); + if ( ! tokens.skip(SdTypes.INHERITS)) return List.of(); + tokens.requireWhitespace(); + List<ASTNode> inherited = new ArrayList<>(); + do { + inherited.add(tokens.require(SdTypes.IDENTIFIER_VAL, SdTypes.IDENTIFIER_WITH_DASH_VAL)); + tokens.skipWhitespace(); + if ( ! tokens.skip(SdTypes.COMMA)) break; + tokens.skipWhitespace(); + } while (true); + return inherited; + } + +} diff --git a/integration/intellij/src/main/java/ai/vespa/intellij/schema/utils/Tokens.java b/integration/intellij/src/main/java/ai/vespa/intellij/schema/utils/Tokens.java new file mode 100644 index 00000000000..f5f80baf854 --- /dev/null +++ b/integration/intellij/src/main/java/ai/vespa/intellij/schema/utils/Tokens.java @@ -0,0 +1,112 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.intellij.schema.utils; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.impl.source.tree.CompositeElement; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import com.intellij.psi.impl.source.tree.TreeElement; +import com.intellij.psi.tree.IElementType; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** A collection of tokens with a current index. */ +public class Tokens { + + private final ASTNode[] nodes; + private int i = 0; + + private Tokens(PsiElement element) { + nodes = element.getNode().getChildren(null); + } + + /** + * Advances to the next token, if it is of the given type. + * + * @return true if the current token was of the given type and we advanced it, false + * if it was not and nothing was changed + */ + public boolean skip(IElementType... tokenTypes) { + boolean is = is(tokenTypes); + if (is) + i++; + return is; + } + + /** + * Advances beyond the next token, if it is whitespace. + * + * @return true if the current token was of the given type and we advanced it, false + * if it was not and nothing was changed + */ + public boolean skipWhitespace() { + boolean is = isWhitespace(); + if (is) + i++; + return is; + } + + /** Returns whether the current token is of the given type */ + public boolean is(IElementType... tokenTypes) { + for (IElementType type : tokenTypes) + if (current().getElementType() == type) return true; + return false; + } + + /** Returns whether the current token is whitespace */ + public boolean isWhitespace() { + return current().getPsi() instanceof PsiWhiteSpace; + } + + /** Returns whether the current token is an element */ + public boolean isElement() { + return current() instanceof TreeElement; + } + + /** Returns the current token if it is of the required type and throws otherwise. */ + public ASTNode require(IElementType... tokenTypes) { + if (!is(tokenTypes)) + throw new IllegalArgumentException("Expected " + toString(tokenTypes) + " but got " + current()); + ASTNode current = current(); + i++; + return current; + } + + /** Returns the current token if it is any element and throws otherwise. */ + public ASTNode requireElement() { + if (!isElement()) + throw new IllegalArgumentException("Expected an element but got " + current().getClass()); + ASTNode current = current(); + i++; + return current; + } + + public void requireWhitespace() { + if (!isWhitespace()) + throw new IllegalArgumentException("Expected whitespace, but got " + current()); + i++; + } + + /** Returns the current token (AST node), or null if we have reached the end. */ + public ASTNode current() { + if (i >= nodes.length) return null; + return nodes[i]; + } + + private String toString(IElementType[] tokens) { + return Arrays.stream(tokens).map(token -> token.getDebugName()).collect(Collectors.joining(", ")); + } + + public static Tokens of(PsiElement element) { + return new Tokens(element); + } + + /** For debugging: Prints the remaining to standard out. */ + public void dump() { + for (int j = i; j < nodes.length; j++) + System.out.println(nodes[j]); + } + +} diff --git a/integration/intellij/src/test/java/ai/vespa/intellij/model/SchemaTest.java b/integration/intellij/src/test/java/ai/vespa/intellij/model/SchemaTest.java index 1a33b17d8d3..6ce93d3fe28 100644 --- a/integration/intellij/src/test/java/ai/vespa/intellij/model/SchemaTest.java +++ b/integration/intellij/src/test/java/ai/vespa/intellij/model/SchemaTest.java @@ -52,7 +52,7 @@ public class SchemaTest extends LightJavaCodeInsightFixtureTestCase { List<RankProfile> parents = profile.findInherited(); assertEquals(2, parents.size()); assertEquals("other_child_profile", parents.get(0).name()); - assertEquals("parent-profile", parents.get(1).name()); + assertEquals("parent_profile", parents.get(1).name()); } @Test |