/*
 * xtc - The eXTensible Compiler
 * Copyright (C) 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.lang;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;

import java.util.HashMap;
import java.util.Map;

import xtc.Constants;

import xtc.tree.GNode;
import xtc.tree.Node;
import xtc.tree.Printer;
import xtc.tree.Transducer;

import xtc.util.Tool;
import xtc.util.Utilities;

import xtc.parser.ParseException;

/**
 * A factory of factories.  This tool reads in a factory declaration,
 * which contains one or more snippets of literal Java or C code and
 * then creates the corresponding Java factory class.  The class has
 * one method per snippet, with each method instantiating the abstract
 * syntax tree representing the code snippet.  Code snippets may be:
 * <ul>
 * <li>Declarations,</li>
 * <li>Statements,</li>
 * <li>Expressions.</li>
 * </ul>
 * Code snippets may also contain pattern variables:
 * <ul>
 * <li><code>#</code><i>Name</i> represents a single value, to be
 * supplied when instantiating the pattern.</li>
 * <li><code>#[</code><i>Name</i><code>]</code> represents a list of
 * values, also to be supplied when instantiating the pattern.</li>
 * </ul>
 * For each pattern variable, the corresponding method has a parameter
 * of the same name; the actual argument supplies the correponding
 * value(s) when instantiating the pattern.  Single-valued pattern
 * variables may appear instead of:
 * <ul>
 * <li>Declarations or statements in blocks,</li>
 * <li>Types or identifiers in variable declarations,</li>
 * <li>Expressions.</li>
 * </ul>
 * List-valued pattern variables may appear instead of:
 * <ul>
 * <li>Declarations or statements in blocks,</li>
 * <li>Arguments to a function or method call.</li>
 * </ul>
 *
 * <p />Consider this example factory description containing C
 * snippets:<pre>
 * alias number;
 *
 * factory xtc.lang.Stuff {
 *   declare { number * #name ; }
 *   block   { { int i; double d; #[statements] } }
 *   add     { #number + 5 }
 *   call    { #function ( #[arguments] ) }
 * }
 * </pre>
 * The optional alias declaration may only appear in factory
 * declarations with C snippets; it lists all typedef names used in
 * the C snippets as a comma-separate list.  In the example,
 * "<code>number</code>" is a typedef name.  It is followed by the
 * factory declaration itself, whose name, here
 * "<code>xtc.lang.Stuff</code>", is the fully qualified name of the
 * generated Java class.  The body of the factory declaration consists
 * of one or more method declarations, with each method declaration
 * consisting of a method name followed by the Java or C snippet
 * enclodes in curley braces.  The example declares four methods:<ul>
 *
 * <li><code>declare</code> creates a variable declaration of some
 * <em>name</em> being a pointer to <code>number</code>.</li>
 *
 * <li><code>block</code> creates a compound statement with variable
 * declarations for <code>i</code> and <code>d</code> and some list of
 * <em>statements</em>.</li>
 *
 * <li><code>add</code> creates an additive expression that adds
 * some expression <em>number</em> to the integer 5.</li>
 *
 * <li><code>call</code> creates a function call of some
 * <em>function</em> on some list of <em>arguments</em>.</li>
 * </ul>
 *
 * <p />The recommended file extension for factory declarations with
 * Java snippets is "<code>.ffj</code>" and with C snippets
 * "<code>.ffc</code>".
 *
 * @author Robert Grimm
 * @version $Revision: 1.16 $
 */
public class FactoryFactory extends Tool {

  /** Create a new factory factory. */
  public FactoryFactory() {
    /* Nothing to do. */
  }

  public String getName() {
    return "xtc Factory Factory";
  }

  public String getCopy() {
    return Constants.COPY;
  }

  public String getExplanation() {
    return
      "This tool translates factory declarations into the corresponding " +
      "Java classes.  Each declaration contains one or more snippets of " +
      "literal Java or C code, which are then translated into methods that " +
      "create the corresponding AST.  Snippets may be declarations, " +
      "statements, or expressions; they may also contain pattern variables. " +
      "The default language for snippets is Java; use the -C option for C.";
  }

