/* * xtc - The eXTensible Compiler * Copyright (C) 2004-2009 Robert Grimm * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package xtc.parser; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import xtc.Constants; import xtc.tree.Attribute; import xtc.tree.Visitor; import xtc.type.AST; import xtc.type.Type; import xtc.type.Wildcard; import xtc.util.Runtime; /** * Visitor to transform productions. This visitor lifts repetitions, * options, and nested choices into their own productions, desugars * reptitions and options, and adds the appropriate semantic values to * expressions that require them. It also ensures that repeated, * optional, and predicated elements are sequences, thus fulfilling * the requirements for {@link CodeGenerator code generation}. Before * applying this visitor on a grammar, the grammar must have been * {@link Simplifier simplified}, all text-only productions should be * marked as such by applying the {@link TextTester} visitor, and all * token-level productions should be marked as such by applying the * {@link Tokenizer} visitor. Note that {@link Generifier} adds the * appropriate semantic values for generic productions. Further note * that repetitions in transient productions are not desugared (nor * lifted) if the corresponding command line option * ("<code>-Orepeated</code") is set. Also, options are not desugared * (nor lifted) if the corresponding command line option * ("<code>-Ooptional</code>") is set. Finally, note that this * visitor assumes that the entire grammar is contained in a single * module. * * <p />This visitor may report errors to the user. * * @author Robert Grimm * @version $Revision: 1.128 $ */ public class Transformer extends Visitor { /** * The variable marker for bindings in repeated or optional * elements. */ public static final String ELEMENT_MARKER = "el"; // ========================================================================= /** * Visitor to add semantic values. * * <p />This visitor assumes that each production's top-level * element is an ordered choice and that each choice's alternatives * are sequences. */ public class Deducer extends Visitor { /** The list of elements. */ protected List<Element> elements; /** Create a new deducer. */ public Deducer() { /* Nothing to do. */ } /** Visit the specified production. */ public void visit(Production p) { // Only try to add values to productions that (1) are not // generic or list-valued, (2) are not directly left-recursive, // or (3) do not contain a quantification (i.e., repetition or // option) that will be desugared. Element e = Analyzer.strip(p.choice); if ((! isGeneric()) && (! isList()) && (! isLeftRecursive()) && ((! (e instanceof Repetition)) || retainRepetitions()) && ((! (e instanceof Option)) || retainOptions())) { if (runtime.test("optionVerbose")) { System.err.println("[Deducing semantic value for " + p.qName + "]"); } elements = new ArrayList<Element>(); dispatch(p.choice); } } /** Visit the specified choice. */ public void visit(OrderedChoice c) { // Process the alternatives. for (Sequence alt : c.alternatives) dispatch(alt); } /** Visit the specified sequence. */ public void visit(Sequence s) { // Remember the current number of elements. final int base = elements.size(); // Process the elements of the sequence. for (Iterator<Element> iter = s.elements.iterator(); iter.hasNext(); ) { Element e = iter.next(); if ((! iter.hasNext()) && (e instanceof OrderedChoice)) { // Continue with the trailing choice. dispatch(e); } else { // Add the current element to the list of traversed elements. elements.add(e); } } // Deduce the semantic value if this sequence does not have a // trailing choice. if (! s.hasTrailingChoice()) { if (isVoid()) { s.add(NullValue.VALUE); } else if (isTextOnly()) { String text = analyzer.matchingText(new Sequence(elements)); if ((null == text) || (! runtime.test("optimizeTerminals"))) { s.add(StringValue.VALUE); } else { s.add(new StringValue(text)); } } else if (isToken()) { String text = analyzer.matchingText(new Sequence(elements)); if ((null == text) || (! runtime.test("optimizeTerminals"))) { s.add(TokenValue.VALUE); } else { s.add(new TokenValue(text)); } } else if (elements.isEmpty()) { s.add(NullValue.VALUE); } else { Binding b = analyzer.bind(elements); if (null != b) { if (Analyzer.isSynthetic(b.name)) { // Patch the variable name to be the semantic value. b.name = CodeGenerator.VALUE; } else if (! CodeGenerator.VALUE.equals(b.name)) { // Preserve the user-specified variable name. s.add(new BindingValue(b)); } } } } // Patch back any added binding. if ((! isVoid()) && (! isTextOnly()) && (! isToken())) { int size = s.size(); // Ignore trailing choices and value elements. if (s.hasTrailingChoice() || ((0 != size) && (s.get(size-1) instanceof ValueElement))) { size--; } // Iterate over all other elements. for (int i=0; i<size; i++) { Element e = elements.get(base + i); if (s.get(i) != e) s.elements.set(i, e); } } // Remove any elements added by processing the sequence. if (0 == base) { elements.clear(); } else { elements.subList(base, elements.size()).clear(); } } } // ========================================================================= /** * Visitor to lift nested choices, repetitions, and options. This * visitor also ensures that repeated, optional, and predicated * elements are sequences. */ public class Lifter extends GrammarVisitor { /** Create a new lifter. */ public Lifter() { super(Transformer.this.runtime, Transformer.this.analyzer); } /** * Bind the specified element. * * @param e The element. * @return The bound element. */ protected Binding bind(Element e) { return new Binding(analyzer.variable(ELEMENT_MARKER), e); } /** * Process the specified repeated, optional, or predicated * element. This method processes the specified element, lifting * it and adding a binding for its value, either only if * necessary. The result is a sequence to fulfill the * corresponding code generation requirement. * * @param e The element. * @param bound The flag for whether the element's value is bound. * @return The resulting sequence. */ protected Sequence process(Element e, boolean bound) { /* * (1) If a repeated, optional, or predicated element explicitly * sets a semantic value (i.e., Analyzer.setsValue() returns * true), it must be lifted. Furthermore, a binding must be * used to ensure that the lifted choice has a meaningful type. * * (2) If a repetition or option is bound, the repeated or * optional element must have a binding as well, unless the * element appears within a text-only or token-level production * (whose value is created directly from the buffered input). * * (3) A sequence's value can only be captured through a binding * if it does not explicitly set the semantic value and if it * does not contain a choice as its last element. If either is * the case, the sequence must be lifted. * * (4) A predicated sequence that has a choice as its last * element must be lifted. * * (5) Text-only and token-level productions, by definition, * cannot contain bindings to yyValue nor actions that set * yyValue. However, they may contain value elements, causing * Analyzer.setsValue() to return true. * * (6) Setting isLastElement to true results in an ordered * choice not being lifted, unless the choice appears in a * predicate. */ if (e instanceof OrderedChoice) { if ((! isTextOnly()) && (! isToken()) && (bound || Analyzer.setsValue(e, false))) { // Bind the choice to preserve its value. Lift choice. return Sequence.ensure((Element)dispatch(bind(e))); } else { // Try to leave the choice in place. isLastElement = true; return Sequence.ensure((Element)dispatch(e)); } } else { Sequence s = Sequence.ensure(e); if ((! isTextOnly()) && (! isToken()) && Analyzer.setsValue(e, false)) { // Bind sequence wrapped in choice to preserve value. Lift choice. return Sequence.ensure((Element)dispatch(bind(new OrderedChoice(s)))); } else if ((! isTextOnly()) && (! isToken()) && bound && s.hasTrailingChoice()) { // Bind sequence wrapped in choice to preserve value. Lift choice. return Sequence.ensure((Element)dispatch(bind(new OrderedChoice(s)))); } else if ((! isTextOnly()) && (! isToken()) && bound) { // Add binding to sequence. Binding b = analyzer.bind(s.elements, ELEMENT_MARKER); if (null == b) { runtime.error("unable to deduce value", s); } return Sequence.ensure((Element)dispatch(s)); } else if (isPredicate && s.hasTrailingChoice()) { // Wrap sequence in choice and lift choice. return Sequence.ensure((Element)dispatch(new OrderedChoice(s))); } else { // Process sequence. Try to leave trailing choice in place. isLastElement = true; return Sequence.ensure((Element)dispatch(s)); } } } /** * Create a new production with the specified type, nonterminal, * and ordered choice. This method creates the production, using * the {@link #current() current production's} attributes and * text-only and token-level marks, recursively processes the * productions, and adds it to the grammar. * * @param type The type. * @param nt The nonterminal. * @param c The ordered choice. */ protected void lift(Type type, NonTerminal nt, OrderedChoice c) { // Create the production. FullProduction p = new FullProduction(new ArrayList<Attribute>(current().attributes), type, nt, nt.qualify(analyzer.module().name.name), c); // Document activity under verbose mode. if (runtime.test("optionVerbose")) { System.err.println("[Lifting expression into new production " + p.qName + ']'); } // Do not inherit any public, explicit, stateful, or resetting // attribute. p.attributes.remove(Constants.ATT_PUBLIC); p.attributes.remove(Constants.ATT_EXPLICIT); p.attributes.remove(Constants.ATT_STATEFUL); p.attributes.remove(Constants.ATT_RESETTING); if (isTextOnly()) { TextTester.markTextOnly(p, runtime.test("optionVerbose")); } else if (isToken()) { Tokenizer.markToken(p, runtime.test("optionVerbose")); } // Recursively process the new production. Transformer.this.process(p); // Add the new production to the grammar. analyzer.add(p); } /** Visit the specified ordered choice. */ public Element visit(OrderedChoice c) { boolean top = isTopLevel; isTopLevel = false; boolean voided = isVoided; isVoided = false; boolean bound = isBound; isBound = false; boolean last = isLastElement; transformInPlace = top && (Analyzer.strip(c) instanceof Quantification); if ((top || last) && (! isPredicate)) { // Continue processing. final int size = c.alternatives.size(); for (int i=0; i<size; i++) { isLastElement = top || last; c.alternatives.set(i, (Sequence)dispatch(c.alternatives.get(i))); } isLastElement = false; return c; } else { // Lift choice. NonTerminal nt = analyzer.choice(); Type type; if (isTextOnly()) { type = AST.STRING; } else if (isToken()) { type = AST.TOKEN; } else if (bound || ((isGeneric() || isList()) && (! voided) && (! isPredicate))) { type = AST.ANY; } else { type = AST.VOID; } lift(type, nt, c); isLastElement = false; return nt; } } /** Visit the specified repetition. */ public Element visit(Repetition r) { isTopLevel = false; boolean voided = isVoided; isVoided = false; boolean bound = isBound; isBound = false; isLastElement = false; boolean inPlace = transformInPlace; transformInPlace = false; // If the repeated optimization is enabled and the production is // not memoized, do not lift the repetition. Also, if the // repetition is the only top-level element (besides a wrapping // sequence and choice), do not lift the repetition. However, // if the repetition is bound and the production is text-only or // token-level, the repetition must be lifted. Furthermore, if // the repetition is the top-level element in a generic // production and will be desugared, the repetition must be // lifted (since a binding will be added by the Generifier // phase). Finally, if the repetition is the top-level element // in a resetting or stateful production and will be desugared, // the repetition must be lifted. if ((retainRepetitions() && ((! bound) || ((! isTextOnly()) && (! isToken())))) || (inPlace && (! changesState() || retainRepetitions()) && (! isGeneric() || retainRepetitions()))) { // Process the repeated element. boolean b = (bound || (isGeneric() && (! voided) && (! isPredicate)) || (isList() && (! voided) && (! isPredicate)) || (inPlace && (! isVoid()))); r.element = process(r.element, b); return r; } NonTerminal nt = (r.once)? analyzer.plus() : analyzer.star(); Type type; if (isTextOnly()) { type = AST.STRING; } else if (isToken()) { type = AST.TOKEN; } else if (bound || ((isGeneric() || isList()) && (! voided) && (! isPredicate))) { // Note that the type will be patched during desugaring. type = AST.WILD_LIST; } else { type = AST.VOID; } OrderedChoice c = new OrderedChoice(r); c.setLocation(r); lift(type, nt, c); return nt; } /** Visit the specified option. */ public Element visit(Option o) { isTopLevel = false; boolean voided = isVoided; isVoided = false; boolean bound = isBound; isBound = false; isLastElement = false; boolean inPlace = transformInPlace; transformInPlace = false; // If the optional optimization is enabled, do not lift the // option. Also, if the option is the only top-level element // (besides a wrapping sequence and choice), do not lift the // option. However, if the option is bound and the production // is text-only or token-level, the option must be lifted. // Furthermore, if the option is the top-level element in a // generic production and will be desugared, the option must be // lifted (since a binding will be added by the Generifier // phase). Finally, if the option is the top-level element in a // resetting or stateful production and will be desugared, the // option must be lifted. if ((retainOptions() && ((! bound) || ((! isTextOnly()) && (! isToken())))) || (inPlace && (! changesState() || retainOptions()) && (! isGeneric() || retainOptions()))) { // Process the optional element. boolean b = (bound || (isGeneric() && (! voided) && (! isPredicate)) || (isList() && (! voided) && (! isPredicate)) || (inPlace && (! isVoid()))); o.element = process(o.element, b); return o; } NonTerminal nt = analyzer.option(); Type type; if (isTextOnly()) { type = AST.STRING; } else if (isToken()) { type = AST.TOKEN; } else if (bound || ((isGeneric() || isList()) && (! voided) && (! isPredicate))) { type = AST.ANY; } else { type = AST.VOID; } OrderedChoice c = new OrderedChoice(o); c.setLocation(o); lift(type, nt, c); return nt; } /** Visit the specified predicate. */ public Element visit(Predicate p) { isTopLevel = false; isVoided = false; isBound = false; isLastElement = false; boolean predicate = isPredicate; isPredicate = true; p.element = process(p.element, false); isPredicate = predicate; return p; } } // ========================================================================= /** * Visitor to desugar repetitions and options. This visitor assumes * that a production's top-level element is an ordered choice with a * sequence for each alternative. */ public class Desugarer extends Visitor { /** Create a new desugarer. */ public Desugarer() { /* Nothing to do. */ } /** * Process the specified sequence. This method adds a nonterminal * and value element at the end of the specified sequence, * recursing into the sequences of any ordered choice appearing as * a sequence's last element. The current production must be a * void, text-only, or token-level production. * * @param s The sequence. * @param nt The nonterminal or <code>null</code> if no nonterminal * should be added. */ protected void process(Sequence s, NonTerminal nt) { if (s.hasTrailingChoice()) { OrderedChoice c = (OrderedChoice)s.get(s.size()-1); for (Sequence alt : c.alternatives) process(alt, nt); } else { if (null != nt) { s.add(nt); } if (isVoid()) { s.add(NullValue.VALUE); } else if (isTextOnly()) { s.add(StringValue.VALUE); } else if (isToken()) { s.add(TokenValue.VALUE); } else { assert false; } } } /** Visit the specified production. */ public void visit(Production p) { Element e = Analyzer.strip(p.choice); if ((e instanceof Repetition) && (! retainRepetitions())) { if (runtime.test("optionVerbose")) { System.err.println("[Desugaring repetition in " + p.qName + ']'); } // If the repeated element is a sequence without choices and // that sequence's value is a nonterminal, record the // nonterminal in the production's repeated property. Sequence s = (Sequence)((Repetition)e).element; if (! s.hasTrailingChoice()) { Binding b = Analyzer.getBinding(s.elements); if ((null != b) && (b.element instanceof NonTerminal)) { p.setProperty(Properties.REPEATED, b.element); } } // Desugar the repetition. p.choice = (OrderedChoice)dispatch(e); } else if ((e instanceof Option) && (! retainOptions())) { if (runtime.test("optionVerbose")) { System.err.println("[Desugaring option in " + p.qName + ']'); } p.choice = (OrderedChoice)dispatch(e); p.setProperty(Properties.OPTION, Boolean.TRUE); } } /** Visit the specified repetition. */ public Element visit(Repetition r) { Sequence repeated = (Sequence)r.element; // Set up the lists of old and new alternatives. List<Sequence> oldAlternatives; if ((1 == repeated.size()) && repeated.hasTrailingChoice()) { oldAlternatives = ((OrderedChoice)repeated.get(0)).alternatives; } else { oldAlternatives = new ArrayList<Sequence>(1); oldAlternatives.add(repeated); } List<Sequence> newAlternatives = new ArrayList<Sequence>(oldAlternatives.size() + ((r.once)? oldAlternatives.size() : 1)); // Determine the production's generic list type. Type type = Wildcard.TYPE; if ((! isVoid()) && (! isTextOnly()) && (! isToken())) { for (Sequence s : oldAlternatives) { Binding b = Analyzer.getBinding(s.elements); if (null == b) { runtime.error("unable to bind repeated element", s); } else { type = ast.unify(type, analyzer.type(b.element), false); } } type = AST.listOf(ast.concretize(type, AST.ANY)); // If the production is synthetic, patch its type. if (Analyzer.isSynthetic(current().name)) { if (runtime.test("optionVerbose")) { System.err.println("[Adjusting " + current().qName + "'s type to " + ast.extern(type) + ']'); } current().type = type; } } // Process the recursive alternatives. for (Sequence s : oldAlternatives) { if (r.once) { // Copy the base alternative(s) to avoid creating a DAG, // which might lead to problems in later phases that modify // any elements (e.g., the terminal optimizer may convert // character classes into character switches, which // internally still use the character class element but with // the exclusive flag set to false). s = analyzer.copy(s); } if (isVoid() || isTextOnly() || isToken()) { process(s, current().name); } else { Binding b1 = Analyzer.getBinding(s.elements); Binding b2 = new Binding(analyzer.variable(), current().name); if (null != b1) { s.add(b2).add(new ProperListValue(type, b1, b2)); } } newAlternatives.add(s); } // Process the base alternative(s). if (r.once) { for (Sequence s : oldAlternatives) { if (isVoid() || isTextOnly() || isToken()) { process(s, null); } else { Binding b = Analyzer.getBinding(s.elements); if (null != b) { s.add(new ProperListValue(type, b)); } } newAlternatives.add(s); } } else { Sequence s = new Sequence(); if (isVoid()) { s.add(NullValue.VALUE); } else if (isTextOnly()) { s.add(StringValue.VALUE); } else if (isToken()) { s.add(TokenValue.VALUE); } else { s.add(EmptyListValue.VALUE); } newAlternatives.add(s); } // Create the new ordered choice and return it. return new OrderedChoice(newAlternatives); } /** Visit the specified option. */ public Element visit(Option o) { Sequence optional = (Sequence)o.element; List<Sequence> oldAlternatives; if ((1 == optional.size()) && optional.hasTrailingChoice()) { oldAlternatives = ((OrderedChoice)optional.get(0)).alternatives; } else { oldAlternatives = new ArrayList<Sequence>(1); oldAlternatives.add(optional); } List<Sequence> newAlternatives = new ArrayList<Sequence>(oldAlternatives.size() + 1); // The matching alternatives. for (Sequence s : oldAlternatives) { if (isVoid() || isTextOnly() || isToken()) { process(s, null); } else { Binding b = Analyzer.getBinding(s.elements); if (null != b) { // Patch the variable name to be the semantic value. b.name = CodeGenerator.VALUE; } } newAlternatives.add(s); } // The empty alternative. Sequence alt = new Sequence(); if (isTextOnly()) { alt.add(StringValue.VALUE); } else if (isToken()) { alt.add(TokenValue.VALUE); } else { alt.add(NullValue.VALUE); } newAlternatives.add(alt); // Create the new ordered choice and return it. return new OrderedChoice(newAlternatives); } } // ========================================================================= /** * Visitor to deduce a production's type. */ public class Typer extends Visitor { /** The list of elements. */ protected List<Element> elements; /** The current type. */ protected Type type; /** Create a new typer. */ public Typer() { /* Nothing to do. */ } /** Visit the specified production. */ public void visit(Production p) { // Only try to type production's semantic value if the // production is synthetic (i.e. lifted) and the type is the // root type. Note that this condition correctly excludes void, // text-only, token-level, and generic productions. if (Analyzer.isSynthetic(p.name) && AST.isAny(p.type)) { elements = new ArrayList<Element>(); type = Wildcard.TYPE; dispatch(p.choice); if ((! type.isWildcard()) && (! AST.isAny(type))) { // Only adjust the type, if a new more specific type has // been found. if (runtime.test("optionVerbose")) { System.err.println("[Adjusting " + p.qName + "'s type to " + ast.extern(type) + ']'); } p.type = ast.concretize(type, AST.ANY); } } } /** Visit the specified choice. */ public void visit(OrderedChoice c) { // Process the alternatives. for (Sequence alt : c.alternatives) dispatch(alt); } /** Visit the specified sequence. */ public void visit(Sequence s) { // Remember the current number of elements. final int base = elements.size(); // Process the elements of the sequence. for (Iterator<Element> iter = s.elements.iterator(); iter.hasNext(); ) { Element e = iter.next(); if ((! iter.hasNext()) && (e instanceof OrderedChoice)) { // Continue with the trailing choice. dispatch(e); } else { // Add the current element to the list of traversed elements. elements.add(e); } } // Deduce the semantic value. if (! s.hasTrailingChoice()) { Binding b = Analyzer.getBinding(elements); if ((null != b) && (CodeGenerator.VALUE.equals(b.name) || ((0 != s.size()) && (s.get(s.size()-1) instanceof BindingValue) && b.name.equals(((BindingValue)s.get(s.size()-1)).binding.name)))) { // Unify the binding's type with any previously determined // type. type = ast.unify(type, analyzer.type(b.element), false); } else if (! Analyzer.setsNullValue(elements)) { // We don't have any meaningful information. Assume the // worst. type = AST.ANY; } } // Remove any elements added by processing the sequence. if (0 == base) { elements.clear(); } else { elements.subList(base, elements.size()).clear(); } } } // ========================================================================= /** The runtime. */ protected final Runtime runtime; /** The analyzer. */ protected final Analyzer analyzer; /** The type operations. */ protected final AST ast; /** * The flag for whether the current module has the {@link * Constants#ATT_PARSE_TREE parseTree} attribute. */ protected boolean hasParseTree; /** The current (full) production. */ protected FullProduction production; /** * Create a new transformer. * * @param runtime The runtime. * @param analyzer The analyzer utility. * @param ast The type operations. */ public Transformer(Runtime runtime, Analyzer analyzer, AST ast) { this.runtime = runtime; this.analyzer = analyzer; this.ast = ast; } /** * Process the specified production. This method may be called * recursively while processing another production. * * @param p The production to process. */ protected void process(FullProduction p) { FullProduction saved = production; production = p; new Deducer().dispatch(production); new Lifter().dispatch(production); new Desugarer().dispatch(production); new Typer().dispatch(p); production = saved; } /** * Determine whether the current production is memoized. * * @return <code>true</code> if the current production is memoized. */ protected boolean isMemoized() { return production.isMemoized(); } /** * Determine whether the current production changes the global * parser state. * * @return <code>true</code> if the current production is resetting * or stateful. */ public boolean changesState() { return production.hasAttribute(Constants.ATT_RESETTING) || production.hasAttribute(Constants.ATT_STATEFUL); } /** * Determine whether the current production is void. * * @return <code>true</code> if the current production is void. */ protected boolean isVoid() { return AST.isVoid(production.type); } /** * Determine whether the current production is text-only. * * @return <code>true</code> if the current production is text-only. */ protected boolean isTextOnly() { return production.getBooleanProperty(Properties.TEXT_ONLY); } /** * Determine whether the current production is token-level. * * @return <code>true</code> if the current production is token-level. */ protected boolean isToken() { return production.getBooleanProperty(Properties.TOKEN); } /** * Determine whether the current production is generic. * * @return <code>true</code> if the current production is generic. */ protected boolean isGeneric() { return Generifier.isGeneric(production); } /** * Determine whether the current production has a list value. * * @return <code>true</code> if the current production has a list value. */ protected boolean isList() { return AST.isList(production.type); } /** * Determine whether the current production is a directly * left-recursive production. * * @return <code>true</code> if the current production is directly * left-recursive. */ protected boolean isLeftRecursive() { return DirectLeftRecurser.isTransformable(production); } /** * Get the current production. * * @return The current production. */ protected Production current() { return production; } /** * Determine whether the current production may contain repetitions. * * @return <code>true</code> if the production may contain * repetitions. */ protected boolean retainRepetitions() { // Note: when reducing a grammar to only the expressions that // contribute to the AST, we retain repetitions to reduce clutter. return ((runtime.test("optimizeRepeated") && (! isMemoized())) || runtime.test("optionValued")); } /** * Determine whether the current production may contain options. * * @return <code>true</code> if the production may contain options. */ protected boolean retainOptions() { // Note: when reducing a grammar to only the expressions that // contribute to the AST, we retain options to reduce clutter. return runtime.test("optimizeOptional") || runtime.test("optionValued"); } /** Visit the specified grammar. */ public void visit(Module m) { // Initialize the per-grammar state. analyzer.register(this); analyzer.init(m); hasParseTree = m.hasAttribute(Constants.ATT_PARSE_TREE); // Now, process the productions. for (int i=0; i<m.productions.size(); i++) { Production p = m.productions.get(i); // Process the production. analyzer.startAdding(); analyzer.process(p); // If there are new productions, add them to the grammar and // make sure they are not processed again. i += analyzer.addNewProductionsAt(i+1); } } /** Visit the specified production. */ public void visit(FullProduction p) { process(p); } }