/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.antlr.works.editor.antlr4.parsing; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.List; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.RuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.TokenSource; import org.antlr.v4.runtime.atn.ATN; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.misc.IntervalSet; import org.antlr.v4.runtime.misc.Utils; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.RuleNode; import org.antlr.v4.runtime.tree.TerminalNode; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.NullAllowed; import org.openide.util.NotImplementedException; import org.openide.util.Parameters; /** * * @author Sam Harwell */ public final class ParseTrees { public static Interval getSourceInterval(@NonNull ParserRuleContext context) { Parameters.notNull("context", context); int startIndex = context.start.getStartIndex(); Token stopSymbol = getStopSymbol(context); if (stopSymbol == null) { return new Interval(startIndex, startIndex - 1); } int stopIndex; if (stopSymbol.getType() != Token.EOF) { stopIndex = stopSymbol.getStopIndex(); } else { TokenSource tokenSource = context.getStart().getTokenSource(); CharStream inputStream = tokenSource != null ? tokenSource.getInputStream() : null; if (inputStream != null) { stopIndex = inputStream.size() - 1; } else { stopIndex = context.start.getStartIndex() - 1; } } stopIndex = Math.max(stopIndex, startIndex - 1); return new Interval(startIndex, stopIndex); } public static Interval getSourceInterval(@NonNull ParseTree context) { Parameters.notNull("context", context); if (context instanceof TerminalNode) { TerminalNode terminalNode = (TerminalNode)context; Token token = terminalNode.getSymbol(); return new Interval(token.getStartIndex(), token.getStopIndex()); } else if (context instanceof RuleNode) { RuleNode ruleNode = (RuleNode)context; RuleContext ruleContext = ruleNode.getRuleContext(); if (ruleContext instanceof ParserRuleContext) { return getSourceInterval((ParserRuleContext)ruleContext); } else { Token startSymbol = getStartSymbol(context); Token stopSymbol = getStopSymbol(context); if (startSymbol == null || stopSymbol == null) { return Interval.INVALID; } return new Interval(startSymbol.getStartIndex(), stopSymbol.getStopIndex()); } } else { return Interval.INVALID; } } public static Token getStopSymbol(@NonNull ParserRuleContext context) { Parameters.notNull("context", context); if (context.stop != null) { return context.stop; } for (int i = context.getChildCount() - 1; i >= 0; i--) { Token symbol = getStopSymbol(context.getChild(i)); if (symbol != null) { return symbol; } } return context.start; } public static Token getStopSymbol(@NonNull ParseTree context) { Parameters.notNull("context", context); if (context instanceof ParserRuleContext) { return getStopSymbol((ParserRuleContext)context); } else if (context instanceof TerminalNode) { return ((TerminalNode)context).getSymbol(); } return null; } public static TerminalNode getStartNode(ParseTree context) { if (context == null) { return null; } if (context instanceof TerminalNode) { return (TerminalNode)context; } for (int i = 0; i < context.getChildCount(); i++) { TerminalNode startNode = getStartNode(context.getChild(i)); if (startNode != null) { return startNode; } } return null; } public static Token getStartSymbol(ParseTree context) { TerminalNode node = getStartNode(context); if (node != null) { return node.getSymbol(); } if (!(context instanceof RuleNode)) { return null; } RuleContext ruleContext = ((RuleNode)context).getRuleContext(); if (ruleContext instanceof ParserRuleContext) { return ((ParserRuleContext)ruleContext).getStart(); } return null; } public static TerminalNode getStopNode(ParseTree context) { if (context == null) { return null; } if (context instanceof TerminalNode) { return (TerminalNode)context; } for (int i = context.getChildCount() - 1; i >= 0; i--) { TerminalNode stopNode = getStopNode(context.getChild(i)); if (stopNode != null) { return stopNode; } } return null; } public static boolean isInContexts(@NonNull ParserRuleContext context, boolean allowGaps, @NonNull int... stack) { Parameters.notNull("context", context); Parameters.notNull("stack", stack); if (allowGaps) { throw new UnsupportedOperationException("Not implemented yet."); } ParserRuleContext currentContext = context; for (int element : stack) { if (currentContext.getRuleIndex() != element) { return false; } currentContext = currentContext.getParent(); if (currentContext == null) { return false; } } return true; } public static <T extends Token> boolean isInAnyContext(Parser parser, RuleContext context, IntervalSet values) { return isInAnyContext(parser, context, values, true); } public static <T extends Token> boolean isInAnyContext(Parser parser, RuleContext context, IntervalSet values, boolean checkTop) { return findTopContext(parser, context, values, checkTop) != null; } public static boolean isInAnyContext(ParserRuleContext context, IntervalSet values) { return isInAnyContext(context, values, true); } public static boolean isInAnyContext(ParserRuleContext context, IntervalSet values, boolean checkTop) { return findTopContext(context, values, checkTop) != null; } public static RuleContext findTopContext(Parser parser, RuleContext context, IntervalSet values) { return findTopContext(parser, context, values, true); } public static RuleContext findTopContext(Parser parser, RuleContext context, IntervalSet values, boolean checkTop) { if (checkTop && values.contains(context.getRuleIndex())) { return context; } if (context.isEmpty()) { return null; } if (values.contains(parser.getATN().states.get(context.invokingState).ruleIndex)) { return context.parent; } return findTopContext(parser, context.parent, values, false); } public static ParserRuleContext findTopContext(ParserRuleContext context, IntervalSet values) { return findTopContext(context, values, true); } public static ParserRuleContext findTopContext(ParserRuleContext context, IntervalSet values, boolean checkTop) { if (checkTop && values.contains(context.getRuleIndex())) { return context; } if (context.isEmpty()) { return null; } return findTopContext((ParserRuleContext)context.parent, values, true); } @CheckForNull public static TerminalNode findTerminalNode(@NonNull ParseTree node, Token symbol) { if (symbol == null) { return null; } if (node instanceof TerminalNode) { TerminalNode terminalNode = (TerminalNode)node; if (Utils.equals(terminalNode.getSymbol(), symbol)) { return terminalNode; } return null; } for (int i = 0; i < node.getChildCount(); i++) { ParseTree child = node.getChild(i); TerminalNode stopNode = ParseTrees.getStopNode(child); if (stopNode == null) { continue; } Token stopSymbol = stopNode.getSymbol(); if (stopSymbol.getStopIndex() < symbol.getStartIndex()) { continue; } TerminalNode startNode = ParseTrees.getStartNode(child); assert startNode != null; stopSymbol = startNode.getSymbol(); if (stopSymbol == null || stopSymbol.getStartIndex() > symbol.getStopIndex()) { break; } if (stopSymbol.equals(symbol)) { return startNode; } TerminalNode terminalNode = findTerminalNode(child, symbol); if (terminalNode != null) { return terminalNode; } } return null; } public static TerminalNode findTerminalNode(Collection<? extends ParseTree> children, Token symbol) { for (ParseTree element : children) { if (!(element instanceof TerminalNode)) { continue; } TerminalNode node = (TerminalNode)element; if (node.getSymbol() == symbol) { return node; } } return null; } public static int getInvokingRule(ATN atn, RuleContext context) { int invokingState = context.invokingState; if (invokingState < 0 || invokingState >= atn.states.size()) { return -1; } return atn.states.get(invokingState).ruleIndex; } public static <T> List<T> emptyIfNull(@NullAllowed List<T> list) { if (list == null) { return Collections.emptyList(); } return list; } /** Return a list of all ancestors of this node. The first node of * list is the root and the last is the parent of this node. * @param <T> * @param t * @return */ @NonNull public static List<? extends ParseTree> getAncestors(@NonNull ParseTree t) { if ( t.getParent()==null ) { return Collections.emptyList(); } List<ParseTree> ancestors = new ArrayList<>(); t = t.getParent(); while ( t!=null ) { ancestors.add(0, t); // insert at start t = t.getParent(); } return ancestors; } @CheckForNull public static RuleNode findAncestor(@NonNull ParseTree tree, int ruleIndex) { for (ParseTree current = tree; current != null; current = current.getParent()) { if (!(current instanceof RuleNode)) { continue; } RuleNode ruleNode = (RuleNode)current; if (ruleNode.getRuleContext().getRuleIndex() == ruleIndex) { return ruleNode; } } return null; } @CheckForNull public static RuleNode findAncestor(@NonNull ParseTree tree, @NonNull BitSet ruleIndexes) { for (ParseTree current = tree; current != null; current = current.getParent()) { if (!(current instanceof RuleNode)) { continue; } RuleNode ruleNode = (RuleNode)current; int ruleIndex = ruleNode.getRuleContext().getRuleIndex(); if (ruleIndex < 0) { continue; } if (ruleIndexes.get(ruleIndex)) { return ruleNode; } } return null; } @CheckForNull public static <ContextClass> ContextClass findAncestor(@NonNull ParseTree tree, @NonNull Class<ContextClass> nodeType) { for (ParseTree current = tree; current != null; current = current.getParent()) { if (!(current instanceof RuleNode)) { continue; } RuleNode ruleNode = (RuleNode)current; RuleContext ruleContext = ruleNode.getRuleContext(); if (nodeType.isInstance(ruleContext)) { return nodeType.cast(ruleContext); } } return null; } /** * Gets whether or not {@code tree} is an epsilon non-terminal in the parse * tree. An epsilon tree is a node which does not contain any * {@link TerminalNode} descendants. * * @param tree A node in a parse tree. * @return {@code true} if {@code tree} is an epsilon node in the parse * tree, otherwise {@code false}. */ public static boolean isEpsilon(@NonNull ParseTree tree) { if (tree instanceof TerminalNode) { return false; } Interval sourceInterval = tree.getSourceInterval(); return sourceInterval.b < sourceInterval.a; } /** * Gets whether or not {@code a} starts after the start of {@code b}. * * @param a The first tree. * @param b The second tree. * @return {@code true} if {@code a} starts after the start of {@code b}, otherwise {@code false}. */ public static boolean startsAfterStartOf(@NonNull ParseTree a, @NonNull ParseTree b) { //TerminalNode<? extends Token> startNodeA = getStartNode(a); //TerminalNode<? extends Token> startNodeB = getStartNode(b); //if (startNodeA == null || startNodeB == null) { // throw new NotImplementedException(); //} Interval sourceIntervalA = a.getSourceInterval(); Interval sourceIntervalB = b.getSourceInterval(); //if (sourceIntervalA.a == sourceIntervalB.a) { // if (isAncestorOf(a, b)) { // return true; // } // // if (isEpsilon(a) || isEpsilon(b)) { // // b could be a child of a later sibling of some ancestor of a // throw new NotImplementedException(); // } //} return sourceIntervalA.a > sourceIntervalB.a; } /** * Gets whether or not {@code a} starts before the start of {@code b}. * * @param a The first tree. * @param b The second tree. * @return {@code true} if {@code a} starts before the start of {@code b}, otherwise {@code false}. */ public static boolean startsBeforeStartOf(@NonNull ParseTree a, @NonNull ParseTree b) { Interval sourceIntervalA = a.getSourceInterval(); Interval sourceIntervalB = b.getSourceInterval(); return sourceIntervalA.a < sourceIntervalB.a; } /** * Gets whether or not {@code a} ends after the end of {@code b}. * * @param a The first tree. * @param b The second tree. * @return {@code true} if {@code a} ends after the end of {@code b}, otherwise {@code false}. */ public static boolean endsAfterEndOf(@NonNull ParseTree a, @NonNull ParseTree b) { Interval sourceIntervalA = a.getSourceInterval(); Interval sourceIntervalB = b.getSourceInterval(); return sourceIntervalA.b > sourceIntervalB.b; } /** * Gets whether or not {@code a} ends before the end of {@code b}. * * @param a The first tree. * @param b The second tree. * @return {@code true} if {@code a} ends before the end of {@code b}, otherwise {@code false}. */ public static boolean endsBeforeEndOf(@NonNull ParseTree a, @NonNull ParseTree b) { Interval sourceIntervalA = a.getSourceInterval(); Interval sourceIntervalB = b.getSourceInterval(); return sourceIntervalA.b < sourceIntervalB.b; } /** * Gets whether or not {@code a} is an ancestor of or equal to {@code b}. * * @param a The first tree. * @param b The second tree. * @return {@code true} if {@code a} is an ancestor of or is equal to {@code b}, otherwise {@code false}. */ public static boolean isAncestorOf(@NonNull ParseTree a, @NonNull ParseTree b) { for (ParseTree current = b; current != null; current = current.getParent()) { if (current.equals(a)) { return true; } } return false; } /** * Gets whether or not the first symbol of {@code tree} is the first * non-whitespace symbol on a line. * * @param tree The parse tree to test. * @return {@code true} if the only characters appearing before the first * token of {@code tree} on the line where {@code tree} starts are * whitespace characters according to {@link Character#isWhitespace}. */ public static boolean elementStartsLine(ParseTree tree) { TerminalNode symbol = ParseTrees.getStartNode(tree); if (symbol == null) { throw new NotImplementedException(); } return elementStartsLine(symbol.getSymbol()); } /** * Gets whether or not {@code token} is the first non-whitespace symbol on a * line. * * @param token The token to test. * @return {@code true} if the only characters appearing before * {@code token} on the same line are whitespace characters according to * {@link Character#isWhitespace}. */ public static boolean elementStartsLine(Token token) { String beginningOfLineText = token.getTokenSource().getInputStream().getText(new Interval(token.getStartIndex() - token.getCharPositionInLine(), token.getStartIndex() - 1)); for (int i = 0; i < beginningOfLineText.length(); i++) { if (!Character.isWhitespace(beginningOfLineText.charAt(i))) { return false; } } return true; } /** * Gets the symbol type of a parse tree terminal node. If the node is not a * terminal node, this method returns {@link Token#INVALID_TYPE}. * * @param node The parse tree node. * @return The symbol type of the terminal node. If {@code node} does not * implement {@link TerminalNode}, this method returns * {@link Token#INVALID_TYPE}. */ @CheckForNull public static int getTerminalNodeType(@NonNull ParseTree node) { if (!(node instanceof TerminalNode)) { return Token.INVALID_TYPE; } return ((TerminalNode)node).getSymbol().getType(); } /** * Gets a typed rule context from a parse tree node. If {@code node} is a * {@link RuleNode}, this method gets the {@link RuleContext} instance from * the node and attempts to cast the result to {@code clazz}. If * {@code node} is not a {@code RuleNode}, or if the context is not of type * {@code clazz}, this method returns {@code null}. * * @param <T> The specific rule context type. * @param node The parse tree node. * @param clazz The specific rule context type. * @return A typed rule context object, or {@code null} if the parse tree * node does not represent a rule node of this specific type. */ @CheckForNull public static <T extends ParserRuleContext> T getTypedRuleContext(@NonNull ParseTree node, @NonNull Class<T> clazz) { if (!(node instanceof RuleNode)) { return null; } RuleContext ruleContext = ((RuleNode)node).getRuleContext(); if (clazz.isInstance(ruleContext)) { return clazz.cast(ruleContext); } return null; } private ParseTrees() { } }