/* * xtc - The eXTensible Compiler * Copyright (C) 2004 Robert Grimm * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package xtc.parser; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import xtc.tree.Visitor; /** * 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 all options * and syntactic predicates are sequences, thus fullfilling the * requirements for {@link CodeGenerator code generation}. Before * applying this visitor on a grammar, the grammar must have been * {@link Simplifier simplified} and all text-only productions should * be marked as such by applying the {@link TextTester} visitor. * * @author Robert Grimm * @version $Revision: 1.2 $ */ public class Transformer extends Visitor { /** The analyzer utility. */ protected final Analyzer analyzer; /** The flag for whether the current production is text-only. */ protected boolean isTextOnly; /** The flag for whether the current production is void. */ protected boolean isVoid; /** The flag for whether the current element is top-level. */ protected boolean isTopLevel; /** * The flag for whether to transform ordered choices, repetition, * and options in place, instead of creating a new production. */ protected boolean transformInPlace; /** * The flag for whether the current element is the last element in a * sequence. */ protected boolean isLastElement; /** The flag for whether we are currently processing a predicate. */ protected boolean isPredicate; /** The flag for whether the current element is bound. */ protected boolean isBound; /** * Create a new transformer. * * @param analyzer The analyzer utility for the new transformer. */ public Transformer(Analyzer analyzer) { this.analyzer = analyzer; } /** * Determine whether the current production may contain repetitions. * A production may contain repetitions if it is transient void or * transient text-only and if the corresponding optimization option * ("<code>-Orepeated</code>") is set. */ protected boolean repeatable() { return (Rats.optimizeRepeated && analyzer.current().isTransient && (isVoid || isTextOnly)); } /** Visit the specified grammar. */ public void visit(Grammar g) { // Initialize the per-grammar state. analyzer.register(this); analyzer.init(g); // Now, process the productions. for (int i=0; i<g.productions.size(); i++) { Production p = (Production)g.productions.get(i); // Initialize the per-production state. isTextOnly = TextTester.isTextOnly(p); isVoid = Type.isVoidT(p.type); transformInPlace = false; isTopLevel = true; isLastElement = false; isPredicate = false; isBound = false; // 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(Production p) { // Note that we initialize all per-production state within the // visit() method for the grammar. p.element = (Element)p.element.accept(this); } /** Visit the specified ordered choice. */ public Element visit(OrderedChoice c) { boolean top = isTopLevel; isTopLevel = false; boolean last = isLastElement; isLastElement = false; boolean bound = isBound; isBound = false; boolean inPlace = transformInPlace; transformInPlace = false; // If this choice is top-level, has only a single option, and that // option consists of only a repetition or option (the '?' kind), // replace this ordered choice with the desugared repetition or // option, unless the repetition need not be desugared. if (top && (1 == c.options.size())) { Element e = (Element)c.options.get(0); if (((! repeatable()) && (e instanceof Repetition)) || (e instanceof Option)) { if (Rats.optionVerbose) { System.out.print("[Transforming top-level "); if (e instanceof Repetition) { System.out.print("repitition"); } else { System.out.print("option"); } System.out.println(" in production " + analyzer.current().nonTerminal.name + " in place]"); } transformInPlace = true; return (Element)e.accept(this); } } // Process the options and ensure that every option has a semantic // value. String type = null; final int length = c.options.size(); for (int i=0; i<length; i++) { Sequence s = Sequence.ensure((Element)((Element)c.options.get(i)).accept(this)); if (! inPlace) { // Only add semantic values if the sequence does not end in // another choice. if (s.isEmpty() || (! (s.get(s.length()-1) instanceof OrderedChoice))) { if (isTextOnly) { if (top || bound || last) { String text = analyzer.matchingText(s); if ((null == text) || (! Rats.optimizeTerminals)) { s.elements.add(TextValue.VALUE); } else { s.elements.add(new StringValue(text)); } type = Type.stringT(); } else { s.elements.add(NullValue.VALUE); type = Type.voidT(); } } else if (isVoid && (! bound)) { s.elements.add(NullValue.VALUE); type = Type.voidT(); } else if (! last) { // In general, we do not add semantic values for the last // choice in a sequence. Binding b = analyzer.bind(s); if (null != b) { // Patch the variable name to be the semantic value. b.name = CodeGenerator.VALUE; if (null == type) { type = Type.type(b.element, analyzer); } else { type = Type.unify(type, Type.type(b.element, analyzer)); } } else { type = Type.rootT(); } } } } c.options.set(i, s); } // If the ordered choice (1) is the top-level choice, (2) is being // transformed in place, or (3) is the last element in a sequence // (but not within a predicate), we are done. if (top || inPlace || (last && (! isPredicate))) { return c; } // Otherwise, lift the choice. NonTerminal nt = analyzer.choice(); Production p = new Production(analyzer.current().isTransient,type,nt,c); if (isTextOnly && Type.isStringT(type)) { TextTester.markTextOnly(p); } analyzer.add(p); // Done. return nt; } /** Visit the specified repetition. */ public Element visit(Repetition r) { isTopLevel = false; isLastElement = false; boolean bound = isBound; isBound = false; boolean inPlace = transformInPlace; transformInPlace = isTextOnly || isVoid; // Process the repeated element first. Element e = (Element)r.element.accept(this); // If the current production may contain repetitions and the // repetition is not bound, then do not desugar it. if (repeatable() && (! bound)) { r.element = Sequence.ensure(e); return r; } // Now, desugar the repetition. NonTerminal nt; if (inPlace) { nt = analyzer.current().nonTerminal; } else { nt = (r.once)? analyzer.plus() : analyzer.star(); } List oldOptions; if (e instanceof OrderedChoice) { oldOptions = ((OrderedChoice)e).options; } else { oldOptions = new ArrayList(1); oldOptions.add(e); } List newOptions = new ArrayList(oldOptions.size() + ((r.once)? oldOptions.size() : 1)); // The recursive options. Iterator iter = oldOptions.iterator(); while (iter.hasNext()) { Sequence s; if (r.once) { s = new Sequence((Element)iter.next()); } else { s = Sequence.ensure((Element)iter.next()); } if (isTextOnly) { s.elements.add(nt); if (bound || inPlace) { s.elements.add(TextValue.VALUE); } else { s.elements.add(NullValue.VALUE); } } else if (isVoid && (! bound)) { s.elements.add(nt); s.elements.add(NullValue.VALUE); } else { Binding b1 = analyzer.bind(s); Binding b2 = new Binding(analyzer.variable(), nt); s.elements.add(b2); s.elements.add(new ListValue((null == b1) ? CodeGenerator.VALUE : b1.name, b2.name)); } newOptions.add(s); } // The base option(s). if (r.once) { iter = oldOptions.iterator(); while (iter.hasNext()) { Sequence s = new Sequence((Element)iter.next()); if (isTextOnly) { if (bound || inPlace) { s.elements.add(TextValue.VALUE); } else { s.elements.add(NullValue.VALUE); } } else if (isVoid && (! bound)) { s.elements.add(NullValue.VALUE); } else { Binding b = analyzer.bind(s); s.elements.add(new SingletonListValue((null == b) ? CodeGenerator.VALUE : b.name)); } newOptions.add(s); } } else { Sequence s = new Sequence(new ArrayList()); if (isTextOnly) { if (bound || inPlace) { s.elements.add(TextValue.VALUE); } else { s.elements.add(NullValue.VALUE); } } else if (isVoid && (! bound)) { s.elements.add(NullValue.VALUE); } else { s.elements.add(EmptyListValue.VALUE); } newOptions.add(s); } // Create the new ordered choice. OrderedChoice c = new OrderedChoice(newOptions); if (inPlace) { return c; } // Create the new production. String type; if (isTextOnly) { if (bound || inPlace) { type = Type.stringT(); } else { type = Type.voidT(); } } else if (isVoid && (! bound)) { type = Type.voidT(); } else { type = Type.listT(); } Production p = new Production(analyzer.current().isTransient, type, nt, c); if (isTextOnly && Type.isStringT(type)) { TextTester.markTextOnly(p); } analyzer.add(p); // Done. return nt; } /** Visit the specified option. */ public Element visit(Option o) { isTopLevel = false; isLastElement = false; boolean bound = isBound; isBound = false; boolean inPlace = transformInPlace; transformInPlace = true; // Process the optional element first. Element e = (Element)o.element.accept(this); // Now, desugar the option. NonTerminal nt = (inPlace)? analyzer.current().nonTerminal : analyzer.option(); List oldOptions; if (e instanceof OrderedChoice) { oldOptions = ((OrderedChoice)e).options; } else { oldOptions = new ArrayList(1); oldOptions.add(e); } List newOptions = new ArrayList(oldOptions.size() + 1); String type = null; // The matching options. Iterator iter = oldOptions.iterator(); while (iter.hasNext()) { Sequence s = Sequence.ensure((Element)iter.next()); if (isTextOnly) { if (bound || inPlace) { s.elements.add(TextValue.VALUE); type = Type.stringT(); } else { s.elements.add(NullValue.VALUE); type = Type.voidT(); } } else if (isVoid && (! bound)) { s.elements.add(NullValue.VALUE); type = Type.voidT(); } else { Binding b = analyzer.bind(s); if (null != b) { // Path the variable name to be the semantic value. b.name = CodeGenerator.VALUE; if (null == type) { type = Type.type(b.element, analyzer); } else { type = Type.unify(type, Type.type(b.element, analyzer)); } } else { type = Type.rootT(); } } newOptions.add(s); } // The (empty) alternative option. Sequence alt = new Sequence(new ArrayList()); if (isTextOnly && (bound || inPlace)) { alt.elements.add(TextValue.VALUE); } else { alt.elements.add(NullValue.VALUE); } newOptions.add(alt); // Create the new ordered choice. OrderedChoice c = new OrderedChoice(newOptions); if (inPlace) { return c; } // Create the new production. Production p = new Production(analyzer.current().isTransient,type,nt,c); if (isTextOnly && Type.isStringT(type)) { TextTester.markTextOnly(p); } analyzer.add(p); // Done. return nt; } /** Visit the specified sequence. */ public Element visit(Sequence s) { isTopLevel = false; isBound = false; transformInPlace = false; final int length = s.length(); for (int i=0; i<length; i++) { isLastElement = (i == length - 1); s.elements.set(i, s.get(i).accept(this)); } isLastElement = false; return s; } /** Visit the specified predicate. */ public Element visit(Predicate p) { boolean predicate = isPredicate; isPredicate = true; isTopLevel = false; isLastElement = false; isBound = false; transformInPlace = false; p.element = Sequence.ensure((Element)p.element.accept(this)); isPredicate = predicate; return p; } /** Visit the specified semantic predicate. */ public Element visit(SemanticPredicate p) { isTopLevel = false; isLastElement = false; isBound = false; transformInPlace = false; p.element = (Element)p.element.accept(this); return p; } /** Visit the specified character case. */ public CharCase visit(CharCase c) { isTopLevel = false; isLastElement = false; isBound = false; transformInPlace = false; if (null != c.element) { c.element = (Element)c.element.accept(this); } return c; } /** Visit the specified character switch. */ public Element visit(CharSwitch s) { isTopLevel = false; isLastElement = false; isBound = false; transformInPlace = false; final int length = s.cases.size(); for (int i=0; i<length; i++) { s.cases.set(i, ((CharCase)s.cases.get(i)).accept(this)); } if (null != s.base) { s.base = (Element)s.base.accept(this); } return s; } /** * Visit the specified unary operator. This method provides the * default implementation for bindings and string matches. */ public Element visit(UnaryOperator op) { isTopLevel = false; isLastElement = false; isBound = true; transformInPlace = false; op.element = (Element)op.element.accept(this); return op; } /** * Visit the specified element. This method provides the default * implementation for nonterminals, terminals, actions, and value * elements. */ public Element visit(Element e) { isTopLevel = false; isLastElement = false; isBound = false; transformInPlace = false; return e; } }