/* * 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.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.text.DateFormat; import xtc.Constants; import xtc.util.Utilities; import xtc.tree.Attribute; import xtc.tree.Location; import xtc.tree.Node; import xtc.tree.Printer; import xtc.tree.Visitor; /** * The code generator. * * <p />The code generator makes the following assumptions about the * intermediate language:<ul> * * <li>Each {@link Production production} must have an {@link * OrderedChoice ordered choice} as its element.<p /></li> * * <li>Each {link Production production} must have been annotated * with the appropriate {@link MetaData meta-data}.<p /></li> * * <li>Each option of an {@link OrderedChoice ordered choice} must * be a {@link Sequence sequence}.<p /></li> * * <li>Generally, all {@link Option options}, {@link Repetition * repetitions}, and nested {@link OrderedChoice ordered choices} must * have been desugared into equivalent productions. However, an * ordered choice may appear as the <i>last</i> element of a sequence * (that is <i>not</i> part of a predicate), and a repetition may * appear if the repeated expressions need not be memoized.<p /></li> * * <li>The element of a {@link Repetition repetition} must be a {@link * Sequence sequence} (with the last element possibly being an ordered * choice; see previous assumption).<p /></li> * * <li>The {@link FollowedBy#element} and {@link * NotFollowedBy#element} fields must reference a sequence.<p /></li> * * <li>All elements in a {@link CharSwitch character switch} must * either be ordered choices or sequences. Furthermore, character * switches may only appear as the last element in a sequence and not * within predicates.<p /></li> * * </ul> * * @author Robert Grimm * @version $Revision: 1.1 $ */ public class CodeGenerator extends Visitor { /** * The {@link Attribute attribute} name for printing debugging * information while parsing. For grammars with this attribute, the * code generator emits code that prints debugging information to * the console while parsing. */ public static final String ATT_DEBUG = "debug"; /** * The {@link Attribute attribute} name for annotating instances of * {@link Node} with their location information: * <code>location</code>. For grammars with this attribute, the * code generator emits code that automatically annotates all nodes * with their grammar {@link Location location information}. */ public static final String ATT_LOCATION = "location"; /** * The {@link Attribute attribute} name for making variable bindings * constant: <code>constantBinding</code>. For grammars with this * attribute, the code generator declares all variable bindings to * be constant (in the case of Java, final). */ public static final String ATT_CONSTANT_BINDING = "constantBinding"; /** * The {@link Attribute attribute} name for not generating specific * parse errors when a string literal or string match fails: * <code>noMatchingErrors</code>. For grammars with this attribute, * parse errrors are only generated at the granularity of an entire * production (pointing to the beginning of the production). */ public static final String ATT_NO_MATCHING_ERRORS = "noMatchingErrors"; /** The size of chunks. */ public static final int CHUNK_SIZE = 10; /** The prefix for parsing method names. */ public static final String PREFIX_METHOD = "p"; /** The prefix for field names that memoize the parsers results. */ public static final String PREFIX_FIELD = "f"; /** The general prefix for internal parser fields and variables. */ public static final String PREFIX = "yy"; /** The name for the character parsing method. */ public static final String PARSE_CHAR = "character"; /** The name for the file name variable. */ public static final String FILE = PREFIX + "File"; /** The name for the line number variable. */ public static final String LINE = PREFIX + "Line"; /** The name for the column number variable. */ public static final String COLUMN = PREFIX + "Column"; /** The name for the character variable. */ public static final String CHAR = PREFIX + "C"; /** The name for the parser variable. */ public static final String PARSER = PREFIX + "Parser"; /** The prefix for the parser variable for nested choices. */ public static final String NESTED_CHOICE = PREFIX + "Choice"; /** The prefix for the parser variable for repetitions. */ public static final String REPETITION = PREFIX + "Repetition"; /** * The prefix for the flag indicating that a repetition has been * matched at least once. */ public static final String REPEATED = PREFIX + "Repeated"; /** The name for the regular result variable. */ public static final String RESULT = PREFIX + "Result"; /** The name for the parse error variable. */ public static final String PARSE_ERROR = PREFIX + "Error"; /** The name for the predicate result variable. */ public static final String PRED_RESULT = PREFIX + "PredResult"; /** The name for the predicate matched variable. */ public static final String MATCHED = PREFIX + "PredMatched"; /** The name for the value variable. */ public static final String VALUE = PREFIX + "Value"; // ======================================================================== /** The analyzer utility. */ protected final Analyzer analyzer; /** The printer utility. */ protected final Printer printer; /** The number of spaces to align variable declarations to. */ protected int alignment = 0; /** The flag for generating debugging code. */ protected boolean attributeDebug; /** * The flag for generating code to annotate nodes with location * information. */ protected boolean attributeLocation; /** The flag for making variable bindings constant. */ protected boolean attributeConstantBinding; /** The flag for not generating matching errors. */ protected boolean attributeNoMatchingErrors; /** The class name for the current grammar. */ protected String cName; /** Flag for whether the memoization fields are organized in chunks. */ protected boolean chunked; /** The map from nonterminals to chunk numbers. */ protected Map chunkMap; /** The number of chunks. */ protected int chunkCount; /** The flag for the first element in a top-level choice. */ protected boolean firstElement; /** The saved first element. */ protected boolean savedFirstElement; /** The expression for the base parser. */ protected String baseParser; /** The flag for using the base parser. */ protected boolean useBaseParser; /** The saved base parser. */ protected String savedBaseParser; /** The saved flag for using the base parser. */ protected boolean savedUseBaseParser; /** The nesting level for nested choices. */ protected int choiceLevel; /** The nesting level for repetitions. */ protected int repetitionLevel; /** The saved repetition level (for predicates). */ protected int savedRepetitionLevel; /** The flag for at-least-once repetitions. */ protected boolean repeatOnce; /** Flag for whether a test has been emitted. */ protected boolean seenTest; /** Flag for whether the current choice ends with a parse error. */ protected boolean endsWithParseError; /** The iterator over the elements of a sequence. */ protected Iterator elementIter; /** The name of the result variable. */ protected String resultName; /** The name of the current binding. */ protected String bindingName; /** The element being bound. */ protected Element bindingElement; /** Flag for whether we are currently emitting a predicate. */ protected boolean predicate; /** * Flag for whether the current predicate is a not-followed-by * predicate. */ protected boolean notFollowedBy; /** The predicate iterator. */ protected Iterator predicateIter; // ======================================================================== /** * Create a new code generator. * * @param analyzer The analyzer for the new code generator. * @param printer The printer for the new code generator. */ public CodeGenerator(Analyzer analyzer, Printer printer) { this.analyzer = analyzer; this.printer = printer; } // ======================================================================== /** * Generate the field name for the specified nonterminal. * * @param nt The nonterminal. * @return The corresponding field name. */ public String fieldName(NonTerminal nt) { if (chunked) { return PREFIX + "Chunk" + chunkMap.get(nt) + "." + PREFIX_FIELD + nt.name; } else { return PREFIX_FIELD + nt.name; } } /** * Generate the method name for the specified nonterminal. * * @param nt The nonterminal. * @return The corresponding method name. */ public String methodName(NonTerminal nt) { return PREFIX_METHOD + nt.name; } // ======================================================================== /** * Determine the variable alignment for parse method declarations. * * @param includeParser Flag for whether a parser variable needs to * be declared. */ protected void alignment(boolean includeParser) { alignment = 0; if (includeParser) { alignment = Math.max(alignment, cName.length() + 1); } alignment = Math.max(alignment, "ParseError".length() + 1); } // ======================================================================== /** Generate code for the specified grammar. */ public void visit(Grammar g) { // (Re)Initialize code generator state. analyzer.register(this); printer.register(this); analyzer.init(g); cName = g.cName; // Record the grammar attributes. Set attributes; if (null != g.attributes) { attributes = new HashSet(g.attributes); } else { attributes = new HashSet(); } attributeDebug = attributes.contains(new Attribute(ATT_DEBUG)); attributeLocation = attributes.contains(new Attribute(ATT_LOCATION)); attributeConstantBinding = attributes.contains(new Attribute(ATT_CONSTANT_BINDING)); attributeNoMatchingErrors = attributes.contains(new Attribute(ATT_NO_MATCHING_ERRORS)); chunked = false; chunkMap = null; chunkCount = 0; // Emit header. printer.sep(); printer.indent().pln("// This file has been generated by"); printer.indent().p("// Rats! Parser Generator, Version "). p(Constants.VERSION).p(", ").pln(Constants.COPY); Date now = new Date(); printer.indent().p("// on "). p(DateFormat.getDateInstance(DateFormat.FULL).format(now)). p(" at "). p(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(now)).pln('.'); printer.indent().pln("// Edit at your own risk."); printer.sep(); printer.pln(); // Emit package name. if (null != g.pName) { printer.indent().p("package ").p(g.pName).pln(';'); printer.pln(); } // Emit imports. printer.indent().pln("import java.io.IOException;"); printer.indent().pln("import java.io.Reader;"); printer.pln(); printer.indent().pln("import xtc.util.Pair;"); if (attributeLocation) { printer.pln(); printer.indent().pln("import xtc.tree.Node;"); } printer.pln(); printer.indent().pln("import xtc.parser.PackratParser;"); printer.indent().pln("import xtc.parser.Result;"); printer.indent().pln("import xtc.parser.SemanticValue;"); printer.indent().pln("import xtc.parser.ParseError;"); printer.pln(); // Emit header. if (null != g.header) { action(g.header); printer.pln(); } // Emit class name. printer.indent().pln("/**"); if (null != g.location) { printer.indent().p(" * Packrat parser for grammar ").p(g.location.file). pln('.'); } else { printer.indent().pln(" * Packrat parser."); } printer.indent(). p(" * This class has been generated by the <i>Rats!</i> parser "). p("generator, v. ").p(Constants.VERSION).pln('.'); printer.indent().pln(" */"); printer.indent().p("public final class ").p(cName). pln(" extends PackratParser {").incr(); printer.pln(); // Emit debug flag. if (attributeDebug) { printer.indent(). p("/** Flag for whether to emit debugging information while "). pln("parsing. */"); printer.indent().pln("public static final boolean DEBUG = true;"); printer.pln(); } // Determine the number of productions that require memoization. int memoCount = 0; Iterator iter = g.productions.iterator(); while (iter.hasNext()) { Production p = (Production)iter.next(); MetaData md = (MetaData)p.getProperty(MetaData.NAME); // Only memoize non-transient productions that are used more // than once. if ((1 < md.usageCount) && (! p.isTransient)) { memoCount++; } } // To chunk or not to chunk. if (Rats.optimizeChunks && (CHUNK_SIZE <= memoCount)) { chunked = true; chunkMap = new HashMap(memoCount * 4 / 3); Integer number = null; String sNumber = null; int i = CHUNK_SIZE; boolean first = true; iter = g.productions.iterator(); while (iter.hasNext()) { Production p = (Production)iter.next(); MetaData md = (MetaData)p.getProperty(MetaData.NAME); // Skip memoization for productions that are transient or used // at most once. if ((1 >= md.usageCount) || (Rats.optimizeTransient && p.isTransient)) { continue; } if (CHUNK_SIZE <= i) { chunkCount++; number = new Integer(chunkCount); sNumber = Integer.toString(chunkCount); i = 0; if (first) { first = false; printer.sep(); } else { printer.decr().indent().pln('}'); } printer.pln(); printer.indent().p("/** Chunk ").p(sNumber). pln(" of memoized results. */"); printer.indent().p("static final class Chunk").p(sNumber). pln(" {").incr(); } NonTerminal nt = p.nonTerminal; chunkMap.put(nt, number); i++; printer.indent().p("Result ").p(PREFIX_FIELD).p(nt.name). pln(';'); } printer.decr().indent().pln('}'); printer.pln(); } // Emit fields. Note that the field for memoizing the result of // the character parsing method is already defined in // PackratParser. printer.sep().pln(); if (chunked) { for (int i=1; i<=chunkCount; i++) { printer.indent().p("private Chunk").p(i).p(' ').p(PREFIX).p("Chunk"). p(i).pln(';'); } } else { iter = g.productions.iterator(); while (iter.hasNext()) { Production p = (Production)iter.next(); MetaData md = (MetaData)p.getProperty(MetaData.NAME); // Only create memoization fields for non-transient // productions that are used more than once. if (((! Rats.optimizeChunks) || (1 < md.usageCount)) && ((! Rats.optimizeTransient) || (! p.isTransient))) { printer.indent().p("private Result ").p(fieldName(p.nonTerminal)). pln(';'); } } } printer.pln(); // Emit constructors. printer.sep().pln(); printer.indent(). pln("/** Create a new packrat parser for the specified file. */"); printer.indent().p("public ").p(cName). pln("(Reader reader, String file) {").incr(); printer.indent().pln("super(reader, file);"); printer.decr().indent().pln('}'); printer.pln(); printer.indent().p("/** Create a new packrat parser, moving ahead one "). pln("character. */"); printer.indent().p("protected ").p(cName).p('(').p(cName). pln(" previous) {").incr(); printer.indent().pln("super(previous);"); printer.decr().indent().pln('}'); printer.pln(); // Emit code for creating the next parser. printer.sep().pln(); printer.indent().pln("protected PackratParser next() {").incr(); printer.indent().p("return new ").p(cName).pln("(this);"); printer.decr().indent().pln('}'); printer.pln(); // Emit code for productions. iter = g.productions.iterator(); while (iter.hasNext()) { analyzer.process((Production)iter.next()); } // Emit code for body. if (null != g.body) { printer.sep().pln(); action(g.body); } // Finish parser class. printer.decr().indent().pln('}'); // Emit footer. if (null != g.footer) { printer.pln().sep().pln(); action(g.footer); } } // ======================================================================== /** Generate code for the specified production. */ public void visit(Production p) { MetaData md = (MetaData)p.getProperty(MetaData.NAME); String field = fieldName(p.nonTerminal); String method = methodName(p.nonTerminal); printer.sep().pln(); printer.indent().pln("/**"); printer.indent().p(" * Parse "); if (p.hasProperty(Constants.SYNTHETIC) && ((Boolean)p.getProperty(Constants.SYNTHETIC)).booleanValue()) { printer.p("synthetic "); } printer.p("nonterminal ").p(p.nonTerminal.name).pln('.'); if (p.hasProperty(DuplicateProductionFolder.DUPLICATES)) { List sources = (List)p.getProperty(DuplicateProductionFolder.DUPLICATES); printer.indent(). pln(" * This nonterminal represents the duplicate productions "). indent().p(" * ").p(Utilities.format(sources)).pln('.'); } printer.indent().pln(" *"); printer.indent().pln(" * @return The result."); printer.indent().pln(" * @throws IOException Signals an I/O error."); printer.indent().pln(" */"); printer.indent(); if (analyzer.isTopLevel(p.nonTerminal)) { // Top-level parsing methods are public. printer.p("public"); } else { // The rest is private. printer.p("private"); } printer.p(" Result ").p(method).pln("() throws IOException {").incr(); // Only memoize non-transient productions that are used more than // once. if (((! Rats.optimizeChunks) || (1 < md.usageCount)) && ((! Rats.optimizeTransient) || (! p.isTransient))) { if (chunked) { String chunk = chunkMap.get(p.nonTerminal).toString(); printer.indent().p("if (null == ").p(PREFIX).p("Chunk"). p(chunk).p(") ").p(PREFIX).p("Chunk").p(chunk).p(" = new Chunk"). p(chunk).pln("();"); } printer.indent().p("if (null == ").p(field).p(") ").p(field).p(" = "). p(method).pln("$1();"); printer.indent().p("return ").p(field).pln(';'); printer.decr().indent().pln('}'); printer.pln(); printer.indent().p("/** Actually parse "); if (p.hasProperty(Constants.SYNTHETIC)) { printer.p("synthetic "); } printer.p("nonterminal <code>").p(p.nonTerminal.name).pln("</code>. */"); printer.indent().p("private Result ").p(method). pln("$1() throws IOException {").incr(); if (attributeDebug) { printer.indent().p("if (DEBUG) System.out.println(\"").p(method). pln("$1: \" + toString());"); printer.pln(); } } else if (attributeDebug) { printer.indent().p("if (DEBUG) System.out.println(\"").p(method). pln(": \" + toString());"); printer.pln(); } // Emit variable declarations. alignment(md.requiresParser || (0 < md.repetitions.size())); if (md.requiresParser) { printer.indent().p(cName).align(alignment).p(PARSER).pln(';'); } printer.indent().p("Result").align(alignment).p(RESULT).pln(';'); printer.indent().p("ParseError").align(alignment).p(PARSE_ERROR). pln(" = ParseError.DUMMY;"); if (md.requiresPredicate) { printer.indent().p("Result").align(alignment).p(PRED_RESULT).pln(';'); } if (md.requiresMatched) { printer.indent().p("boolean").align(alignment).p(MATCHED).pln(';'); } for (int i=0; i<md.repetitions.size(); i++) { printer.indent().p(cName).align(alignment).p(REPETITION).p(i+1).pln(';'); if (((Boolean)md.repetitions.get(i)).booleanValue()) { printer.indent().p("boolean").align(alignment).p(REPEATED). p(i+1).pln(';'); } } if (Type.isVoidT(p.type)) { printer.indent().p(Type.voidRefT()). align(Type.voidRefT().length(), alignment); } else { printer.indent().p(p.type).align(p.type.length(), alignment); } printer.p(VALUE).pln(';'); if (md.requiresChar) { printer.indent().p(Type.charT()).align(alignment).p(CHAR).pln(';'); } // Emit code for production element. resultName = RESULT; baseParser = "this"; useBaseParser = true; choiceLevel = -1; repetitionLevel = 0; savedRepetitionLevel = 0; repeatOnce = false; seenTest = false; endsWithParseError = false; p.element.accept(this); if (seenTest) { printer.pln(); printer.indent().pln("// Done."); if (endsWithParseError) { parseError(); } printer.indent().p("return ").p(PARSE_ERROR).pln(';'); } printer.decr().indent().pln('}'); printer.pln(); } // ======================================================================== /** * Emit the code for assigning the result variable, threading the * parse error, and for testing the result. * * @param methodName The name of the parser method to use. * @param saveParser Flag for whether to save the parser in the * parser variable. */ protected void result(String methodName, boolean saveParser) { printer.pln(); // Clear the first element flag. firstElement = false; if (useBaseParser) { // The first result of an ordered choice or repetition as well // as the first element after a repetition always builds on the // current base parser. The first element of a predicate also // builds on the current base parser. if (saveParser) { // Assign parser and result. printer.indent().p(PARSER).p(" = ").p(baseParser).pln(';'); printer.indent().p(resultName).p(" = ").p(PARSER).p('.'). p(methodName).pln("();"); } else { // Assign result. printer.indent().p(resultName).p(" = ").p(baseParser).p('.'). p(methodName).pln("();"); } // Thread parse error. if ((! notFollowedBy()) && (! PARSE_CHAR.equals(methodName))) { printer.indent().p(PARSE_ERROR).p(" = ").p(PARSE_ERROR).p(".select("). p(resultName).pln(".parseError());"); } useBaseParser = false; } else { // All other elements build on the last regular/predicate // result, depending on whether we are processing regular or // predicate elements. if (saveParser) { // Assign parser and result. printer.indent().p(PARSER).p(" = (").p(cName).p(')').p(resultName). pln(".parser;"); printer.indent().p(resultName).p(" = ").p(PARSER).p('.'). p(methodName).pln("();"); } else { // Assign result. printer.indent().p(resultName).p(" = ((").p(cName).p(')').p(resultName). p(".parser).").p(methodName).pln("();"); } // Thread parse error. if ((! notFollowedBy()) && (! PARSE_CHAR.equals(methodName))) { printer.indent().p(PARSE_ERROR).p(" = ").p(PARSE_ERROR).p(".select("). p(resultName).pln(".parseError());"); } } } /** Emit the code testing whether the result has a value. */ protected void valueTest() { printer.indent().p("if (").p(resultName).pln(".hasValue()) {").incr(); } /** * Emit the code for testing the result. * * @param text The expected text value. */ protected void valueTest(String text) { printer.indent().p("if (").p(resultName).pln(".hasValue() &&"). indent().p(" \"").escape(text, Utilities.JAVA_ESCAPES).p("\".equals("). p(resultName).pln(".semanticValue())) {").incr(); } /** * Note that a test has been emitted. This method should be called * at the <i>end</i> of the method that emitted the test. */ protected void tested() { seenTest = true; } /** * Emit the code for the next element. If the next element is the * last element in the main sequence, the code for returning a * semantic value is also emitted. * * @see #returnValue() */ protected void nextElement() { // Process predicate elements first. if (predicate) { if (predicateIter.hasNext()) { // Emit code for the next predicate element. ((Element)predicateIter.next()).accept(this); return; } else if (savedRepetitionLevel < repetitionLevel) { // Assign the repetition parser variable and, if necessary, // the repeated flag; then continue with the loop. printer.pln(); printer.indent().p(REPETITION).p(repetitionLevel).p(" = ("). p(cName).p(')').p(resultName).pln(".parser;"); if (repeatOnce) { printer.indent().p(REPEATED).p(repetitionLevel).pln(" = true;"); } printer.indent().pln("continue;"); return; } else { // Assign matched variable for not-followed-by predicates. if (notFollowedBy) { printer.pln(); printer.indent().p(MATCHED).pln(" = true;"); return; } // Restore regular element processing and fall through for // followed-by predicates. predicate = false; baseParser = savedBaseParser; useBaseParser = savedUseBaseParser; resultName = RESULT; } } // Process the next regular grammar element. if (elementIter.hasNext()) { ((Element)elementIter.next()).accept(this); } else if (0 < repetitionLevel) { printer.pln(); printer.indent().p(REPETITION).p(repetitionLevel).p(" = ("). p(cName).p(')').p(resultName).pln(".parser;"); if (repeatOnce) { printer.indent().p(REPEATED).p(repetitionLevel).pln(" = true;"); } printer.indent().pln("continue;"); } else { returnValue(); } } /** * Emit the code for annotating semantic values with their location. */ private void location() { // Do not include location information if the grammar does not // have the location attribute or the type of the production's // semantic value is not a node. Note that void and text-only // productions automatically fall under the second case. if ((! attributeLocation) || Type.isNotANode(analyzer.current().type)) { return; } // Emit the location test. printer.indent().p("if (").p(VALUE).pln(" instanceof Node) {").incr(); printer.indent().p("((Node)").p(VALUE).p(").").p("setLocation("). p(FILE).p(", ").p(LINE).p(", ").p(COLUMN).pln(");"); printer.decr().indent().pln('}'); } /** * Emit the code for returning a semantic value. */ protected void returnValue() { printer.pln(); if (useBaseParser) { location(); printer.indent().p("return new SemanticValue(").p(VALUE). p(", ").p(baseParser).p(", ").p(PARSE_ERROR).pln(");"); useBaseParser = false; } else { location(); if (Rats.optimizeValues) { printer.indent().p("return ").p(RESULT).p(".createValue("). p(VALUE).p(", ").p(PARSE_ERROR).pln(");"); } else { printer.indent().p("return new SemanticValue(").p(VALUE). p(", ").p(RESULT).p(".parser, ").p(PARSE_ERROR).pln(");"); } } } /** * Emit the code for generating a parse error. */ protected void parseError() { printer.indent().p(PARSE_ERROR).p(" = ").p(PARSE_ERROR). p(".select(\""). p(Utilities.toDescription(analyzer.current().nonTerminal.name)). pln(" expected\", this);"); } /** * Emit the code for generating a parse error. * * @param text The expected text. */ protected void parseError(String text) { printer.indent().p(PARSE_ERROR).p(" = ").p(PARSE_ERROR). p(".select(\"\\\""). p(Utilities.escape(text, Utilities.JAVA_ESCAPES | Utilities.ESCAPE_DOUBLE)). p("\\\" expected\", ").p(PARSER).pln(");"); } // ======================================================================== /** * Return the name of the parser variable for the current nested * choice level. * * @return The nested choice parser variable. */ protected String nestedChoice() { return NESTED_CHOICE + Integer.toString(choiceLevel); } /** Generate code for the specified ordered choice. */ public void visit(OrderedChoice c) { String base = baseParser; boolean used = useBaseParser; choiceLevel++; // For non-top-level choices, declare a parser variable and save // the current parser. if (0 != choiceLevel) { printer.pln(); if (useBaseParser) { printer.indent().p("final ").p(cName).p(' ').p(nestedChoice()). p(" = ").p(base).pln(';'); } else { printer.indent().p("final ").p(cName).p(' ').p(nestedChoice()). p(" = (").p(cName).p(')').p(resultName).pln(".parser;"); } } // Process the options. Iterator optionIter = c.options.iterator(); int optionNumber = 0; while (optionIter.hasNext()) { elementIter = ((Sequence)optionIter.next()).elements.iterator(); firstElement = (0 == choiceLevel); baseParser = (0 == choiceLevel)? "this" : nestedChoice(); useBaseParser = true; seenTest = false; optionNumber++; printer.pln(); if (0 == choiceLevel) { printer.indent().p("// Option ").p(optionNumber).pln('.'); } else { printer.indent().p("// Nested option ").p(optionNumber).pln('.'); } nextElement(); } choiceLevel--; useBaseParser = used; baseParser = base; } // ======================================================================== /** Generate code for the specified repetition. */ public void visit(Repetition r) { firstElement = false; String base = baseParser; boolean used = useBaseParser; boolean once = repeatOnce; repeatOnce = r.once; repetitionLevel++; // Save current parser. printer.pln(); printer.indent().p(REPETITION).p(repetitionLevel).p(" = "); if (useBaseParser) { printer.p(base).pln(';'); } else { printer.p('(').p(cName).p(')').p(resultName).pln(".parser;"); } // Reset repeated flag if necessary. if (repeatOnce) { printer.indent().p(REPEATED).p(repetitionLevel).pln(" = false;"); } // Save current code generation state. Iterator iter; if (predicate) { iter = predicateIter; predicateIter = ((Sequence)r.element).elements.iterator(); } else { iter = elementIter; elementIter = ((Sequence)r.element).elements.iterator(); } // Emit code for the repeated elements. printer.indent().pln("while (true) {").incr(); baseParser = REPETITION + Integer.toString(repetitionLevel); useBaseParser = true; nextElement(); printer.indent().pln("break;"); printer.decr().indent().pln('}'); // Restore code generation state. if (predicate) { predicateIter = iter; } else { elementIter = iter; } // Emit code for the rest of the current sequence. if (repeatOnce) { printer.pln(); printer.indent().p("if (").p(REPEATED).p(repetitionLevel).pln(") {"). incr(); } repetitionLevel--; repeatOnce = once; baseParser = REPETITION + Integer.toString(repetitionLevel + 1); useBaseParser = true; if (! r.once) { seenTest = false; } nextElement(); if (r.once) { printer.decr().indent().pln('}'); tested(); } baseParser = base; useBaseParser = used; } // ======================================================================== /** Generate code for the specified followed-by predicate. */ public void visit(FollowedBy p) { if (predicate) { throw new IllegalStateException("Predicate within predicate"); } predicate = true; notFollowedBy = false; savedFirstElement = firstElement; savedBaseParser = baseParser; if (! useBaseParser) { // Only set a new base parser if the base parser is not used for // the next element. baseParser = "((" + cName + ")" + RESULT + ".parser)"; } savedUseBaseParser = useBaseParser; savedRepetitionLevel = repetitionLevel; useBaseParser = true; resultName = PRED_RESULT; predicateIter = ((Sequence)p.element).elements.iterator(); // Emit code for the followed-by predicate and the rest of the // rule sequence. nextElement(); tested(); } // ======================================================================== /** * Determine whether we are processing a not-followed-by predicate. * * @return <code>true</code> if we are processing a not-followed-by * predicate. */ protected boolean notFollowedBy() { return (predicate && notFollowedBy); } /** Generate code for the specified not-followed-by predicate. */ public void visit(NotFollowedBy p) { if (predicate) { throw new IllegalStateException("Predicate within predicate"); } predicate = true; notFollowedBy = true; savedFirstElement = firstElement; savedBaseParser = baseParser; if (! useBaseParser) { // Only set a new base parser if the base parser is not used for // the next element. baseParser = "((" + cName + ")" + RESULT + ".parser)"; } savedUseBaseParser = useBaseParser; useBaseParser = true; savedRepetitionLevel = repetitionLevel; resultName = PRED_RESULT; predicateIter = ((Sequence)p.element).elements.iterator(); // Emit code for the not-followed-by predicate. printer.pln(); printer.indent().p(MATCHED).pln(" = false;"); nextElement(); // Restore regular element processing. predicate = false; firstElement = savedFirstElement; baseParser = savedBaseParser; useBaseParser = savedUseBaseParser; resultName = RESULT; // Emit code for the rest of the rule sequence. printer.pln(); printer.indent().p("if (! ").p(MATCHED).pln(") {").incr(); nextElement(); printer.decr().indent().pln("} else {").incr(); parseError(); printer.decr().indent().pln('}'); tested(); } // ======================================================================== /** Generate code for the specified semantic predicate. */ public void visit(SemanticPredicate p) { printer.pln().indent().p("if ("); Action a = (Action)p.element; if (1 == a.code.size()) { printer.p((String)a.code.get(0)).pln(") {").incr(); } else { boolean first = true; Iterator iter = a.code.iterator(); while (iter.hasNext()) { if (first) { printer.p((String)iter.next()).incr(); first = false; } else { printer.pln().indent().p((String)iter.next()); } printer.pln(") {"); } } nextElement(); printer.decr().indent().pln('}'); if (! notFollowedBy()) { endsWithParseError = true; } tested(); } // ======================================================================== /** Generate code for the specified binding. */ public void visit(Binding b) { // Save old name and element. String oldName = bindingName; Element oldElement = bindingElement; // Set up new name and element; bindingName = b.name; bindingElement = b.element; // Visit element. b.element.accept(this); // Restore old name and element. bindingName = oldName; bindingElement = oldElement; } /** * Determine whether the current element has a binding. * * @return <code>true</code> if the current element has a binding. */ protected boolean hasBinding() { return (null != bindingName); } /** Actually emit the code for the last visited binding. */ protected void binding() { if (bindingElement instanceof NonTerminal) { String type = analyzer.lookup((NonTerminal)bindingElement).type; binding1(type, bindingName, type, resultName + ".semanticValue()"); } else if (bindingElement instanceof CharTerminal) { if (VALUE.equals(bindingName)) { binding1(Type.charRefT(), bindingName, null, "new Character(" + resultName + ".charValue())"); } else { binding1(Type.charT(), bindingName, null, resultName + ".charValue()"); } } else if (bindingElement instanceof StringLiteral) { binding1(Type.stringT(), bindingName, null, "\"" + Utilities.escape(((StringLiteral)bindingElement).text, Utilities.JAVA_ESCAPES) + "\""); } else if (bindingElement instanceof StringMatch) { binding1(Type.stringT(), bindingName, null, "\"" + Utilities.escape(((StringMatch)bindingElement).text, Utilities.JAVA_ESCAPES) + "\""); } else { throw new IllegalStateException("Unrecognized binding element " + bindingElement); } } /** * Emit the binding code. * * @param type The variable type. * @param name The variable name. * @param cast The cast type, or <code>null</code> for no cast. * @param expr The value producing expression. */ private void binding1(String type, String name, String cast, String expr) { printer.indent(); if (VALUE.equals(name)) { printer.p(VALUE).p(" = "); } else { if (attributeConstantBinding) { printer.p("final "); } printer.p(type).p(' ').p(name).p(" = "); } if (null != cast) { printer.p('(').p(cast).p(')'); } printer.p(expr).pln(';'); } /** Clear binding information after usage. */ protected void clearBinding() { bindingName = null; bindingElement = null; } // ======================================================================== /** Generate code for the specified string match. */ public void visit(StringMatch m) { final boolean first = firstElement; // At this point, the element of the string match must be a // nonterminal. NonTerminal nt = (NonTerminal)m.element; result(methodName(nt), ((! notFollowedBy()) && (! first))); valueTest(m.text); if (hasBinding()) { binding(); clearBinding(); } nextElement(); if (notFollowedBy()) { printer.decr().indent().pln('}'); } else if (attributeNoMatchingErrors || (Rats.optimizeErrors && first)) { printer.decr().indent().pln('}'); endsWithParseError = true; } else { printer.decr().indent().pln("} else {").incr(); parseError(m.text); printer.decr().indent().pln('}'); } tested(); } // ======================================================================== /** Generate code for the specified nonterminal. */ public void visit(NonTerminal nt) { result(methodName(nt), false); valueTest(); if (hasBinding()) { binding(); clearBinding(); } nextElement(); printer.decr().indent().pln('}'); tested(); } // ======================================================================== /** Generate code for the any character element. */ public void visit(AnyChar a) { result(PARSE_CHAR, false); valueTest(); if (hasBinding()) { binding(); clearBinding(); } nextElement(); printer.decr().indent().pln('}'); if (! notFollowedBy()) { endsWithParseError = true; } tested(); } // ======================================================================== /** Generate code for the specified character literal. */ public void visit(CharLiteral l) { result(PARSE_CHAR, false); valueTest(); String name; if (hasBinding()) { binding(); name = bindingName; clearBinding(); } else { printer.indent().p(CHAR).p(" = ").p(resultName).pln(".charValue();"); name = CHAR; } printer.pln(); printer.indent().p("if (\'").escape(l.c, Utilities.JAVA_ESCAPES). p("\' == ").p(name).pln(") {").incr(); nextElement(); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); if (! notFollowedBy()) { endsWithParseError = true; } tested(); } // ======================================================================== /** Generate code for the specified character class. */ public void visit(CharClass c) { result(PARSE_CHAR, false); valueTest(); String name; if (hasBinding()) { binding(); name = bindingName; clearBinding(); } else { printer.indent().p(CHAR).p(" = ").p(resultName).pln(".charValue();"); name = CHAR; } printer.pln(); final int length = c.ranges.size(); Iterator iter = c.ranges.iterator(); if (1 == length) { printer.indent().p("if "); } else { printer.indent().p("if ("); } while (iter.hasNext()) { CharRange r = (CharRange)iter.next(); if (c.exclusive) { if (r.first == r.last) { printer.p("(\'").escape(r.first, Utilities.JAVA_ESCAPES).p("\' != "). p(name).p(')'); } else { printer.p('(').p(name).p(" < \'"). escape(r.first, Utilities.JAVA_ESCAPES).p(") || (\'"). escape(r.last, Utilities.JAVA_ESCAPES).p("\' < ").p(name).p("))"); } } else { if (r.first == r.last) { printer.p("(\'").escape(r.first, Utilities.JAVA_ESCAPES).p("\' == "). p(name).p(')'); } else { printer.p("((\'").escape(r.first, Utilities.JAVA_ESCAPES).p("\' <= "). p(name).p(") && (").p(name).p(" <= \'"). escape(r.last, Utilities.JAVA_ESCAPES).p("\'))"); } } if (iter.hasNext()) { if (c.exclusive) { printer.pln(" &&"); } else { printer.pln(" ||"); } printer.indent().p(" "); } } if (1 == length) { printer.pln(" {").incr(); } else { printer.pln(") {").incr(); } nextElement(); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); if (! notFollowedBy()) { endsWithParseError = true; } tested(); } // ======================================================================== /** Generate code for the specified literal. */ public void visit(StringLiteral l) { final boolean first = firstElement; final int length = l.text.length(); for (int i=0; i<length; i++) { char c = l.text.charAt(i); result(PARSE_CHAR, ((0 == i) && (! notFollowedBy()) && (! first))); valueTest(); printer.indent().p(CHAR).p(" = ").p(resultName).pln(".charValue();"); printer.pln(); printer.indent().p("if (\'").escape(c, Utilities.JAVA_ESCAPES). p("\' == ").p(CHAR).pln(") {").incr(); } if (hasBinding()) { binding(); clearBinding(); } nextElement(); for (int i=0; i<length; i++) { if (notFollowedBy()) { printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); } else if (attributeNoMatchingErrors || (Rats.optimizeErrors && first)) { printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); endsWithParseError = true; } else { printer.decr().indent().pln("} else {").incr(); parseError(l.text); printer.decr().indent().pln('}'); printer.decr().indent().pln("} else {").incr(); parseError(l.text); printer.decr().indent().pln('}'); } } tested(); } // ======================================================================== /** Generate code for the specified character switch. */ public void visit(CharSwitch s) { result(PARSE_CHAR, false); valueTest(); String name; if (hasBinding()) { binding(); name = bindingName; clearBinding(); } else { printer.indent().p(CHAR).p(" = ").p(resultName).pln(".charValue();"); name = CHAR; } printer.pln(); printer.indent().p("switch (").p(name).pln(") {").incr(); Iterator iter = s.cases.iterator(); while (iter.hasNext()) { CharCase c = (CharCase)iter.next(); Iterator iter2 = c.klass.ranges.iterator(); while (iter2.hasNext()) { CharRange r = (CharRange)iter2.next(); for (char k=r.first; k<=r.last; k++) { printer.indentLess().p("case \'").escape(k, Utilities.JAVA_ESCAPES). pln("\':"); } } if (null == c.element) { printer.indent().pln("/* No match. */"); printer.indent().pln("break;"); } else { printer.indent().p('{').incr(); // The line terminator is printed by emitting code for // c.element. seenTest = false; if (c.element instanceof OrderedChoice) { c.element.accept(this); } else { elementIter = ((Sequence)c.element).elements.iterator(); nextElement(); } printer.decr().indent().pln('}'); if (seenTest) { printer.indent().pln("break;"); } } printer.pln(); } if (null == s.base) { printer.indentLess().pln("default:"); printer.indent().pln("/* No match. */"); } else { printer.indentLess().pln("default:"); printer.indent().p('{').incr(); // The line terminator is printed by emitting code for s.base. if (s.base instanceof OrderedChoice) { s.base.accept(this); } else { elementIter = ((Sequence)s.base).elements.iterator(); nextElement(); } printer.decr().indent().pln('}'); } printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); endsWithParseError = true; tested(); } // ======================================================================== /** Actually emit code for the specified action. */ protected void action(Action a) { Iterator iter = a.code.iterator(); while (iter.hasNext()) { printer.indent().pln(iter.next().toString()); } } /** Generate code for the specified action. */ public void visit(Action a) { printer.pln(); action(a); nextElement(); } // ======================================================================== /** Generate code for the specified null value. */ public void visit(NullValue v) { printer.pln(); printer.indent().p(VALUE).pln(" = null;"); nextElement(); } /** Generate code for the specified string value. */ public void visit(StringValue v) { printer.pln(); printer.indent().p(VALUE).p(" = \""). escape(v.text, Utilities.JAVA_ESCAPES).pln("\";"); nextElement(); } /** Generate code for the specified text value. */ public void visit(TextValue v) { if (predicate) { throw new IllegalStateException("Text value within predicate"); } printer.pln(); if (firstElement) { printer.indent().p(VALUE).pln(" = \"\";"); } else if (useBaseParser) { printer.indent().p(VALUE).p(" = getDifference(").p(baseParser).pln(");"); } else { printer.indent().p(VALUE).p(" = getDifference(").p(RESULT). pln(".parser);"); } nextElement(); } /** Generate code for the specified empty list value. */ public void visit(EmptyListValue v) { printer.pln(); printer.indent().p(VALUE).pln(" = Pair.EMPTY;"); nextElement(); } /** Generate code for the specified singleton list value. */ public void visit(SingletonListValue v) { printer.pln(); printer.indent().p(VALUE).p(" = new Pair(").p(v.value).pln(");"); nextElement(); } /** Generate code for the specified list value. */ public void visit(ListValue v) { printer.pln(); printer.indent().p(VALUE).p(" = new Pair(").p(v.value).p(", ").p(v.list). pln(");"); nextElement(); } }