diff --git a/tool/src/org/antlr/v5/analysis/AltType.java b/tool/src/org/antlr/v5/analysis/AltType.java new file mode 100644 index 000000000..4b284be67 --- /dev/null +++ b/tool/src/org/antlr/v5/analysis/AltType.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2012-present The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.antlr.v5.analysis; + +public enum AltType { + other, + binaryLR, + suffixLR, + prefix, +} diff --git a/tool/src/org/antlr/v5/analysis/AssocType.java b/tool/src/org/antlr/v5/analysis/AssocType.java new file mode 100644 index 000000000..a317c472e --- /dev/null +++ b/tool/src/org/antlr/v5/analysis/AssocType.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2012-present The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.antlr.v5.analysis; + +public enum AssocType { + left, + right +} diff --git a/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAltInfo.java b/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAltInfo.java index e7e2362fe..13535a2f4 100644 --- a/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAltInfo.java +++ b/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAltInfo.java @@ -18,10 +18,6 @@ public class LeftRecursiveRuleAltInfo { public AltAST originalAltAST; public int nextPrec; - public LeftRecursiveRuleAltInfo(int altNum, String altText) { - this(altNum, altText, null, null, false, null); - } - public LeftRecursiveRuleAltInfo(int altNum, String altText, String leftRecursiveRuleRefLabel, String altLabel, diff --git a/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAnalyzer.java b/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAnalyzer.java index b6c672395..6e219c1ba 100644 --- a/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAnalyzer.java +++ b/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleAnalyzer.java @@ -6,62 +6,45 @@ package org.antlr.v5.analysis; -import org.antlr.runtime.CommonToken; import org.antlr.runtime.Token; import org.antlr.runtime.TokenStream; -import org.antlr.runtime.tree.CommonTreeNodeStream; import org.antlr.runtime.tree.Tree; import org.antlr.v5.Tool; import org.antlr.v5.codegen.CodeGenerator; -import org.antlr.v5.parse.ANTLRParser; -import org.antlr.v5.parse.GrammarASTAdaptor; -import org.antlr.v5.parse.LeftRecursiveRuleWalker; import org.antlr.v5.runtime.misc.IntervalSet; import org.antlr.v5.runtime.misc.Pair; -import org.antlr.v5.tool.ErrorType; +import org.antlr.v5.tool.LeftRecursiveRule; import org.antlr.v5.tool.ast.AltAST; import org.antlr.v5.tool.ast.GrammarAST; -import org.antlr.v5.tool.ast.GrammarASTWithOptions; import org.antlr.v5.tool.ast.RuleRefAST; import org.stringtemplate.v4.ST; import org.stringtemplate.v4.STGroup; import org.stringtemplate.v4.STGroupFile; import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** Using a tree walker on the rules, determine if a rule is directly left-recursive and if it follows - * our pattern. - */ -public class LeftRecursiveRuleAnalyzer extends LeftRecursiveRuleWalker { - public static enum ASSOC { left, right } +import java.util.*; + +import static org.antlr.v5.parse.ANTLRLexer.PLUS_ASSIGN; +import static org.antlr.v5.parse.ANTLRLexer.POUND; +import static org.antlr.v5.parse.ANTLRParser.*; +public class LeftRecursiveRuleAnalyzer { public Tool tool; - public String ruleName; - public LinkedHashMap binaryAlts = new LinkedHashMap(); - public LinkedHashMap ternaryAlts = new LinkedHashMap(); - public LinkedHashMap suffixAlts = new LinkedHashMap(); - public List prefixAndOtherAlts = new ArrayList(); + public LeftRecursiveRule rule; - /** Pointer to ID node of ^(= ID element) */ - public List> leftRecursiveRuleRefLabels = - new ArrayList>(); + public LinkedHashMap binaryAlts = new LinkedHashMap<>(); + public LinkedHashMap suffixAlts = new LinkedHashMap<>(); + public List prefixAndOtherAlts = new ArrayList<>(); - /** Tokens from which rule AST comes from */ public final TokenStream tokenStream; - public GrammarAST retvals; + /** Pointer to ID node of ^(= ID element) */ + public List> leftRecursiveRuleRefLabels = new ArrayList<>(); public final static STGroup recRuleTemplates; public final STGroup codegenTemplates; public final String language; - public Map altAssociativity = new HashMap(); - static { String templateGroupFile = "org/antlr/v5/tool/templates/LeftRecursiveRules.stg"; recRuleTemplates = new STGroupFile(templateGroupFile); @@ -74,14 +57,11 @@ public static enum ASSOC { left, right } } } - public LeftRecursiveRuleAnalyzer(GrammarAST ruleAST, - Tool tool, String ruleName, String language) - { - super(new CommonTreeNodeStream(new GrammarASTAdaptor(ruleAST.token.getInputStream()), ruleAST)); + public LeftRecursiveRuleAnalyzer(Tool tool, LeftRecursiveRule rule, TokenStream tokenStream, String language) { this.tool = tool; - this.ruleName = ruleName; + this.rule = rule; this.language = language; - this.tokenStream = ruleAST.g.tokenStream; + this.tokenStream = tokenStream; if (this.tokenStream == null) { throw new NullPointerException("grammar must have a token stream"); } @@ -90,142 +70,24 @@ public LeftRecursiveRuleAnalyzer(GrammarAST ruleAST, codegenTemplates = CodeGenerator.create(tool, null, language).getTemplates(); } - @Override - public void setReturnValues(GrammarAST t) { - retvals = t; - } - - @Override - public void setAltAssoc(AltAST t, int alt) { - ASSOC assoc = ASSOC.left; - if ( t.getOptions()!=null ) { - String a = t.getOptionString("assoc"); - if ( a!=null ) { - if ( a.equals(ASSOC.right.toString()) ) { - assoc = ASSOC.right; - } - else if ( a.equals(ASSOC.left.toString()) ) { - assoc = ASSOC.left; - } - else { - tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION_VALUE, t.g.fileName, t.getOptionAST("assoc").getToken(), "assoc", assoc); - } - } - } - - if ( altAssociativity.get(alt)!=null && altAssociativity.get(alt)!=assoc ) { - tool.errMgr.toolError(ErrorType.INTERNAL_ERROR, "all operators of alt " + alt + " of left-recursive rule must have same associativity"); - } - altAssociativity.put(alt, assoc); - -// System.out.println("setAltAssoc: op " + alt + ": " + t.getText()+", assoc="+assoc); - } - - @Override - public void binaryAlt(AltAST originalAltTree, int alt) { - AltAST altTree = (AltAST)originalAltTree.dupTree(); - String altLabel = altTree.altLabel!=null ? altTree.altLabel.getText() : null; - - String label = null; - boolean isListLabel = false; - GrammarAST lrlabel = stripLeftRecursion(altTree); - if ( lrlabel!=null ) { - label = lrlabel.getText(); - isListLabel = lrlabel.getParent().getType() == PLUS_ASSIGN; - leftRecursiveRuleRefLabels.add(new Pair(lrlabel,altLabel)); - } - - stripAltLabel(altTree); - - // rewrite e to be e_[rec_arg] - int nextPrec = nextPrecedence(alt); - altTree = addPrecedenceArgToRules(altTree, nextPrec); - - stripAltLabel(altTree); - String altText = text(altTree); - altText = altText.trim(); - LeftRecursiveRuleAltInfo a = - new LeftRecursiveRuleAltInfo(alt, altText, label, altLabel, isListLabel, originalAltTree); - a.nextPrec = nextPrec; - binaryAlts.put(alt, a); - //System.out.println("binaryAlt " + alt + ": " + altText + ", rewrite=" + rewriteText); - } - - @Override - public void prefixAlt(AltAST originalAltTree, int alt) { - AltAST altTree = (AltAST)originalAltTree.dupTree(); - stripAltLabel(altTree); - - int nextPrec = precedence(alt); - // rewrite e to be e_[prec] - altTree = addPrecedenceArgToRules(altTree, nextPrec); - String altText = text(altTree); - altText = altText.trim(); - String altLabel = altTree.altLabel!=null ? altTree.altLabel.getText() : null; - LeftRecursiveRuleAltInfo a = - new LeftRecursiveRuleAltInfo(alt, altText, null, altLabel, false, originalAltTree); - a.nextPrec = nextPrec; - prefixAndOtherAlts.add(a); - //System.out.println("prefixAlt " + alt + ": " + altText + ", rewrite=" + rewriteText); - } - - @Override - public void suffixAlt(AltAST originalAltTree, int alt) { - AltAST altTree = (AltAST)originalAltTree.dupTree(); - String altLabel = altTree.altLabel!=null ? altTree.altLabel.getText() : null; - - String label = null; - boolean isListLabel = false; - GrammarAST lrlabel = stripLeftRecursion(altTree); - if ( lrlabel!=null ) { - label = lrlabel.getText(); - isListLabel = lrlabel.getParent().getType() == PLUS_ASSIGN; - leftRecursiveRuleRefLabels.add(new Pair(lrlabel,altLabel)); - } - stripAltLabel(altTree); - String altText = text(altTree); - altText = altText.trim(); - LeftRecursiveRuleAltInfo a = - new LeftRecursiveRuleAltInfo(alt, altText, label, altLabel, isListLabel, originalAltTree); - suffixAlts.put(alt, a); -// System.out.println("suffixAlt " + alt + ": " + altText + ", rewrite=" + rewriteText); - } - - @Override - public void otherAlt(AltAST originalAltTree, int alt) { - AltAST altTree = (AltAST)originalAltTree.dupTree(); - stripAltLabel(altTree); - String altText = text(altTree); - String altLabel = altTree.altLabel!=null ? altTree.altLabel.getText() : null; - LeftRecursiveRuleAltInfo a = - new LeftRecursiveRuleAltInfo(alt, altText, null, altLabel, false, originalAltTree); - // We keep other alts with prefix alts since they are all added to the start of the generated rule, and - // we want to retain any prior ordering between them - prefixAndOtherAlts.add(a); -// System.out.println("otherAlt " + alt + ": " + altText); - } - - // --------- get transformed rules ---------------- - public String getArtificialOpPrecRule() { ST ruleST = recRuleTemplates.getInstanceOf("recRule"); - ruleST.add("ruleName", ruleName); + ruleST.add("ruleName", rule.name); ST ruleArgST = codegenTemplates.getInstanceOf("recRuleArg"); ruleST.add("argName", ruleArgST); ST setResultST = codegenTemplates.getInstanceOf("recRuleSetResultAction"); ruleST.add("setResultAction", setResultST); - ruleST.add("userRetvals", retvals); + ruleST.add("userRetvals", rule.ruleInfo.retvals); - LinkedHashMap opPrecRuleAlts = new LinkedHashMap(); + LinkedHashMap opPrecRuleAlts = new LinkedHashMap<>(); opPrecRuleAlts.putAll(binaryAlts); - opPrecRuleAlts.putAll(ternaryAlts); opPrecRuleAlts.putAll(suffixAlts); for (int alt : opPrecRuleAlts.keySet()) { LeftRecursiveRuleAltInfo altInfo = opPrecRuleAlts.get(alt); ST altST = recRuleTemplates.getInstanceOf("recRuleAlt"); ST predST = codegenTemplates.getInstanceOf("recRuleAltPredicate"); predST.add("opPrec", precedence(alt)); - predST.add("ruleName", ruleName); + predST.add("ruleName", rule.name); altST.add("pred", predST); altST.add("alt", altInfo); altST.add("precOption", LeftRecursiveRuleTransformer.PRECEDENCE_OPTION_NAME); @@ -235,189 +97,147 @@ public String getArtificialOpPrecRule() { ruleST.add("primaryAlts", prefixAndOtherAlts); - tool.log("left-recursion", ruleST.render()); - - return ruleST.render(); + String renderResult = ruleST.render(); + tool.log("left-recursion", renderResult); + return renderResult; } - public AltAST addPrecedenceArgToRules(AltAST t, int prec) { - if ( t==null ) return null; - // get all top-level rule refs from ALT - List outerAltRuleRefs = t.getNodesWithTypePreorderDFS(IntervalSet.of(RULE_REF)); - for (GrammarAST x : outerAltRuleRefs) { - RuleRefAST rref = (RuleRefAST)x; - boolean recursive = rref.getText().equals(ruleName); - boolean rightmost = rref == outerAltRuleRefs.get(outerAltRuleRefs.size()-1); - if ( recursive && rightmost ) { - GrammarAST dummyValueNode = new GrammarAST(new CommonToken(ANTLRParser.INT, ""+prec)); - rref.setOption(LeftRecursiveRuleTransformer.PRECEDENCE_OPTION_NAME, dummyValueNode); - } + public void analyze() { + List ruleAltInfos = rule.ruleInfo.ruleAltInfos; + for (RuleAltInfo ruleAltInfo : ruleAltInfos) { + processAlternative(ruleAltInfo); } - return t; } - /** - * Match (RULE RULE_REF (BLOCK (ALT .*) (ALT RULE_REF[self] .*) (ALT .*))) - * Match (RULE RULE_REF (BLOCK (ALT .*) (ALT (ASSIGN ID RULE_REF[self]) .*) (ALT .*))) - */ - public static boolean hasImmediateRecursiveRuleRefs(GrammarAST t, String ruleName) { - if ( t==null ) return false; - GrammarAST blk = (GrammarAST)t.getFirstChildWithType(BLOCK); - if ( blk==null ) return false; - int n = blk.getChildren().size(); - for (int i = 0; i < n; i++) { - GrammarAST alt = (GrammarAST)blk.getChildren().get(i); - Tree first = alt.getChild(0); - if ( first==null ) continue; - if (first.getType() == ELEMENT_OPTIONS) { - first = alt.getChild(1); - if (first == null) { - continue; - } + private void processAlternative(RuleAltInfo ruleAltInfo) { + int altNumber = ruleAltInfo.number; + String label = null; + boolean isListLabel = false; + + AltType altType = ruleAltInfo.type; + if (altType == AltType.binaryLR || altType == AltType.suffixLR) { + GrammarAST leftRecursiveRuleRefLabel = ruleAltInfo.leftRecursiveRuleRefLabel; + if (leftRecursiveRuleRefLabel != null) { + label = leftRecursiveRuleRefLabel.getText(); + isListLabel = leftRecursiveRuleRefLabel.getParent().getType() == PLUS_ASSIGN; + leftRecursiveRuleRefLabels.add(new Pair<>(leftRecursiveRuleRefLabel, ruleAltInfo.label)); } - if ( first.getType()==RULE_REF && first.getText().equals(ruleName) ) return true; - Tree rref = first.getChild(1); - if ( rref!=null && rref.getType()==RULE_REF && rref.getText().equals(ruleName) ) return true; } - return false; - } - // TODO: this strips the tree properly, but since text() - // uses the start of stop token index and gets text from that - // ineffectively ignores this routine. - public GrammarAST stripLeftRecursion(GrammarAST altAST) { - GrammarAST lrlabel=null; - GrammarAST first = (GrammarAST)altAST.getChild(0); - int leftRecurRuleIndex = 0; - if ( first.getType() == ELEMENT_OPTIONS ) { - first = (GrammarAST)altAST.getChild(1); - leftRecurRuleIndex = 1; + int nextPrec = -1; + boolean isBinaryOrPrefix = altType == AltType.binaryLR || altType == AltType.prefix; + if (isBinaryOrPrefix) { + // rewrite e to be e_[rec_arg] + nextPrec = altType == AltType.binaryLR ? nextPrecedence(altNumber, ruleAltInfo.assocType) : precedence(altNumber); } - Tree rref = first.getChild(1); // if label=rule - if ( (first.getType()==RULE_REF && first.getText().equals(ruleName)) || - (rref!=null && rref.getType()==RULE_REF && rref.getText().equals(ruleName)) ) - { - if ( first.getType()==ASSIGN || first.getType()==PLUS_ASSIGN ) lrlabel = (GrammarAST)first.getChild(0); - // remove rule ref (first child unless options present) - altAST.deleteChild(leftRecurRuleIndex); - // reset index so it prints properly (sets token range of - // ALT to start to right of left recur rule we deleted) - GrammarAST newFirstChild = (GrammarAST)altAST.getChild(leftRecurRuleIndex); - altAST.setTokenStartIndex(newFirstChild.getTokenStartIndex()); + + String altText = text(ruleAltInfo, nextPrec).trim(); + LeftRecursiveRuleAltInfo lrInfo = new LeftRecursiveRuleAltInfo(altNumber, altText, label, ruleAltInfo.label, isListLabel, ruleAltInfo.ast); + + if (isBinaryOrPrefix) { + lrInfo.nextPrec = nextPrec; + } + + if (altType == AltType.binaryLR) { + binaryAlts.put(altNumber, lrInfo); + } else if (altType == AltType.suffixLR) { + suffixAlts.put(altNumber, lrInfo); + } else { + prefixAndOtherAlts.add(lrInfo); } - return lrlabel; } - /** Strip last 2 tokens if → label; alter indexes in altAST */ - public void stripAltLabel(GrammarAST altAST) { - int start = altAST.getTokenStartIndex(); - int stop = altAST.getTokenStopIndex(); - // find => - for (int i=stop; i>=start; i--) { - if ( tokenStream.get(i).getType()==POUND ) { - altAST.setTokenStopIndex(i-1); - return; + private int nextPrecedence(int altNumber, AssocType assocType) { + int p = precedence(altNumber); + return assocType == AssocType.right ? p : p + 1; + } + + private int precedence(int altNumber) { + return rule.ruleInfo.ruleAltInfos.size() - altNumber + 1; + } + + private String text(RuleAltInfo ruleAltInfo, int nextPrecedence) { + AltAST altAST = ruleAltInfo.ast; + if (altAST==null) return ""; + + IntervalSet ignoredTokenIndexes = new IntervalSet(); + IntervalSet ignoredOptionTokenIndexes = new IntervalSet(); + + List optionsSubTrees = altAST.getNodesWithType(ELEMENT_OPTIONS); + for (GrammarAST optionSubTree : optionsSubTrees) { + Tree child = optionSubTree.getChild(0); + Tree optionNameAst = child.getChild(0); + if (optionNameAst != null) { + if (optionNameAst.getText().equals("assoc")) { + ignoredTokenIndexes.add(optionSubTree.getTokenStartIndex(), optionSubTree.getTokenStopIndex()); + } else { + ignoredOptionTokenIndexes.add(child.getTokenStartIndex(), child.getTokenStopIndex()); + } } } - } - public String text(GrammarAST t) { - if ( t==null ) return ""; - - int tokenStartIndex = t.getTokenStartIndex(); - int tokenStopIndex = t.getTokenStopIndex(); - - // ignore tokens from existing option subtrees like: - // (ELEMENT_OPTIONS (= assoc right)) - // - // element options are added back according to the values in the map - // returned by getOptions(). - IntervalSet ignore = new IntervalSet(); - List optionsSubTrees = t.getNodesWithType(ELEMENT_OPTIONS); - for (GrammarAST sub : optionsSubTrees) { - ignore.add(sub.getTokenStartIndex(), sub.getTokenStopIndex()); + for (RuleRefAST ruleRefAST : ruleAltInfo.leftRecursiveLeftmostRuleRefs) { + Tree parent = ruleRefAST.getParent(); + int startIgnoreIndex = ruleRefAST.getTokenStartIndex(); + if (parent instanceof GrammarAST) { + int parenTokenType = ((GrammarAST) parent).token.getType(); + if (parenTokenType == ASSIGN || parenTokenType == PLUS_ASSIGN) { + startIgnoreIndex = parent.getChild(0).getTokenStartIndex(); + } + } + ignoredTokenIndexes.add(startIgnoreIndex, ruleRefAST.getTokenStopIndex()); } // Individual labels appear as RULE_REF or TOKEN_REF tokens in the tree, // but do not support the ELEMENT_OPTIONS syntax. Make sure to not try // and add the tokenIndex option when writing these tokens. - IntervalSet noOptions = new IntervalSet(); - List labeledSubTrees = t.getNodesWithType(new IntervalSet(ASSIGN,PLUS_ASSIGN)); + List labeledSubTrees = altAST.getNodesWithType(new IntervalSet(ASSIGN,PLUS_ASSIGN)); for (GrammarAST sub : labeledSubTrees) { - noOptions.add(sub.getChild(0).getTokenStartIndex()); + ignoredOptionTokenIndexes.add(sub.getChild(0).getTokenStartIndex()); } - StringBuilder buf = new StringBuilder(); - int i=tokenStartIndex; - while ( i<=tokenStopIndex ) { - if ( ignore.contains(i) ) { - i++; + int tokenStartIndex = altAST.getTokenStartIndex(); + int tokenStopIndex = altAST.getTokenStopIndex(); + + StringBuilder buffer = new StringBuilder(); + int tokenIndex = tokenStartIndex; + + while (tokenIndex <= tokenStopIndex) { + if (ignoredTokenIndexes.contains(tokenIndex)) { + tokenIndex++; continue; } - Token tok = tokenStream.get(i); - - // Compute/hold any element options - StringBuilder elementOptions = new StringBuilder(); - if (!noOptions.contains(i)) { - GrammarAST node = t.getNodeWithTokenIndex(tok.getTokenIndex()); - if ( node!=null && - (tok.getType()==TOKEN_REF || - tok.getType()==STRING_LITERAL || - tok.getType()==RULE_REF) ) - { - elementOptions.append("tokenIndex=").append(tok.getTokenIndex()); - } - - if ( node instanceof GrammarASTWithOptions ) { - GrammarASTWithOptions o = (GrammarASTWithOptions)node; - for (Map.Entry entry : o.getOptions().entrySet()) { - if (elementOptions.length() > 0) { - elementOptions.append(','); - } - - elementOptions.append(entry.getKey()); - elementOptions.append('='); - elementOptions.append(entry.getValue().getText()); - } - } + Token token = tokenStream.get(tokenIndex); + int tokenType = token.getType(); + if (tokenType == POUND) { + break; } - buf.append(tok.getText()); // add actual text of the current token to the rewritten alternative - i++; // move to the next token + GrammarAST node = altAST.getNodeWithTokenIndex(token.getTokenIndex()); + buffer.append(token.getText()); // add actual text of the current token to the rewritten alternative + boolean includeTokenOption = !ignoredOptionTokenIndexes.contains(tokenIndex); + tokenIndex++; // Are there args on a rule? - if ( tok.getType()==RULE_REF && i<=tokenStopIndex && tokenStream.get(i).getType()==ARG_ACTION ) { - buf.append('['+tokenStream.get(i).getText()+']'); - i++; + if (tokenType == RULE_REF && tokenIndex <= tokenStopIndex && tokenStream.get(tokenIndex).getType() == ARG_ACTION) { + buffer.append('[').append(tokenStream.get(tokenIndex).getText()).append(']'); + tokenIndex++; } - // now that we have the actual element, we can add the options. - if (elementOptions.length() > 0) { - buf.append('<').append(elementOptions).append('>'); + if (includeTokenOption && node != null && (tokenType == TOKEN_REF || tokenType == STRING_LITERAL || tokenType == RULE_REF)) { + buffer.append("'); } } - return buf.toString(); - } - - public int precedence(int alt) { - return numAlts-alt+1; - } - - // Assumes left assoc - public int nextPrecedence(int alt) { - int p = precedence(alt); - if ( altAssociativity.get(alt)==ASSOC.right ) return p; - return p+1; - } - @Override - public String toString() { - return "PrecRuleOperatorCollector{" + - "binaryAlts=" + binaryAlts + - ", ternaryAlts=" + ternaryAlts + - ", suffixAlts=" + suffixAlts + - ", prefixAndOtherAlts=" +prefixAndOtherAlts+ - '}'; + return buffer.toString(); } } diff --git a/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleTransformer.java b/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleTransformer.java index 0664fab15..67c058b5b 100644 --- a/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleTransformer.java +++ b/tool/src/org/antlr/v5/analysis/LeftRecursiveRuleTransformer.java @@ -9,7 +9,6 @@ import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.ParserRuleReturnScope; -import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.antlr.v5.Tool; import org.antlr.v5.misc.OrderedHashMap; @@ -64,16 +63,18 @@ public LeftRecursiveRuleTransformer(GrammarRootAST ast, Collection rules, public void translateLeftRecursiveRules() { String language = g.getLanguage(); // translate all recursive rules - List leftRecursiveRuleNames = new ArrayList(); + List leftRecursiveRuleNames = new ArrayList<>(); for (Rule r : rules) { - if ( !Grammar.isTokenName(r.name) ) { - if ( LeftRecursiveRuleAnalyzer.hasImmediateRecursiveRuleRefs(r.ast, r.name) ) { - boolean fitsPattern = translateLeftRecursiveRule(ast, (LeftRecursiveRule)r, language); - if ( fitsPattern ) { + if (!Grammar.isTokenName(r.name)) { + if (r instanceof LeftRecursiveRule) { + LeftRecursiveRule leftRecursiveRule = (LeftRecursiveRule) r; + if (!leftRecursiveRule.ruleInfo.nonConformingLeftRecursion) { + translateLeftRecursiveRule(ast, leftRecursiveRule, language); leftRecursiveRuleNames.add(r.name); } - else { // better given an error that non-conforming left-recursion exists - tool.errMgr.grammarError(ErrorType.NONCONFORMING_LR_RULE, g.fileName, ((GrammarAST)r.ast.getChild(0)).token, r.name); + else { + // TODO: better given an error that non-conforming left-recursion exists + tool.errMgr.grammarError(ErrorType.NONCONFORMING_LR_RULE, g.fileName, ((GrammarAST) r.ast.getChild(0)).token, r.name); } } } @@ -90,31 +91,19 @@ public void translateLeftRecursiveRules() { } } - /** Return true if successful */ - public boolean translateLeftRecursiveRule(GrammarRootAST ast, + public void translateLeftRecursiveRule(GrammarRootAST ast, LeftRecursiveRule r, String language) { - //tool.log("grammar", ruleAST.toStringTree()); GrammarAST prevRuleAST = r.ast; - String ruleName = prevRuleAST.getChild(0).getText(); - LeftRecursiveRuleAnalyzer leftRecursiveRuleWalker = - new LeftRecursiveRuleAnalyzer(prevRuleAST, tool, ruleName, language); - boolean isLeftRec; - try { -// System.out.println("TESTING ---------------\n"+ -// leftRecursiveRuleWalker.text(ruleAST)); - isLeftRec = leftRecursiveRuleWalker.rec_rule(); - } - catch (RecognitionException re) { - isLeftRec = false; // didn't match; oh well - } - if ( !isLeftRec ) return false; + + LeftRecursiveRuleAnalyzer leftRecursiveRuleAnalyzer = + new LeftRecursiveRuleAnalyzer(tool, r, prevRuleAST.g.tokenStream, language); + leftRecursiveRuleAnalyzer.analyze(); // replace old rule's AST; first create text of altered rule GrammarAST RULES = (GrammarAST)ast.getFirstChildWithType(ANTLRParser.RULES); - String newRuleText = leftRecursiveRuleWalker.getArtificialOpPrecRule(); -// System.out.println("created: "+newRuleText); + String newRuleText = leftRecursiveRuleAnalyzer.getArtificialOpPrecRule(); // now parse within the context of the grammar that originally created // the AST we are transforming. This could be an imported grammar so // we cannot just reference this.g because the role might come from @@ -143,16 +132,15 @@ public boolean translateLeftRecursiveRule(GrammarRootAST ast, basics.visit(t, "rule"); // track recursive alt info for codegen - r.recPrimaryAlts = new ArrayList(); - r.recPrimaryAlts.addAll(leftRecursiveRuleWalker.prefixAndOtherAlts); + r.recPrimaryAlts = new ArrayList<>(); + r.recPrimaryAlts.addAll(leftRecursiveRuleAnalyzer.prefixAndOtherAlts); if (r.recPrimaryAlts.isEmpty()) { tool.errMgr.grammarError(ErrorType.NO_NON_LR_ALTS, g.fileName, ((GrammarAST)r.ast.getChild(0)).getToken(), r.name); } - r.recOpAlts = new OrderedHashMap(); - r.recOpAlts.putAll(leftRecursiveRuleWalker.binaryAlts); - r.recOpAlts.putAll(leftRecursiveRuleWalker.ternaryAlts); - r.recOpAlts.putAll(leftRecursiveRuleWalker.suffixAlts); + r.recOpAlts = new OrderedHashMap<>(); + r.recOpAlts.putAll(leftRecursiveRuleAnalyzer.binaryAlts); + r.recOpAlts.putAll(leftRecursiveRuleAnalyzer.suffixAlts); // walk alt info records and set their altAST to point to appropriate ALT subtree // from freshly created AST @@ -169,7 +157,7 @@ public boolean translateLeftRecursiveRule(GrammarRootAST ast, // define labels on recursive rule refs we delete; they don't point to nodes of course // these are so $label in action translation works - for (Pair pair : leftRecursiveRuleWalker.leftRecursiveRuleRefLabels) { + for (Pair pair : leftRecursiveRuleAnalyzer.leftRecursiveRuleRefLabels) { GrammarAST labelNode = pair.a; GrammarAST labelOpNode = (GrammarAST)labelNode.getParent(); GrammarAST elementNode = (GrammarAST)labelOpNode.getChild(1); @@ -177,10 +165,9 @@ public boolean translateLeftRecursiveRule(GrammarRootAST ast, r.alt[1].labelDefs.map(labelNode.getText(), lp); } // copy to rule from walker - r.leftRecursiveRuleRefLabels = leftRecursiveRuleWalker.leftRecursiveRuleRefLabels; + r.leftRecursiveRuleRefLabels = leftRecursiveRuleAnalyzer.leftRecursiveRuleRefLabels; tool.log("grammar", "added: "+t.toStringTree()); - return true; } public RuleAST parseArtificialRule(final Grammar g, String ruleText) { diff --git a/tool/src/org/antlr/v5/analysis/RuleAltInfo.java b/tool/src/org/antlr/v5/analysis/RuleAltInfo.java new file mode 100644 index 000000000..d494d8da3 --- /dev/null +++ b/tool/src/org/antlr/v5/analysis/RuleAltInfo.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012-present The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.antlr.v5.analysis; + +import org.antlr.v5.tool.ast.AltAST; +import org.antlr.v5.tool.ast.GrammarAST; +import org.antlr.v5.tool.ast.RuleRefAST; + +import java.util.HashSet; + +public class RuleAltInfo { + public int number; // original alt index (from 1) + public AltAST ast; + public String label; + public AltType type = AltType.other; + public boolean nonConformingLeftRecursion = false; + public AssocType assocType = AssocType.left; + public HashSet leftRecursiveLeftmostRuleRefs = new HashSet<>(); + public HashSet leftRecursiveRightmostRuleRefs = new HashSet<>(); + public GrammarAST leftRecursiveRuleRefLabel; + + public boolean isLeftRecursive() { + return type == AltType.suffixLR || type == AltType.binaryLR; + } +} diff --git a/tool/src/org/antlr/v5/analysis/RuleAltInfoExtractor.java b/tool/src/org/antlr/v5/analysis/RuleAltInfoExtractor.java new file mode 100644 index 000000000..2a331e01e --- /dev/null +++ b/tool/src/org/antlr/v5/analysis/RuleAltInfoExtractor.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2012-present The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.antlr.v5.analysis; + +import org.antlr.runtime.tree.Tree; +import org.antlr.v5.Tool; +import org.antlr.v5.tool.ErrorType; +import org.antlr.v5.tool.ast.ActionAST; +import org.antlr.v5.tool.ast.AltAST; +import org.antlr.v5.tool.ast.GrammarAST; +import org.antlr.v5.tool.ast.RuleRefAST; + +import static org.antlr.v5.parse.ANTLRParser.*; +import static org.antlr.v5.parse.ANTLRParser.RULE_REF; + +public class RuleAltInfoExtractor { + public final Tool tool; + public final String ruleName; + + public RuleAltInfoExtractor(Tool tool, String ruleName) { + this.tool = tool; + this.ruleName = ruleName; + } + + public RuleAltInfo extract(AltAST altAst, int altNum) { + RuleAltInfo result = new RuleAltInfo(); + result.ast = altAst; + result.number = altNum; + result.label = altAst.altLabel!=null ? altAst.altLabel.getText() : null; + extract(result, altAst, true, true); + return result; + } + + private AltType extract(RuleAltInfo ruleAltInfo, Tree tree, boolean leftmost, boolean rightmost) { + if (tree == null) return AltType.other; + + switch (tree.getType()) { + // Just an alternative + // e + // : e ID + case ALT: + Tree leftChild = tree.getChild(0); + Tree rightChild = getLastNotActionAst(tree); + AltType leftmostAltType = extract(ruleAltInfo, leftChild, leftmost, false); + AltType rightmostAltType = extract(ruleAltInfo, rightChild, false, rightmost); + boolean matchRightmostAlt = isMatchRule(rightmostAltType); + if (isMatchRule(leftmostAltType)) { + if (matchRightmostAlt) { + if (leftChild == rightChild) { + // a : a | 'B'; + ruleAltInfo.nonConformingLeftRecursion = true; + } + ruleAltInfo.type = AltType.binaryLR; + } else { + ruleAltInfo.type = leftmostAltType; + } + } else { + ruleAltInfo.type = matchRightmostAlt ? rightmostAltType : AltType.other; + } + return ruleAltInfo.type; + // All alternatives should be left recursive + // e + // : (e '+' e | e '-' e) + case BLOCK: + for (int i = 0; i < tree.getChildCount(); i++) { + // TODO + } + break; + // Option + // e + // : e '+' e + case ELEMENT_OPTIONS: + AltType result = extract(ruleAltInfo, tree.getParent().getChild(1), leftmost, rightmost); + String assocString = tree.getChild(0).getChild(1).getText(); + try { + ruleAltInfo.assocType = AssocType.valueOf(assocString); + } catch (IllegalArgumentException e) { + AltAST altAst = ruleAltInfo.ast; + tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION_VALUE, + altAst.g.fileName, altAst.getOptionAST("assoc").getToken(), "assoc", assocString); + } + return result; + // Label + // e + // : l1=e '+' l2=e + case ASSIGN: + case PLUS_ASSIGN: + if (leftmost) { + ruleAltInfo.leftRecursiveRuleRefLabel = (GrammarAST) tree.getChild(0); + } + return extract(ruleAltInfo, tree.getChild(1), leftmost, rightmost); + case RULE_REF: + Tree firstChild = tree.getChild(0); + if (firstChild instanceof ActionAST) { + // a : a[4] 'x' | ... + ruleAltInfo.nonConformingLeftRecursion = true; + } + + if (tree.getText().equals(ruleName)) { + if (leftmost) { + ruleAltInfo.leftRecursiveLeftmostRuleRefs.add((RuleRefAST) tree); + return AltType.suffixLR; + } else if (rightmost) { + ruleAltInfo.leftRecursiveRightmostRuleRefs.add((RuleRefAST) tree); + return AltType.prefix; + } else { + return AltType.other; + } + } else { + return AltType.other; + } + } + + return AltType.other; + } + + private Tree getLastNotActionAst(Tree tree) { + for (int i = tree.getChildCount() - 1; i >= 0; i--) { + Tree child = tree.getChild(i); + int type = child.getType(); + if (type != ACTION && type != SEMPRED) { + return child; + } + } + return null; + } + + public static boolean isMatchRule(AltType altType) { + return altType != null && altType != AltType.other; + } +} diff --git a/tool/src/org/antlr/v5/analysis/RuleInfo.java b/tool/src/org/antlr/v5/analysis/RuleInfo.java new file mode 100644 index 000000000..2730335bc --- /dev/null +++ b/tool/src/org/antlr/v5/analysis/RuleInfo.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2012-present The ANTLR Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.antlr.v5.analysis; + +import org.antlr.runtime.tree.Tree; +import org.antlr.v5.Tool; +import org.antlr.v5.tool.ast.AltAST; +import org.antlr.v5.tool.ast.GrammarAST; +import org.antlr.v5.tool.ast.RuleAST; + +import java.util.ArrayList; +import java.util.List; + +import static org.antlr.v5.parse.GrammarTreeVisitor.BLOCK; +import static org.antlr.v5.parse.GrammarTreeVisitor.RETURNS; + +public class RuleInfo { + public final List ruleAltInfos; + public final boolean isLeftRecursive; + public final boolean nonConformingLeftRecursion; + public final GrammarAST retvals; + + public RuleInfo(List ruleAltInfos, GrammarAST retvals, boolean leftRecursive, boolean nonConformingLeftRecursion) { + this.ruleAltInfos = ruleAltInfos; + this.isLeftRecursive = leftRecursive; + this.nonConformingLeftRecursion = nonConformingLeftRecursion; + this.retvals = retvals; + } + + /** + * Match (RULE RULE_REF (BLOCK (ALT .*) (ALT RULE_REF[self] .*) (ALT .*))) + * Match (RULE RULE_REF (BLOCK (ALT .*) (ALT (ASSIGN ID RULE_REF[self]) .*) (ALT .*))) + */ + public static RuleInfo collectRuleInfo(Tool tool, RuleAST ruleAST) { + List ruleAltInfos = new ArrayList<>(); + if ( ruleAST==null ) return new RuleInfo(ruleAltInfos, null, false, false); + GrammarAST block = (GrammarAST)ruleAST.getFirstChildWithType(BLOCK); + if ( block==null ) return new RuleInfo(ruleAltInfos, null, false, false); + + GrammarAST retvals = null; + Tree returns = ruleAST.getFirstChildWithType(RETURNS); + if (returns != null) { + retvals = (GrammarAST)returns.getChild(0); + } + + List children = block.getChildren(); + int n = children.size(); + RuleAltInfoExtractor ruleAltInfoExtractor = new RuleAltInfoExtractor(tool, ruleAST.getRuleName()); + boolean isLeftRecursive = false; + boolean nonConformingLeftRecursion = false; + for (int i = 0; i < n; i++) { + RuleAltInfo ruleAltInfo = ruleAltInfoExtractor.extract((AltAST) children.get(i), i + 1); + ruleAltInfos.add(ruleAltInfo); + if (ruleAltInfo.isLeftRecursive()) { + isLeftRecursive = true; + if (ruleAltInfo.nonConformingLeftRecursion) { + nonConformingLeftRecursion = true; + } + } + } + return new RuleInfo(ruleAltInfos, retvals, isLeftRecursive, nonConformingLeftRecursion); + } +} diff --git a/tool/src/org/antlr/v5/parse/LeftRecursiveRuleWalker.g b/tool/src/org/antlr/v5/parse/LeftRecursiveRuleWalker.g deleted file mode 100644 index c67776d07..000000000 --- a/tool/src/org/antlr/v5/parse/LeftRecursiveRuleWalker.g +++ /dev/null @@ -1,224 +0,0 @@ -/* - * [The "BSD license"] - * Copyright (c) 2012-2016 Terence Parr - * Copyright (c) 2012-2016 Sam Harwell - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** Find left-recursive rules */ -tree grammar LeftRecursiveRuleWalker; - -options { - tokenVocab=ANTLRParser; - ASTLabelType=GrammarAST; -} - -@header { -package org.antlr.v5.parse; - -import org.antlr.v5.misc.*; -import org.antlr.v5.tool.*; -import org.antlr.v5.tool.ast.*; -} - -@members { -private String ruleName; -private int currentOuterAltNumber; // which outer alt of rule? -public int numAlts; // how many alts for this rule total? - -public void setAltAssoc(AltAST altTree, int alt) {} -public void binaryAlt(AltAST altTree, int alt) {} -public void prefixAlt(AltAST altTree, int alt) {} -public void suffixAlt(AltAST altTree, int alt) {} -public void otherAlt(AltAST altTree, int alt) {} -public void setReturnValues(GrammarAST t) {} -} - -@rulecatch { } - -// TODO: can get parser errors for not matching pattern; make them go away -public -rec_rule returns [boolean isLeftRec] -@init -{ - currentOuterAltNumber = 1; -} - : ^( r=RULE id=RULE_REF {ruleName=$id.getText();} - ruleModifier? -// (ARG_ACTION)? shouldn't allow args, right? - (^(RETURNS a=ARG_ACTION {setReturnValues($a);}))? -// ( ^(THROWS .+) )? don't allow - ( ^(LOCALS ARG_ACTION) )? // TODO: copy these to gen'd code - ( ^(OPTIONS .*) - | ^(AT ID ACTION) // TODO: copy - )* - ruleBlock {$isLeftRec = $ruleBlock.isLeftRec;} - exceptionGroup - ) - ; - -exceptionGroup - : exceptionHandler* finallyClause? - ; - -exceptionHandler - : ^(CATCH ARG_ACTION ACTION) - ; - -finallyClause - : ^(FINALLY ACTION) - ; - -ruleModifier - : PUBLIC - | PRIVATE - | PROTECTED - ; - -ruleBlock returns [boolean isLeftRec] -@init{boolean lr=false; this.numAlts = $start.getChildCount();} - : ^( BLOCK - ( - o=outerAlternative - {if ($o.isLeftRec) $isLeftRec = true;} - {currentOuterAltNumber++;} - )+ - ) - ; - -/** An alt is either prefix, suffix, binary, or ternary operation or "other" */ -outerAlternative returns [boolean isLeftRec] - : (binary)=> binary - {binaryAlt((AltAST)$start, currentOuterAltNumber); $isLeftRec=true;} - | (prefix)=> prefix - {prefixAlt((AltAST)$start, currentOuterAltNumber);} - | (suffix)=> suffix - {suffixAlt((AltAST)$start, currentOuterAltNumber); $isLeftRec=true;} - | nonLeftRecur {otherAlt((AltAST)$start, currentOuterAltNumber);} - ; - -binary - : ^( ALT elementOptions? recurse element* recurse epsilonElement* ) - {setAltAssoc((AltAST)$ALT,currentOuterAltNumber);} - ; - -prefix - : ^( ALT elementOptions? - element+ - recurse epsilonElement* - ) - {setAltAssoc((AltAST)$ALT,currentOuterAltNumber);} - ; - -suffix - : ^( ALT elementOptions? recurse element+ ) - {setAltAssoc((AltAST)$ALT,currentOuterAltNumber);} - ; - -nonLeftRecur - : ^(ALT elementOptions? element+) - ; - -recurse - : ^(ASSIGN ID recurseNoLabel) - | ^(PLUS_ASSIGN ID recurseNoLabel) - | recurseNoLabel - ; - -recurseNoLabel : {((CommonTree)input.LT(1)).getText().equals(ruleName)}? RULE_REF; - -token returns [GrammarAST t=null] - : ^(ASSIGN ID s=token {$t = $s.t;}) - | ^(PLUS_ASSIGN ID s=token {$t = $s.t;}) - | b=STRING_LITERAL {$t = $b;} - | ^(b=STRING_LITERAL elementOptions) {$t = $b;} - | ^(c=TOKEN_REF elementOptions) {$t = $c;} - | c=TOKEN_REF {$t = $c;} - ; - -elementOptions - : ^(ELEMENT_OPTIONS elementOption*) - ; - -elementOption - : ID - | ^(ASSIGN ID ID) - | ^(ASSIGN ID STRING_LITERAL) - | ^(ASSIGN ID ACTION) - | ^(ASSIGN ID INT) - ; - -element - : atom - | ^(NOT element) - | ^(RANGE atom atom) - | ^(ASSIGN ID element) - | ^(PLUS_ASSIGN ID element) - | ^(SET setElement+) - | RULE_REF - | ebnf - | epsilonElement - ; - -epsilonElement - : ACTION - | SEMPRED - | EPSILON - | ^(ACTION elementOptions) - | ^(SEMPRED elementOptions) - ; - -setElement - : ^(STRING_LITERAL elementOptions) - | ^(TOKEN_REF elementOptions) - | STRING_LITERAL - | TOKEN_REF - ; - -ebnf: block - | ^( OPTIONAL block ) - | ^( CLOSURE block ) - | ^( POSITIVE_CLOSURE block ) - ; - -block - : ^(BLOCK ACTION? alternative+) - ; - -alternative - : ^(ALT elementOptions? element+) - ; - -atom - : ^(RULE_REF ARG_ACTION? elementOptions?) - | ^(STRING_LITERAL elementOptions) - | STRING_LITERAL - | ^(TOKEN_REF elementOptions) - | TOKEN_REF - | ^(WILDCARD elementOptions) - | WILDCARD - | ^(DOT ID element) - ; diff --git a/tool/src/org/antlr/v5/semantics/RuleCollector.java b/tool/src/org/antlr/v5/semantics/RuleCollector.java index 30a05194a..665efe684 100644 --- a/tool/src/org/antlr/v5/semantics/RuleCollector.java +++ b/tool/src/org/antlr/v5/semantics/RuleCollector.java @@ -6,7 +6,7 @@ package org.antlr.v5.semantics; -import org.antlr.v5.analysis.LeftRecursiveRuleAnalyzer; +import org.antlr.v5.analysis.RuleInfo; import org.antlr.v5.misc.OrderedHashMap; import org.antlr.v5.misc.Utils; import org.antlr.v5.parse.GrammarTreeVisitor; @@ -54,8 +54,9 @@ public void discoverRule(RuleAST rule, GrammarAST ID, { int numAlts = block.getChildCount(); Rule r; - if ( LeftRecursiveRuleAnalyzer.hasImmediateRecursiveRuleRefs(rule, ID.getText()) ) { - r = new LeftRecursiveRule(g, ID.getText(), rule); + RuleInfo ruleInfo = RuleInfo.collectRuleInfo(g.tool, rule); + if (ruleInfo.isLeftRecursive) { + r = new LeftRecursiveRule(g, ID.getText(), rule, ruleInfo); } else { r = new Rule(g, ID.getText(), rule, numAlts); diff --git a/tool/src/org/antlr/v5/tool/LeftRecursiveRule.java b/tool/src/org/antlr/v5/tool/LeftRecursiveRule.java index 7b9cc6336..fd5d9a246 100644 --- a/tool/src/org/antlr/v5/tool/LeftRecursiveRule.java +++ b/tool/src/org/antlr/v5/tool/LeftRecursiveRule.java @@ -7,6 +7,7 @@ package org.antlr.v5.tool; import org.antlr.v5.analysis.LeftRecursiveRuleAltInfo; +import org.antlr.v5.analysis.RuleInfo; import org.antlr.v5.misc.OrderedHashMap; import org.antlr.v5.runtime.misc.Pair; import org.antlr.v5.tool.ast.AltAST; @@ -22,15 +23,17 @@ public class LeftRecursiveRule extends Rule { public List recPrimaryAlts; public OrderedHashMap recOpAlts; public RuleAST originalAST; + public RuleInfo ruleInfo; /** Did we delete any labels on direct left-recur refs? Points at ID of ^(= ID el) */ public List> leftRecursiveRuleRefLabels = new ArrayList>(); - public LeftRecursiveRule(Grammar g, String name, RuleAST ast) { + public LeftRecursiveRule(Grammar g, String name, RuleAST ast, RuleInfo ruleInfo) { super(g, name, ast, 1); originalAST = ast; alt = new Alternative[numberOfAlts+1]; // always just one + this.ruleInfo = ruleInfo; for (int i=1; i<=numberOfAlts; i++) alt[i] = new Alternative(this, i); }