package java_cup; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.Enumeration; /** This class serves as the main driver for the JavaCup system. * It accepts user options and coordinates overall control flow. * The main flow of control includes the following activities: * <ul> * <li> Parse user supplied arguments and options. * <li> Open output files. * <li> Parse the specification from standard input. * <li> Check for unused terminals, non-terminals, and productions. * <li> Build the state machine, tables, etc. * <li> Output the generated code. * <li> Close output files. * <li> Print a summary if requested. * </ul> * * Options to the main program include: <dl> * <dt> -package name * <dd> specify package generated classes go in [default none] * <dt> -parser name * <dd> specify parser class name [default "parser"] * <dt> -symbols name * <dd> specify name for symbol constant class [default "sym"] * <dt> -nonterms * <dd> put non terminals in symbol constant class * <dt> -expect # * <dd> number of conflicts expected/allowed [default 0] * <dt> -compact_red * <dd> compact tables by defaulting to most frequent reduce * <dt> -nowarn * <dd> don't warn about useless productions, etc. * <dt> -nosummary * <dd> don't print the usual summary of parse states, etc. * <dt> -progress * <dd> print messages to indicate progress of the system * <dt> -time * <dd> print time usage summary * <dt> -dump_grammar * <dd> produce a dump of the symbols and grammar * <dt> -dump_states * <dd> produce a dump of parse state machine * <dt> -dump_tables * <dd> produce a dump of the parse tables * <dt> -dump * <dd> produce a dump of all of the above * <dt> -debug * <dd> turn on debugging messages within JavaCup * </dl> * * @version last updated: 11/25/95 * @author Scott Hudson */ public class Main { /*-----------------------------------------------------------*/ /*--- Constructor(s) ----------------------------------------*/ /*-----------------------------------------------------------*/ /** Only constructor is private, so we do not allocate any instances of this class. */ private Main() { } /*-------------------------*/ /* Options set by the user */ /*-------------------------*/ /** User option -- do we print progress messages. */ protected static boolean print_progress = false; /** User option -- do we produce a dump of the state machine */ protected static boolean opt_dump_states = false; /** User option -- do we produce a dump of the parse tables */ protected static boolean opt_dump_tables = false; /** User option -- do we produce a dump of the grammar */ protected static boolean opt_dump_grammar = false; /** User option -- do we show timing information as a part of the summary */ protected static boolean opt_show_timing = false; /** User option -- do we run produce extra debugging messages */ protected static boolean opt_do_debug = false; /** User option -- do we compact tables by making most common reduce the default action */ protected static boolean opt_compact_red = false; /** User option -- should we include non terminal symbol numbers in the symbol constant class. */ protected static boolean include_non_terms = false; /** User option -- do not print a summary. */ protected static boolean no_summary = false; /** User option -- number of conflicts to expect */ protected static int expect_conflicts = 0; /*----------------------------------------------------------------------*/ /* Timing data (not all of these time intervals are mutually exclusive) */ /*----------------------------------------------------------------------*/ /** Timing data -- when did we start */ protected static long start_time = 0; /** Timing data -- when did we end preliminaries */ protected static long prelim_end = 0; /** Timing data -- when did we end parsing */ protected static long parse_end = 0; /** Timing data -- when did we end checking */ protected static long check_end = 0; /** Timing data -- when did we end dumping */ protected static long dump_end = 0; /** Timing data -- when did we end state and table building */ protected static long build_end = 0; /** Timing data -- when did we end nullability calculation */ protected static long nullability_end = 0; /** Timing data -- when did we end first set calculation */ protected static long first_end = 0; /** Timing data -- when did we end state machine construction */ protected static long machine_end = 0; /** Timing data -- when did we end table construction */ protected static long table_end = 0; /** Timing data -- when did we end checking for non-reduced productions */ protected static long reduce_check_end = 0; /** Timing data -- when did we finish emitting code */ protected static long emit_end = 0; /** Timing data -- when were we completely done */ protected static long final_time = 0; /* Additional timing information is also collected in emit */ /** Path to create output files */ private static String out_path = null; /*-----------------------------------------------------------*/ /*--- Main Program ------------------------------------------*/ /*-----------------------------------------------------------*/ /** The main driver for the system. * @param argv an array of strings containing command line arguments. */ public static void main(String argv[]) throws internal_error, java.io.IOException, java.lang.Exception { boolean did_output = false; start_time = System.currentTimeMillis(); /* process user options and arguments */ parse_args(argv); /* open output files */ if (print_progress) System.err.println("Opening files..."); open_files(); prelim_end = System.currentTimeMillis(); /* parse spec into internal data structures */ if (print_progress) System.err.println("Parsing specification from standard input..."); parse_grammar_spec(); parse_end = System.currentTimeMillis(); /* don't proceed unless we are error free */ if (lexer.error_count == 0) { /* check for unused bits */ if (print_progress) System.err.println("Checking specification..."); check_unused(); check_end = System.currentTimeMillis(); /* build the state machine and parse tables */ if (print_progress) System.err.println("Building parse tables..."); build_parser(); build_end = System.currentTimeMillis(); /* output the generated code */ if (print_progress) System.err.println("Writing parser..."); emit_parser(); did_output = true; emit_end = System.currentTimeMillis(); } else { /* fix up the times to make the summary easier */ emit_end = parse_end; } /* do requested dumps */ if (opt_dump_grammar) dump_grammar(); if (opt_dump_states) dump_machine(); if (opt_dump_tables) dump_tables(); dump_end = System.currentTimeMillis(); /* close output files */ if (print_progress) System.err.println("Closing files..."); close_files(); /* produce a summary if desired */ if (!no_summary) emit_summary(did_output); } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Print a "usage message" that described possible command line options, * then exit. * @param message a specific error message to preface the usage message by. */ protected static void usage(String message) { System.err.println(); System.err.println(message); System.err.println(); System.err.println( "Usage: " + version.program_name + " [options]\n" + " and expects a specification file on standard input.\n" + " Legal options include:\n" + " -out path specify the output files path [default current directory]\n" + " -package name specify package generated classes go in [default none]\n" + " -parser name specify parser class name [default \"parser\"]\n" + " -symbols name specify name for symbol constant class [default \"sym\"]\n"+ " -nonterms put non terminals in symbol constant class\n" + " -expect # number of conflicts expected/allowed [default 0]\n" + " -compact_red compact tables by defaulting to most frequent reduce\n" + " -nowarn don't warn about useless productions, etc.\n" + " -nosummary don't print the usual summary of parse states, etc.\n" + " -progress print messages to indicate progress of the system\n" + " -time print time usage summary\n" + " -dump_grammar produce a human readable dump of the symbols and grammar\n"+ " -dump_states produce a dump of parse state machine\n"+ " -dump_tables produce a dump of the parse tables\n"+ " -dump produce a dump of all of the above\n" ); System.exit(1); } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Parse command line options and arguments to set various user-option * flags and variables. * @param argv the command line arguments to be parsed. */ protected static void parse_args(String argv[]) { int len = argv.length; int i; /* parse the options */ for (i=0; i<len; i++) { /* try to get the various options */ if (argv[i].equals("-package")) { /* must have an arg */ if (++i >= len || argv[i].startsWith("-") || argv[i].endsWith(".cup")) usage("-package must have a name argument"); /* record the name */ emit.package_name = argv[i]; } else if (argv[i].equals("-parser")) { /* must have an arg */ if (++i >= len || argv[i].startsWith("-") || argv[i].endsWith(".cup")) usage("-parser must have a name argument"); /* record the name */ emit.parser_class_name = argv[i]; } else if (argv[i].equals("-input")) { /* must have an arg */ if (++i >= len || argv[i].startsWith("-") || argv[i].endsWith(".cup")) usage("-input must have a name argument"); /* record the name */ emit.input_file_name = argv[i]; } else if (argv[i].equals("-symbols")) { /* must have an arg */ if (++i >= len || argv[i].startsWith("-") || argv[i].endsWith(".cup")) usage("-symbols must have a name argument"); /* record the name */ emit.symbol_const_class_name = argv[i]; } else if (argv[i].equals("-nonterms")) { include_non_terms = true; } else if (argv[i].equals("-expect")) { /* must have an arg */ if (++i >= len || argv[i].startsWith("-") || argv[i].endsWith(".cup")) usage("-expect must have a name argument"); /* record the number */ try { expect_conflicts = Integer.parseInt(argv[i]); } catch (NumberFormatException e) { usage("-expect must be followed by a decimal integer"); } } else if (argv[i].equals("-out")) { /* must have an arg */ if (++i >= len || argv[i].startsWith("-")) usage("-out must have a path argument"); /* validate path */ if (argv[i].length() != 0) { out_path = argv[i] + File.separator; File f = new File(out_path); if (!f.exists() || !f.isDirectory()) out_path = null; } if (out_path == null) usage("-out argument must be a valid existing path"); } else if (argv[i].equals("-compact_red")) opt_compact_red = true; else if (argv[i].equals("-nosummary")) no_summary = true; else if (argv[i].equals("-nowarn")) emit.nowarn = true; else if (argv[i].equals("-dump_states")) opt_dump_states = true; else if (argv[i].equals("-dump_tables")) opt_dump_tables = true; else if (argv[i].equals("-progress")) print_progress = true; else if (argv[i].equals("-dump_grammar")) opt_dump_grammar = true; else if (argv[i].equals("-dump")) opt_dump_states = opt_dump_tables = opt_dump_grammar = true; else if (argv[i].equals("-time")) opt_show_timing = true; else if (argv[i].equals("-debug")) opt_do_debug = true; else { usage("Unrecognized option \"" + argv[i] + "\""); } } } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /*-------*/ /* Files */ /*-------*/ /** Input file. This is a buffered version of System.in. */ protected static BufferedInputStream input_file; /** Output file for the parser class. */ protected static PrintStream parser_class_file; /** Output file for the symbol constant class. */ protected static PrintStream symbol_class_file; /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Open various files used by the system. */ protected static void open_files() { File fil; String out_name; /* use a buffered version of standard input */ if (emit.input_file_name != null) try { input_file = new BufferedInputStream(new FileInputStream(emit.input_file_name)); } catch (Exception ex) { ex.printStackTrace(); System.exit(3); } else input_file = new BufferedInputStream(System.in); /* open each of the output files */ if (out_path == null) out_path = ""; /* parser class */ out_name = out_path + emit.parser_class_name + ".java"; fil = new File(out_name); try { parser_class_file = new PrintStream( new BufferedOutputStream(new FileOutputStream(fil), 4096)); } catch(Exception e) { System.err.println("Can't open \"" + out_name + "\" for output"); System.exit(3); } /* symbol constants class */ out_name = out_path + emit.symbol_const_class_name + ".java"; fil = new File(out_name); try { symbol_class_file = new PrintStream( new BufferedOutputStream(new FileOutputStream(fil), 4096)); } catch(Exception e) { System.err.println("Can't open \"" + out_name + "\" for output"); System.exit(4); } } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Close various files used by the system. */ protected static void close_files() throws java.io.IOException { if (input_file != null) input_file.close(); if (parser_class_file != null) parser_class_file.close(); if (symbol_class_file != null) symbol_class_file.close(); } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Parse the grammar specification from standard input. This produces * sets of terminal, non-terminals, and productions which can be accessed * via static variables of the respective classes, as well as the setting * of various variables (mostly in the emit class) for small user supplied * items such as the code to scan with. */ protected static void parse_grammar_spec() throws java.lang.Exception { parser parser_obj; /* create a parser and parse with it */ parser_obj = new parser(); try { if (opt_do_debug) parser_obj.debug_parse(); else parser_obj.parse(); } catch (Exception e) { /* something threw an exception. catch it and emit a message so we have a line number to work with, then re-throw it */ lexer.emit_error("Internal error: Unexpected exception"); throw e; } } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Check for unused symbols. Unreduced productions get checked when * tables are created. */ protected static void check_unused() { terminal term; non_terminal nt; /* check for unused terminals */ for (Enumeration t = terminal.all(); t.hasMoreElements(); ) { term = (terminal)t.nextElement(); /* don't issue a message for EOF */ if (term == terminal.EOF) continue; /* or error */ if (term == terminal.error) continue; /* is this one unused */ if (term.use_count() == 0) { /* count it and warn if we are doing warnings */ emit.unused_term++; if (!emit.nowarn) { System.err.println("Warning: Terminal \"" + term.name() + "\" was declared but never used"); lexer.warning_count++; } } } /* check for unused non terminals */ for (Enumeration n = non_terminal.all(); n.hasMoreElements(); ) { nt = (non_terminal)n.nextElement(); /* is this one unused */ if (nt.use_count() == 0) { /* count and warn if we are doing warnings */ emit.unused_term++; if (!emit.nowarn) { System.err.println("Warning: Non terminal \"" + nt.name() + "\" was declared but never used"); lexer.warning_count++; } } } } /* . . . . . . . . . . . . . . . . . . . . . . . . .*/ /* . . Internal Results of Generating the Parser . .*/ /* . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Start state in the overall state machine. */ protected static lalr_state start_state; /** Resulting parse action table. */ protected static parse_action_table action_table; /** Resulting reduce-goto table. */ protected static parse_reduce_table reduce_table; /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Build the (internal) parser from the previously parsed specification. * This includes:<ul> * <li> Computing nullability of non-terminals. * <li> Computing first sets of non-terminals and productions. * <li> Building the viable prefix recognizer machine. * <li> Filling in the (internal) parse tables. * <li> Checking for unreduced productions. * </ul> */ protected static void build_parser() throws internal_error { /* compute nullability of all non terminals */ if (opt_do_debug || print_progress) System.err.println(" Computing non-terminal nullability..."); non_terminal.compute_nullability(); nullability_end = System.currentTimeMillis(); /* compute first sets of all non terminals */ if (opt_do_debug || print_progress) System.err.println(" Computing first sets..."); non_terminal.compute_first_sets(); first_end = System.currentTimeMillis(); /* build the LR viable prefix recognition machine */ if (opt_do_debug || print_progress) System.err.println(" Building state machine..."); start_state = lalr_state.build_machine(emit.start_production); machine_end = System.currentTimeMillis(); /* build the LR parser action and reduce-goto tables */ if (opt_do_debug || print_progress) System.err.println(" Filling in tables..."); action_table = new parse_action_table(); reduce_table = new parse_reduce_table(); for (Enumeration st = lalr_state.all(); st.hasMoreElements(); ) { ((lalr_state)st.nextElement()).build_table_entries( action_table, reduce_table); } table_end = System.currentTimeMillis(); /* check and warn for non-reduced productions */ if (opt_do_debug || print_progress) System.err.println(" Checking for non-reduced productions..."); action_table.check_reductions(); reduce_check_end = System.currentTimeMillis(); /* if we have more conflicts than we expected issue a message and die */ if (emit.num_conflicts > expect_conflicts) { System.err.println("*** More conflicts encountered than expected " + "-- parser generation aborted"); lexer.error_count++; build_end = System.currentTimeMillis(); /* do dumps and summary as needed */ if (opt_dump_grammar) dump_grammar(); if (opt_dump_states) dump_machine(); if (!no_summary) emit_summary(false); System.exit(100); } } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Call the emit routines necessary to write out the generated parser. */ protected static void emit_parser() throws internal_error { emit.symbols(symbol_class_file, include_non_terms); emit.parser(parser_class_file, action_table, reduce_table, start_state.index(), emit.start_production, opt_compact_red); } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Helper routine to optionally return a plural or non-plural ending. * @param val the numerical value determining plurality. */ protected static String plural(int val) { if (val == 1) return ""; else return "s"; } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Emit a long summary message to standard error (System.err) which * summarizes what was found in the specification, how many states were * produced, how many conflicts were found, etc. A detailed timing * summary is also produced if it was requested by the user. * @param output_produced did the system get far enough to generate code. */ protected static void emit_summary(boolean output_produced) { final_time = System.currentTimeMillis(); if (no_summary) return; System.err.println("------- " + version.title_str + " Parser Generation Summary -------"); /* error and warning count */ System.err.println(" " + lexer.error_count + " error" + plural(lexer.error_count) + " and " + lexer.warning_count + " warning" + plural(lexer.warning_count)); /* basic stats */ System.err.print(" " + terminal.number() + " terminal" + plural(terminal.number()) + ", "); System.err.print(non_terminal.number() + " non terminal" + plural(non_terminal.number()) + ", and "); System.err.println(production.number() + " production" + plural(production.number()) + " declared, "); System.err.println(" producing " + lalr_state.number() + " unique parse states."); /* unused symbols */ System.err.println(" " + emit.unused_term + " terminal" + plural(emit.unused_term) + " declared but not used."); System.err.println(" " + emit.unused_non_term + " non terminal" + plural(emit.unused_term) + " declared but not used."); /* productions that didn't reduce */ System.err.println(" " + emit.not_reduced + " production" + plural(emit.not_reduced) + " never reduced."); /* conflicts */ System.err.println(" " + emit.num_conflicts + " conflict" + plural(emit.num_conflicts) + " detected" + " (" + expect_conflicts + " expected)."); /* code location */ if (output_produced) System.err.println(" Code written to \"" + emit.parser_class_name + ".java\", and \"" + emit.symbol_const_class_name + ".java\"."); else System.err.println(" No code produced."); if (opt_show_timing) show_times(); System.err.println( "---------------------------------------------------- (" + version.version_str + ")"); } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Produce the optional timing summary as part of an overall summary. */ protected static void show_times() { long total_time = final_time - start_time; System.err.println(". . . . . . . . . . . . . . . . . . . . . . . . . "); System.err.println(" Timing Summary"); System.err.println(" Total time " + timestr(final_time-start_time, total_time)); System.err.println(" Startup " + timestr(prelim_end-start_time, total_time)); System.err.println(" Parse " + timestr(parse_end-prelim_end, total_time) ); if (check_end != 0) System.err.println(" Checking " + timestr(check_end-parse_end, total_time)); if (check_end != 0 && build_end != 0) System.err.println(" Parser Build " + timestr(build_end-check_end, total_time)); if (nullability_end != 0 && check_end != 0) System.err.println(" Nullability " + timestr(nullability_end-check_end, total_time)); if (first_end != 0 && nullability_end != 0) System.err.println(" First sets " + timestr(first_end-nullability_end, total_time)); if (machine_end != 0 && first_end != 0) System.err.println(" State build " + timestr(machine_end-first_end, total_time)); if (table_end != 0 && machine_end != 0) System.err.println(" Table build " + timestr(table_end-machine_end, total_time)); if (reduce_check_end != 0 && table_end != 0) System.err.println(" Checking " + timestr(reduce_check_end-table_end, total_time)); if (emit_end != 0 && build_end != 0) System.err.println(" Code Output " + timestr(emit_end-build_end, total_time)); if (emit.symbols_time != 0) System.err.println(" Symbols " + timestr(emit.symbols_time, total_time)); if (emit.parser_time != 0) System.err.println(" Parser class " + timestr(emit.parser_time, total_time)); if (emit.action_code_time != 0) System.err.println(" Actions " + timestr(emit.action_code_time, total_time)); if (emit.production_table_time != 0) System.err.println(" Prod table " + timestr(emit.production_table_time, total_time)); if (emit.action_table_time != 0) System.err.println(" Action tab " + timestr(emit.action_table_time, total_time)); if (emit.goto_table_time != 0) System.err.println(" Reduce tab " + timestr(emit.goto_table_time, total_time)); System.err.println(" Dump Output " + timestr(dump_end-emit_end, total_time)); } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Helper routine to format a decimal based display of seconds and * percentage of total time given counts of milliseconds. Note: this * is broken for use with some instances of negative time (since we don't * use any negative time here, we let if be for now). * @param time_val the value being formatted (in ms). * @param total_time total time percentages are calculated against (in ms). */ protected static String timestr(long time_val, long total_time) { boolean neg; long ms = 0; long sec = 0; long percent10; String pad; /* work with positives only */ neg = time_val < 0; if (neg) time_val = -time_val; /* pull out seconds and ms */ ms = time_val % 1000; sec = time_val / 1000; /* construct a pad to blank fill seconds out to 4 places */ if (sec < 10) pad = " "; else if (sec < 100) pad = " "; else if (sec < 1000) pad = " "; else pad = ""; /* calculate 10 times the percentage of total */ percent10 = (time_val*1000)/total_time; /* build and return the output string */ return (neg ? "-" : "") + pad + sec + "." + ((ms%1000)/100) + ((ms%100)/10) + (ms%10) + "sec" + " (" + percent10/10 + "." + percent10%10 + "%)"; } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Produce a human readable dump of the grammar. */ public static void dump_grammar() throws internal_error { int cnt; Enumeration t, n, p; production prod; System.err.println("===== Terminals ====="); for (t = terminal.all(), cnt=0; t.hasMoreElements(); cnt++) { System.err.print(((terminal)t.nextElement()).name() + " "); if ((cnt+1) % 5 == 0) System.err.println(); } System.err.println(); System.err.println(); System.err.println("===== Non terminals ====="); for (n=non_terminal.all(), cnt=0; n.hasMoreElements(); cnt++) { System.err.print(((non_terminal)n.nextElement()).name() + " "); if ((cnt+1) % 5 == 0) System.err.println(); } System.err.println(); System.err.println(); System.err.println("===== Productions ====="); for (p=production.all(); p.hasMoreElements(); ) { prod = (production)p.nextElement(); System.err.print(prod.lhs().the_symbol().name() + " ::= "); for (int i=0; i<prod.rhs_length(); i++) if (prod.rhs(i).is_action()) System.err.print("{action} "); else System.err.print( ((symbol_part)prod.rhs(i)).the_symbol().name() + " "); System.err.println(); } System.err.println(); } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Produce a (semi-) human readable dump of the complete viable prefix * recognition state machine. */ public static void dump_machine() { lalr_state ordered[] = new lalr_state[lalr_state.number()]; /* put the states in sorted order for a nicer display */ for (Enumeration s = lalr_state.all(); s.hasMoreElements(); ) { lalr_state st = (lalr_state)s.nextElement(); ordered[st.index()] = st; } System.err.println("===== Viable Prefix Recognizer ====="); for (int i = 0; i<lalr_state.number(); i++) { if (ordered[i] == start_state) System.err.print("START "); System.err.println(ordered[i]); System.err.println("-------------------"); } } /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ /** Produce a (semi-) human readable dumps of the parse tables */ public static void dump_tables() { System.err.println(action_table); System.err.println(reduce_table); } /*-----------------------------------------------------------*/ };