All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ai.vespa.schemals.tree.SchemaNode Maven / Gradle / Ivy

package ai.vespa.schemals.tree;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;

import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

import ai.vespa.schemals.parser.Token;
import ai.vespa.schemals.parser.Token.TokenType;
import ai.vespa.schemals.parser.TokenSource;
import ai.vespa.schemals.parser.Node.NodeType;
import ai.vespa.schemals.parser.Token.ParseExceptionSource;
import ai.vespa.schemals.index.Symbol;
import ai.vespa.schemals.index.Symbol.SymbolStatus;
import ai.vespa.schemals.index.Symbol.SymbolType;
import ai.vespa.schemals.parser.Node;
import ai.vespa.schemals.parser.SubLanguageData;
import ai.vespa.schemals.parser.ast.indexingElm;
import ai.vespa.schemals.tree.indexinglanguage.ILUtils;
import ai.vespa.schemals.tree.rankingexpression.RankNode;
import ai.vespa.schemals.tree.rankingexpression.RankingExpressionUtils;
import ai.vespa.schemals.parser.ast.expression;
import ai.vespa.schemals.parser.ast.featureListElm;

/**
 * SchemaNode represents a node in the AST of a file the language server parses.
 * The node exposes a general interface to handle different AST from different parsers.
 */
public class SchemaNode implements Iterable {

    public enum LanguageType {
        SCHEMA,
        INDEXING,
        RANK_EXPRESSION,
        CUSTOM
    }

    private LanguageType language; // Language specifies which parser the node comes from
    private String identifierString;
    private Range range;
    private boolean isDirty = false;

    private Symbol symbolAtNode;
    
    // This array has to be in order, without overlapping elements
    private ArrayList children = new ArrayList();
    private SchemaNode parent;
    
    private Node originalSchemaNode;
    private ai.vespa.schemals.parser.indexinglanguage.Node originalIndexingNode;
    private ai.vespa.schemals.parser.rankingexpression.Node originalRankExpressionNode;

    // Special properties for node in the CUSTOM language
    private String contentString;
    private Class simulatedSchemaClass;

    // The tokenType for leaf nodes for different languages
    private TokenType schemaType;
    private ai.vespa.schemals.parser.rankingexpression.Token.TokenType rankExpressionType;
    private ai.vespa.schemals.parser.indexinglanguage.Token.TokenType indexinglanguageType;

    private Optional rankNode = Optional.empty();

    private SchemaNode(LanguageType language, Range range, String identifierString, boolean isDirty) {
        this.language = language;
        this.range = range;
        this.identifierString = identifierString;
        this.isDirty = isDirty;
    }

    // To create tokens outside the other languages
    public SchemaNode(Range range, String contentString, String identifierString) {
        this(LanguageType.CUSTOM, range, identifierString, false);
        this.contentString = contentString;
    }
    
    public SchemaNode(Node node) {
        this(
            LanguageType.SCHEMA,
            CSTUtils.getNodeRange(node),
            node.getClass().getName(),
            node.isDirty()
        );

        this.originalSchemaNode = node;
        this.schemaType = calculateSchemaType();

        for (var child : node) {
            if (child == null) continue;
            SchemaNode newNode = new SchemaNode(child);
            newNode.setParent(this);
            children.add(newNode);
        }
    }

    public SchemaNode(ai.vespa.schemals.parser.indexinglanguage.Node node, Position rangeOffset) {
        this(
            LanguageType.INDEXING,
            CSTUtils.addPositionToRange(rangeOffset, ILUtils.getNodeRange(node)),
            node.getClass().getName(),
            node.isDirty()
        );

        this.originalIndexingNode = node;

        if (node instanceof ai.vespa.schemals.parser.indexinglanguage.Token) {
            this.indexinglanguageType = (ai.vespa.schemals.parser.indexinglanguage.Token.TokenType)node.getType();
        }

        for (var child : node) {
            if (child == null) continue;
            SchemaNode newNode = new SchemaNode(child, rangeOffset);
            newNode.setParent(this);
            children.add(newNode);
        }

    }

    public SchemaNode(ai.vespa.schemals.parser.rankingexpression.Node node, Position rangeOffset) {
        this(
            LanguageType.RANK_EXPRESSION,
            CSTUtils.addPositionToRange(rangeOffset, RankingExpressionUtils.getNodeRange(node)),
            node.getClass().getName(),
            node.isDirty()
        );

        this.originalRankExpressionNode = node;
        if (node instanceof ai.vespa.schemals.parser.rankingexpression.Token) {
            this.rankExpressionType = (ai.vespa.schemals.parser.rankingexpression.Token.TokenType) node.getType();
        }

        for (var child : node) {
            if (child == null) continue;
            SchemaNode newNode = new SchemaNode(child, rangeOffset);
            newNode.setParent(this);
            children.add(newNode);
        }

    }