  public void init() {
    super.init();
    runtime.
      bool("C", "createCFactory", false, "Create a factory for C ASTs.").
      bool("simplify", "simplifyAST", false, "Simplify the Java AST.");
  }

  public void prepare() {
    super.prepare();
    if (runtime.test("createCFactory") && runtime.test("simplifyAST")) {
      runtime.error("simplify option only valid for Java ASTs");
    }
  }

  public File locate(String name) throws IOException {
    File file = super.locate(name);
    if (Integer.MAX_VALUE < file.length()) {
      throw new IllegalArgumentException(file + ": file too large");
    }
    return file;
  }

  public Node parse(Reader in, File file) throws IOException, ParseException {
    if (runtime.test("createCFactory")) {
      CFactoryParser parser =
        new CFactoryParser(in, file.toString(), (int)file.length());
      return (Node)parser.value(parser.pFactory(0));

    } else {
      JavaFactoryParser parser =
        new JavaFactoryParser(in, file.toString(), (int)file.length());
      return (Node)parser.value(parser.pFactory(0));
    }
  }

  public void process(Node node) {
    GNode factory = GNode.cast(node);

    // Collapse the factory class name into a string.
    StringBuilder buf   = new StringBuilder();
    boolean       first = true;
    for (Object o : GNode.cast(factory.get(0))) {
      if (first) {
        first = false;
      } else {
        buf.append('.');
      }
      buf.append((String)o);
    }
    String name = buf.toString();

    // Determine the output file and open a printer to it.
    File file = new File(runtime.getOutputDirectory(),
                         Utilities.getName(name) + ".java");
    Printer out;
    try {
      out = new Printer(new PrintWriter(runtime.getWriter(file)));
    } catch (IOException x) {
      if (null == x.getMessage()) {
        runtime.error(file.toString() + ": I/O error");
      } else {
        runtime.error(file.toString() + ": " + x.getMessage());
      }
      return;
    }

    // Print the class declaration.
    String pkg = Utilities.getQualifier(name);

    printHeader(out);

    if (null != pkg) {
      out.indent().p("package ").p(pkg).pln(';');
      out.pln();
    }

    out.indent().pln("import java.util.List;").pln();

    if (! "xtc.tree".equals(pkg)) {
      out.indent().pln("import xtc.tree.Node;");
      out.indent().pln("import xtc.tree.GNode;");
      out.pln();
    }

    out.indent().pln("/**");
    out.indent().p(" * Node factory <code>").p(name).pln("</code>.");
    out.indent().pln(" *");
    out.indent().pln(" * <p />This class has been generated by");
    out.indent().p(" * the xtc Factory Factory, version ").p(getVersion()).
      pln(',');
    out.indent().p(" * ").p(getCopy()).pln('.');
    out.indent().pln(" */");
    out.indent().p("public class ").p(Utilities.getName(name)).
      pln(" {").incr();
    out.pln();

    out.indent().pln("/** Create a new node factory. */");
    out.indent().p("public ").p(Utilities.getName(name)).pln("() {").incr();
    out.indent().pln("// Nothing to do.");
    out.decr().indent().pln('}');
    out.pln();

    Map<String,String> variables = new HashMap<String,String>();
    variables.put("NodeVariable",     "Node"  );
    variables.put("NodeListVariable", "List<Node>");
    variables.put("StringVariable",   "String");
    Transducer         trans     = new Transducer(out, variables);
    JavaAstSimplifier  simple    = null;
    if (runtime.test("simplifyAST")) simple = new JavaAstSimplifier();
    for (Object o : GNode.cast(factory.get(1))) {
      GNode clause = GNode.cast(o);
      Node  ast    = clause.getNode(1);
      if (runtime.test("simplifyAST")) ast = (Node)simple.dispatch(ast);

      trans.process(clause.getString(0), ast);
      out.pln();
    }

    out.decr().indent().pln('}');
    out.flush().close();
  }

  /**
   * Run the factory factory with the specified command line arguments.
   *
   * @param args The command line arguments.
   */
  public static void main(String[] args) {
    new FactoryFactory().run(args);
  }

}
