/** Visit the specified nonterminal. */
    public void visit(NonTerminal nt) {
      Production p;

      try {
        p = analyzer.lookup(nt);
      } catch (IllegalArgumentException x) {
        // Too many productions. We assume the worst.
        isLexical = false;
        return;
      }

      if (null == p) {
        // No such production. We assume the worst.
        isLexical = false;

      } else if (analyzer.isProcessed(p.qName)) {
        // If the corresponding production has already been processed,
        // make sure it is lexical.
        if (!p.getBooleanProperty(Properties.LEXICAL)) {
          isLexical = false;
        }

      } else if (!analyzer.isBeingWorkedOn(p.qName)) {
        // The production has not been processed and is not yet under
        // consideration.  If is text-only, accept it.  If it is void,
        // check it out.
        if (p.getBooleanProperty(Properties.TEXT_ONLY)) {
          // Nothing to do.
        } else if (AST.isVoid(p.type)) {
          dispatch(p);
        } else {
          isLexical = false;
        }
      }
    }
  /** Visit the specified grammar. */
  public Object visit(Module m) {
    // Recognize lexical syntax first.
    new Tester(runtime, analyzer).dispatch(m);

    // Initialize the per-grammar state.
    analyzer.register(this);
    analyzer.init(m);

    // Make sure that all lexical and void productions are tested for
    // whether they consume the input.
    for (Production p : m.productions) {
      if (p.getBooleanProperty(Properties.LEXICAL) || AST.isVoid(p.type)) {
        analyzer.notWorkingOnAny();
        analyzer.consumesInput(p.qName);
      }
    }

    // Determine which productions to process.
    List<Production> todo;
    if (m.hasProperty(Properties.ROOT)) {
      todo = new ArrayList<Production>(1);
      todo.add(analyzer.lookup((NonTerminal) m.getProperty(Properties.ROOT)));
    } else {
      todo = m.productions;
    }

    // Process the productions.
    for (Production p : todo) {
      // Skip processed or non-public productions.
      if (analyzer.isProcessed(p.qName) || (!p.hasAttribute(Constants.ATT_PUBLIC))) {
        continue;
      }

      // Mark production as processed to avoid recursive processing.
      analyzer.processed(p.qName);

      if (p.getBooleanProperty(Properties.LEXICAL)) {
        // We have reached a lexical production.  If it consumes the
        // input, we mark it as token-level.
        analyzer.notWorkingOnAny();
        if (analyzer.consumesInput(p.qName)) {
          markToken(p, runtime.test("optionVerbose"));
        }
      } else {
        // Recurse into the production.
        analyzer.process(p);
      }
    }

    // Done.
    return null;
  }
    /** Visit the specified grammar. */
    public void visit(Module m) {
      // Initialize the per-grammar state.
      analyzer.register(this);
      analyzer.init(m);

      // Process the productions.
      for (Production p : m.productions) {
        // Make sure that the production has not been processed
        // already and that it returns a string.
        if (analyzer.isProcessed(p.qName)) {
          continue;
        } else if (p.getBooleanProperty(Properties.TEXT_ONLY)) {
          mark(p);
          analyzer.processed(p.qName);
          continue;
        } else if (!AST.isVoid(p.type)) {
          analyzer.processed(p.qName);
          continue;
        }

        // Clear the per-production state.
        isLexical = true;

        // Process the production.
        analyzer.process(p);

        // Tabulate the results.
        if (isLexical) {
          // All visited productions are guaranteed to be lexical.
          for (NonTerminal nt : analyzer.working()) {
            // This lookup is guaranteed to work, as the production's
            // fully qualified name was added by visit(Production).
            Production p2 = analyzer.lookup(nt);
            mark(p2);
            analyzer.processed(p2.qName);
          }

        } else {
          // We only know that the current production is not lexical.
          analyzer.processed(p.qName);
        }
      }
    }