
package java_cup;

import java.io.*;
import java_cup.runtime.*;

/**
 * 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>-interface
 * <dd>emit symbol constant <i>interface</i>, rather than class
 * <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
 * <dt>-nopositions
 * <dd>don't generate the positions code
 * <dt>-locations
 * <dd>generate handles xleft/xright for symbol positions in actions
 * <dt>-noscanner
 * <dd>don't refer to java_cup.runtime.Scanner in the parser (for compatibility
 * with old runtimes)
 * <dt>-version
 * <dd>print version information for JavaCUP and halt.
 * </dl>
 *
 * @version last updated: 7/3/96
 * @author Frank Flannery
 */

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 eclipse debug symbols */
  protected static boolean opt_do_debugsymbols = 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;

  /* frankf added this 6/18/96 */
  /** User option -- should generator generate code for left/right values? */
  protected static boolean lr_values = true;
  protected static boolean locations = false;
  protected static boolean xmlactions = false;
  protected static boolean genericlabels = false;

  /** User option -- should symbols be put in a class or an interface? [CSA] */
  protected static boolean sym_interface = false;

  /**
   * User option -- should generator suppress references to
   * java_cup.runtime.Scanner for compatibility with old runtimes?
   */
  protected static boolean suppress_scanner = false;

  /*----------------------------------------------------------------------*/
  /* 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 */

  /*-----------------------------------------------------------*/
  /*--- 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();

    /** clean all static members, that contain remaining stuff from earlier calls */
    terminal.clear();
    production.clear();
    action_production.clear();
    emit.clear();
    non_terminal.clear();
    parse_reduce_row.clear();
    parse_action_row.clear();
    lalr_state.clear();
    ErrorManager.clear();

    /* process user options and arguments */
    parse_args(argv);

    /*
     * frankf 6/18/96 hackish, yes, but works
     */
    emit.set_lr_values(lr_values);
    emit.set_locations(locations);
    emit.set_xmlactions(xmlactions);
    emit.set_genericlabels(genericlabels);
    /* open output set_xmlactionsfiles */
    if (print_progress)
      System.err.println("Opening files...");
    /* use a buffered version of standard input */
    input_file = new BufferedInputStream(System.in);

    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 (ErrorManager.getManager().getErrorCount() == 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 # of conflicts permits */
      if (ErrorManager.getManager().getErrorCount() != 0) {
        // conflicts! don't emit code, don't dump tables.
        opt_dump_tables = false;
      } else { // everything's okay, emit parser.
        if (print_progress)
          System.err.println("Writing parser...");
        open_files();
        emit_parser();
        did_output = true;
      }
    }
    /* fix up the times to make the summary easier */
    emit_end = System.currentTimeMillis();

    /* 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 input/output files */
    if (print_progress)
      System.err.println("Closing files...");
    close_files();

    /* produce a summary if desired */
    if (!no_summary)
      emit_summary(did_output);

    /*
     * If there were errors during the run, exit with non-zero status
     * (makefile-friendliness). --CSA
     */
    if (ErrorManager.getManager().getErrorCount() != 0)
      System.exit(100);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * 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(version.title_str + "\n" + "Usage: " + version.program_name + " [options] [filename]\n"
        + "  and expects a specification file on standard input if no filename is given.\n"
        + "  Legal options include:\n" + "    -package name  specify package generated classes go in [default none]\n"
        + "    -destdir name  specify the destination directory, to store the generated files in\n"
        + "    -parser name   specify parser class name [default \"parser\"]\n"
        + "    -typearg args  specify type arguments for parser class\n"
        + "    -symbols name  specify name for symbol constant class [default \"sym\"]\n"
        + "    -interface     put symbols in an interface, rather than a class\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"
        + "    -nopositions   don't propagate the left and right token position values\n"
        + "    -locations     generate handles xleft/xright for symbol positions in actions\n"
        + "    -xmlactions    make the generated parser yield its parse tree as XML\n"
        + "    -genericlabels automatically generate labels to all symbols in XML mode\n"
        + "    -noscanner     don't refer to java_cup.runtime.Scanner\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"
        + "    -version       print the version information for CUP and exit\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("-destdir")) {
        /* must have an arg */
        if (++i >= len || argv[i].startsWith("-") || argv[i].endsWith(".cup"))
          usage("-destdir must have a name argument");
        /* record the name */
        Main.dest_dir = new java.io.File(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("-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("-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 if (argv[i].equals("-debugsymbols"))
        opt_do_debugsymbols = true;
      /* frankf 6/18/96 */
      else if (argv[i].equals("-nopositions"))
        lr_values = false;
      else if (argv[i].equals("-locations"))
        locations = true;
      else if (argv[i].equals("-xmlactions"))
        xmlactions = true;
      else if (argv[i].equals("-genericlabels"))
        genericlabels = true;
      /* CSA 12/21/97 */
      else if (argv[i].equals("-interface"))
        sym_interface = true;
      /* CSA 23-Jul-1999 */
      else if (argv[i].equals("-noscanner"))
        suppress_scanner = true;
      /* CSA 23-Jul-1999 */
      else if (argv[i].equals("-version")) {
        System.out.println(version.title_str);
        System.exit(1);
      }
      /* TUM changes; suggested by Henning Niss 20050628 */
      else if (argv[i].equals("-typearg")) {
        if (++i >= len || argv[i].startsWith("-") || argv[i].endsWith(".cup"))
          usage("-symbols must have a name argument");

        /* record the typearg */
        emit.class_type_argument = argv[i];
      }

      /* CSA 24-Jul-1999; suggestion by Jean Vaucher */
      else if (!argv[i].startsWith("-") && i == len - 1) {
        /* use input from file. */
        try {
          System.setIn(new FileInputStream(argv[i]));
        } catch (java.io.FileNotFoundException e) {
          usage("Unable to open \"" + argv[i] + "\" for input");
        }
      } 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 PrintWriter parser_class_file;

  /** Output file for the symbol constant class. */
  protected static PrintWriter symbol_class_file;

  /** Output directory. */
  protected static File dest_dir = null;
  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /** Open various files used by the system. */
  protected static void open_files() {
    File fil;
    String out_name;

    /* open each of the output files */

    /* parser class */
    out_name = emit.parser_class_name + ".java";
    fil = new File(dest_dir, out_name);
    try {
      parser_class_file = new PrintWriter(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 = emit.symbol_const_class_name + ".java";
    fil = new File(dest_dir, out_name);
    try {
      symbol_class_file = new PrintWriter(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 */
    ComplexSymbolFactory csf = new ComplexSymbolFactory();
    parser_obj = new parser(new Lexer(csf), csf);
    parser_obj.setDebugSymbols(opt_do_debugsymbols);
    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
       */
      ErrorManager.getManager().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() {

    /* check for unused terminals */
    for (var term : terminal.all()) {
      /* 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) {
          ErrorManager.getManager().emit_warning("Terminal \"" + term.name() + "\" was declared but never used");
        }
      }
    }

    /* check for unused non terminals */
    for (var nt:non_terminal.all()){
      /* is this one unused */
      if (nt.use_count() == 0) {
        /* count and warn if we are doing warnings */
        emit.unused_term++;
        if (!emit.nowarn) {
          ErrorManager.getManager().emit_warning("Non terminal \"" + nt.name() + "\" was declared but never used");
        }
      }
    }

  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . */
  /* . . 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 (var lst:lalr_state.all_states()){
      lst.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) {
      ErrorManager.getManager()
          .emit_error("*** More conflicts encountered than expected " + "-- parser generation aborted");
      // indicate the problem.
      // we'll die on return, after clean up.
    }
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /** 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, sym_interface);
    emit.parser(parser_class_file, action_table, reduce_table, start_state.index(), emit.start_production,
        opt_compact_red, suppress_scanner);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * 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("  " + ErrorManager.getManager().getErrorCount() + " error"
        + plural(ErrorManager.getManager().getErrorCount()) + " and " + ErrorManager.getManager().getWarningCount()
        + " warning" + plural(ErrorManager.getManager().getWarningCount()));

    /* 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.title_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 {
    System.err.println("===== Terminals =====");
    for (int tidx = 0, cnt = 0; tidx < terminal.number(); tidx++, cnt++) {
      System.err.print("[" + tidx + "]" + terminal.find(tidx).name() + " ");
      if ((cnt + 1) % 5 == 0)
        System.err.println();
    }
    System.err.println();
    System.err.println();

    System.err.println("===== Non terminals =====");
    for (int nidx = 0, cnt = 0; nidx < non_terminal.number(); nidx++, cnt++) {
      System.err.print("[" + nidx + "]" + non_terminal.find(nidx).name() + " ");
      if ((cnt + 1) % 5 == 0)
        System.err.println();
    }
    System.err.println();
    System.err.println();

    System.err.println("===== Productions =====");
    for (int pidx = 0; pidx < production.number(); pidx++) {
      production prod = production.find(pidx);
      System.err.print("[" + pidx + "] " + 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 (var st : lalr_state.all_states()){
      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);
  }

  /*-----------------------------------------------------------*/

}
