/* * xtc - The eXTensible Compiler * Copyright (C) 2007-2008 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.util.Runtime; /** * Visitor to inject source code annotations into a grammar. * * <p />This visitor distinguishes between bindable elements, i.e., * elements that would contribute to the value of a generic * production, and valuable element, i.e., elements that can be * rewritten to have a semantic value. The latter include voided * elements and references to void productions that consume the input. * * <p />For directly left-recursive alternatives in generic * productions, this visitor may annotate the last sequence with the * {@link Properties#FORMATTING} property, indicating valuable * elements that need to be captured in the promise generated by * {@link DirectLeftRecurser}. * * <p />This visitor assumes that the entire grammar is contained in a * single module, that the grammar has been tokenized, and that * productions that may consume the input have been marked. * * @see Tokenizer * @see Analyzer#consumesInput(Element) * * @author Robert Grimm * @version $Revision: 1.33 $ */ public class Annotator extends Visitor { /** The marker for synthetic variables. */ public static final String MARKER = "pt"; // ========================================================================== /** A mutable boxed integer. */ public static class Index { /** The actual index. */ public int index; /** Create a new index initialized to -1. */ public Index() { index = -1; } /** * Create a copy of the specified index. * * @param index The index. */ public Index(Index index) { this.index = index.index; } } // ========================================================================== /** A pair of indices. */ public static class IndexPair { /** The first index. */ public int index1; /** The second index. */ public int index2; /** Create a new pair of indices, initialized to -1. */ public IndexPair() { index1 = -1; index2 = -1; } } // ========================================================================== /** * A visitor to detect whether productions need to be rewritten. * This visitor detects non-left-recursive generic or list-valued * productions that contain:<ol> * * <li>a bindable element with a list value preceded by one or more * valuable elements, but no bindable elements;</li> * * <li>a bindable element with a list value followed by one or more * valuable elements, but no more bindable elements.</li> * * </ol> * * For each such production, this visitor sets the {@link * Properties#SPLIT} property. For each (flattened) alternative * that contains any of the two cases, it also annotates the last * sequence with a {@link Annotator.IndexPair}. The first index is * non-negative for the first case. The second index is * non-negative for the second case. */ public class Detector extends Visitor { /** The flag for whether the production needs to be split. */ protected boolean needToSplit; /** The list of elements. */ protected List<Element> elements; /** Create a new detector. */ public Detector() { elements = new ArrayList<Element>(); } /** Visit the specified module. */ public void visit(Module m) { // Initialize the per-grammar state. analyzer.register(this); analyzer.init(m); // Process the productions. for (Production p : m.productions) { // Only truly generic and list-vallued productions are // candidates, since they determine a program's AST. However, // we cannot split directly left-recursive productions because // all component values must be part of the resulting node, // whose creation is delayed through a promise. if (((! Generifier.isGeneric((FullProduction)p)) && (! isList(p.type))) || DirectLeftRecurser.isTransformable((FullProduction)p)) { continue; } // Do the work. needToSplit = false; analyzer.process(p); if (needToSplit) p.setProperty(Properties.SPLIT, Boolean.TRUE); } } /** Visit the specified production. */ public void visit(FullProduction p) { dispatch(p.choice); } /** Visit the specified choice. */ public void visit(OrderedChoice c) { 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(); // Add this sequence's elements to the list of elements. 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); } } // Actually process the elements. if (! s.hasTrailingChoice()) { // Flag for at least one bindable element. boolean hasBindable = false; // Flag for at least one valuable element. boolean hasValuable = false; // Index of the first bindable element with a list value. int first = -1; // Index of the last bindable element with a list value. int last = -1; // Flag for a bindable element with a list value succeeded by // one or more valuable elements. boolean hasFollowing = false; final int size = elements.size(); for (int i=0; i<size; i++) { final Element e = elements.get(i); if (analyzer.isBindable(e)) { if (AST.isList(analyzer.type(e))) { if ((! hasBindable) && hasValuable) first = i; last = i; } else { last = -1; } hasFollowing = false; hasBindable = true; } else if (isValuable(e)) { if (-1 != last) hasFollowing = true; hasValuable = true; } } if (-1 != first || hasFollowing) { IndexPair result = new IndexPair(); if (-1 != first) result.index1 = first; if (hasFollowing) result.index2 = last; needToSplit = true; s.setProperty(Properties.SPLIT, result); } } // Remove any elements added by this method invocation. if (0 == base) { elements.clear(); } else { elements.subList(base, elements.size()).clear(); } } } // ========================================================================== /** * A visitor to rewrite productions. This visitor rewrites * productions that have been marked with the {@link * Properties#SPLIT} property to generate the same basic AST with * annotations as without annotations. * * @see Annotator.Detector */ public class Rewriter extends Visitor { /** The list of elements. */ protected List<Element> elements; /** The flag for whether the current choice is top-level. */ protected boolean isTopLevel; /** The current top-level alternative. */ protected Sequence alternative; /** The list of replacement sequences. */ protected List<Sequence> replacements; /** Create a new rewriter. */ public Rewriter() { elements = new ArrayList<Element>(); } /** Visit the specified module. */ public void visit(Module m) { // Initialize the per-grammar state. analyzer.register(this); analyzer.init(m); for (int i=0; i<m.productions.size(); i++) { FullProduction p = (FullProduction)m.productions.get(i); // We only rewrite marked productions. if (! p.getBooleanProperty(Properties.SPLIT)) continue; // 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. i += analyzer.addNewProductionsAt(i+1); } } /** Visit the specified production. */ public void visit(FullProduction p) { isTopLevel = true; dispatch(p.choice); } /** Visit the specified choice. */ public void visit(OrderedChoice c) { // Track whether this choice is top-level. boolean top = isTopLevel; isTopLevel = false; // Make space for the replacements. if (top) replacements = new ArrayList<Sequence>(c.alternatives.size()); // Process the alternatives. for (Sequence s : c.alternatives) { if (top) alternative = s; dispatch(s); } // Patch the top-level choice's alternatives. if (top) c.alternatives = replacements; } /** Visit the specified sequence. */ public void visit(Sequence s) { // Remember the current number of elements. final int base = elements.size(); // Add this sequence's elements to the list of elements. 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); } } // Actually process the elements. if (! s.hasTrailingChoice()) { IndexPair split = (IndexPair)s.getProperty(Properties.SPLIT); if (null == split) { // There's nothing to split here. Just construct the // replacement sequence. Sequence r = new Sequence(new ArrayList<Element>(elements)); r.name = alternative.name; r.setLocation(alternative); replacements.add(r); } else { // There's something to split here. Production p = analyzer.current(); // Determine the node marker for the new production. If the // elements have a node marker, use the last one; otherwise, // use one for the current production. NodeMarker mark = null; for (Element e : elements) { if (e instanceof NodeMarker) mark = (NodeMarker)e; } if (null == mark) mark = new NodeMarker(p.name.name); // Determine the nonterminal and only alternative for the // new production. final NonTerminal nt = analyzer.split(); final Sequence alt; if (-1 == split.index2) { alt = new Sequence(elements.size() - split.index1 + 1); alt.addAll(elements.subList(split.index1, elements.size())); alt.add(mark); } else if (-1 == split.index1) { alt = new Sequence(split.index2 + 2); alt.addAll(elements.subList(0, split.index2 + 1)); alt.add(mark); } else { alt = new Sequence(split.index2 - split.index1 + 2); alt.addAll(elements.subList(split.index1, split.index2 + 1)); alt.add(mark); } alt.name = alternative.name; alt.setLocation(alternative); // Create the new production. FullProduction q = new FullProduction(new ArrayList<Attribute>(p.attributes), AST.GENERIC, nt, nt.qualify(analyzer.module().name.name), new OrderedChoice(alt)); // Do not inherit any public, explicit, stateful, or // restting attribute. q.attributes.remove(Constants.ATT_PUBLIC); q.attributes.remove(Constants.ATT_EXPLICIT); q.attributes.remove(Constants.ATT_STATEFUL); q.attributes.remove(Constants.ATT_RESETTING); // But do ensure that the new production is transient. if (! q.hasAttribute(Constants.ATT_TRANSIENT) && ! q.hasAttribute(Constants.ATT_INLINE)) { q.attributes.add(Constants.ATT_TRANSIENT); } // Document activity under verbose mode. if (runtime.test("optionVerbose")) { System.err.println("[Lifting split sequence into new production " + q.qName + ']'); } // Add the new production to the grammar. analyzer.add(q); // Create the replacement. final Sequence r; if (-1 == split.index2) { r = new Sequence(split.index1 + 1); r.addAll(elements.subList(0, split.index1)); r.add(new Binding(CodeGenerator.VALUE, nt)); } else if (-1 == split.index1) { r = new Sequence(elements.size() - split.index2); r.add(new Binding(CodeGenerator.VALUE, nt)); r.addAll(elements.subList(split.index2 + 1, elements.size())); } else { r = new Sequence(split.index1 + elements.size() - split.index2); r.addAll(elements.subList(0, split.index1)); r.add(new Binding(CodeGenerator.VALUE, nt)); r.addAll(elements.subList(split.index2 + 1, elements.size())); } r.name = alternative.name; r.setLocation(alternative); replacements.add(r); } } // Remove any elements added by this method. 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 flag for whether the current production is generic. */ protected boolean isGeneric; /** The flag for whether the current production is list-valued. */ protected boolean isList; /** * The flag for whether the current production or sequence is * directly left-recursive. */ protected boolean isRecursive; /** The flag for whether the current choice is top-level. */ protected boolean isTopLevel; /** * The index of the next sequence to process. The stacks capturing * the state for tracking bindable and valuable must have as many * elements as indicated by this index. */ protected int toProcessIdx; /** The elements as a list of sequences. */ protected List<Sequence> sequences; /** * The list of bindings for valuable elements before the regular, * bindable value, organized as a stack of copies. * * @see #toProcessIdx */ protected List<List<Binding>> before; /** * The index of the sequence with the regular, bindable value, * organized as a stack of copies. A value of -1 indicates no * bindable value. * * @see #toProcessIdx */ protected List<Index> sequenceIdx; /** * The index of the regular, bindable value, organized as a stack of * copies. A value of -1 indicates no bindable value. * * @see #toProcessIdx */ protected List<Index> elementIdx; /** * The list of bindings for valuable elements after the regular, * bindable value, organized as a stack of copies. * * @see #toProcessIdx */ protected List<List<Binding>> after; /** * Create a new annotator. * * @param runtime The runtime. * @param analyzer The analyzer utility. */ public Annotator(Runtime runtime, Analyzer analyzer) { this.runtime = runtime; this.analyzer = analyzer; this.toProcessIdx = 0; this.sequences = new ArrayList<Sequence>(); this.before = new ArrayList<List<Binding>>(); this.sequenceIdx = new ArrayList<Index>(); this.elementIdx = new ArrayList<Index>(); this.after = new ArrayList<List<Binding>>(); } /** * Push a copy of the state for tracking bindable and valuable * elements. This method pushes copies of the top elemens of the * tracking state onto the respective stacks. If the stacks are * empty, it initializes the lists of bindings with empty lists and * the indices with -1. */ protected void push() { assert before.size() == sequenceIdx.size(); assert before.size() == elementIdx.size(); assert before.size() == after.size(); if (0 == before.size()) { before.add(new ArrayList<Binding>()); sequenceIdx.add(new Index()); elementIdx.add(new Index()); after.add(new ArrayList<Binding>()); } else { before.add(new ArrayList<Binding>(before.get(before.size()-1))); sequenceIdx.add(new Index(sequenceIdx.get(sequenceIdx.size()-1))); elementIdx.add(new Index(elementIdx.get(elementIdx.size()-1))); after.add(new ArrayList<Binding>(after.get(after.size()-1))); } } /** * Pop the top elements from the state for tracking bindable and * valuable elements. */ protected void pop() { assert before.size() == sequenceIdx.size(); assert before.size() == elementIdx.size(); assert before.size() == after.size(); before.remove(before.size()-1); sequenceIdx.remove(sequenceIdx.size()-1); elementIdx.remove(elementIdx.size()-1); after.remove(after.size()-1); } /** * Reset the current state for tracking bindable and valuable * elements. This method replaces the current lists of bindings * with new, empty lists and sets the current indices to -1. */ protected void reset() { before.set(before.size()-1, new ArrayList<Binding>()); sequenceIndex().index = -1; elementIndex().index = -1; after.set(after.size()-1, new ArrayList<Binding>()); } /** * Get the current list of bindings before the regular value. * * @return The current list of bindings. */ protected List<Binding> before() { return before.get(before.size()-1); } /** * Get the current sequence index of the regular value. * * @return The current sequence index. */ protected Index sequenceIndex() { return sequenceIdx.get(sequenceIdx.size()-1); } /** * Get the current element index of the regular value. * * @return The current element index. */ protected Index elementIndex() { return elementIdx.get(elementIdx.size()-1); } /** * Get the current list of bindings after the regular value. * * @return The current list of bindings. */ protected List<Binding> after() { return after.get(after.size()-1); } /** * Determine whether the specified element is valuable. This method * returns <code>true</code> if the specified element has a semantic * value or can be rewritten to yield a semantic value. Rewriting * includes eliminating any voided element or converting a void * production into a production returning a semantic value. * * @param e The element. * @return <code>true</code> if the specified element is valuable. */ protected boolean isValuable(Element e) { switch (e.tag()) { case VOIDED: assert isValuable(((VoidedElement)e).element); return true; case NONTERMINAL: return isValuable(analyzer.lookup((NonTerminal)e)); case CHOICE: case OPTION: case REPETITION: case ANY_CHAR: case CHAR_CLASS: case CHAR_LITERAL: case STRING_LITERAL: case STRING_MATCH: case PARSE_TREE_NODE: case BINDING: return true; default: return false; } } /** * Determine whether the specified element is a possibly bound or * voided parse tree node. * * @param e The element. * @return <code>true</code> if the specified element is a parse * tree node. */ protected boolean isParseTreeNode(Element e) { switch (e.tag()) { case VOIDED: case BINDING: return isParseTreeNode(((UnaryOperator)e).element); case PARSE_TREE_NODE: return true; default: return false; } } /** * Determine whether a bindable element has been processed. * * @see Analyzer#isBindable(Element) * * @return <code>true</code> if a bindable element has been * processed. */ protected boolean hasBindable() { return -1 != sequenceIndex().index; } /** * Determine whether any valuable elements have been processed. * * @see #isValuable(Element) * * @return <code>true</code> if any valuable elements have been * processed. */ protected boolean hasValuable() { return (0 < before().size()) || (0 < after().size()); } /** * Determine whether the specified element already is bound and * voided. * * @param e The element. * @return <code>true</code> if the specified element already is * bound and voided. */ protected boolean isBoundAndVoided(Element e) { return (e instanceof VoidedElement) && (((VoidedElement)e).element instanceof Binding); } /** * Bind and void the specified element. The specified element must * be valuable, i.e., can be rewritten to yield a semantic value. * * @see #isValuable(Element) * * @param e The element. * @return The voided, bound element. */ protected VoidedElement bindAndVoid(final Element e) { Element tmp = e; // Strip any voided element. if (tmp instanceof VoidedElement) { tmp = ((VoidedElement)tmp).element; } // Make sure the element is bound. if (tmp instanceof Binding) { // Preserve already existing bindings, unless they refer to // yyValue. Binding b = (Binding)tmp; if (CodeGenerator.VALUE.equals(b.name)) { b.name = analyzer.variable(MARKER); } assert ! isParseTreeNode(b.element); } else { assert ! isParseTreeNode(tmp); tmp = new Binding(analyzer.variable(MARKER), tmp); tmp.setLocation(e); // Preserve the location. } // Void out the element. VoidedElement result = new VoidedElement(tmp); result.setLocation(e); // Preserve the location. // Done. return result; } /** * Bind and void the current regular, bindable element. * * @return The binding. */ protected Binding bindAndVoid() { Sequence s = sequences.get(sequenceIndex().index); VoidedElement v = bindAndVoid(s.get(elementIndex().index)); s.elements.set(elementIndex().index, v); return (Binding)v.element; } /** * Process all elements in the current list of sequences. */ protected void annotate() { // If any element explicitly sets the semantic value through an // action, then all hands are off. for (Sequence s : sequences) { for (Element e : s.elements) { if ((e instanceof Action) && ((Action)e).setsValue()) { // Increment toProcessIdx to make the decrement in // visit(Sequence) a no-op. toProcessIdx++; push(); assert toProcessIdx == before.size(); return; } } } // Next, if any element binds yyValue directly, we need to // preserve the explicit binding. int valueSequenceIdx = -1; int valueElementIdx = -1; boolean requiresAnnotation = false; final int size = sequences.size(); for (int i=0; i<size; i++) { final Sequence s = sequences.get(i); final int seqsize = s.hasTrailingChoice() ? s.size()-1 : s.size(); for (int j=0; j<seqsize; j++) { // Skip any left-recursive nonterminal. if (isRecursive && (0 == i) && (0 == j)) continue; // Process the element. final Element e = s.get(j); if ((e instanceof Binding) && CodeGenerator.VALUE.equals(((Binding)e).name)) { // Note that we track the last binding to yyValue, // overriding any previous bindings here. valueSequenceIdx = i; valueElementIdx = j; } else if (isValuable(e)) { requiresAnnotation = true; } } } if (-1 != valueSequenceIdx) { if (! requiresAnnotation) { // Increment toProcessIdx to make the decrement in // visit(Sequence) a no-op. toProcessIdx++; push(); assert toProcessIdx == before.size(); return; } else if (requiresAnnotation) { Binding value = null; // Recreate the before and after bindings from scratch since // the previous list of sequences may not have contained a // binding to yyValue. before.clear(); sequenceIdx.clear(); elementIdx.clear(); after.clear(); for (int i=0; i<size; i++) { final Sequence s = sequences.get(i); push(); final int seqsize = s.hasTrailingChoice()? s.size()-1 : s.size(); for (int j=0; j<seqsize; j++) { // Skip any left-recursive nonterminal. if (isRecursive && (0 == i) && (0 == j)) continue; // Process the element. Element e = s.get(j); if (i < toProcessIdx) { // The element has been processed. if ((i == valueSequenceIdx) && (j == valueElementIdx)) { // Record the (voided) binding to yyValue. value = (Binding)((VoidedElement)e).element; } else if (isBoundAndVoided(e)) { // Record a previously bound an voided element. if (null == value) { before().add((Binding)((VoidedElement)e).element); } else { after().add((Binding)((VoidedElement)e).element); } } else if (analyzer.isBindable(e) && (! isParseTreeNode(e))) { // Ensure the element has a binding. if (! (e instanceof Binding)) { e = new Binding(analyzer.variable(MARKER), e); s.elements.set(j, e); } if (null == value) { before().add((Binding)e); } else { after().add((Binding)e); } } } else if (isValuable(e)) { // The element has not yet been processed. VoidedElement v = bindAndVoid(e); s.elements.set(j, v); if ((i == valueSequenceIdx) && (j == valueElementIdx)) { value = (Binding)v.element; } else if (null == value) { before().add((Binding)v.element); } else { after().add((Binding)v.element); } } } } assert null != value; // We must have seen the binding. ParseTreeNode n = new ParseTreeNode(before(), value, after()); Binding b = new Binding(CodeGenerator.VALUE, n); sequences.get(sequences.size()-1).add(b); toProcessIdx = sequences.size(); assert toProcessIdx == before.size(); return; } } // Now, process the elements, one at a time. for (int i=toProcessIdx; i<size; i++) { final Sequence s = sequences.get(i); push(); for (int j=0; j < (s.hasTrailingChoice() ? s.size()-1 : s.size()); j++) { // Skip any left-recursive nonterminal. if (isRecursive && (0 == i) && (0 == j)) continue; // Process the element. final Element e = s.get(j); if (analyzer.isBindable(e)) { // The element represents a regular value. if ((isGeneric || isList) && AST.isList(analyzer.type(e))) { // If the current production is generic or list-valued and // the current element has a list value, we cannot include // the current element in an annotated node. if (hasValuable()) { // Emit a new annotated node. Binding b = hasBindable() ? bindAndVoid() : null; // Note: structural modification. s.elements.add(j, new ParseTreeNode(before(), b, after())); j++; // Do not revisit the current element. } reset(); } else if ((! hasBindable()) || (! hasValuable())) { // If we have not yet seen any bindable or valuable // elements, we simply remember the current element // as the current bindable element. sequenceIndex().index = i; elementIndex().index = j; } else { // Otherwise, we bind and void the current bindable // element and then add the corresponding parse tree node // to the current sequence. Note that we shift the // current element to the right, which will then be // recorded as the current bindable element on the next // iteration. // Note: structural modification. s.elements.add(j, new ParseTreeNode(before(),bindAndVoid(),after())); reset(); } } else if (isValuable(e)) { // The element is valuable. VoidedElement v = bindAndVoid(e); s.elements.set(j, v); // Update the sequence. if (! hasBindable()) { before().add((Binding)v.element); } else { after().add((Binding)v.element); } } } // Ensure that a parse tree node capturing a bindable element is // in the same sequence as the bindable element. Otherwise, any // sequences with a higher index but visited later cannot see // the element. if (i < size-1) { if (hasBindable()) { if (hasValuable()) { // Note: structural modification. Also note: we must // insert before the trailing choice. s.elements.add(s.size() - 1, new ParseTreeNode(before(), bindAndVoid(), after())); } reset(); } } } // Make sure we preserve any valuable elements. if (hasValuable()) { if (isGeneric && (! hasBindable())) { // The formatting node capturing the valuable elements needs // be created inside an action. sequences.get(sequences.size()-1). setProperty(Properties.FORMATTING, before()); } else { Binding b = hasBindable() ? bindAndVoid() : null; // Note: structural modification. sequences.get(sequences.size()-1). add(new ParseTreeNode(before(), b, after())); } } // Remember that we have processed all sequences. toProcessIdx = sequences.size(); assert toProcessIdx == before.size(); } /** * Recursively process the specified element. * * @param e The element. */ protected void recurse(Element e) { switch (e.tag()) { case BINDING: case OPTION: case REPETITION: case STRING_MATCH: case VOIDED: // Recurse on the unary operator's element. recurse(((UnaryOperator)e).element); break; case CHOICE: case SEQUENCE: { // Save the state. boolean savedIsGeneric = isGeneric; boolean savedIsList = isList; boolean savedIsRecursive = isRecursive; boolean savedIsTopLevel = isTopLevel; int savedToProcessIdx = toProcessIdx; List<Sequence> savedSequences = sequences; List<List<Binding>> savedBefore = before; List<Index> savedSequenceIdx = sequenceIdx; List<Index> savedElementIdx = elementIdx; List<List<Binding>> savedAfter = after; // Re-initialize the state. isGeneric = false; isList = false; isRecursive = false; isTopLevel = false; toProcessIdx = 0; sequences = new ArrayList<Sequence>(); before = new ArrayList<List<Binding>>(); sequenceIdx = new ArrayList<Index>(); elementIdx = new ArrayList<Index>(); after = new ArrayList<List<Binding>>(); // Actually recurse on the choice or sequence. dispatch(e); // Restore the state. isGeneric = savedIsGeneric; isList = savedIsList; isRecursive = savedIsRecursive; isTopLevel = savedIsTopLevel; toProcessIdx = savedToProcessIdx; sequences = savedSequences; before = savedBefore; sequenceIdx = savedSequenceIdx; elementIdx = savedElementIdx; after = savedAfter; } break; default: // Nothing to do. } } /** Visit the specified grammar. */ public void visit(Module m) { // Take care of generic productions first. new Detector().dispatch(m); new Rewriter().dispatch(m); // Initialize the per-grammar state. analyzer.register(this); analyzer.init(m); // Annotate the grammar's productions. for (Production p : m.productions) { // Do not process lexical productions or void productions that // do not consume any input. if (p.getBooleanProperty(Properties.LEXICAL) || (AST.isVoid(p.type) && (! p.getBooleanProperty(Properties.CONSUMER)))) { continue; } boolean processed = false; if (isSingleRepetition((FullProduction)p)) { for (Sequence s : p.choice.alternatives) { // Note that we can safely call recurse() since // analyzer.current() is only accessed for left-recursive // productions. recurse(s.get(0)); } processed = true; } else if (AST.isVoid(p.type) || AST.isString(p.type) || AST.isNode(p.type) || AST.isAny(p.type)) { analyzer.process(p); processed = true; } else if (AST.isList(p.type)) { final Type elem = AST.getArgument(p.type); if (AST.isString(elem) || AST.isNode(elem) || AST.isAny(elem)) { analyzer.process(p); processed = true; } } if (! processed) { // We don't know how to process the production. if ((! m.hasAttribute(Constants.ATT_NO_WARNINGS)) && (! p.hasAttribute(Constants.ATT_NO_WARNINGS))) { runtime.warning("unable to add parse tree annotations", p); } } } // Retype the grammar's productions. for (Production p : m.productions) { // Skip productions that are not token-level but are lexical or // are void and do not consume the input. if ((! p.getBooleanProperty(Properties.TOKEN)) && (p.getBooleanProperty(Properties.LEXICAL) || (AST.isVoid(p.type) && (! p.getBooleanProperty(Properties.CONSUMER))))) { continue; } if (p.getBooleanProperty(Properties.TOKEN)) { // The production is token-level. Make it actually return a // token. p.type = AST.TOKEN; } else if (AST.isVoid(p.type)) { // The void production is not lexical and consumes the input. // Make it generic. p.type = AST.GENERIC; } else if (AST.isString(p.type) || AST.isToken(p.type)) { // The production is not lexical and returns a string or // token. Make it return a node. p.type = AST.NODE; } else if (AST.isList(p.type)) { Type elem = AST.getArgument(p.type); if (AST.isString(elem) || AST.isToken(elem)) { // Change a list of strings or tokens into a list of nodes. p.type = AST.listOf(AST.NODE); } } } // Et voila. } /** Visit the specified production. */ public void visit(FullProduction p) { isGeneric = isGeneric(p); isList = AST.isList(p.type); isRecursive = DirectLeftRecurser.isTransformable(p); isTopLevel = true; dispatch(p.choice); } /** Visit the specified choice. */ public void visit(OrderedChoice c) { final boolean rec = isRecursive; final boolean top = isTopLevel; isTopLevel = false; for (Sequence alt : c.alternatives) { // If the current choice is top-level, set the flag for whether // the current sequence is directly left-recursive. if (top) { if (rec) { isRecursive = DirectLeftRecurser. isRecursive(alt, (FullProduction)analyzer.current()); } else { isRecursive = false; } } dispatch(alt); } } /** Visit the specified sequence. */ public void visit(Sequence s) { // Add the sequence to the list of sequences. sequences.add(s); // Iterate over the elements of this sequence. However, we cannot // use a Java iterator, since annotate() may add elements to // sequences and, in the presence of trailing choices, may thus // cause a concurrent modification exception. We do not need to // update the sequence's size, since all elements are recursed on // during the iteration getting us to an invocation of annotate(). final int size = s.elements.size(); for (int i=0; i<size; i++) { Element e = s.get(i); if ((size - 1 == i) && (e instanceof OrderedChoice)) { // Continue with the trailing choice. dispatch(e); } else { // Recurse on the element. recurse(e); } } // Process annotations. if (! s.hasTrailingChoice()) { annotate(); } // Remove the sequence again. sequences.remove(sequences.size() - 1); // Pop the processing state. toProcessIdx--; pop(); assert toProcessIdx == before.size(); } // ========================================================================== /** * Determine whether the specified production effectively is * generic. This method returns <code>true</code> if the specified * production is, in fact, generic or it is a void production that * also is non-lexical and consumes the input. * * @see Generifier#isGeneric(FullProduction) * * @param p The production. * @return <code>true</code> if the specified production should be * treated as generic. */ public static boolean isGeneric(FullProduction p) { return (Generifier.isGeneric(p) || (AST.isVoid(p.type) && (! p.getBooleanProperty(Properties.LEXICAL)) && p.getBooleanProperty(Properties.CONSUMER))); } /** * Determine whether the specified production can have a semantic * value. Any production that is not void or that consumes the * input can have a semantic value. This method assumes that * productions have been correctly annotated with the {@link * Properties#CONSUMER} property. * * @param p The production. * @return <code>true</code> if the specified production can have a * semantic value. */ public static boolean isValuable(FullProduction p) { return (! AST.isVoid(p.type)) || p.getBooleanProperty(Properties.CONSUMER); } /** * Determine whether the specified type is a list type that can be * processed by this visitor. * * @param type The type. * @return <code>true</code> if the specified type is a list type * that can be processed by this visitor. */ public static boolean isList(Type type) { if (! AST.isList(type)) return false; Type elem = AST.getArgument(type); return (AST.isAny(elem) || AST.isNode(elem) || AST.isString(elem)); } /** * Determine whether the specified production recognizes only a * single repetition. This method returns <code>true</code> if the * specified production returns a list of objects, nodes, generic * nodes, tokens, or strings and each alternative consists of only a * single, optionally bound repetition. * * @param p The production. * @return <code>true</code> if the specified production recognizes * only a single repetition. */ public static boolean isSingleRepetition(FullProduction p) { if (! isList(p.type)) return false; for (Sequence s : p.choice.alternatives) { if (1 != s.size()) return false; final Element e = s.get(0); if ((! (e instanceof Repetition)) && (! ((e instanceof Binding) && (((Binding)e).element instanceof Repetition)))) { return false; } } return true; } }