    private void setParent(SchemaNode parent) {
        this.parent = parent;
    }

    public void addChild(SchemaNode child) {
        child.setParent(this);
        this.children.add(child);
    }

    public void addChildren(List children) {
        for (SchemaNode child : children) {
            addChild(child);
        }
    }

    public void clearChildren() {
        for (SchemaNode child : children) {
            child.setParent(null);
        }

        children.clear();
    }

    private TokenType calculateSchemaType() {
        if (language != LanguageType.SCHEMA) return null;
        if (isDirty) return null;

        NodeType nodeType = originalSchemaNode.getType();
        if (!(nodeType instanceof TokenType)) return null;
        return (TokenType)nodeType;
    }

    public TokenType getSchemaType() {
        
        return schemaType;
    }

    public ai.vespa.schemals.parser.rankingexpression.Token.TokenType getRankExpressionType() {
        return rankExpressionType;
    }

    public ai.vespa.schemals.parser.indexinglanguage.Token.TokenType getIndexingLanguageType() {
        return indexinglanguageType;
    }


    // Return token type (if the node is a token), even if the node is dirty
    public TokenType getDirtyType() {
        if (language != LanguageType.SCHEMA) return null;
        Node.NodeType originalType = originalSchemaNode.getType();
        if (originalType instanceof TokenType)return (TokenType)originalType;
        return null;
    }

    public TokenType setSchemaType(TokenType type) {
        if (language != LanguageType.SCHEMA && language != LanguageType.CUSTOM) return null;

        this.schemaType = type;
        
        return type;
    }

    public void setSymbol(SymbolType type, String fileURI) {
        if (this.hasSymbol()) {
            throw new IllegalArgumentException("Cannot set symbol for node: " + this.toString() + ". Already has symbol.");
        }
        this.symbolAtNode = new Symbol(this, type, fileURI);
    }

    public void setSymbol(SymbolType type, String fileURI, Symbol scope) {
        if (this.hasSymbol()) {
            throw new IllegalArgumentException("Cannot set symbol for node: " + this.toString() + ". Already has symbol.");
        }
        this.symbolAtNode = new Symbol(this, type, fileURI, scope);
    }

    public void setSymbol(SymbolType type, String fileURI, Optional scope) {
        if (scope.isPresent()) {
            setSymbol(type, fileURI, scope.get());
        } else {
            setSymbol(type, fileURI);
        }
    }

    public void setSymbol(SymbolType type, String fileURI, Symbol scope, String shortIdentifier) {
        if (this.hasSymbol()) {
            throw new IllegalArgumentException("Cannot set symbol for node: " + this.toString() + ". Already has symbol.");
        }
        this.symbolAtNode = new Symbol(this, type, fileURI, scope, shortIdentifier);
    }

    public void removeSymbol() {
        this.symbolAtNode = null;
    }

    public void setSymbolType(SymbolType newType) {
        if (!this.hasSymbol()) return;
        this.symbolAtNode.setType(newType);
    }

    public void setSymbolStatus(SymbolStatus newStatus) {
        if (!this.hasSymbol()) return;
        this.symbolAtNode.setStatus(newStatus);
    }

    public void setSymbolScope(Symbol newScope) {
        if (!this.hasSymbol()) return;
        this.symbolAtNode.setScope(newScope);
    }

    public boolean hasSymbol() {
        return this.symbolAtNode != null;
    }

    public Symbol getSymbol() {
        if (!hasSymbol()) throw new IllegalArgumentException("get Symbol called on node without a symbol!");
        return this.symbolAtNode;
    }

    public boolean containsOtherLanguageData(LanguageType language) {
        if (this.language != LanguageType.SCHEMA) return false;

        return (
            (language == LanguageType.INDEXING && originalSchemaNode instanceof indexingElm) ||
            (language == LanguageType.RANK_EXPRESSION && (
                (originalSchemaNode instanceof featureListElm) ||
                (originalSchemaNode instanceof expression)  
            ))
        );
    }

    public boolean containsExpressionData() {
        if (this.language != LanguageType.SCHEMA) return false;

        return (originalSchemaNode instanceof expression);
    }

    public SubLanguageData getILScript() {
        if (!containsOtherLanguageData(LanguageType.INDEXING)) return null;
        indexingElm elmNode = (indexingElm)originalSchemaNode;
        return elmNode.getILScript();
    }

    public String getRankExpressionString() {
        if (!containsOtherLanguageData(LanguageType.RANK_EXPRESSION)) return null;

        if (originalSchemaNode instanceof featureListElm) {
            featureListElm elmNode = (featureListElm)originalSchemaNode;
            return elmNode.getFeatureListString();
        }

        expression expressionNode = (expression)originalSchemaNode;
        return expressionNode.getExpressionString();
    }

