/*
 * xtc - The eXTensible Compiler
 * Copyright (C) 2005-2007 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 xtc.tree.Visitor;

import xtc.util.Runtime;

/**
 * The parent class of all grammar module visitors.  This class
 * provides a skeleton visitor for processing {@link Grammar grammars}
 * and self-contained {@link Module modules}, while maintaining a set
 * of useful flags.
 *
 * @author Robert Grimm
 * @version $Revision: 1.24 $
 */
public abstract class GrammarVisitor extends Visitor {

  /** The runtime. */
  protected final Runtime runtime;

  /** The analyzer utility. */
  protected final Analyzer analyzer;

  /**
   * Flag for whether the current element is the top-level element of
   * a production.
   */
  protected boolean isTopLevel;

  /** Flag for whether the current element is voided. */
  protected boolean isVoided;

  /** Flag for whether the current element is bound. */
  protected boolean isBound;

  /**
   * Flag for whether the current element is the last element of a
   * sequence.
   */
  protected boolean isLastElement;

  /** Flag for whether the current element is in a predicate. */
  protected boolean isPredicate;

  /** Flag for whether the current element is repeated at least once. */
  protected boolean isRepeatedOnce;

  /**
   * Flag for whether the parent element requires that the directly
   * embedded elements are sequences for {@link CodeGenerator code
   * generation}.  It is used by the {@link Simplifier} to avoid
   * stripping sequences that need to be restored later on again.
   */
  protected boolean needsSequence;

  /**
   * Flag for whether to transform ordered choices, repetition, and
   * options in place, instead of creating a new production.  This
   * flag is used by the {@link Transformer.Lifter lifter} visitor.
   */
  protected boolean transformInPlace;

  /**
   * Create a new grammar visitor.
   *
   * @param runtime The runtime.
   * @param analyzer The analyzer utility.
   */
  public GrammarVisitor(Runtime runtime, Analyzer analyzer) {
    this.runtime  = runtime;
    this.analyzer = analyzer;
  }

  /** Visit the specified grammar. */
  public Object visit(Grammar g) {
    // Initialize the per-grammar state.
    analyzer.register(this);
    analyzer.init(g);

    // Process the modules.
    for (Module m : g.modules) {
      analyzer.process(m);

      // Process the module's productions.
      for (Production p : m.productions) analyzer.process(p);
    }
    
    // Done.
    return null;
  }

  /** Visit the specified self-contained module. */
  public Object visit(Module m) {
    // Initialize the per-grammar state.
    analyzer.register(this);
    analyzer.init(m);

    // Process the productioins.
    for (Production p : m.productions) analyzer.process(p);

    // Done.
    return null;
  }

  /** Visit the specified production. */
  public Production visit(Production p) {
    Object closure   = analyzer.enter(p);

    // Initialize the per-production flags.
    isTopLevel       = true;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    isPredicate      = false;
    isRepeatedOnce   = false;
    needsSequence    = false;
    transformInPlace = false;

    p.choice         = (OrderedChoice)dispatch(p.choice);

    analyzer.exit(closure);

    return p;
  }

  /** Visit the specified ordered choice. */
  public Element visit(OrderedChoice c) {
    boolean top      = isTopLevel;
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    boolean last     = isLastElement;
    transformInPlace = false;

    final int length = c.alternatives.size();
    for (int i=0; i<length; i++) {
      isLastElement  = top || last;
      needsSequence  = true;
      c.alternatives.set(i, (Sequence)dispatch(c.alternatives.get(i)));
    }

    isLastElement    = false;
    needsSequence    = false;
    return c;
  }

  /** Visit the specified repetition. */
  public Element visit(Repetition r) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    boolean rep      = isRepeatedOnce;
    isRepeatedOnce   = r.once;
    needsSequence    = false;
    transformInPlace = false;

    r.element        = (Element)dispatch(r.element);

    isRepeatedOnce   = rep;
    return r;
  }

  /** Visit the specified option. */
  public Element visit(Option o) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    o.element        = (Element)dispatch(o.element);
    return o;
  }

  /** Visit the specified sequence. */
  public Element visit(Sequence s) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    boolean last     = isLastElement;
    needsSequence    = false;

    final int size = s.size();
    for (int i=0; i<size; i++) {
      isLastElement  = last && (i == size-1);
      s.elements.set(i, (Element)dispatch(s.get(i)));
    }

    isLastElement    = false;
    return s;
  }

  /** Visit the specified predicate. */
  public Element visit(Predicate p) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    boolean seq      = needsSequence;
    needsSequence    = true;
    transformInPlace = false;
    boolean pred     = isPredicate;
    isPredicate      = true;

    p.element        = (Element)dispatch(p.element);

    isPredicate      = pred;
    needsSequence    = seq;
    return p;
  }

  /** Visit the specified semantic predicate. */
  public Element visit(SemanticPredicate p) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    p.element        = (Element)dispatch(p.element);
    return p;
  }

  /** Visit the specified voided element. */
  public Element visit(VoidedElement v) {
    isTopLevel       = false;
    isVoided         = true;
    isBound          = false;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    v.element        = (Element)dispatch(v.element);
    return v;
  }

  /** Visit the specified binding. */
  public Element visit(Binding b) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = true;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    b.element        = (Element)dispatch(b.element);
    return b;
  }

  /** Visit the specified string match. */
  public Element visit(StringMatch m) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = true;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    m.element        = (Element)dispatch(m.element);
    return m;
  }

  /** Visit the specified character case. */
  public CharCase visit(CharCase c) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    c.element        = (Element)dispatch(c.element);
    return c;
  }

  /** Visit the specified character switch. */
  public Element visit(CharSwitch s) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    final int length = s.cases.size();
    for (int i=0; i<length; i++) {
      s.cases.set(i, (CharCase)dispatch(s.cases.get(i)));
    }
    s.base           = (Element)dispatch(s.base);
    return s;
  }

  /** Visit the specified parser action. */
  public Element visit(ParserAction pa) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    pa.element       = (Element)dispatch(pa.element);
    return pa;
  }

  /**
   * Visit the specified element.  This method provides the default
   * implementation for nonterminals, terminals (besides character
   * switches), node markers, actions, parse tree nodes, null
   * literals, and value elements.
   */
  public Element visit(Element e) {
    isTopLevel       = false;
    isVoided         = false;
    isBound          = false;
    isLastElement    = false;
    needsSequence    = false;
    transformInPlace = false;

    return e;
  }

}
