diff --git a/tool/src/org/antlr/v4/codegen/model/ElementFrequenciesVisitor.java b/tool/src/org/antlr/v4/codegen/model/ElementFrequenciesVisitor.java index 1c9e70436..f61ce8abb 100644 --- a/tool/src/org/antlr/v4/codegen/model/ElementFrequenciesVisitor.java +++ b/tool/src/org/antlr/v4/codegen/model/ElementFrequenciesVisitor.java @@ -96,7 +96,7 @@ public class ElementFrequenciesVisitor extends GrammarTreeVisitor { } assert a != SENTINEL; - FrequencySet result = combineAndClip(a, b, 1); + FrequencySet result = combineAndClip(a, b, Integer.MAX_VALUE); for (Map.Entry entry : result.entrySet()) { entry.getValue().v = Math.min(a.count(entry.getKey()), b.count(entry.getKey())); } @@ -177,6 +177,31 @@ public class ElementFrequenciesVisitor extends GrammarTreeVisitor { minFrequencies.push(combineAndClip(minFrequencies.pop(), minFrequencies.pop(), 2)); } + @Override + protected void enterBlockSet(GrammarAST tree) { + frequencies.push(new FrequencySet()); + minFrequencies.push(new FrequencySet()); + } + + @Override + protected void exitBlockSet(GrammarAST tree) { + for (Map.Entry entry : frequencies.peek().entrySet()) { + // This visitor counts a block set as a sequence of elements, not a + // sequence of alternatives of elements. Reset the count back to 1 + // for all items when leaving the set to ensure duplicate entries in + // the set are treated as a maximum of one item. + entry.getValue().v = 1; + } + + if (minFrequencies.peek().size() > 1) { + // Everything is optional + minFrequencies.peek().clear(); + } + + frequencies.push(combineAndClip(frequencies.pop(), frequencies.pop(), 2)); + minFrequencies.push(combineAndClip(minFrequencies.pop(), minFrequencies.pop(), 2)); + } + @Override protected void exitSubrule(GrammarAST tree) { if (tree.getType() == CLOSURE || tree.getType() == POSITIVE_CLOSURE) { @@ -185,7 +210,7 @@ public class ElementFrequenciesVisitor extends GrammarTreeVisitor { } } - if (tree.getType() == CLOSURE) { + if (tree.getType() == CLOSURE || tree.getType() == OPTIONAL) { // Everything inside a closure is optional, so the minimum // number of occurrences for all elements is 0. minFrequencies.peek().clear(); diff --git a/tool/src/org/antlr/v4/codegen/model/RuleFunction.java b/tool/src/org/antlr/v4/codegen/model/RuleFunction.java index ad6f049ad..c211720a9 100644 --- a/tool/src/org/antlr/v4/codegen/model/RuleFunction.java +++ b/tool/src/org/antlr/v4/codegen/model/RuleFunction.java @@ -171,8 +171,9 @@ public class RuleFunction extends OutputModelObject { */ public Set getDeclsForAllElements(List altASTs) { Set needsList = new HashSet(); - Set optional = new HashSet(); + Set nonOptional = new HashSet(); List allRefs = new ArrayList(); + boolean firstAlt = true; for (AltAST ast : altASTs) { IntervalSet reftypes = new IntervalSet(RULE_REF, TOKEN_REF); List refs = ast.getNodesWithType(reftypes); @@ -182,13 +183,22 @@ public class RuleFunction extends OutputModelObject { FrequencySet altFreq = minAndAltFreq.b; for (GrammarAST t : refs) { String refLabelName = t.getText(); - if (minFreq.count(refLabelName) == 0) { - optional.add(refLabelName); - } if ( altFreq.count(refLabelName)>1 ) { needsList.add(refLabelName); } + + if (firstAlt && minFreq.count(refLabelName) != 0) { + nonOptional.add(refLabelName); + } } + + for (String ref : nonOptional.toArray(new String[nonOptional.size()])) { + if (minFreq.count(ref) == 0) { + nonOptional.remove(ref); + } + } + + firstAlt = false; } Set decls = new LinkedHashSet(); for (GrammarAST t : allRefs) { @@ -196,7 +206,7 @@ public class RuleFunction extends OutputModelObject { List d = getDeclForAltElement(t, refLabelName, needsList.contains(refLabelName), - optional.contains(refLabelName)); + !nonOptional.contains(refLabelName)); decls.addAll(d); } return decls;