    public boolean hasIndexingNode() {
        return false; // this.indexingNode != null;
    }

    public boolean hasRankExpressionNode() {
        return false; // this.rankExpressionNode != null;
    }

    public Node getOriginalSchemaNode() {
        return originalSchemaNode;
    }

    public ai.vespa.schemals.parser.indexinglanguage.Node getOriginalIndexingNode() {
        return this.originalIndexingNode;
    }

    public ai.vespa.schemals.parser.rankingexpression.Node getOriginalRankExpressionNode() {
        return this.originalRankExpressionNode;
    }

    public void setSimulatedASTClass(Class astClass) {
        if (language != LanguageType.CUSTOM) throw new IllegalArgumentException("Cannot set Simulated AST Class on a Schema node of type other than Custom");

        simulatedSchemaClass = astClass;
    }

    public boolean isASTInstance(Class astClass) {
        if (language == LanguageType.CUSTOM && astClass == simulatedSchemaClass) return true;
        if (language == LanguageType.SCHEMA) return astClass.isInstance(originalSchemaNode);
        if (language == LanguageType.RANK_EXPRESSION) return astClass.isInstance(originalRankExpressionNode);
        if (language == LanguageType.INDEXING) return astClass.isInstance(originalIndexingNode);
        return false;
    }

    public boolean isSchemaASTInstance(Class astClass) {
        if (language == LanguageType.CUSTOM) return astClass.equals(simulatedSchemaClass);
        
        return astClass.isInstance(originalSchemaNode);
    }

    public boolean isRankExpressionASTInstance(Class astClass) {
        return astClass.isInstance(originalRankExpressionNode);
    }

    public Class getASTClass() {
        if (language == LanguageType.CUSTOM) return simulatedSchemaClass;

        if (language == LanguageType.SCHEMA && originalSchemaNode != null) {
            return originalSchemaNode.getClass();
        }

        if (language == LanguageType.RANK_EXPRESSION && originalRankExpressionNode != null) {
            return originalRankExpressionNode.getClass();
        }

        if (language == LanguageType.INDEXING && originalIndexingNode != null) {
            return originalIndexingNode.getClass();
        }

        return null;
    }

    public String getIdentifierString() {
        return identifierString;
    }

    public void setNewStartCharacter(int startCharacter) {
        if (this.originalSchemaNode == null) return;
        int currentOffset = originalSchemaNode.getBeginOffset();
        int characterDelta = startCharacter - range.getStart().getCharacter();

        originalSchemaNode.setBeginOffset(currentOffset + characterDelta);
        this.range = CSTUtils.getNodeRange(originalSchemaNode);
    }

    public void setNewEndCharacter(int endCharacter) {
        if (originalSchemaNode == null) return;
        int currentOffset = originalSchemaNode.getEndOffset();
        int characterDelta = endCharacter - range.getEnd().getCharacter();

        originalSchemaNode.setEndOffset(currentOffset + characterDelta);
        this.range = CSTUtils.getNodeRange(originalSchemaNode);
    }

    public int getOriginalBeginOffset() {
        switch (language) {
            case SCHEMA:
                if (originalSchemaNode == null) return -1;
                return originalSchemaNode.getBeginOffset();
            case INDEXING:
                if (originalIndexingNode == null) return -1;
                return originalIndexingNode.getBeginOffset();
            case RANK_EXPRESSION:
                if (originalRankExpressionNode == null) return -1;
                return originalRankExpressionNode.getBeginOffset();
            default:
                return -1;
        }
    }

    public String getClassLeafIdentifierString() {
        if (language == LanguageType.CUSTOM && getASTClass() != null) {
            return getASTClass().getSimpleName();
        }
        int lastIndex = identifierString.lastIndexOf('.');
        return identifierString.substring(lastIndex + 1);
    }

    public Range getRange() {
        return range;
    }

    public SchemaNode getParent(int levels) {
        if (levels == 0) {
            return this;
        }

        if (parent == null) {
            return null;
        }

        return parent.getParent(levels - 1);
    }

    public SchemaNode getParent() {
        return getParent(1);
    }

    public void insertChildAfter(int index, SchemaNode child) {
        this.children.add(index+1, child);
        child.setParent(this);
    }

    /**
     * Returns the previous SchemaNode of the sibilings of the node.
     * If there is no previous node, it moves upwards to the parent and tries to get the previous node.
     *
     * @return the previous SchemaNode, or null if there is no previous node
     */
    public SchemaNode getPrevious() {
        if (parent == null)return null;

        int parentIndex = parent.indexOf(this);

        if (parentIndex == -1)return null; // invalid setup

        if (parentIndex == 0)return parent;
        return parent.get(parentIndex - 1);
    }

