/*
 * xtc - The eXTensible Compiler
 * Copyright (C) 2004-2009 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.Iterator;
import java.util.List;

import xtc.Constants;

import xtc.tree.Attribute;
import xtc.tree.Visitor;

import xtc.type.AST;
import xtc.type.Type;
import xtc.type.Wildcard;

import xtc.util.Runtime;

/**
 * Visitor to transform productions.  This visitor lifts repetitions,
 * options, and nested choices into their own productions, desugars
 * reptitions and options, and adds the appropriate semantic values to
 * expressions that require them.  It also ensures that repeated,
 * optional, and predicated elements are sequences, thus fulfilling
 * the requirements for {@link CodeGenerator code generation}.  Before
 * applying this visitor on a grammar, the grammar must have been
 * {@link Simplifier simplified}, all text-only productions should be
 * marked as such by applying the {@link TextTester} visitor, and all
 * token-level productions should be marked as such by applying the
 * {@link Tokenizer} visitor.  Note that {@link Generifier} adds the
 * appropriate semantic values for generic productions.  Further note
 * that repetitions in transient productions are not desugared (nor
 * lifted) if the corresponding command line option
 * ("<code>-Orepeated</code") is set.  Also, options are not desugared
 * (nor lifted) if the corresponding command line option
 * ("<code>-Ooptional</code>") is set.  Finally, note that this
 * visitor assumes that the entire grammar is contained in a single
 * module.
 *
 * <p />This visitor may report errors to the user.
 *
 * @author Robert Grimm
 * @version $Revision: 1.128 $
 */
public class Transformer extends Visitor {

  /**
   * The variable marker for bindings in repeated or optional
   * elements.
   */
  public static final String ELEMENT_MARKER = "el";

  // =========================================================================

  /**
   * Visitor to add semantic values.
   *
   * <p />This visitor assumes that each production's top-level
   * element is an ordered choice and that each choice's alternatives
   * are sequences.
   */
  public class Deducer extends Visitor {

    /** The list of elements. */
    protected List<Element> elements;

    /** Create a new deducer. */
    public Deducer() { /* Nothing to do. */ }

    /** Visit the specified production. */
    public void visit(Production p) {
      // Only try to add values to productions that (1) are not
      // generic or list-valued, (2) are not directly left-recursive,
      // or (3) do not contain a quantification (i.e., repetition or
      // option) that will be desugared.
      Element e = Analyzer.strip(p.choice);

      if ((! isGeneric()) &&
          (! isList()) &&
          (! isLeftRecursive()) &&
          ((! (e instanceof Repetition)) || retainRepetitions()) &&
          ((! (e instanceof Option)) || retainOptions())) {
        if (runtime.test("optionVerbose")) {
          System.err.println("[Deducing semantic value for " + p.qName + "]");
        }

        elements = new ArrayList<Element>();
        dispatch(p.choice);
      }
    }

    /** Visit the specified choice. */
    public void visit(OrderedChoice c) {
      // Process the alternatives.
      for (Sequence alt : c.alternatives) dispatch(alt);
    }

    /** Visit the specified sequence. */
    public void visit(Sequence s) {
      // Remember the current number of elements.
      final int base = elements.size();

      // Process the elements of the sequence.
      for (Iterator<Element> iter = s.elements.iterator(); iter.hasNext(); ) {
        Element e = iter.next();

        if ((! iter.hasNext()) && (e instanceof OrderedChoice)) {
          // Continue with the trailing choice.
          dispatch(e);
        } else {
          // Add the current element to the list of traversed elements.
          elements.add(e);
        }
      }

      // Deduce the semantic value if this sequence does not have a
      // trailing choice.
      if (! s.hasTrailingChoice()) {
        if (isVoid()) {
          s.add(NullValue.VALUE);

        } else if (isTextOnly()) {
          String text = analyzer.matchingText(new Sequence(elements));
          if ((null == text) || (! runtime.test("optimizeTerminals"))) {
            s.add(StringValue.VALUE);
          } else {
            s.add(new StringValue(text));
          }

        } else if (isToken()) {
          String text = analyzer.matchingText(new Sequence(elements));
          if ((null == text) || (! runtime.test("optimizeTerminals"))) {
            s.add(TokenValue.VALUE);
          } else {
            s.add(new TokenValue(text));
          }

        } else if (elements.isEmpty()) {
          s.add(NullValue.VALUE);

        } else {
          Binding b = analyzer.bind(elements);
          if (null != b) {
            if (Analyzer.isSynthetic(b.name)) {
              // Patch the variable name to be the semantic value.
              b.name = CodeGenerator.VALUE;
            } else if (! CodeGenerator.VALUE.equals(b.name)) {
              // Preserve the user-specified variable name.
              s.add(new BindingValue(b));
            }
          }
        }
      }

      // Patch back any added binding.
      if ((! isVoid()) && (! isTextOnly()) && (! isToken())) {
        int size = s.size();

        // Ignore trailing choices and value elements.
        if (s.hasTrailingChoice() ||
            ((0 != size) &&
             (s.get(size-1) instanceof ValueElement))) {
          size--;
        }

        // Iterate over all other elements.
        for (int i=0; i<size; i++) {
          Element e = elements.get(base + i);
          if (s.get(i) != e) s.elements.set(i, e);
        }
      }

      // Remove any elements added by processing the sequence.
      if (0 == base) {
        elements.clear();
      } else {
        elements.subList(base, elements.size()).clear();
      }
    }

  }

  // =========================================================================

  /**
   * Visitor to lift nested choices, repetitions, and options.  This
   * visitor also ensures that repeated, optional, and predicated
   * elements are sequences.
   */
  public class Lifter extends GrammarVisitor {

    /** Create a new lifter. */
    public Lifter() {
      super(Transformer.this.runtime, Transformer.this.analyzer);
    }

    /**
     * Bind the specified element.
     *
     * @param e The element.
     * @return The bound element.
     */
    protected Binding bind(Element e) {
      return new Binding(analyzer.variable(ELEMENT_MARKER), e);
    }

    /**
     * Process the specified repeated, optional, or predicated
     * element.  This method processes the specified element, lifting
     * it and adding a binding for its value, either only if
     * necessary.  The result is a sequence to fulfill the
     * corresponding code generation requirement.
     *
     * @param e The element.
     * @param bound The flag for whether the element's value is bound.
     * @return The resulting sequence.
     */
    protected Sequence process(Element e, boolean bound) {
      /*
       * (1) If a repeated, optional, or predicated element explicitly
       * sets a semantic value (i.e., Analyzer.setsValue() returns
       * true), it must be lifted.  Furthermore, a binding must be
       * used to ensure that the lifted choice has a meaningful type.
       *
       * (2) If a repetition or option is bound, the repeated or
       * optional element must have a binding as well, unless the
       * element appears within a text-only or token-level production
       * (whose value is created directly from the buffered input).
       *
       * (3) A sequence's value can only be captured through a binding
       * if it does not explicitly set the semantic value and if it
       * does not contain a choice as its last element.  If either is
       * the case, the sequence must be lifted.
       *
       * (4) A predicated sequence that has a choice as its last
       * element must be lifted.
       *
       * (5) Text-only and token-level productions, by definition,
       * cannot contain bindings to yyValue nor actions that set
       * yyValue.  However, they may contain value elements, causing
       * Analyzer.setsValue() to return true.
       *
       * (6) Setting isLastElement to true results in an ordered
       * choice not being lifted, unless the choice appears in a
       * predicate.
       */

      if (e instanceof OrderedChoice) {

        if ((! isTextOnly()) && (! isToken()) && 
            (bound || Analyzer.setsValue(e, false))) {
          // Bind the choice to preserve its value.  Lift choice.
          return Sequence.ensure((Element)dispatch(bind(e)));

        } else {
          // Try to leave the choice in place.
          isLastElement = true;
          return Sequence.ensure((Element)dispatch(e));
        }

      } else {
        Sequence s = Sequence.ensure(e);

        if ((! isTextOnly()) && (! isToken()) && Analyzer.setsValue(e, false)) {
          // Bind sequence wrapped in choice to preserve value.  Lift choice.
          return Sequence.ensure((Element)dispatch(bind(new OrderedChoice(s))));

        } else if ((! isTextOnly()) && (! isToken()) && 
                   bound && s.hasTrailingChoice()) {
          // Bind sequence wrapped in choice to preserve value.  Lift choice.
          return Sequence.ensure((Element)dispatch(bind(new OrderedChoice(s))));

        } else if ((! isTextOnly()) && (! isToken()) && bound) {
          // Add binding to sequence.
          Binding b = analyzer.bind(s.elements, ELEMENT_MARKER);
          if (null == b) {
            runtime.error("unable to deduce value", s);
          }
          return Sequence.ensure((Element)dispatch(s));

        } else if (isPredicate && s.hasTrailingChoice()) {
          // Wrap sequence in choice and lift choice.
          return Sequence.ensure((Element)dispatch(new OrderedChoice(s)));

        } else {
          // Process sequence.  Try to leave trailing choice in place.
          isLastElement = true;
          return Sequence.ensure((Element)dispatch(s));
        }

      }
    }

    /**
     * Create a new production with the specified type, nonterminal,
     * and ordered choice.  This method creates the production, using
     * the {@link #current() current production's} attributes and
     * text-only and token-level marks, recursively processes the
     * productions, and adds it to the grammar.
     *
     * @param type The type.
     * @param nt The nonterminal.
     * @param c The ordered choice.
     */
    protected void lift(Type type, NonTerminal nt, OrderedChoice c) {
      // Create the production.
      FullProduction p =
        new FullProduction(new ArrayList<Attribute>(current().attributes), type,
                           nt, nt.qualify(analyzer.module().name.name), c);

      // Document activity under verbose mode.
      if (runtime.test("optionVerbose")) {
        System.err.println("[Lifting expression into new production " +
                           p.qName + ']');
      }

      // Do not inherit any public, explicit, stateful, or resetting
      // attribute.
      p.attributes.remove(Constants.ATT_PUBLIC);
      p.attributes.remove(Constants.ATT_EXPLICIT);
      p.attributes.remove(Constants.ATT_STATEFUL);
      p.attributes.remove(Constants.ATT_RESETTING);
      if (isTextOnly()) {
        TextTester.markTextOnly(p, runtime.test("optionVerbose"));
      } else if (isToken()) {
        Tokenizer.markToken(p, runtime.test("optionVerbose"));
      }

      // Recursively process the new production.
      Transformer.this.process(p);

      // Add the new production to the grammar.
      analyzer.add(p);
    }

    /** Visit the specified ordered choice. */
    public Element visit(OrderedChoice c) {
      boolean top      = isTopLevel;
      isTopLevel       = false;
      boolean voided   = isVoided;
      isVoided         = false;
      boolean bound    = isBound;
      isBound          = false;
      boolean last     = isLastElement;
      transformInPlace = top && (Analyzer.strip(c) instanceof Quantification);

      if ((top || last) && (! isPredicate)) {
        // Continue processing.
        final int size = c.alternatives.size();
        for (int i=0; i<size; i++) {
          isLastElement  = top || last;
          c.alternatives.set(i, (Sequence)dispatch(c.alternatives.get(i)));
        }

        isLastElement = false;
        return c;
        
      } else {
        // Lift choice.
        NonTerminal nt = analyzer.choice();
        
        Type type;
        if (isTextOnly()) {
          type = AST.STRING;
        } else if (isToken()) {
          type = AST.TOKEN;
        } else if (bound || ((isGeneric() || isList()) &&
                             (! voided) &&
                             (! isPredicate))) {
          type = AST.ANY;
        } else {
          type = AST.VOID;
        }

        lift(type, nt, c);
        
        isLastElement = false;
        return nt;
      }
    }

    /** Visit the specified repetition. */
    public Element visit(Repetition r) {
      isTopLevel       = false;
      boolean voided   = isVoided;
      isVoided         = false;
      boolean bound    = isBound;
      isBound          = false;
      isLastElement    = false;
      boolean inPlace  = transformInPlace;
      transformInPlace = false;

      // If the repeated optimization is enabled and the production is
      // not memoized, do not lift the repetition.  Also, if the
      // repetition is the only top-level element (besides a wrapping
      // sequence and choice), do not lift the repetition.  However,
      // if the repetition is bound and the production is text-only or
      // token-level, the repetition must be lifted.  Furthermore, if
      // the repetition is the top-level element in a generic
      // production and will be desugared, the repetition must be
      // lifted (since a binding will be added by the Generifier
      // phase).  Finally, if the repetition is the top-level element
      // in a resetting or stateful production and will be desugared,
      // the repetition must be lifted.
      if ((retainRepetitions() &&
           ((! bound) || ((! isTextOnly()) && (! isToken())))) ||
          (inPlace &&
           (! changesState() || retainRepetitions()) &&
           (! isGeneric() || retainRepetitions()))) {
        // Process the repeated element.
        boolean b = (bound ||
                     (isGeneric() && (! voided) && (! isPredicate)) ||
                     (isList() && (! voided) && (! isPredicate)) ||
                     (inPlace && (! isVoid())));
        r.element = process(r.element, b);
        return r;
      }

      NonTerminal nt = (r.once)? analyzer.plus() : analyzer.star();

      Type type;
      if (isTextOnly()) {
        type = AST.STRING;
      } else if (isToken()) {
        type = AST.TOKEN;
      } else if (bound || ((isGeneric() || isList()) &&
                           (! voided) &&
                           (! isPredicate))) {
        // Note that the type will be patched during desugaring.
        type = AST.WILD_LIST;
      } else {
        type = AST.VOID;
      }

      OrderedChoice c = new OrderedChoice(r);
      c.setLocation(r);

      lift(type, nt, c);

      return nt;
    }

    /** Visit the specified option. */
    public Element visit(Option o) {
      isTopLevel       = false;
      boolean voided   = isVoided;
      isVoided         = false;
      boolean bound    = isBound;
      isBound          = false;
      isLastElement    = false;
      boolean inPlace  = transformInPlace;
      transformInPlace = false;

      // If the optional optimization is enabled, do not lift the
      // option.  Also, if the option is the only top-level element
      // (besides a wrapping sequence and choice), do not lift the
      // option.  However, if the option is bound and the production
      // is text-only or token-level, the option must be lifted.
      // Furthermore, if the option is the top-level element in a
      // generic production and will be desugared, the option must be
      // lifted (since a binding will be added by the Generifier
      // phase).  Finally, if the option is the top-level element in a
      // resetting or stateful production and will be desugared, the
      // option must be lifted.
      if ((retainOptions() && 
           ((! bound) || ((! isTextOnly()) && (! isToken())))) ||
          (inPlace &&
           (! changesState() || retainOptions()) &&
           (! isGeneric() || retainOptions()))) {
        // Process the optional element.
        boolean b = (bound ||
                     (isGeneric() && (! voided) && (! isPredicate)) ||
                     (isList() && (! voided) && (! isPredicate)) ||
                     (inPlace && (! isVoid())));
        o.element = process(o.element, b);
        return o;
      }

      NonTerminal nt = analyzer.option();

      Type type;
      if (isTextOnly()) {
        type = AST.STRING;
      } else if (isToken()) {
        type = AST.TOKEN;
      } else if (bound || ((isGeneric() || isList()) &&
                           (! voided) &&
                           (! isPredicate))) {
        type = AST.ANY;
      } else {
        type = AST.VOID;
      }

      OrderedChoice c = new OrderedChoice(o);
      c.setLocation(o);

      lift(type, nt, c);

      return nt;
    }

    /** Visit the specified predicate. */
    public Element visit(Predicate p) {
      isTopLevel        = false;
      isVoided          = false;
      isBound           = false;
      isLastElement     = false;
      boolean predicate = isPredicate;
      isPredicate       = true;

      p.element         = process(p.element, false);

      isPredicate       = predicate;
      return p;
    }

  }

  // =========================================================================

  /**
   * Visitor to desugar repetitions and options.  This visitor assumes
   * that a production's top-level element is an ordered choice with a
   * sequence for each alternative.
   */
  public class Desugarer extends Visitor {

    /** Create a new desugarer. */
    public Desugarer() { /* Nothing to do. */ }

    /**
     * Process the specified sequence.  This method adds a nonterminal
     * and value element at the end of the specified sequence,
     * recursing into the sequences of any ordered choice appearing as
     * a sequence's last element.  The current production must be a
     * void, text-only, or token-level production.
     *
     * @param s The sequence.
     * @param nt The nonterminal or <code>null</code> if no nonterminal
     *   should be added.
     */
    protected void process(Sequence s, NonTerminal nt) {
      if (s.hasTrailingChoice()) {
        OrderedChoice c = (OrderedChoice)s.get(s.size()-1);
        for (Sequence alt : c.alternatives) process(alt, nt);

      } else {
        if (null != nt) {
          s.add(nt);
        }

        if (isVoid()) {
          s.add(NullValue.VALUE);
        } else if (isTextOnly()) {
          s.add(StringValue.VALUE);
        } else if (isToken()) {
          s.add(TokenValue.VALUE);
        } else {
          assert false;
        }
      }
    }

    /** Visit the specified production. */
    public void visit(Production p) {
      Element e = Analyzer.strip(p.choice);

      if ((e instanceof Repetition) && (! retainRepetitions())) {
        if (runtime.test("optionVerbose")) {
          System.err.println("[Desugaring repetition in " + p.qName + ']');
        }

        // If the repeated element is a sequence without choices and
        // that sequence's value is a nonterminal, record the
        // nonterminal in the production's repeated property.
        Sequence s = (Sequence)((Repetition)e).element;
        if (! s.hasTrailingChoice()) {
          Binding b = Analyzer.getBinding(s.elements);
          if ((null != b) && (b.element instanceof NonTerminal)) {
            p.setProperty(Properties.REPEATED, b.element);
          }
        }

        // Desugar the repetition.
        p.choice = (OrderedChoice)dispatch(e);

      } else if ((e instanceof Option) && (! retainOptions())) {
        if (runtime.test("optionVerbose")) {
          System.err.println("[Desugaring option in " + p.qName + ']');
        }

        p.choice = (OrderedChoice)dispatch(e);
        p.setProperty(Properties.OPTION, Boolean.TRUE);
      }
    }

    /** Visit the specified repetition. */
    public Element visit(Repetition r) {
      Sequence repeated = (Sequence)r.element;

      // Set up the lists of old and new alternatives.
      List<Sequence> oldAlternatives;
      if ((1 == repeated.size()) && repeated.hasTrailingChoice()) {
        oldAlternatives = ((OrderedChoice)repeated.get(0)).alternatives;
      } else {
        oldAlternatives = new ArrayList<Sequence>(1);
        oldAlternatives.add(repeated);
      }

      List<Sequence> newAlternatives =
        new ArrayList<Sequence>(oldAlternatives.size() +
                                ((r.once)? oldAlternatives.size() : 1));

      // Determine the production's generic list type.
      Type type = Wildcard.TYPE;
      if ((! isVoid()) && (! isTextOnly()) && (! isToken())) {
        for (Sequence s : oldAlternatives) {
          Binding  b = Analyzer.getBinding(s.elements);
          if (null == b) {
            runtime.error("unable to bind repeated element", s);
          } else {
            type = ast.unify(type, analyzer.type(b.element), false);
          }
        }

        type = AST.listOf(ast.concretize(type, AST.ANY));

        // If the production is synthetic, patch its type.
        if (Analyzer.isSynthetic(current().name)) {
          if (runtime.test("optionVerbose")) {
            System.err.println("[Adjusting " + current().qName +
                               "'s type to " +
                               ast.extern(type) + ']');
          }

          current().type = type;
        }
      }

      // Process the recursive alternatives.
      for (Sequence s : oldAlternatives) {
        if (r.once) {
          // Copy the base alternative(s) to avoid creating a DAG,
          // which might lead to problems in later phases that modify
          // any elements (e.g., the terminal optimizer may convert
          // character classes into character switches, which
          // internally still use the character class element but with
          // the exclusive flag set to false).
          s = analyzer.copy(s);
        }

        if (isVoid() || isTextOnly() || isToken()) {
          process(s, current().name);

        } else {
          Binding b1 = Analyzer.getBinding(s.elements);
          Binding b2 = new Binding(analyzer.variable(), current().name);
          if (null != b1) {
            s.add(b2).add(new ProperListValue(type, b1, b2));
          }
        }

        newAlternatives.add(s);
      }

      // Process the base alternative(s).
      if (r.once) {
        for (Sequence s : oldAlternatives) {
          if (isVoid() || isTextOnly() || isToken()) {
            process(s, null);

          } else {
            Binding b = Analyzer.getBinding(s.elements);
            if (null != b) {
              s.add(new ProperListValue(type, b));
            }
          }

          newAlternatives.add(s);
        }

      } else {
        Sequence s = new Sequence();

        if (isVoid()) {
          s.add(NullValue.VALUE);
        } else if (isTextOnly()) {
          s.add(StringValue.VALUE);
        } else if (isToken()) {
          s.add(TokenValue.VALUE);
        } else {
          s.add(EmptyListValue.VALUE);
        }

        newAlternatives.add(s);
      }

      // Create the new ordered choice and return it.
      return new OrderedChoice(newAlternatives);
    }

    /** Visit the specified option. */
    public Element visit(Option o) {
      Sequence optional = (Sequence)o.element;

      List<Sequence> oldAlternatives;
      if ((1 == optional.size()) && optional.hasTrailingChoice()) {
        oldAlternatives = ((OrderedChoice)optional.get(0)).alternatives;
      } else {
        oldAlternatives = new ArrayList<Sequence>(1);
        oldAlternatives.add(optional);
      }

      List<Sequence> newAlternatives =
        new ArrayList<Sequence>(oldAlternatives.size() + 1);
      
      // The matching alternatives.
      for (Sequence s : oldAlternatives) {
        if (isVoid() || isTextOnly() || isToken()) {
          process(s, null);

        } else {
          Binding b = Analyzer.getBinding(s.elements);
          if (null != b) {
            // Patch the variable name to be the semantic value.
            b.name = CodeGenerator.VALUE;
          }
        }

        newAlternatives.add(s);
      }

      // The empty alternative.
      Sequence alt = new Sequence();
      
      if (isTextOnly()) {
        alt.add(StringValue.VALUE);
      } else if (isToken()) {
        alt.add(TokenValue.VALUE);
      } else {
        alt.add(NullValue.VALUE);
      }
      newAlternatives.add(alt);

      // Create the new ordered choice and return it.
      return new OrderedChoice(newAlternatives);
    }

  }

  // =========================================================================

  /**
   * Visitor to deduce a production's type.
   */
  public class Typer extends Visitor {

    /** The list of elements. */
    protected List<Element> elements;

    /** The current type. */
    protected Type type;

    /** Create a new typer. */
    public Typer() { /* Nothing to do. */ }

    /** Visit the specified production. */
    public void visit(Production p) {
      // Only try to type production's semantic value if the
      // production is synthetic (i.e. lifted) and the type is the
      // root type.  Note that this condition correctly excludes void,
      // text-only, token-level, and generic productions.
      if (Analyzer.isSynthetic(p.name) && AST.isAny(p.type)) {
        elements = new ArrayList<Element>();
        type     = Wildcard.TYPE;
        dispatch(p.choice);
        if ((! type.isWildcard()) && (! AST.isAny(type))) {
          // Only adjust the type, if a new more specific type has
          // been found.
          if (runtime.test("optionVerbose")) {
            System.err.println("[Adjusting " + p.qName + "'s type to " +
                               ast.extern(type) + ']');
          }

          p.type = ast.concretize(type, AST.ANY);
        }
      }
    }

    /** Visit the specified choice. */
    public void visit(OrderedChoice c) {
      // Process the alternatives.
      for (Sequence alt : c.alternatives) dispatch(alt);
    }

    /** Visit the specified sequence. */
    public void visit(Sequence s) {
      // Remember the current number of elements.
      final int base = elements.size();

      // Process the elements of the sequence.
      for (Iterator<Element> iter = s.elements.iterator(); iter.hasNext(); ) {
        Element e = iter.next();

        if ((! iter.hasNext()) && (e instanceof OrderedChoice)) {
          // Continue with the trailing choice.
          dispatch(e);
        } else {
          // Add the current element to the list of traversed elements.
          elements.add(e);
        }
      }

      // Deduce the semantic value.
      if (! s.hasTrailingChoice()) {
        Binding b = Analyzer.getBinding(elements);

        if ((null != b) &&
            (CodeGenerator.VALUE.equals(b.name) ||
             ((0 != s.size()) &&
              (s.get(s.size()-1) instanceof BindingValue) &&
              b.name.equals(((BindingValue)s.get(s.size()-1)).binding.name)))) {
          // Unify the binding's type with any previously determined
          // type.
          type = ast.unify(type, analyzer.type(b.element), false);

        } else if (! Analyzer.setsNullValue(elements)) {
          // We don't have any meaningful information.  Assume the
          // worst.
          type = AST.ANY;
        }
      }

      // Remove any elements added by processing the sequence.
      if (0 == base) {
        elements.clear();
      } else {
        elements.subList(base, elements.size()).clear();
      }
    }

  }

  // =========================================================================

  /** The runtime. */
  protected final Runtime runtime;

  /** The analyzer. */
  protected final Analyzer analyzer;

  /** The type operations. */
  protected final AST ast;

  /**
   * The flag for whether the current module has the {@link
   * Constants#ATT_PARSE_TREE parseTree} attribute.
   */
  protected boolean hasParseTree;

  /** The current (full) production. */
  protected FullProduction production;

  /**
   * Create a new transformer.
   *
   * @param runtime The runtime.
   * @param analyzer The analyzer utility.
   * @param ast The type operations.
   */
  public Transformer(Runtime runtime, Analyzer analyzer, AST ast) {
    this.runtime  = runtime;
    this.analyzer = analyzer;
    this.ast      = ast;
  }

  /**
   * Process the specified production.  This method may be called
   * recursively while processing another production.
   *
   * @param p The production to process.
   */
  protected void process(FullProduction p) {
    FullProduction saved = production;
    production           = p;

    new Deducer().dispatch(production);
    new Lifter().dispatch(production);
    new Desugarer().dispatch(production);
    new Typer().dispatch(p);

    production = saved;
  }

  /**
   * Determine whether the current production is memoized.
   *
   * @return <code>true</code> if the current production is memoized.
   */
  protected boolean isMemoized() {
    return production.isMemoized();
  }

  /**
   * Determine whether the current production changes the global
   * parser state.
   *
   * @return <code>true</code> if the current production is resetting
   *   or stateful.
   */
  public boolean changesState() {
    return production.hasAttribute(Constants.ATT_RESETTING) ||
      production.hasAttribute(Constants.ATT_STATEFUL);
  }

  /**
   * Determine whether the current production is void.
   *
   * @return <code>true</code> if the current production is void.
   */
  protected boolean isVoid() {
    return AST.isVoid(production.type);
  }

  /**
   * Determine whether the current production is text-only.
   *
   * @return <code>true</code> if the current production is text-only.
   */
  protected boolean isTextOnly() {
    return production.getBooleanProperty(Properties.TEXT_ONLY);
  }

  /**
   * Determine whether the current production is token-level.
   *
   * @return <code>true</code> if the current production is token-level.
   */
  protected boolean isToken() {
    return production.getBooleanProperty(Properties.TOKEN);
  }

  /**
   * Determine whether the current production is generic.
   *
   * @return <code>true</code> if the current production is generic.
   */
  protected boolean isGeneric() {
    return Generifier.isGeneric(production);
  }

  /**
   * Determine whether the current production has a list value.
   *
   * @return <code>true</code> if the current production has a list value.
   */
  protected boolean isList() {
    return AST.isList(production.type);
  }

  /**
   * Determine whether the current production is a directly
   * left-recursive production.
   *
   * @return <code>true</code> if the current production is directly
   *   left-recursive.
   */
  protected boolean isLeftRecursive() {
    return DirectLeftRecurser.isTransformable(production);
  }

  /**
   * Get the current production.
   *
   * @return The current production.
   */
  protected Production current() {
    return production;
  }

  /**
   * Determine whether the current production may contain repetitions.
   *
   * @return <code>true</code> if the production may contain
   *   repetitions.
   */
  protected boolean retainRepetitions() {
    // Note: when reducing a grammar to only the expressions that
    // contribute to the AST, we retain repetitions to reduce clutter.
    return ((runtime.test("optimizeRepeated") && (! isMemoized())) ||
            runtime.test("optionValued"));
  }

  /**
   * Determine whether the current production may contain options.
   *
   * @return <code>true</code> if the production may contain options.
   */
  protected boolean retainOptions() {
    // Note: when reducing a grammar to only the expressions that
    // contribute to the AST, we retain options to reduce clutter.
    return runtime.test("optimizeOptional") || runtime.test("optionValued");
  }

  /** Visit the specified grammar. */
  public void visit(Module m) {
    // Initialize the per-grammar state.
    analyzer.register(this);
    analyzer.init(m);
    hasParseTree = m.hasAttribute(Constants.ATT_PARSE_TREE);

    // Now, process the productions.
    for (int i=0; i<m.productions.size(); i++) {
      Production p = m.productions.get(i);

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

      // If there are new productions, add them to the grammar and
      // make sure they are not processed again.
      i += analyzer.addNewProductionsAt(i+1);
    }
  }

  /** Visit the specified production. */
  public void visit(FullProduction p) {
    process(p);
  }

}
