/* * xtc - The eXTensible Compiler * Copyright (C) 2004-2011 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.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import xtc.Constants; import xtc.Constants.FuzzyBoolean; import xtc.tree.Attribute; import xtc.tree.GNode; import xtc.tree.Locatable; import xtc.tree.Printer; import xtc.tree.Visitor; import xtc.type.AST; import xtc.type.InstantiatedT; import xtc.type.Type; import xtc.util.Runtime; import xtc.util.Utilities; /** * The code generator. * * <p />The code generator makes the following assumptions about the * intermediate language:<ul> * * <li>The entire grammar is contained in a single {@link Module * module}.</li> * * <li>All imported types have been registered with {@link * AST#importType} and all imported modules have been registered with * {@link AST#importModule}.</li> * * <li>If the grammar references any types that may be {@link * Locatable locatable} (as opposed to definitely being or not being * locatable), the grammar must have a {@link Properties#LOCATABLE * locatable} property with value <code>Boolean.TRUE</code>.<p /></li> * * <li>If the grammar contains any {@link GenericValue generic * values}, the grammar must have a {@link Properties#GENERIC generic} * property with value <code>Boolean.TRUE</code>.<p /></li> * * <li>If the grammar contains any {@link ActionBaseValue action base * values}, {@link GenericActionValue generic action value}, or {@link * GenericRecursionValue generic recursion values}, the grammar must * have a {@link Properties#RECURSIVE recursive} property with value * <code>Boolean.TRUE</code>.<p /></li> * * <li>Each {@link Production production} must be a {@link * FullProduction full production} and must have been annotated with * the appropriate {@link MetaData meta-data}.<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), a repetition may appear * if the list of repeated expressions need not be memoized, an an * option may appear if its value is not bound or depends on a single * bound element.<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). If the repetition is bound, * {@link Analyzer#bind(List)} must be able to capture the semantic * value of the repeated element.<p /></li> * * <li>The element of a {@link Option option} must be a {@link * Sequence sequence} (with the last element possibly being an ordered * choice). If the option is bound, {@link Analyzer#bind(List)} must * be able to capture the semantic value of the optional element.<p * /></li> * * <li>The element of a {@link StringMatch string match} must be a * {@link NonTerminal nonterminal}.<p /></li> * * <li>The element of a {@link FollowedBy} or {@link NotFollowedBy} * predicate must be 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.294 $ */ public class CodeGenerator extends Visitor { /** 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 prefix for field names that count accesses to memoized results. */ public static final String PREFIX_COUNT_FIELD = "c"; /** The general prefix for internal parser fields and variables. */ public static final String PREFIX = "yy"; /** The name for the variable referencing the verbose mode printer. */ public static final String PRINTER = PREFIX + "Out"; /** The name for the variable referencing the global state object. */ public static final String STATE = PREFIX + "State"; /** The name of the character parsing method. */ public static final String PARSE_CHAR = "character"; /** The name of the index argument. */ public static final String ARG_INDEX = PREFIX + "Start"; /** The name of the memoization column variable. */ public static final String COLUMN = PREFIX + "Column"; /** The name of the character variable. */ public static final String CHAR = PREFIX + "C"; /** The name of the index variable. */ public static final String INDEX = PREFIX + "Index"; /** The name for the result variable. */ public static final String RESULT = PREFIX + "Result"; /** The name of the predicate index variable. */ public static final String PRED_INDEX = PREFIX + "PredIndex"; /** 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 PRED_MATCHED = PREFIX + "PredMatched"; /** The name for the base index variable. */ public static final String BASE_INDEX = PREFIX + "Base"; /** The prefix for the index variable for nested choices. */ public static final String NESTED_CHOICE = PREFIX + "Choice"; /** The prefix for the index variable for repetitions. */ public static final String REPETITION = PREFIX + "Repetition"; /** The prefix for the index variable for options. */ public static final String OPTION = PREFIX + "Option"; /** * The prefix for the flag indicating that a repetition has been * matched at least once. */ public static final String REPEATED = PREFIX + "Repeated"; /** * The prefix for the variable referencing the semantic value of a * bound repetition. */ public static final String REP_VALUE = PREFIX + "RepValue"; /** * The prefix for the variable referencing the semantic value of a * bound option. */ public static final String OP_VALUE = PREFIX + "OpValue"; /** The name for the value variable (i.e., <code>yyValue</code>). */ public static final String VALUE = PREFIX + "Value"; /** The name for the parse error variable. */ public static final String PARSE_ERROR = PREFIX + "Error"; // ======================================================================== /** The runtime. */ protected final Runtime runtime; /** The analyzer utility. */ protected final Analyzer analyzer; /** The type operations. */ protected final AST ast; /** The printer utility. */ protected final Printer printer; /** The flag for generating debugging code. */ protected boolean attributeVerbose; /** * The flag for generating code to annotate nodes with location * information. */ protected boolean attributeWithLocation; /** The flag for making variable bindings constant. */ protected boolean attributeConstant; /** The flag for flattening lists. */ protected boolean attributeFlatten; /** The flag for generating a parse tree. */ protected boolean attributeParseTree; /** The flag for using raw types. */ protected boolean attributeRawTypes; /** The flag for performing case-insensitive comparisons. */ protected boolean attributeIgnoringCase; /** The flag for using a global state object. */ protected boolean attributeStateful; /** The flag for having a string set attribute. */ protected boolean attributeStringSet; /** The class name for the global state object. */ protected String stateClassName; /** The class name for the generic node factory. */ protected String factoryClassName; /** The flag for creating a main method. */ protected boolean attributeMain; /** The nonterminal for the main method. */ protected String mainMethodNonterminal = null; /** The flag for using a grammar-specified printer in the main method. */ protected boolean attributePrinter; /** The class name for the grammar-specified printer. */ protected String printerClassName; /** The flag for including code to produce a memoization profile. */ protected boolean attributeProfile; /** The flag for including a method to dump the memoization table. */ protected boolean attributeDump; /** The class name for the current grammar. */ protected String className; /** Flag for whether the memoization fields are organized in chunks. */ protected boolean chunked; /** The map from nonterminals to chunk numbers. */ protected Map<NonTerminal, Integer> 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 index. */ protected String baseIndex; /** The flag for using the base index. */ protected boolean useBaseIndex; /** The saved base index. */ protected String savedBaseIndex; /** The saved flag for using the base index. */ protected boolean savedUseBaseIndex; /** The indentation level for choices. */ protected int indentLevel; /** The nesting level for nested choices. */ protected int choiceLevel; /** The flag for repetitions. */ protected boolean repeated; /** The saved repetition flag for predicates. */ protected boolean savedRepeated; /** The flag for at-least-once repetitions. */ protected boolean repeatedOnce; /** The saved at-least-once flag for predicates. */ protected boolean savedRepeatedOnce; /** The nesting level for repetitions. */ protected int repetitionLevel; /** * The name of the variable referencing the element value for bound * repetitions, or <code>null</code> if no such repetition is * currently being processed. */ protected String repeatedElement; /** The types of bound repetitions, i.e. {@link MetaData#boundRepetitions}. */ protected List<Type> repetitionTypes; /** The flag for options. */ protected boolean optional; /** The saved option flag for predicates. */ protected boolean savedOptional; /** The nesting level for options. */ protected int optionLevel; /** * The name of the variable referencing the element value for bound * options, or <code>null</code> if no such option is currently * being processed. */ protected String optionalElement; /** The types of bound options, i.e. {@link MetaData#options}. */ protected List<Type> optionTypes; /** The flag for whether an alternative creates a node value. */ protected boolean createsNodeValue; /** 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<Element> elementIter; /** The name of the index variable. */ protected String indexName; /** 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; /** The type of the element being bound. */ protected Type bindingType; /** 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<Element> predicateIter; // ======================================================================== /** * Create a new code generator. * * @param runtime The runtime. * @param analyzer The analyzer. * @param ast The type operations. * @param printer The printer. */ public CodeGenerator(Runtime runtime, Analyzer analyzer, AST ast, Printer printer) { this.runtime = runtime; this.analyzer = analyzer; this.ast = ast; this.printer = printer; } // ======================================================================== /** * Get the primitive boolean type as a string. * * @return The boolean type. */ public String booleanT() { return "boolean"; } /** * Get the primitive character type as a string. * * @return The character type. */ public String charT() { return "char"; } /** * Get the primtive integer type as a string. * * @return The integer type. */ public String intT() { return "int"; } /** * Get the primitive index type as a string. * * @return The index type. */ public String indexT() { return "int"; } /** * Extern the specified type. If the specified type is not * <code>null</code>, this method returns its external * representation. Otherwise, it returns <code>null</code>. * * @param type The type. * @return The type as a string. */ public String extern(Type type) { return null == type ? null : ast.extern(type); } /** * Get the raw, non-generic type for the specified type. * * @param type The type as a string. * @return The raw type as a string. */ public String rawT(String type) { final int idx = type.indexOf('<'); return -1 == idx ? type : type.substring(0, idx); } // ======================================================================== /** * Get the null expression. * * @return The null expression. */ public String nullExpr() { return "null"; } /** * Get a string expression. * * @param text The string's text. * @return The string expression. */ public String stringExpr(String text) { return '"' + Utilities.escape(text, Utilities.JAVA_ESCAPES) + '"'; } /** * Get the empty list expression. * * @return The empty list expression. */ public String emptyListExpr() { return attributeRawTypes ? "Pair.EMPTY" : "Pair.empty()"; } // ======================================================================== /** * Generate a field name for the specified nonterminal. * * @param nt The nonterminal. * @param prefix The field name's prefix. * @return The corresponding field name. */ public String fieldName(NonTerminal nt, String prefix) { if (chunked) { return COLUMN + ".chunk" + chunkMap.get(nt) + "." + prefix + nt.toIdentifier(); } else { return COLUMN + "." + prefix + nt.toIdentifier(); } } /** * 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.toIdentifier(); } // ======================================================================== /** Emit code for verbose operation. */ protected void verbose() { printer.sep().pln(); printer.indent().pln("/**"); printer.indent().pln(" * Trace entering the specified production."); printer.indent().pln(" *"); printer.indent().pln(" * @param name The name."); printer.indent().pln(" * @param index The index."); printer.indent().pln(" */"); printer.indent().pln("protected void traceEnter(String name, int index) {"). incr(); printer.indent().pln("if (! DEBUG) return;"); printer.pln(); printer.indent().p(PRINTER).p(".p(\"enter \").p(name).p(\" @ \")."). pln("p(index);"); printer.indent().p("if (PEEK) ").p(PRINTER).p(".p(\" : \\\"\")."). pln("escape(peek(index)).p('\\\"');"); printer.indent().p(PRINTER).pln(".pln().flush();"); printer.decr().indent().pln('}'); printer.pln(); printer.indent().pln("/**"); printer.indent().p(" * Trace a successful exit from the specified "). pln("production."); printer.indent().pln(" *"); printer.indent().pln(" * @param name The name."); printer.indent().pln(" * @param index The index."); printer.indent().pln(" */"); printer.indent().p("protected void traceSuccess(String name, "). pln("int index) {").incr(); printer.indent().pln("if (! DEBUG) return;"); printer.indent().p(PRINTER).p(".p(\"exit \").p(name).p(\" @ \")."). pln("p(index).pln(\" with match\").flush();"); printer.decr().indent().pln('}'); printer.pln(); printer.indent().pln("/**"); printer.indent().p(" * Trace a failed exit from the specified "). pln("production."); printer.indent().pln(" *"); printer.indent().pln(" * @param name The name."); printer.indent().pln(" * @param index The index."); printer.indent().pln(" */"); printer.indent().p("protected void traceFailure(String name, "). pln("int index) {").incr(); printer.indent().pln("if (! DEBUG) return;"); printer.indent().p(PRINTER).p(".p(\"exit \").p(name).p(\" @ \")."). pln("p(index).pln(\" with error\").flush();"); printer.decr().indent().pln('}'); printer.pln(); printer.indent().pln("/**"); printer.indent().p(" * Trace a lookup in the memoization table for "). pln("the specified production."); printer.indent().pln(" *"); printer.indent().pln(" * @param name The name."); printer.indent().pln(" * @param index The index."); printer.indent().pln(" * @param result The result."); printer.indent().pln(" */"); printer.indent().p("protected void traceLookup(String name, int index, "). pln("Result result) {").incr(); printer.indent().pln("if (! DEBUG) return;"); printer.pln(); printer.indent().p(PRINTER).p(".p(\"lookup \").p(name).p(\" @ \")."). pln("p(index);"); printer.indent().p("if (PEEK) ").p(PRINTER).p(".p(\" : \\\"\")."). pln("escape(peek(index)).p('\\\"');"); printer.indent().p(PRINTER).pln(".p(\" -> \");"); printer.indent().pln("if (result.hasValue()) {").incr(); printer.indent().p(PRINTER).pln(".p(\"match\");"); printer.decr().indent().pln("} else {").incr(); printer.indent().p(PRINTER).pln(".p(\"error\");"); printer.decr().indent().pln('}'); printer.indent().p(PRINTER).pln(".pln().flush();"); printer.decr().indent().pln('}'); printer.pln(); } // ======================================================================== /** Emit code for printing the memoization profile. */ protected void profile() { // Emit the method header. printer.sep().pln(); printer.indent().pln("/**"); printer.indent().pln(" * Print a profile of the memoization table."); printer.indent().pln(" *"); printer.indent().p(" * @param printer The printer for writing the "). pln("profile."); printer.indent().pln(" */"); printer.indent().pln("public void profile(Printer printer) {").incr(); // Emit the profile initialization code. printer.indent().pln("// Initialize the profile."); if (attributeRawTypes) { printer.indent().p("HashMap maxima = new HashMap();"); } else { printer.indent().p("HashMap<String, Integer> maxima = "). pln("new HashMap<String, Integer>();"); } printer.pln(); int maxNameSize = 0; for (Production p : analyzer.module().productions) { if (runtime.test("optimizeTransient") && ! p.isMemoized()) continue; final String name = p.name.toIdentifier(); maxNameSize = Math.max(maxNameSize, name.length()); if (attributeRawTypes) { printer.indent().p("maxima.put(\"").p(name). pln("\", Integer.valueOf(0));"); } else { printer.indent().p("maxima.put(\"").p(name).pln("\", 0);"); } } printer.pln(); // Emit the code to process the memoization table. printer.indent().pln("// Process the memoization table."); printer.indent().pln("for (int i=0; i<yyCount; i++) {").incr(); printer.indent().p(className).p("Column column = (").p(className). pln("Column)yyColumns[i];"); printer.pln(); printer.indent().pln("if (null != column) {").incr(); if (0 == chunkCount) { for (Production p : analyzer.module().productions) { if ((! runtime.test("optimizeTransient")) || p.isMemoized()) { final String name = p.name.toIdentifier(); printer.indent().p("profile(maxima, \"").p(name).p("\", ").buffer(). p("column.").p(PREFIX_COUNT_FIELD).p(name).p(");"). fit(" ").pln(); } } } else { int number = 0; int idx = CHUNK_SIZE; boolean first = true; for (Production p : analyzer.module().productions) { if (runtime.test("optimizeTransient") && ! p.isMemoized()) continue; if (CHUNK_SIZE <= idx) { number++; idx = 0; if (first) { first = false; } else { printer.decr().indent().pln('}'); printer.pln(); } printer.indent().p("Chunk").p(number).p(" chunk").p(number). p(" = column.chunk").p(number).pln(';'); printer.indent().p("if (null != chunk").p(number).pln(") {").incr(); } final String name = p.name.toIdentifier(); printer.indent().p("profile(maxima, \"").p(name).p("\", ").buffer(). p("chunk").p(number).p('.').p(PREFIX_COUNT_FIELD).p(name).p(");"). fit(" ").pln(); idx++; } printer.decr().indent().pln('}'); } printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.pln(); // Emit the code to print the profile. printer.indent().pln("// Print the profile."); for (Production p : analyzer.module().productions) { if (runtime.test("optimizeTransient") && ! p.isMemoized()) continue; printer.indent().p("print(printer, ").p(maxNameSize).p(", maxima, "). buffer().p('"').p(p.name.toIdentifier()).p("\");").fitMore().pln(); } printer.decr().indent().pln('}'); printer.pln(); // Emit the code for the profile helper method. printer.indent().pln("/**"); printer.indent().p(" * Update the profile for the specified production"). pln(" and count."); printer.indent().pln(" *"); printer.indent().pln(" * @param maxima The profile."); printer.indent().pln(" * @param name The production's name."); printer.indent().pln(" * @param count The access count."); printer.indent().pln(" */"); if (attributeRawTypes) { printer.indent().p("private void profile(HashMap maxima, String name, "). pln("int count) {").incr(); printer.indent().pln("int old = ((Integer)maxima.get(name)).intValue();"); printer.indent().pln("int max = Math.max(old, count);"); printer.indent().p("if (old < max) "). pln("maxima.put(name, Integer.valueOf(max));"); } else { printer.indent().p("private void profile(HashMap<String, Integer> "). pln("maxima,"); printer.indent().pln(" String name, int count) {"). incr(); printer.indent().pln("int old = maxima.get(name);"); printer.indent().pln("int max = Math.max(old, count);"); printer.indent().pln("if (old < max) maxima.put(name, max);"); } printer.decr().indent().pln('}'); printer.pln(); // Emit the code for the print helper method. printer.indent().pln("/**"); printer.indent().p(" * Print the profile for the specified production"). pln(" and count."); printer.indent().pln(" *"); printer.indent().pln(" * @param printer The printer."); printer.indent().pln(" * @param align The alignment."); printer.indent().pln(" * @param maxima The profile."); printer.indent().pln(" * @param name The production's name."); printer.indent().pln(" */"); if (attributeRawTypes) { printer.indent().pln("private void print(Printer printer, int align,"); printer.indent().pln(" HashMap maxima, String name) {"). incr(); printer.indent().p("int count = ((Integer)maxima.get(name))."). pln("intValue();"); } else { printer.indent().pln("private void print(Printer printer, int align,"); printer.indent().p(" HashMap<String, Integer> "). pln("maxima, String name) {").incr(); printer.indent().pln("int count = maxima.get(name);"); } printer.indent().pln("align += 4;"); printer.indent().pln("if (1 != count) {").incr(); printer.indent().pln("printer.p(\"- \");"); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("printer.p(\"* \");"); printer.decr().indent().pln('}'); printer.indent().pln("printer.p(name).align(align).p(\" : \").pln(count);"); printer.decr().indent().pln('}'); printer.pln(); } // ======================================================================== /** Emit code for dumping the memoization table. */ protected void dump() { printer.sep().pln(); printer.indent().pln("/**"); printer.indent().pln(" * Dump the memoization table."); printer.indent().pln(" *"); printer.indent().pln(" * @param printer The printer for writing the table."); printer.indent().pln(" */"); printer.indent().pln("public void dump(Printer printer) {").incr(); printer.indent().pln("for (int i=0; i<yyCount; i++) {").incr(); printer.indent().p(className).p("Column column = (").p(className). pln("Column)yyColumns[i];"); printer.indent().pln("printer.indent().p(i).p(\" = \");"); printer.pln(); printer.indent().pln("if (null == column) {").incr(); printer.indent().pln("printer.pln(\"null;\");"); printer.pln(); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("printer.pln('{').incr();"); if (0 == chunkCount) { printer.pln(); for (Production p : analyzer.module().productions) { if ((! runtime.test("optimizeTransient")) || p.isMemoized()) { final String name = p.name.toIdentifier(); printer.indent().p("dump(printer, \"").p(name).p("\", "). buffer().p("column.").p(PREFIX_FIELD).p(name).p(");"). fit(" ").pln(); } } } else { int number = 0; int idx = CHUNK_SIZE; boolean first = true; for (Production p : analyzer.module().productions) { if (runtime.test("optimizeTransient") && ! p.isMemoized()) continue; if (CHUNK_SIZE <= idx) { number++; idx = 0; if (first) { first = false; } else { printer.pln(); printer.indent().pln("printer.decr().indent().pln(\"};\");"); printer.decr().indent().pln('}'); } printer.pln(); printer.indent().p("Chunk").p(number).p(" chunk").p(number). p(" = column.chunk").p(number).pln(';'); printer.indent().p("printer.indent().p(\"Chunk(").p(number). p(") = \");"); printer.pln(); printer.indent().p("if (null == chunk").p(number).pln(") {").incr(); printer.indent().pln("printer.pln(\"null;\");"); printer.pln(); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("printer.pln('{').incr();"); printer.pln(); } final String name = p.name.toIdentifier(); printer.indent().p("dump(printer, \"").p(name).p("\", ").buffer(). p("chunk").p(number).p('.').p(PREFIX_FIELD).p(name).p(");"). fit(" ").pln(); idx++; } printer.pln(); printer.indent().pln("printer.decr().indent().pln(\"};\");"); printer.decr().indent().pln('}'); } printer.pln(); printer.indent().pln("printer.decr().indent().pln(\"};\");"); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.pln(); printer.indent().pln("/**"); printer.indent().pln(" * Dump a memoized result."); printer.indent().pln(" *"); printer.indent().pln(" * @param printer The printer."); printer.indent().pln(" * @param name The name of the result."); printer.indent().pln(" * @param result The value of the result."); printer.indent().pln(" */"); printer.indent().p("private void dump(Printer printer, String name, "). pln("Result result) {").incr(); printer.indent().pln("printer.indent().p(name).p(\" = \");"); printer.pln(); printer.indent().pln("if (null == result) {").incr(); printer.indent().pln("printer.pln(\"null;\");"); printer.decr().indent().pln("} else if (result.hasValue()) {").incr(); printer.indent().pln("printer.p(\"Value(\").p(result.index).pln(\");\");"); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("printer.p(\"Error(\").p(result.index).pln(\");\");"); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.pln(); } // ======================================================================== /** * Emit code for a static main method. * * @param nt The name of the top-level nonterminal to parse. */ protected void mainMethod(String nt) { final int align = (printer.level() * Constants.INDENTATION) + (4 * Constants.INDENTATION) + Math.max(6, className.length()) + 1 + Constants.FIRST_COLUMN; printer.sep().pln(); printer.indent().pln("/**"); printer.indent().pln(" * Parse the specified files."); printer.indent().pln(" *"); printer.indent().pln(" * @param args The file names."); printer.indent().pln(" */"); printer.indent().pln("public static void main(String[] args) {").incr(); printer.indent().pln("if ((null == args) || (0 == args.length)) {").incr(); printer.indent().pln("System.err.println(\"Usage: <file-name>+\");"); printer.pln(); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("for (int i=0; i<args.length; i++) {").incr(); printer.indent().p("System.err.println(\"Processing \" + args[i] + "). pln("\" ...\");"); printer.pln(); printer.indent().p("Reader").align(align).pln("in = null;"); printer.indent().pln("try {").incr(); printer.indent().p("in").align(align + 3). pln("= new BufferedReader(new FileReader(args[i]));"); printer.indent().p(className).align(align).p("p = "). buffer().p("new ").p(className). p("(in, args[i], (int)new File(args[i]).length());").fitMore().pln(); printer.indent().p("Result").align(align).p("r = p.p").p(nt).pln("(0);"); printer.pln(); printer.indent().pln("if (r.hasValue()) {").incr(); printer.indent().pln("SemanticValue v = (SemanticValue)r;"); printer.pln(); if (attributePrinter) { printer.indent().pln("if (v.value instanceof Node) {").incr(); printer.indent().pln("Printer ptr = new"); printer.indentMore().p("Printer(new BufferedWriter(new "). pln("OutputStreamWriter(System.out)));"); printer.indent().p("new ").p(printerClassName). pln("(ptr).dispatch((Node)v.value);"); printer.indent().pln("ptr.flush();").pln(); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("System.out.println(v.value.toString());"); printer.decr().indent().pln('}'); } else { printer.indent().pln("if (v.value instanceof Node) {").incr(); printer.indent().pln("Printer ptr = new"); printer.indentMore().p("Printer(new BufferedWriter(new "). pln("OutputStreamWriter(System.out)));"); printer.indent().pln("ptr.format((Node)v.value).pln().flush();"); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("System.out.println(v.value.toString());"); printer.decr().indent().pln('}'); } printer.pln(); printer.decr().indent().pln("} else {").incr(); printer.indent().pln("ParseError err = (ParseError)r;"); printer.indent().pln("if (-1 == err.index) {").incr(); printer.indent().pln("System.err.println(\" Parse error\");"); printer.decr().indent().pln("} else {").incr(); printer.indent().p("System.err.println(\" \" + p.location(err.index) + "). pln("\": \" + err.msg);"); printer.decr().indent().pln("}"); printer.decr().indent().pln("}"); printer.pln(); printer.decr().indent().pln("} catch (Throwable x) {").incr(); printer.indent().pln("while (null != x.getCause()) {").incr(); printer.indent().pln("x = x.getCause();"); printer.decr().indent().pln("}"); printer.indent().pln("x.printStackTrace();"); printer.decr().indent().pln("} finally {").incr(); printer.indent().pln("try {").incr(); printer.indent().pln("in.close();"); printer.decr().indent().pln("} catch (Throwable x) {").incr(); printer.indent().pln("/* Ignore. */"); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); printer.pln(); } // ======================================================================== /** Generate code for the specified grammar. */ public void visit(Module m) { // (Re)Initialize code generator state. analyzer.register(this); printer.register(this); analyzer.init(m); className = Utilities.getName(m.getClassName()); // Record the grammar attributes. if (null == m.attributes) { m.attributes = new ArrayList<Attribute>(); } attributeVerbose = m.hasAttribute(Constants.ATT_VERBOSE); attributeWithLocation = m.hasAttribute(Constants.ATT_WITH_LOCATION); attributeConstant = m.hasAttribute(Constants.ATT_CONSTANT); attributeFlatten = m.hasAttribute(Constants.ATT_FLATTEN); attributeParseTree = m.hasAttribute(Constants.ATT_PARSE_TREE); attributeRawTypes = m.hasAttribute(Constants.ATT_RAW_TYPES); attributeIgnoringCase = m.hasAttribute(Constants.ATT_IGNORING_CASE); attributeStateful = m.hasAttribute(Constants.ATT_STATEFUL.getName()); attributeStringSet = m.hasAttribute(Constants.NAME_STRING_SET); attributeMain = m.hasAttribute(Constants.NAME_MAIN); attributePrinter = m.hasAttribute(Constants.NAME_PRINTER); attributeProfile = m.hasAttribute(Constants.ATT_PROFILE); attributeDump = m.hasAttribute(Constants.ATT_DUMP); if (attributeStateful) { stateClassName = (String)m.getAttributeValue(Constants.ATT_STATEFUL.getName()); } if (attributeMain) { mainMethodNonterminal = (String)m.getAttributeValue(Constants.NAME_MAIN); } if (attributePrinter) { printerClassName = (String)m.getAttributeValue(Constants.NAME_PRINTER); } if (m.hasAttribute(Constants.NAME_FACTORY)) { factoryClassName = (String)m.getAttributeValue(Constants.NAME_FACTORY); } boolean isVerbose = attributeVerbose; if (! isVerbose) { // Scan productions for verbose attribute. for (Production p : m.productions) { if (p.hasAttribute(Constants.ATT_VERBOSE)) { isVerbose = true; break; } } } chunked = false; chunkMap = null; chunkCount = 0; // Emit package name. final String packageName = Utilities.getQualifier(m.getClassName()); if (null != packageName) { printer.indent().p("package ").p(packageName).pln(';'); printer.pln(); } // Emit imports. printer.indent().pln("import java.io.Reader;"); if (attributeMain) { printer.indent().pln("import java.io.BufferedReader;"); printer.indent().pln("import java.io.BufferedWriter;"); printer.indent().pln("import java.io.File;"); printer.indent().pln("import java.io.FileReader;"); printer.indent().pln("import java.io.OutputStreamWriter;"); } printer.indent().pln("import java.io.IOException;"); printer.pln(); if (attributeProfile) { printer.indent().pln("import java.util.HashMap;"); } if (attributeStringSet) { printer.indent().pln("import java.util.HashSet;"); printer.indent().pln("import java.util.Set;"); } if (attributeProfile || attributeStringSet) { printer.pln(); } if (m.getBooleanProperty(Properties.RECURSIVE)) { printer.indent().pln("import xtc.util.Action;"); } printer.indent().pln("import xtc.util.Pair;"); printer.pln(); boolean needsNewline = false; if (m.getBooleanProperty(Properties.LOCATABLE)) { printer.indent().pln("import xtc.tree.Locatable;"); needsNewline = true; } if (m.getBooleanProperty(Properties.GENERIC) || attributeMain) { printer.indent().pln("import xtc.tree.Node;"); needsNewline = true; } if (m.getBooleanProperty(Properties.GENERIC)) { if (null == factoryClassName) { printer.indent().pln("import xtc.tree.GNode;"); } else if (Utilities.isQualified(factoryClassName)) { printer.indent().p("import ").p(factoryClassName).pln(';'); factoryClassName = Utilities.getName(factoryClassName); } needsNewline = true; } if (attributeParseTree) { printer.indent().pln("import xtc.tree.Token;"); printer.indent().pln("import xtc.tree.TextToken;"); printer.indent().pln("import xtc.tree.Formatting;"); needsNewline = true; } if (isVerbose || attributeMain || attributeProfile || attributeDump) { printer.indent().pln("import xtc.tree.Printer;"); needsNewline = true; } if (attributePrinter) { printer.indent().pln("import xtc.tree.Visitor;"); needsNewline = true; } if (needsNewline) printer.pln(); printer.indent().pln("import xtc.parser.ParserBase;"); printer.indent().pln("import xtc.parser.Column;"); 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 != m.header) { action(m.header); printer.pln(); } // Emit class name. printer.indent().pln("/**"); printer.indent().p(" * Packrat parser for grammar <code>"). p(m.name.name).pln("</code>."); printer.indent().pln(" *"); printer.indent().p(" * <p />This class has been generated by the "). pln("<i>Rats!</i> parser"); printer.indent().p(" * generator, version ").p(Constants.VERSION). p(", ").p(Constants.COPY).pln('.'); printer.indent().pln(" */"); if (attributeRawTypes) { printer.indent().pln("@SuppressWarnings(\"unchecked\")"); } printer.indent(); if (m.hasAttribute(Constants.NAME_VISIBILITY)) { String visible = (String)m.getAttributeValue(Constants.NAME_VISIBILITY); if (Constants.ATT_PUBLIC.getValue().equals(visible)) { printer.p("public "); } } else { printer.p("public "); } printer.p("final class ").p(className).pln(" extends ParserBase {"). incr().pln(); // Emit debug flag. if (isVerbose) { printer.indent(). p("/** Flag for whether to emit tracing information while "). pln("parsing. */"); printer.indent().pln("public static final boolean DEBUG = true;"); printer.pln(); printer.indent(). pln("/** Flag for whether to emit a peek into the input. */"); printer.indent().pln("public static final boolean PEEK = true;"); printer.pln(); } // Emit any sets and flags. if (m.hasAttribute(Constants.NAME_STRING_SET) || m.hasAttribute(Constants.NAME_FLAG)) { for (Attribute att : m.attributes) { if (att.getName().equals(Constants.NAME_STRING_SET)) { String set = (String)att.getValue(); printer.indent().p("/** The ").p(set).pln(" set. */"); if (attributeRawTypes) { printer.indent().p("public static final Set ").p(set). pln(" = new HashSet();"); } else { printer.indent().p("public static final Set<String> ").p(set). pln(" = new HashSet<String>();"); } printer.pln(); } else if (att.getName().equals(Constants.NAME_FLAG)) { String flag = (String)att.getValue(); printer.indent().p("/** The ").p(flag).pln(" flag. */"); printer.indent().p("public static final boolean ").p(flag). pln(" = true;"); printer.pln(); } } } // Determine the number of productions that require memoization. int memoCount = 0; for (Production p : m.productions) { if ((! runtime.test("optimizeTransient")) || p.isMemoized()) { memoCount++; } } // To chunk or not to chunk. if (runtime.test("optimizeChunks") && (CHUNK_SIZE <= memoCount)) { chunked = true; chunkMap = new HashMap<NonTerminal, Integer>(memoCount * 4 / 3); Integer number = null; String sNumber = null; int i = CHUNK_SIZE; boolean first = true; for (Production p : m.productions) { // Skip memoization for productions that are transient. if (runtime.test("optimizeTransient") && ! p.isMemoized()) 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(); } final NonTerminal nt = p.name; chunkMap.put(nt, number); i++; printer.indent().p("Result ").p(PREFIX_FIELD).p(nt.toIdentifier()). pln(';'); if (attributeProfile) { printer.indent().p("int ").p(PREFIX_COUNT_FIELD). p(nt.toIdentifier()).pln(';'); } } printer.decr().indent().pln('}'); printer.pln(); } // Emit column. printer.sep().pln(); printer.indent().pln("/** Memoization table column. */"); printer.indent().p("static final class ").p(className). pln("Column extends Column {").incr(); if (chunked) { for (int i=1; i<=chunkCount; i++) { printer.indent().p("Chunk").p(i).p(' ').p("chunk"). p(i).pln(';'); } } else { for (Production p : m.productions) { if ((! runtime.test("optimizeTransient")) || p.isMemoized()) { printer.indent().p("Result ").p(PREFIX_FIELD). p(p.name.toIdentifier()).pln(';'); if (attributeProfile) { printer.indent().p("int ").p(PREFIX_COUNT_FIELD). p(p.name.toIdentifier()).pln(';'); } } } } printer.decr().indent().pln('}'); printer.pln(); // Emit global state field and printer field. if (attributeStateful || isVerbose) { printer.sep().pln(); if (attributeStateful) { printer.indent().pln("/** The global state object. */"); printer.indent().p("protected final ").p(stateClassName).p(' '). p(STATE).pln(';'); printer.pln(); } if (isVerbose) { printer.indent().pln("/** The printer for tracing this parser. */"); printer.indent().p("protected final Printer ").p(PRINTER).pln(';'); printer.pln(); } } // Emit constructors. printer.sep().pln(); printer.indent().pln("/**"); printer.indent().pln(" * Create a new packrat parser."); printer.indent().pln(" *"); printer.indent().pln(" * @param reader The reader."); printer.indent().pln(" * @param file The file name."); printer.indent().pln(" */"); printer.indent().p("public ").p(className). pln("(final Reader reader, final String file) {").incr(); printer.indent().pln("super(reader, file);"); if (attributeStateful) { printer.indent().p(STATE).p(" = new ").p(stateClassName).pln("();"); } if (isVerbose) { printer.indent().p(PRINTER).pln(" = new Printer(System.out);"); } printer.decr().indent().pln('}'); printer.pln(); printer.indent().pln("/**"); printer.indent().pln(" * Create a new packrat parser."); printer.indent().pln(" *"); printer.indent().pln(" * @param reader The file reader."); printer.indent().pln(" * @param file The file name."); printer.indent().pln(" * @param size The file size."); printer.indent().pln(" */"); printer.indent().p("public ").p(className). pln("(final Reader reader, final String file, final int size) {").incr(); printer.indent().pln("super(reader, file, size);"); if (attributeStateful) { printer.indent().p(STATE).p(" = new ").p(stateClassName).pln("();"); } if (isVerbose) { printer.indent().p(PRINTER).pln(" = new Printer(System.out);"); } printer.decr().indent().pln('}'); printer.pln(); // Emit code for creating a column. printer.sep().pln(); printer.indent().pln("protected Column newColumn() {").incr(); printer.indent().p("return new ").p(className).pln("Column();"); printer.decr().indent().pln('}'); printer.pln(); // Emit code for productions. for (Production p : m.productions) { boolean savedVerbose = attributeVerbose; boolean savedLocation = attributeWithLocation; boolean savedConstant = attributeConstant; boolean savedCase = attributeIgnoringCase; if ((! savedVerbose) && p.hasAttribute(Constants.ATT_VERBOSE)) { attributeVerbose = true; } if ((! savedLocation) && p.hasAttribute(Constants.ATT_WITH_LOCATION)) { attributeWithLocation = true; } if ((! savedConstant) && p.hasAttribute(Constants.ATT_CONSTANT)) { attributeConstant = true; } if ((! savedCase) && p.hasAttribute(Constants.ATT_IGNORING_CASE)) { attributeIgnoringCase = true; } analyzer.process(p); attributeIgnoringCase = savedCase; attributeConstant = savedConstant; attributeWithLocation = savedLocation; attributeVerbose = savedVerbose; } // Emit code for body. if (null != m.body) { printer.sep().pln(); action(m.body); printer.pln(); } // Emit code for toText method. if (m.getBooleanProperty(Properties.GENERIC) || m.hasAttribute(Constants.ATT_GENERIC_AS_VOID)) { printer.sep().pln(); if (attributeParseTree) { printer.indent().pln("/**"); printer.indent().p(" * Get the text for the specified annotated "). pln("token."); printer.indent().pln(" *"); printer.indent().pln(" * @param n The annotated token."); printer.indent().pln(" * @return The corresponding text."); printer.indent().pln(" */"); printer.indent().pln("protected static final String toText(Node n) {"). incr(); printer.indent().pln("return n.getTokenText();"); printer.decr().indent().pln('}'); } else { printer.indent().pln("/**"); printer.indent().pln(" * Get the specified text."); printer.indent().pln(" *"); printer.indent().pln(" * @param s The text."); printer.indent().pln(" * @return The text."); printer.indent().pln(" */"); printer.indent().pln("protected static final String toText(String s) {"). incr(); printer.indent().pln("return s;"); printer.decr().indent().pln('}'); } printer.pln(); } // Emit code for add method. if (attributeStringSet) { printer.sep().pln(); printer.indent().pln("/**"); printer.indent().pln(" * Add the specified values to the specified set."); printer.indent().pln(" *"); printer.indent().pln(" * @param set The set."); printer.indent().pln(" * @param values The new values."); printer.indent().pln(" */"); if (attributeRawTypes) { printer.indent().p("protected static final "). pln("void add(Set set, Object[] values) {").incr(); printer.indent().pln("for (int i=0; i<values.length; i++) {").incr(); printer.indent().pln("set.add(values[i]);"); printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); } else { printer.indent().p("protected static final "). pln("<T> void add(Set<T> set, T[] values) {").incr(); printer.indent().pln("for (T v : values) set.add(v);"); printer.decr().indent().pln('}'); } printer.pln(); printer.indent().pln("/**"); printer.indent(). pln(" * Check whether the specified set contains the specified value."); printer.indent().pln(" *"); printer.indent().pln(" * @param set The set."); printer.indent().pln(" * @param value The value."); printer.indent(). pln(" * @return <code>true</code> if the set contains the value."); printer.indent().pln(" */"); if (attributeRawTypes) { printer.indent().p("protected static final "). pln("boolean contains(Set set, Object value) {").incr(); printer.indent().pln("return set.contains(value);"); printer.decr().indent().pln('}'); } else { printer.indent().p("protected static final "). pln("<T> boolean contains(Set<T> set, T value) {").incr(); printer.indent().pln("return set.contains(value);"); printer.decr().indent().pln('}'); } printer.pln(); } // Emit code for verbose operation. if (attributeVerbose) { verbose(); } // Emit code for profiling the memoization table. if (attributeProfile) { profile(); } // Emit code for dumping the memoization table. if (attributeDump) { dump(); } // Emit code for main method. if (attributeMain) { mainMethod(mainMethodNonterminal); } // Finish parser class. printer.decr().indent().pln('}'); // Emit footer. if (null != m.footer) { printer.pln().sep().pln(); action(m.footer); } } // ======================================================================== /** Generate code for the specified production. */ public void visit(FullProduction p) { MetaData md = (MetaData)p.getProperty(Properties.META_DATA); repetitionTypes = md.boundRepetitions; optionTypes = md.options; String field = fieldName(p.name, PREFIX_FIELD); String method = methodName(p.name); printer.sep().pln(); printer.indent().pln("/**"); printer.indent().p(" * Parse "); if (p.getBooleanProperty(Constants.SYNTHETIC)) { printer.p("synthetic "); } printer.p("nonterminal ").buffer().p(p.qName.name).p('.').fit(" * ").pln(); if (p.hasProperty(Properties.DUPLICATES)) { printer.indent(); printer.p(" * This nonterminal represents the duplicate productions "); List<String> src = Properties.getDuplicates(p); for (Iterator<String> iter = src.iterator(); iter.hasNext(); ) { String name = iter.next(); printer.buffer(); if ((1 < src.size()) && (! iter.hasNext())) { printer.p("and "); } printer.p(name); if ((2 == src.size()) && (iter.hasNext())) { printer.p(' '); } else if (iter.hasNext()) { printer.p(", "); } else { printer.p('.'); } printer.fit(" * "); } printer.pln(); } printer.indent().pln(" *"); printer.indent().p(" * @param ").p(ARG_INDEX).pln(" The index."); printer.indent().pln(" * @return The result."); printer.indent().pln(" * @throws IOException Signals an I/O error."); printer.indent().pln(" */"); printer.indent(); if (p.hasAttribute(Constants.ATT_PUBLIC)) { // Top-level parsing methods are public. printer.p("public"); } else { // The rest is private. printer.p("private"); } long line = printer.line(); printer.p(" Result ").p(method).p("(final ").p(indexT()).p(' '). p(ARG_INDEX).p(") ").buffer().p("throws IOException {").fitMore().pln(). incr(); if (line + 1 < printer.line()) { printer.pln(); } // Only memoize non-transient productions. if ((! runtime.test("optimizeTransient")) || p.isMemoized()) { printer.indent().p(className).p("Column ").p(COLUMN).p(" = ("). p(className).p("Column)column(").p(ARG_INDEX).pln(");"); if (chunked) { String chunk = chunkMap.get(p.name).toString(); printer.indent().p("if (").p(nullExpr()).p(" == ").p(COLUMN). p(".chunk").p(chunk).p(") ").p(COLUMN).p(".chunk").p(chunk). p(" = new Chunk").p(chunk).pln("();"); } printer.indent().p("if (").p(nullExpr()).p(" == ").p(field).p(") "). buffer().p(field).p(" = ").p(method).p("$1(").p(ARG_INDEX).p(");"). fitMore().pln(); if (attributeProfile) { printer.indent().p(fieldName(p.name, PREFIX_COUNT_FIELD)).pln("++;"); } if (attributeVerbose) { printer.indent().p("traceLookup(\"").p(p.name.toIdentifier()). p("\", ").p(ARG_INDEX).p(", "). buffer().p(field).p(");").fitMore().pln(); } printer.indent().p("return ").p(field).pln(';'); printer.decr().indent().pln('}'); printer.pln(); printer.indent().p("/** Actually parse "); printer.p(p.qName.name).pln(". */"); line = printer.line(); printer.indent().p("private Result ").p(method). p("$1(final ").p(indexT()).p(' ').p(ARG_INDEX).p(") ").buffer(). p("throws IOException {").fitMore().pln().incr(); if (line + 1 < printer.line()) { printer.pln(); } } // Emit variable declarations. First, determine the alignment for // the variable names by finding the maximum number of characters // in a type name. Second, print the individual declarations. String ptype = extern(p.type); if (attributeRawTypes) ptype = rawT(ptype); int w = Math.max("ParseError".length(), ptype.length()); if (! attributeRawTypes) { for (Type t : repetitionTypes) { if (null != t) { w = Math.max(w, extern(t).length()); } } } for (Type t : optionTypes) { if (null != t) { String s = extern(t); if (attributeRawTypes) s = rawT(s); w = Math.max(w, s.length()); } } int align = (printer.level() * Constants.INDENTATION) + w + 1 + Constants.FIRST_COLUMN; if (md.requiresChar) { printer.indent().p(intT()).align(align).p(CHAR).pln(';'); } if (md.requiresIndex) { printer.indent().p(indexT()).align(align).p(INDEX).pln(';'); } if (md.requiresResult) { printer.indent().p("Result").align(align).p(RESULT).pln(';'); } if (md.requiresPredIndex) { printer.indent().p(indexT()).align(align).p(PRED_INDEX).pln(';'); } if (md.requiresPredResult) { printer.indent().p("Result").align(align).p(PRED_RESULT).pln(';'); } if (md.requiresPredMatch) { printer.indent().p(booleanT()).align(align).p(PRED_MATCHED). pln(';'); } if (md.requiresBaseIndex) { printer.indent().p(indexT()).align(align).p(BASE_INDEX).pln(';'); } for (int i=0; i<md.repetitions.size(); i++) { printer.indent().p(indexT()).align(align).p(REPETITION).p(i+1).pln(';'); if (md.repetitions.get(i)) { printer.indent().p(booleanT()).align(align).p(REPEATED).p(i+1).pln(';'); } if (null != repetitionTypes.get(i)) { printer.indent(); if (attributeRawTypes) { printer.p(rawT(extern(new InstantiatedT(AST.ANY, AST.LIST)))); } else { printer.p(extern(repetitionTypes.get(i))); } printer.align(align).p(REP_VALUE).p(i+1).pln(';'); } } for (int i=0; i<md.options.size(); i++) { printer.indent().p(indexT()).align(align).p(OPTION).p(i+1). pln(';'); Type t = md.options.get(i); if (null != t) { String s = extern(t); if (attributeRawTypes) s = rawT(s); printer.indent().p(s).align(align).p(OP_VALUE).p(i+1).pln(';'); } } printer.indent().p(ptype).align(align).p(VALUE).pln(';'); printer.indent().p("ParseError").align(align).p(PARSE_ERROR). pln(" = ParseError.DUMMY;"); // Emit code for verbose operation. if (attributeVerbose) { printer.pln(); printer.indent().p("traceEnter(\"").p(p.name.toIdentifier()). p("\", ").p(ARG_INDEX).pln(");"); } // Emit code for state management. if (attributeStateful) { if (p.hasAttribute(Constants.ATT_RESETTING)) { printer.pln(); printer.indent().pln("// Reset the global state object."); printer.indent().p(STATE).p(".reset(column(").p(ARG_INDEX). pln(").file);"); } if (p.hasAttribute(Constants.ATT_STATEFUL)) { printer.pln(); printer.indent().pln("// Start a state modification."); printer.indent().p(STATE).pln(".start();"); } } // Emit code for production element. indexName = INDEX; resultName = RESULT; baseIndex = ARG_INDEX; useBaseIndex = true; indentLevel = 0; choiceLevel = -1; repeated = false; repeatedOnce = false; repeatedElement = null; savedRepeated = false; savedRepeatedOnce = false; optional = false; optionLevel = 0; optionalElement = null; savedOptional = false; createsNodeValue = false; seenTest = false; endsWithParseError = false; dispatch(p.choice); if (seenTest) { if (attributeStateful && p.hasAttribute(Constants.ATT_STATEFUL)) { printer.pln(); printer.indent().pln("// Abort the state modification."); printer.indent().p(STATE).pln(".abort();"); } printer.pln(); printer.indent().pln("// Done."); if (attributeVerbose) { printer.indent().p("traceFailure(\"").p(p.name.toIdentifier()). p("\", ").p(ARG_INDEX).pln(");"); } if (p.hasAttribute(Constants.ATT_EXPLICIT)) { printer.indent().p("return new ParseError(\""). p(Utilities.split(p.name.unqualify().name, ' ')).p(" expected\", "). p(ARG_INDEX).pln(");"); } else { if (endsWithParseError && (p.isMemoized() || ! runtime.test("optimizeErrors2"))) { 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 saveIndex The flag for whether to save the index in the * base index variable. * @param threadError The flag for whether to thread the parse * error. */ protected void result(String methodName, boolean saveIndex, boolean threadError) { printer.pln(); // Clear the first element flag. firstElement = false; // Set up the receiver of the result. String receiver = PARSE_CHAR.equals(methodName)? CHAR : resultName; // Set up the alignment for the equals sign. int align = receiver.length(); if (saveIndex) { align = Math.max(align, BASE_INDEX.length()); } if (! notFollowedBy() && ! PARSE_CHAR.equals(methodName)) { align = Math.max(align, PARSE_ERROR.length()); } align += (printer.level() * Constants.INDENTATION) + 1 + Constants.FIRST_COLUMN; if (useBaseIndex) { // The first result of an ordered choice or repetition as well // as the first element after a repetition always builds on the // current base index. The first element of a predicate also // builds on the current base index. if (saveIndex) { // Assign index and result. printer.indent().p(BASE_INDEX).align(align).p("= ").buffer(). p(baseIndex).p(';').fitMore().pln(); printer.indent().p(receiver).align(align).p("= ").buffer(). p(methodName).p('(').p(BASE_INDEX).p(");").fitMore().pln(); } else { printer.indent().p(receiver).align(align).p("= ").buffer(). p(methodName).p('(').p(baseIndex).p(");").fitMore().pln(); } // Thread parse error. if (threadError) threadParseError(align); useBaseIndex = false; } else { // All other elements build on the last regular/predicate // result, depending on whether we are processing regular or // predicate elements. if (saveIndex) { // Assign parser and result. printer.indent().p(BASE_INDEX).align(align).p("= ").buffer(). p(resultName).p(".index;").fitMore().pln(); printer.indent().p(receiver).align(align).p("= ").buffer(). p(methodName).p('(').p(BASE_INDEX).p(");").fitMore().pln(); } else { // Assign result. printer.indent().p(receiver).align(align).p("= ").buffer(). p(methodName).p('(').p(resultName).p(".index);").fitMore().pln(); } // Thread parse error. if (threadError) threadParseError(align); } } /** * Emit code for threading parse error. * * @param align The alignment for the assignment operator. */ protected void threadParseError(int align) { printer.indent().p(PARSE_ERROR).align(align).p("= ").buffer(). p(resultName).p(".select(").p(PARSE_ERROR); if (optional) { printer.p(", ").p(OPTION).p(optionLevel); } else if (repeated && ! repeatedOnce) { printer.p(", ").p(REPETITION).p(repetitionLevel); } printer.p(");").fitMore().pln(); } /** 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 testing whether the character has a value. */ protected void charValueTest() { printer.indent().p("if (-1 != ").p(CHAR).pln(") {").incr(); } /** * Emit the code for testing the result. * * @param text The expected text value. * @param ignoreCase The flag for whether to ignore the case. */ protected void stringValueTest(String text, boolean ignoreCase) { if (attributeParseTree) { if (ignoreCase) { printer.indent().p("if (").p(resultName).pln(".hasValue() &&"). indent().p(" ((Node)").p(resultName). p(".semanticValue()).getTokenText().equalsIgnoreCase(\""). escape(text, Utilities.JAVA_ESCAPES).pln("\")) {").incr(); } else { printer.indent().p("if (").p(resultName).pln(".hasValue() &&"). indent().p(" ((Node)").p(resultName). p(".semanticValue()).getTokenText().equals(\""). escape(text, Utilities.JAVA_ESCAPES).pln("\")) {").incr(); } } else if (runtime.test("optimizeMatches")) { if (ignoreCase) { printer.indent().p("if (").p(resultName).p(".hasValueIgnoreCase(\""). escape(text, Utilities.JAVA_ESCAPES).pln("\")) {").incr(); } else { printer.indent().p("if (").p(resultName).p(".hasValue(\""). escape(text, Utilities.JAVA_ESCAPES).pln("\")) {").incr(); } } else { if (ignoreCase) { printer.indent().p("if (").p(resultName).pln(".hasValue() &&"). indent().p(" \"").escape(text, Utilities.JAVA_ESCAPES). p("\".equalsIgnoreCase(").p(resultName). pln(".semanticValue().toString())) {").incr(); } else { printer.indent().p("if (").p(resultName).pln(".hasValue() &&"). indent().p(" \"").escape(text, Utilities.JAVA_ESCAPES). p("\".equals(").p(resultName).pln(".semanticValue())) {").incr(); } } } /** * Emit the code for assigning the index variable. * * @param oldIndex The old index. * @param isLastChar Flag for whether the just recognized character * is the terminal's last character. */ protected void index(String oldIndex, boolean isLastChar) { // The index variable is not used after assignment if the current // character is the last character of a predicate and does not // appear within a repetition or option. The current character is // the current element for character terminals and the last // character for string terminals. if (! predicate || (predicate && (predicateIter.hasNext() || ! isLastChar || repeated || optional))) { printer.indent().p(indexName).p(" = ").p(oldIndex).pln(" + 1;"); useBaseIndex = true; baseIndex = indexName; } } /** * Emit the code for saving the index variable. * * @param savedIndex The saved index. * @param spacer Any extra space. * @param base The current base index. */ protected void saveIndex(String savedIndex, String spacer, String base) { if (useBaseIndex) { if (! savedIndex.equals(base)) { printer.indent().p(savedIndex).p(spacer).p(" = ").p(base).pln(';'); } useBaseIndex = false; } else { printer.indent().p(savedIndex).p(spacer).p(" = ").p(resultName). pln(".index;"); } } /** * 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. dispatch(predicateIter.next()); return; } else if (repeated) { // Assign the repetition parser variable and, if necessary, // the repeated flag; then continue with the loop. printer.pln(); saveIndex(REPETITION + repetitionLevel, "", baseIndex); if (repeatedOnce) { printer.indent().p(REPEATED).p(repetitionLevel).pln(" = true;"); } if (null != repeatedElement) { printer.indent().p(REP_VALUE).p(repetitionLevel).p(" = ").buffer(); if (attributeRawTypes) { printer.p("new Pair("); } else { printer.p("new ").p(extern(repetitionTypes.get(repetitionLevel-1))). p('('); } printer.p(repeatedElement).p(", ").p(REP_VALUE).p(repetitionLevel). p(");").fitMore().pln(); } printer.indent().pln("continue;"); return; } else if (optional) { printer.pln(); saveIndex(OPTION + optionLevel, " ", baseIndex); if (null != optionalElement) { printer.indent().p(OP_VALUE).p(optionLevel).p(" = "). p(optionalElement).pln(';'); } return; } else { // Assign matched variable for not-followed-by predicates. if (notFollowedBy) { printer.pln(); printer.indent().p(PRED_MATCHED).pln(" = true;"); return; } // Restore regular element processing and fall through for // followed-by predicates. predicate = false; optional = savedOptional; repeated = savedRepeated; repeatedOnce = savedRepeatedOnce; firstElement = savedFirstElement; baseIndex = savedBaseIndex; useBaseIndex = savedUseBaseIndex; indexName = INDEX; resultName = RESULT; } } // Process the next regular grammar element. if (elementIter.hasNext()) { dispatch(elementIter.next()); } else if (repeated) { printer.pln(); saveIndex(REPETITION + repetitionLevel, "", baseIndex); if (repeatedOnce) { printer.indent().p(REPEATED).p(repetitionLevel).pln(" = true;"); } if (null != repeatedElement) { printer.indent().p(REP_VALUE).p(repetitionLevel).p(" = ").buffer(); if (attributeRawTypes) { printer.p("new Pair("); } else { printer.p("new ").p(extern(repetitionTypes.get(repetitionLevel-1))). p('('); } printer.p(repeatedElement).p(", ").p(REP_VALUE).p(repetitionLevel). p(");").fitMore().pln(); } printer.indent().pln("continue;"); } else if (optional) { printer.pln(); saveIndex(OPTION + optionLevel, " ", baseIndex); if (null != optionalElement) { printer.indent().p(OP_VALUE).p(optionLevel).p(" = "). p(optionalElement).pln(';'); } } 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 withLocation 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 (! attributeWithLocation) return; if (runtime.test("optimizeLocation") && (! createsNodeValue)) return; FuzzyBoolean hasLocation = ast.hasLocation(analyzer.current().type); if (FuzzyBoolean.FALSE == hasLocation) { return; } else if (FuzzyBoolean.MAYBE == hasLocation) { printer.indent().p("if (").p(VALUE).pln(" instanceof Locatable) {").incr(); printer.indent().p("setLocation((Locatable)").p(VALUE).p(", "). p(ARG_INDEX).pln(");"); printer.decr().indent().pln('}'); } else { printer.indent().p("setLocation(").p(VALUE).p(", ").p(ARG_INDEX).pln(");"); } } /** * Emit the code for returning a semantic value. */ protected void returnValue() { printer.pln(); if (attributeStateful && analyzer.current().hasAttribute(Constants.ATT_STATEFUL)) { printer.indent().pln("// Commit the state modification."); printer.indent().p(STATE).pln(".commit();"); printer.pln(); } location(); if (attributeVerbose) { printer.indent().p("traceSuccess(\""). p(analyzer.current().name.toIdentifier()).p("\", ").p(ARG_INDEX). pln(");"); } if (useBaseIndex) { printer.indent().p("return new SemanticValue(").p(VALUE). p(", ").p(baseIndex).p(", ").p(PARSE_ERROR).pln(");"); useBaseIndex = false; } else { if (runtime.test("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(".index, ").p(PARSE_ERROR).pln(");"); } } } /** * Emit the code for generating a parse error based on the * production's name. */ protected void parseError() { printer.indent().p(PARSE_ERROR).p(" = ").p(PARSE_ERROR).p(".select(\""). p(Utilities.split(analyzer.current().name.unqualify().name, ' ')). p(" expected\", "). p(ARG_INDEX).pln(");"); } /** * Emit the code for generating a parse error based on a fixed text. * * @param text The expected text. */ protected void parseError(String text) { printer.indent().p(PARSE_ERROR).p(" = ").p(PARSE_ERROR).p(".select(\"'"). escape(text, Utilities.JAVA_ESCAPES | Utilities.ESCAPE_DOUBLE). p("' expected\", ").p(BASE_INDEX).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) { final String base = baseIndex; final boolean used = useBaseIndex; final boolean creates = createsNodeValue; final int indent = indentLevel; indentLevel = printer.level(); choiceLevel++; // Make sure that nested choices are at a deeper scope than the // enclosing choice. final boolean scoped = (indent == indentLevel); if (scoped) { printer.indent().pln("{ // Start scope for nested choice.").incr(); } // For non-top-level choices, declare a parser variable and save // the current parser. if (0 != choiceLevel) { printer.pln(); if (useBaseIndex) { printer.indent().p("final ").p(indexT()).p(' ').p(nestedChoice()). p(" = ").p(base).pln(';'); useBaseIndex = false; } else { printer.indent().p("final ").p(indexT()).p(' ').p(nestedChoice()). p(" = ").buffer().p(resultName).p(".index;").fitMore().pln(); } } // Process the alternatives. int alternativeNumber = 0; for (Sequence s : c.alternatives) { elementIter = s.elements.iterator(); if (0 == choiceLevel) { firstElement = true; } baseIndex = (0 == choiceLevel)? ARG_INDEX : nestedChoice(); useBaseIndex = true; createsNodeValue = creates; seenTest = false; alternativeNumber++; printer.pln(); if (0 == choiceLevel) { printer.indent().p("// Alternative "); } else { printer.indent().p("// Nested alternative "); } if (null == s.name) { printer.p(alternativeNumber).pln('.'); } else { printer.p('<').p(s.name.name).pln(">."); } nextElement(); } if (scoped) { printer.decr().indent().pln("} // End scope for nested choice."); } choiceLevel--; indentLevel = indent; useBaseIndex = used; baseIndex = base; } // ======================================================================== /** Generate code for the specified repetition. */ public void visit(Repetition r) { assert r.element instanceof Sequence; firstElement = false; String base = baseIndex; boolean used = useBaseIndex; String repel = repeatedElement; if (hasBinding()) { // Per class documentation, the repeated element must be a // sequence, whose semantic value is captured by the binding // returned by Analyzer.getBinding(). Binding b = Analyzer.getBinding(((Sequence)r.element).elements); repeatedElement = b.name; bindingType = AST.listOf(ast.concretize(analyzer.type(b.element), AST.ANY)); } else { repeatedElement = null; bindingType = null; } String name = bindingName; bindingName = null; Element bound = bindingElement; bindingElement = null; Type type = bindingType; bindingType = null; boolean rep = repeated; repeated = true; boolean once = repeatedOnce; repeatedOnce = r.once; boolean opt = optional; optional = false; repetitionLevel++; // Save current parser. printer.pln(); saveIndex(REPETITION + repetitionLevel, "", base); // Reset repeated flag if necessary. if (repeatedOnce) { printer.indent().p(REPEATED).p(repetitionLevel).pln(" = false;"); } // Reset list value for bound repetitions. if (null != name) { printer.indent().p(REP_VALUE).p(repetitionLevel).p(" = "). p(emptyListExpr()).pln(';'); } // Save current code generation state. Iterator<Element> 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(); baseIndex = REPETITION + repetitionLevel; useBaseIndex = 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 (repeatedOnce) { printer.pln(); printer.indent().p("if (").p(REPEATED).p(repetitionLevel).pln(") {"). incr(); } repetitionLevel--; repeated = rep; repeatedOnce = once; repeatedElement = repel; optional = opt; bindingName = name; bindingElement = bound; bindingType = type; boolean closeBrace = false; String blockName = name; if (hasBinding()) { if (! r.once) { printer.indent().p("{ // Start scope for ").p(blockName).pln('.').incr(); closeBrace = true; } binding(); clearBinding(); } baseIndex = REPETITION + Integer.toString(repetitionLevel + 1); useBaseIndex = true; if (! r.once) { seenTest = false; } nextElement(); if (r.once) { printer.decr().indent().pln('}'); tested(); } else if (closeBrace) { printer.decr().indent().p("} // End scope for ").p(blockName).pln('.'); } baseIndex = base; useBaseIndex = used; } // ======================================================================== /** Generate code for the specified option. */ public void visit(Option o) { assert o.element instanceof Sequence; firstElement = false; String base = baseIndex; boolean used = useBaseIndex; String optel = optionalElement; if (hasBinding()) { // Per class documentation, the optional element must be a // sequence, whose semantic value is captured by // Analyzer.getBinding(). Binding b = Analyzer.getBinding(((Sequence)o.element).elements); optionalElement = b.name; bindingType = ast.concretize(analyzer.type(b.element), AST.ANY); } else { optionalElement = null; bindingType = null; } String name = bindingName; bindingName = null; Element bound = bindingElement; bindingElement = null; Type type = bindingType; bindingType = null; boolean opt = optional; optional = true; boolean rep = repeated; repeated = false; optionLevel++; // Save current parser. printer.pln(); saveIndex(OPTION + optionLevel, " ", base); // Reset optional value for bound options. if (null != name) { printer.indent().p(OP_VALUE).p(optionLevel).p(" = ").p(nullExpr()). pln(';'); } // Save current code generation state. Iterator<Element> iter; if (predicate) { iter = predicateIter; predicateIter = ((Sequence)o.element).elements.iterator(); } else { iter = elementIter; elementIter = ((Sequence)o.element).elements.iterator(); } // Emit code for the optional elements. baseIndex = OPTION + optionLevel; useBaseIndex = true; nextElement(); // Restore code generation state. if (predicate) { predicateIter = iter; } else { elementIter = iter; } // Emit code for the rest of the current sequence. optionLevel--; optional = opt; optionalElement = optel; repeated = rep; bindingName = name; bindingElement = bound; bindingType = type; boolean closeBrace = false; String blockName = name; if (hasBinding()) { printer.indent().p("{ // Start scope for ").p(blockName).pln('.').incr(); closeBrace = true; binding(); clearBinding(); } baseIndex = OPTION + Integer.toString(optionLevel + 1); useBaseIndex = true; seenTest = false; nextElement(); if (closeBrace) { printer.decr().indent().p("} // End scope for ").p(blockName).pln('.'); } baseIndex = base; useBaseIndex = used; } // ======================================================================== /** Generate code for the specified followed-by predicate. */ public void visit(FollowedBy p) { assert ! predicate; assert p.element instanceof Sequence; predicate = true; notFollowedBy = false; savedOptional = optional; optional = false; savedRepeated = repeated; repeated = false; savedRepeatedOnce = repeatedOnce; repeatedOnce = false; savedFirstElement = firstElement; savedBaseIndex = baseIndex; if (! useBaseIndex) { // Only set a new base index if the base index is not used for // the next element. baseIndex = RESULT + ".index"; } savedUseBaseIndex = useBaseIndex; useBaseIndex = true; indexName = PRED_INDEX; 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) { assert ! predicate; assert p.element instanceof Sequence; predicate = true; notFollowedBy = true; savedOptional = optional; optional = false; savedRepeated = repeated; repeated = false; savedRepeatedOnce = repeatedOnce; repeatedOnce = false; savedFirstElement = firstElement; savedBaseIndex = baseIndex; if (! useBaseIndex) { // Only set a new base index if the base index is not used for // the next element. baseIndex = RESULT + ".index"; } savedUseBaseIndex = useBaseIndex; useBaseIndex = true; indexName = PRED_INDEX; resultName = PRED_RESULT; predicateIter = ((Sequence)p.element).elements.iterator(); // Emit code for the not-followed-by predicate. printer.pln(); printer.indent().p(PRED_MATCHED).pln(" = false;"); nextElement(); // Restore regular element processing. predicate = false; optional = savedOptional; repeated = savedRepeated; repeatedOnce = savedRepeatedOnce; firstElement = savedFirstElement; baseIndex = savedBaseIndex; useBaseIndex = savedUseBaseIndex; indexName = INDEX; resultName = RESULT; // Emit code for the rest of the rule sequence. printer.pln(); printer.indent().p("if (! ").p(PRED_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(a.code.get(0)).pln(") {").incr(); } else { boolean first = true; int column = printer.column(); for (String s : a.code) { if (first) { printer.p(s); first = false; } else { printer.pln().align(column).p(s); } } printer.pln(") {").incr(); } nextElement(); printer.decr().indent().pln('}'); if (! notFollowedBy()) { endsWithParseError = true; } tested(); } // ======================================================================== /** Generate code for the specified voided element. */ public void visit(VoidedElement v) { // Visit the element. dispatch(v.element); } // ======================================================================== /** Generate code for the specified binding. */ public void visit(Binding b) { // Save old name and element. String oldName = bindingName; Element oldElement = bindingElement; Type oldType = bindingType; // Set up new name and element; bindingName = b.name; bindingElement = b.element; bindingType = null; // For now, this field is only used in options. // Visit element. dispatch(b.element); // Restore old name and element. bindingName = oldName; bindingElement = oldElement; bindingType = oldType; } /** * 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() { switch (bindingElement.tag()) { case NONTERMINAL: { Type type = VALUE.equals(bindingName) ? analyzer.current().type : analyzer.lookup((NonTerminal)bindingElement).type; Type cast = attributeRawTypes && ! AST.isAny(type) ? type : null; binding1(extern(type), bindingName, extern(cast), resultName + ".semanticValue()"); } break; case ANY_CHAR: case CHAR_CLASS: case CHAR_LITERAL: case CHAR_SWITCH: if (VALUE.equals(bindingName)) { binding1(extern(AST.CHAR), bindingName, null, "Character.valueOf((" + charT() + ")" + CHAR + ")"); } else { binding1(charT(), bindingName, null, "(" + charT() + ")" + CHAR); } break; case STRING_LITERAL: { final String text = ((StringLiteral)bindingElement).text; binding1(extern(AST.STRING), bindingName, null, '"' + Utilities.escape(text, Utilities.JAVA_ESCAPES) + '"'); } break; case STRING_MATCH: if (attributeParseTree) { String cast = attributeRawTypes ? extern(AST.NODE) : null; binding1(extern(AST.NODE), bindingName, cast, resultName+".semanticValue()"); } else { binding1(extern(AST.STRING), bindingName, null, "\"" + Utilities.escape(((StringMatch)bindingElement).text, Utilities.JAVA_ESCAPES) + "\""); } break; case REPETITION: { int level = repetitionLevel + 1; String expr = REP_VALUE + level + ".reverse()"; if ((! attributeRawTypes) && (! repetitionTypes.get(repetitionLevel).equals(bindingType))) { expr = "cast(" + expr + ')'; } binding1(extern(bindingType), bindingName, null, expr); } break; case OPTION: { int level = optionLevel + 1; String cast = null; String expr = OP_VALUE + level; if (! optionTypes.get(optionLevel).equals(bindingType)) { if (attributeRawTypes) { cast = extern(bindingType); } else { expr = "cast(" + expr + ')'; } } binding1(extern(bindingType), bindingName, cast, expr); } break; case NULL: binding1(extern(AST.ANY), bindingName, null, nullExpr()); break; default: throw new AssertionError("Unrecognized binding element " + bindingElement); } } /** * Emit the binding code. * * @param type The variable type as a string. * @param name The variable name. * @param cast The cast type as a string, or <code>null</code> for no cast. * @param expr The value producing expression. */ private void binding1(String type, String name, String cast, String expr) { if (attributeRawTypes) { type = rawT(type); if (null != cast) cast = rawT(cast); } printer.indent(); if (VALUE.equals(name)) { printer.p(VALUE); } else { if (attributeConstant) printer.p("final "); printer.p(type).p(' ').p(name); } printer.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() && (! runtime.test("optimizeErrors1") || ! first), false); stringValueTest(m.text, attributeIgnoringCase); if (hasBinding()) { binding(); clearBinding(); } nextElement(); if (notFollowedBy()) { printer.decr().indent().pln('}'); } else if (runtime.test("optimizeErrors1") && 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, ! notFollowedBy()); valueTest(); if (hasBinding()) { binding(); clearBinding(); } nextElement(); // If the referenced production is transient and does not generate // parse errors, we might need to generate one in this production. if (! notFollowedBy() && ! analyzer.lookup(nt).isMemoized() && runtime.test("optimizeErrors2")) { endsWithParseError = true; } printer.decr().indent().pln('}'); tested(); } // ======================================================================== /** Generate code for the any character element. */ public void visit(AnyChar a) { String oldIndex = useBaseIndex? baseIndex : resultName + ".index"; result(PARSE_CHAR, false, false); charValueTest(); index(oldIndex, true); 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) { String oldIndex = useBaseIndex? baseIndex : resultName + ".index"; result(PARSE_CHAR, false, false); printer.indent().p("if (\'").escape(l.c, Utilities.JAVA_ESCAPES). p("\' == ").p(CHAR).pln(") {").incr(); index(oldIndex, true); if (hasBinding()) { binding(); clearBinding(); } nextElement(); printer.decr().indent().pln('}'); if (! notFollowedBy()) { endsWithParseError = true; } tested(); } // ======================================================================== /** Generate code for the specified character class. */ public void visit(CharClass c) { String oldIndex = useBaseIndex? baseIndex : resultName + ".index"; result(PARSE_CHAR, false, false); charValueTest(); index(oldIndex, true); String name; if (hasBinding()) { binding(); name = bindingName; clearBinding(); printer.pln(); } else { name = CHAR; } final int length = c.ranges.size(); Iterator<CharRange> iter = c.ranges.iterator(); if (1 == length) { printer.indent().p("if "); } else { printer.indent().p("if ("); } while (iter.hasNext()) { CharRange r = 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); String oldIndex = useBaseIndex? baseIndex : resultName + ".index"; result(PARSE_CHAR, 0 == i && ! notFollowedBy() && (! runtime.test("optimizeErrors1") || ! first), false); printer.indent().p("if (\'").escape(c, Utilities.JAVA_ESCAPES). p("\' == ").p(CHAR).pln(") {").incr(); index(oldIndex, i == length-1); } if (hasBinding()) { binding(); clearBinding(); } nextElement(); for (int i=0; i<length; i++) { if (notFollowedBy()) { printer.decr().indent().pln('}'); } else if (runtime.test("optimizeErrors1") && first) { printer.decr().indent().pln('}'); endsWithParseError = true; } else { 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) { String oldIndex = useBaseIndex? baseIndex : resultName + ".index"; result(PARSE_CHAR, false, false); charValueTest(); index(oldIndex, true); printer.pln(); String base = baseIndex; boolean used = useBaseIndex; String name; if (hasBinding()) { binding(); name = bindingName; clearBinding(); printer.pln(); } else { name = CHAR; } printer.indent().p("switch (").p(name).pln(") {").incr(); for (CharCase c : s.cases) { for (CharRange r : c.klass.ranges) { 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. baseIndex = base; useBaseIndex = used; seenTest = false; if (c.element instanceof OrderedChoice) { dispatch(c.element); } else { elementIter = ((Sequence)c.element).elements.iterator(); nextElement(); } printer.decr().indent().pln('}'); if (seenTest || optional) { 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. baseIndex = base; useBaseIndex = used; if (s.base instanceof OrderedChoice) { dispatch(s.base); } else { elementIter = ((Sequence)s.base).elements.iterator(); nextElement(); } printer.decr().indent().pln('}'); } printer.decr().indent().pln('}'); printer.decr().indent().pln('}'); endsWithParseError = true; tested(); } // ======================================================================== /** Generate code for the specified node marker. */ public void visit(NodeMarker m) { nextElement(); } // ======================================================================== /** Actually emit code for the specified action. */ protected void action(Action a) { int baseLevel = printer.level(); int level = 0; Iterator<String> codeIter = a.code.iterator(); Iterator<Integer> indentIter = a.indent.iterator(); while (codeIter.hasNext()) { int newLevel = indentIter.next(); int diff = newLevel - level; level = newLevel; if (0 < diff) { for (int i=0; i<diff; i++) { printer.incr(); } } else { for (int i=0; i>diff; i--) { printer.decr(); } } printer.indent().pln(codeIter.next()); } printer.setLevel(baseLevel); } /** Generate code for the specified action. */ public void visit(Action a) { // If the action sets the semantic value, we conservatively assume // that it creates a node value. if (a.setsValue()) createsNodeValue = true; printer.pln(); action(a); nextElement(); } /** Generate code for the specified parser action. */ public void visit(ParserAction pa) { // We conservatively assume that parser actions may create a node // value. createsNodeValue = true; printer.pln(); // Set up CodeGenerator.BASE_INDEX. saveIndex(BASE_INDEX, "", baseIndex); printer.pln(); // Emit the actual action code. action((Action)pa.element); printer.pln(); // Thread parse error. if (! notFollowedBy()) threadParseError(0); // Test for value. valueTest(); // Assign to CodeGenerator.VALUE, i.e., yyValue. printer.indent().p(VALUE).p(" = "); if (attributeRawTypes && (! AST.isAny(analyzer.current().type))) { printer.p('(').p(rawT(extern(analyzer.current().type))).p(')'); } printer.p(RESULT).p(".semanticValue();"); // Process the next element. nextElement(); // Finish the value test. printer.decr().indent().pln('}'); tested(); } // ======================================================================== /** Generate code for the specified parse tree node. */ public void visit(ParseTreeNode n) { // Parse tree nodes may only appear within bindings. assert hasBinding(); // Emit the variable type and name. printer.indent(); if (VALUE.equals(bindingName)) { printer.p(VALUE); } else { if (attributeConstant) printer.p("final "); printer.p(extern(AST.NODE)).p(' ').p(bindingName); } // Determine the name of the annotated node. String node = (null == n.node) ? nullExpr() : var(n.node); // Emit the class name. printer.p(" = Formatting."); // Emit the factory method calls. if ((1 == n.predecessors.size()) && (0 == n.successors.size())) { printer.p("before1(").p(var(n.predecessors.get(0))).p(", "). p(node).p(')'); } else if ((1 == n.predecessors.size()) && (1 == n.successors.size())) { printer.p("round1(").p(var(n.predecessors.get(0))).p(", "). p(node).p(", ").p(var(n.successors.get(0))).p(')'); } else if ((0 == n.predecessors.size()) && (1 == n.successors.size())) { printer.p("after1(").p(node).p(", ").p(var(n.successors.get(0))). p(')'); } else { printer.pln("variable().").indentMore(); // Emit the calls to add the nodes. boolean first = true; for (Binding b : n.predecessors) { if (first) { first = false; } else { printer.p('.'); } printer.p("add(").p(var(b)).p(')'); } // If there is no node, there are no successors. if (null != n.node) { if (! first) printer.p('.'); printer.p("addNode(").p(node).p(')'); for (Binding b : n.successors) { printer.p(".add(").p(var(b)).p(')'); } } } // Wrap up. printer.pln(';'); clearBinding(); nextElement(); } // ======================================================================== /** Generate code for the specified null literal. */ public void visit(NullLiteral l) { // A null literal requires a binding to have a visible effect. // However, we only emit the binding, if the variable name is not // synthetic. An emitted binding must be wrapped it in a scope to // avoid variable redefinition errors. boolean emit = false; String name = null; if (hasBinding()) { if (! Analyzer.isSynthetic(bindingName)) { emit = true; name = bindingName; printer.indent().p("{ // Start scope for ").p(name).pln('.').incr(); binding(); } // Always clear the binding. clearBinding(); } nextElement(); if (emit) { printer.decr().indent().p("} // End scope for ").p(name).pln('.'); } } // ======================================================================== /** Generate code for the specified null value. */ public void visit(NullValue v) { printer.pln(); printer.indent().p(VALUE).p(" = ").p(nullExpr()).pln(';'); nextElement(); } /** Emit code for determining the textual different. */ protected void emitDifference() { if (firstElement) { printer.p("\"\""); } else { printer.p("difference(").p(ARG_INDEX).p(", "); if (useBaseIndex) { printer.p(baseIndex); } else { printer.p(RESULT).p(".index"); } printer.p(')'); } } /** Generate code for the specified string value. */ public void visit(StringValue v) { printer.pln(); printer.indent().p(VALUE).p(" = "); if (null == v.text) { emitDifference(); } else { printer.p('"').escape(v.text, Utilities.JAVA_ESCAPES).p('"'); } printer.pln(';'); nextElement(); } /** Generate code for the specified token value. */ public void visit(TokenValue v) { printer.pln(); printer.indent().p(VALUE).p(" = new TextToken("); if (null == v.text) { emitDifference(); } else { printer.p('"').escape(v.text, Utilities.JAVA_ESCAPES).p('"'); } printer.pln(");"); // If the location optimization is enabled and yyValue is declared // to be a node or token, then add the source location directly to // the just created node. Otherwise, just record that the // alternative creates a node value. final Type type = analyzer.current().type; if (attributeWithLocation && runtime.test("optimizeLocation") && AST.isNode(type)) { printer.indent().p(VALUE).p(".setLocation(location(").p(ARG_INDEX). pln("));"); } else { createsNodeValue = true; } nextElement(); } /** * Convert a binding into the corresponding variable name. If the * specified binding is for a synthetic variable and the bound * element is a null literal, this method returns "null" to inline * the null value. Otherwise, it returns the binding's name. * * @param b The binding. * @return The corresponding variable name. */ protected String var(Binding b) { return Analyzer.isSynthetic(b.name) && (b.element instanceof NullLiteral) ? nullExpr() : b.name; } /** Generate code for the specified binding value. */ public void visit(BindingValue v) { printer.pln(); printer.indent().p(VALUE).p(" = ").p(var(v.binding)).pln(';'); nextElement(); } /** Generate code for the specified empty list value. */ public void visit(EmptyListValue v) { printer.pln().indent().p(VALUE).p(" = ").p(emptyListExpr()).pln(';'); nextElement(); } /** Generate code for the specified proper list value. */ public void visit(ProperListValue v) { printer.pln(); printer.indent().p(VALUE).p(" = "); boolean first = true; for (Binding b : v.elements) { if (first) { first = false; } else { printer.p(", "); } printer.p("new "); if (attributeRawTypes) { printer.p("Pair"); } else { printer.p(extern(v.type)); } printer.p('(').p(var(b)); } if (null != v.tail) printer.p(", ").p(var(v.tail)); for (int i=0; i<v.elements.size(); i++) printer.p(')'); printer.pln(';'); nextElement(); } /** Generate code for the specified action base value. */ public void visit(ActionBaseValue v) { printer.pln(); printer.indent().p(VALUE).p(" = "); // Do we need a cast? if (! AST.isAny(analyzer.current().type)) { if (attributeRawTypes) { printer.p('(').p(rawT(extern(analyzer.current().type))).p(')'); } } printer.p("apply(").p(var(v.list)).p(", ").p(var(v.seed)); if (attributeWithLocation && FuzzyBoolean.TRUE == ast.hasLocation(analyzer.current().type)) { printer.p(", ").p(ARG_INDEX); } printer.pln(");"); nextElement(); } /** * Calculate the number of a generic node's children. This method * returns the fixed number of children if none of the children has * a list value, <code>Integer.MAX_VALUE</code> if any of the * children has a non-null list value, and * <code>Integer.MIN_VALUE</code> if any of the children has a list * value that may be <code>null</code>. * * @param base The number of children not from the specified list. * @param children The list of bindings representing the children. * @return The number of children. */ protected int numberOfChildren(int base, List<Binding> children) { for (Binding b : children) { if (attributeFlatten && AST.isList(analyzer.type(b.element))) { if (analyzer.mayBeNull(b.element)) { return Integer.MIN_VALUE; } else { base = Integer.MAX_VALUE; } } else if (base != Integer.MAX_VALUE) { base++; } } return base; } /** * Emit an expression calculating a generic node's number of children. * * @param base The number of children not from the specified list. * @param children The list of bindings representing the children. */ protected void emitNumberOfChildren(int base, List<Binding> children) { boolean printed = false; for (Binding b : children) { if (attributeFlatten && AST.isList(analyzer.type(b.element))) { if (printed) { printer.p(" + "); } else { printed = true; } boolean test = analyzer.mayBeNull(b.element); if (test) { printer.pln().indentMore().p('(').p(nullExpr()).p(" == ").p(b.name). p(" ? 0 : "); } // Note: This expression used to contain an explicit cast to // Pair, which has been removed since all bindings are // declared with their correct types. printer.p(b.name).p(".size()"); if (test) printer.p(')'); } else { base++; } } if (! printed) { printer.p(base); } else if (0 != base) { printer.p(" + ").p(base); } } /** * Emit an expression adding the children to a generic node. * * @param first The name of the optional first child. * @param children The list of children. */ protected void emitChildren(String first, List<Binding> children) { printer.p(')'); boolean indent = true; if (null != first) { if (indent) { printer.pln('.').indentMore(); indent = false; } else { printer.p('.'); } printer.p("add(").p(first).p(')'); } boolean statement = false; for (Binding b : children) { if ((! attributeFlatten) || (! AST.isList(analyzer.type(b.element)))) { // A non-flattened list value or a non-list value. if (statement) { printer.pln(';').indent().p(VALUE).p('.'); statement = false; indent = false; } else if (indent) { printer.pln('.').indentMore(); indent = false; } else { printer.p('.'); } printer.p("add("); } else if (analyzer.mayBeNull(b.element)) { // A possibly null list value. Note: The addAll() expression // used to contain an explicit cast to Pair, which has been // removed since all bindings are declared with their correct // types. printer.pln(';').indent().p("if (").p(nullExpr()).p(" != ").p(var(b)). p(") ").p(VALUE).p(".addAll("); statement = true; indent = false; } else { // A non-null list value. if (statement) { printer.pln(';').indent().p(VALUE).p('.'); statement = false; indent = false; } else if (indent) { printer.pln('.').indentMore(); indent = false; } else { printer.p('.'); } // Note: The addAll() expression used to contain an explicit // cast to Pair, which has been removed since all bindings are // declared with their correct types. printer.p("addAll("); } printer.p(var(b)).p(')'); } } /** Emit the class name of the class creating generic nodes. */ protected void emitFactoryName() { if (null == factoryClassName) { printer.p("GNode"); } else { printer.p(factoryClassName); } } /** * Emit a statement adding formatting to a generic node. * * @param formatting The list of bindings. */ protected void emitFormatting(List<Binding> formatting) { final int size = formatting.size(); if (0 == size) return; printer.indent().p(VALUE).p(" = Formatting."); if (1 == size) { printer.p("after1(").p(VALUE).p(", ").p(var(formatting.get(0))).p(')'); } else { printer.p("variable().addNode(").p(VALUE).pln(").").indentMore(); boolean first = true; for (Binding b : formatting) { if (first) { first = false; } else { printer.p('.'); } printer.p("add(").p(var(b)).p(')'); } } printer.pln(';'); } /** * Emit the action creating a new generic node. * * @param v The generic action value. */ protected void emitAction(GenericActionValue v) { if (attributeRawTypes) { printer.indent().p("public Object run(Object ").p(v.first).pln(") {"). incr(); } else { printer.indent().p("public Node run(Node ").p(v.first).pln(") {").incr(); } final String name = Utilities.unqualify(v.name); final int numChildren = numberOfChildren(1, v.children); final boolean defineValue = ((Integer.MIN_VALUE == numChildren) || (0 < v.formatting.size())); if (defineValue) { printer.indent().p("Node ").p(VALUE).p(" = "); } else { printer.indent().p("return "); } emitFactoryName(); boolean emitAdditions = true; if (runtime.test("optimizeGenericNodes") && (0 <= numChildren)) { if (1 == numChildren) { printer.p(".create(\"").p(name).p("\", ").p(v.first).p(')'); emitAdditions = false; } else if (GNode.MAX_FIXED >= numChildren) { printer.p(".create(\"").p(name).p("\", ").p(v.first).p(", "); for (Iterator<Binding> iter = v.children.iterator(); iter.hasNext(); ) { Binding b = iter.next(); printer.p(var(b)); if (iter.hasNext()) { printer.p(", "); } else { printer.p(')'); } } emitAdditions = false; } else if (1 == v.children.size()) { Binding b = v.children.get(0); // Note: The cast to Pair for b.name has been removed. printer.p(".createFromPair(\"").p(name).p("\", ").p(v.first). p(", ").p(var(b)).p(')'); emitAdditions = false; } } if (emitAdditions) { printer.p(".create(\"").p(name).p("\", "); emitNumberOfChildren(1, v.children); emitChildren(v.first, v.children); } printer.pln(';'); emitFormatting(v.formatting); if (defineValue) { printer.indent().p("return ").p(VALUE).pln(';'); } printer.decr().indent().p('}'); } /** Generate code for the specified generic node value. */ public void visit(GenericNodeValue v) { printer.pln(); printer.indent().p(VALUE).p(" = "); emitFactoryName(); final String name = Utilities.unqualify(v.name); final int numChildren = numberOfChildren(0, v.children); boolean emitAdditions = true; if (runtime.test("optimizeGenericNodes") && (0 <= numChildren)) { if (0 == numChildren) { printer.p(".create(\"").p(name).p("\", false)"); emitAdditions = false; } else if (GNode.MAX_FIXED >= numChildren) { printer.p(".create(\"").p(name).p("\", "); for (Iterator<Binding> iter = v.children.iterator(); iter.hasNext(); ) { Binding b = iter.next(); printer.p(var(b)); if (iter.hasNext()) { printer.p(", "); } else { printer.p(')'); } } emitAdditions = false; } else if (1 == v.children.size()) { Binding b = v.children.get(0); // Note: The cast to Pair has been removed. printer.p(".createFromPair(\"").p(name).p("\", ").p(var(b)).p(')'); emitAdditions = false; } else if (2 == v.children.size()) { Binding b1 = v.children.get(0); Binding b2 = v.children.get(1); if ((! AST.isList(analyzer.type(b1.element))) && AST.isList(analyzer.type(b2.element))) { // Note: The cast to Pair for b2.name has been removed. printer.p(".createFromPair(\"").p(name).p("\", ").p(var(b1)). p(", ").p(var(b2)).p(')'); emitAdditions = false; } } } if (emitAdditions) { printer.p(".create(\"").p(name).p("\", "); emitNumberOfChildren(0, v.children); emitChildren(null, v.children); } printer.pln(';'); // If the location optimization is enabled and yyValue is declared // to be a node, then add the source location directly to the just // created node. Otherwise, just record that the alternative // creates a node value. if (attributeWithLocation && runtime.test("optimizeLocation") && AST.isNode(analyzer.current().type)) { printer.indent().p(VALUE).p(".setLocation(location(").p(ARG_INDEX). pln("));"); } else { createsNodeValue = true; } emitFormatting(v.formatting); nextElement(); } /** Generate code for the specified generic action value. */ public void visit(GenericActionValue v) { printer.pln(); if (attributeRawTypes) { printer.indent().p(VALUE).pln(" = new Action() {").incr(); } else { printer.indent().p(VALUE).pln(" = new Action<Node>() {").incr(); } emitAction(v); printer.pln("};").decr(); nextElement(); } /** Generate code for the specified generic recursion value. */ public void visit(GenericRecursionValue v) { printer.pln(); if (attributeRawTypes) { printer.indent().p(VALUE).pln(" = new Pair(new Action() {").incr(); } else { printer.indent().p(VALUE). pln(" = new Pair<Action<Node>>(new Action<Node>() {").incr(); } emitAction(v); printer.p("}, ").p(var(v.list)).pln(");").decr(); nextElement(); } }