    /**
     * Returns the next SchemaNode of the sibilings of the node.
     * If there is no next node, it returns the next node of the parent node.
     *
     * @return the next SchemaNode or null if there is no next node
     */
    public SchemaNode getNext() {
        if (parent == null) return null;

        int parentIndex = parent.indexOf(this);

        if (parentIndex == -1) return null;
        
        if (parentIndex == parent.size() - 1) return parent.getNext();

        return parent.get(parentIndex + 1);
    }

    /**
     * Returns the sibling node at the specified relative index.
     * A sibling node is a node that shares the same parent node.
     *
     * @param relativeIndex the relative index of the sibling node
     * @return the sibling node at the specified relative index, or null if the sibling node does not exist
     */
    public SchemaNode getSibling(int relativeIndex) {
        if (parent == null)return null;

        int parentIndex = parent.indexOf(this);

        if (parentIndex == -1) return null; // invalid setup

        int siblingIndex = parentIndex + relativeIndex;
        if (siblingIndex < 0 || siblingIndex >= parent.size()) return null;
        
        return parent.get(siblingIndex);
    }

    /**
     * Returns the previous sibling of this schema node.
     * A sibling node is a node that shares the same parent node. 
     *
     * @return the previous sibling of this schema node, or null if there is no previous sibling
     */
    public SchemaNode getPreviousSibling() {
        return getSibling(-1);
    }

    /**
     * Returns the next sibling of this schema node.
     * A sibling node is a node that shares the same parent node.
     *
     * @return the next sibling of this schema node, or null if there is no previous sibling
     */
    public SchemaNode getNextSibling() {
        return getSibling(1);
    }

    public int indexOf(SchemaNode child) {
        return this.children.indexOf(child);
    }

    public int size() {
        return children.size();
    }

    public SchemaNode get(int i) {
        return children.get(i);
    }

    public String getText() {
        
        if (language == LanguageType.SCHEMA) {
            return originalSchemaNode.getSource();
        }

        if (language == LanguageType.INDEXING) {
            return originalIndexingNode.getSource();
        }

        if (language == LanguageType.RANK_EXPRESSION) {
            return originalRankExpressionNode.getSource();
        }

        if (language == LanguageType.CUSTOM) {
            return contentString;
        }

        return null;
    }

    public boolean isLeaf() {
        return children.size() == 0;
    }

    public SchemaNode findFirstLeaf() {
        SchemaNode ret = this;
        while (ret.size() > 0) {
            ret = ret.get(0);
        }
        return ret;
    }

    public boolean getIsDirty() {
        return isDirty;
    }

    public IllegalArgumentException getIllegalArgumentException() {

        if (language == LanguageType.SCHEMA) {
            if (originalSchemaNode instanceof Token) {
                return ((Token)originalSchemaNode).getIllegalArguemntException();
            }
        }

        // if (language == LanguageType.INDEXING) {
        //     if (originalIndexingNode instanceof ai.vespa.schemals.parser.indexinglanguage.Token) {
        //         return ((ai.vespa.schemals.parser.indexinglanguage.Token)originalIndexingNode)
        //     }
        // }

        return null;
    }

    public ParseExceptionSource getParseExceptionSource() {
        if (language == LanguageType.SCHEMA) {
            if (originalSchemaNode instanceof Token) {
                return ((Token)originalSchemaNode).getParseExceptionSource();
            }
        }
        return null;
    }

    public TokenSource getTokenSource() {
        if (language == LanguageType.SCHEMA) {
            return originalSchemaNode.getTokenSource();
        }

        return null;
    }

    public LanguageType getLanguageType() { return language; }

    public void setRankNode(RankNode node) {
        rankNode = Optional.of(node);
    }

    public Optional getRankNode() { return rankNode; }

    public String toString() {
        Position pos = getRange().getStart();
        String astClassStr = getASTClass() == null ? "AST_NULL" : getASTClass().toString();
        astClassStr = astClassStr.substring(astClassStr.lastIndexOf('.') + 1);
        String ret = "Node('" + getText() + "', [" + astClassStr + "] at " + pos.getLine() + ":" + pos.getCharacter();
        if (hasSymbol()) {
            ret += " [SYMBOL " + getSymbol().getType().toString() + " " + getSymbol().getStatus().toString() + ": " + getSymbol().getLongIdentifier() +  "]";
        }
        ret += ")";
        return ret;
    }

	@Override
	public Iterator iterator() {
        return new Iterator() {
            int currentIndex = 0;

			@Override
			public boolean hasNext() {
                return currentIndex < children.size();
			}

			@Override
			public SchemaNode next() {
                return children.get(currentIndex++);
			}
        };
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy