/* * 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.tree; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import xtc.util.Utilities; /** * Visitor to convert trees of generic nodes into methods that * programmatically create the trees. Trees may contain generic nodes * that represent pattern variables. During transduction, pattern * variable are replaced with method arguments of the same names. In * general, a variable represents a single child of a newly created * generic node. However, if the variable's type is * <code>List<T></code>, all of the list's elements are directly * added as children to the newly created generic node. Any node * representing a pattern variable must have exactly one child that is * a string representing the variable's name. * * @author Robert Grimm * @version $Revision: 1.5 $ */ public class Transducer extends Visitor { /** The printer. */ protected final Printer printer; /** The mapping from pattern variable names to their types. */ protected final Map<String, String> variables; /** The temporary variable count. */ protected int varcount; /** * Create a new transducer. The newly created transducer treats all * trees as literals, without pattern variables. * * @param printer The printer. */ public Transducer(Printer printer) { this(printer, new HashMap<String, String>()); } /** * Create a new transducer. The mapping from pattern variable names * to types may be empty (but not <code>null</code>) to indicate * that all tree nodes are literal, i.e., do not contain any "holes" * that are filled during creation. * * @param printer The printer. * @param variables The mapping from node names representing pattern * variables to their values' types. */ public Transducer(Printer printer, Map<String, String> variables) { this.printer = printer; this.variables = variables; } /** * Determine whether the specified node represents a pattern variable. * * @param n The node. * @return <code>true</code> if it represents a pattern variable. */ public boolean isPatternVariable(Node n) { n = n.strip(); return null == n ? false : n.isGeneric() && variables.containsKey(n.getName()); } /** * Ensure that the specified node is a well-formed pattern variable. * * @param n The node. * @return The node as a pattern variable. * @throws IllegalArgumentException Signals that the specified node * is not a pattern variable or is a malformed pattern variable. */ public GNode toPatternVariable(Node n) { n = n.strip(); if ((null == n) || (! n.isGeneric()) || (! variables.containsKey(n.getName()))) { throw new IllegalArgumentException("Not a pattern variable: " + n); } else if ((1 != n.size()) || (! Token.test(n.get(0)))) { throw new IllegalArgumentException("Malformed pattern variable: " + n); } return (GNode)n; } /** * Get the pattern variable's name. * * @param n The node. * @return The corresponding variable name. * @throws IllegalArgumentException Signals that the node does not * represent a pattern variable or is malformed. */ public String getVariableName(Node n) { return Token.cast(toPatternVariable(n).get(0)); } /** * Get the pattern variable's type. * * @param n The node. * @return The corresponding variable type. * @throws IllegalArgumentException Signals that the node does not * represent a pattern variable or is malformed. */ public String getVariableType(Node n) { return variables.get(toPatternVariable(n).getName()); } /** * Determine whether the specified type is a list type. * * @param t The type. * @return <code>true</code> if the specified type is a list type. */ public boolean isListType(String t) { return t.equals("List") || t.startsWith("List<"); } /** * Convert the specified object to a literal. If the specified * variable name is <code>null</code> and the object is a node, the * object must be a pattern variable. * * @param o The object. * @param var The variable name for nodes that are not pattern * variables. * @return The corresponding literal. * @throws IllegalArgumentException Signals that the specified object * is not recognized. */ public String toLiteral(Object o, String var) { if (null == o) { return "null"; } else if (Token.test(o)) { return '"'+Utilities.escape(Token.cast(o), Utilities.JAVA_ESCAPES)+'"'; } else if (o instanceof Node) { return null == var ? getVariableName((Node)o) : var; } else if (o instanceof Boolean) { return ((Boolean)o).booleanValue() ? "true" : "false"; } else if (o instanceof Double) { return ((Double)o).doubleValue() + "D"; } else if (o instanceof Float) { return ((Float)o).floatValue() + "F"; } else if (o instanceof Long) { return ((Long)o).longValue() + "L"; } else if (o instanceof Integer) { return o.toString(); } else if (o instanceof Short) { return "Short.valueOf(" + ((Short)o).shortValue() + ')'; } else if (o instanceof Byte) { return "Byte.valueOf(" + ((Byte)o).byteValue() + ')'; } else if (o instanceof Character) { return "'" + ((Character)o).charValue() + "'"; } else { throw new IllegalArgumentException("Unrecognized value: " + o); } } /** * Process the specified node. * * @param method The method name. * @param n The node. * @throws IllegalArgumentException Signals that the specified is * not a generic node or that it is a pattern variable. */ public void process(String method, Node n) { // Perform consistency checks: neither node nor method may be null. if (null == method) { throw new NullPointerException("Null method name"); } else if (null == n) { throw new NullPointerException("Null node"); } n = n.strip(); if (! GNode.test(n)) { throw new IllegalArgumentException("Not an (annotated) generic node: "+n); } else if (isPatternVariable(n)) { throw new IllegalArgumentException("Pattern variable: " + n); } // Reset this visitor's state. varcount = 0; // Declare the list of pattern holes and their types. final List<String> holes = new ArrayList<String>(); final List<String> types = new ArrayList<String>(); // Fill in the list of pattern holes and their types. new Visitor() { @SuppressWarnings("unused") public void visit(GNode n) { if (isPatternVariable(n)) { String name = getVariableName(n); String type = getVariableType(n); int idx = holes.indexOf(name); if (-1 == idx) { holes.add(name); types.add(type); } else if (! types.get(idx).equals(type)) { types.set(idx, "Object"); } } else { for (Object o : n) { if (o instanceof Node) { dispatch((Node)o); } } } } }.dispatch(n); // Emit the method header. String desc = Utilities.split(n.getName(), ' '); printer.indent().pln("/**"); printer.indent().p(" * Create ").p(Utilities.toArticle(desc)).p(' '). p(desc).pln('.'); printer.indent().pln(" *"); for (String h : holes) { printer.indent().p(" * @param ").p(h).p(" The ").p(h).pln('.'); } printer.indent().pln(" * @return The generic node."); printer.indent().pln(" */"); printer.indent().p("public Node ").p(method).p('('); final int align = printer.column(); boolean first = true; Iterator<String> iterH = holes.iterator(); Iterator<String> iterT = types.iterator(); while (iterH.hasNext()) { if (! first) { printer.buffer(); } printer.p(iterT.next()).p(' ').p(iterH.next()); if (iterH.hasNext()) { printer.p(", "); } if (first) { first = false; } else { printer.fit(align); } } printer.pln(") {").incr(); // Emit the method body. String result = (String)dispatch(n); if (null == result) result = getVariableName(n); printer.indent().p("return ").p(result).pln(';'); // Close the method body. printer.decr().indent().pln('}'); } /** Visit the specified generic node. */ public String visit(GNode n) { // Pattern variables do not require any code. if (isPatternVariable(n)) return null; // Iterate over the children, recursively processing any nodes. // Also, determine whether any of the pattern variables used for // this node has a list type. final int size = n.size(); List<String> vars = null; boolean hasList = false; if (0 < size) vars = new ArrayList<String>(size); for (Object o : n) { if (o instanceof Node) { Node child = (Node)o; if (isPatternVariable(child)) { vars.add(null); if (isListType(getVariableType(child))) { hasList = true; } } else { vars.add((String)dispatch(child)); } } else { vars.add(null); } } // Emit code for declaring and creating the node. String result = "v$" + (++varcount); printer.indent().p("Node ").p(result).p(" = GNode.create(\""). p(n.getName()).p("\""); // Emit the code for the node's children. if (0 == size) { // A node with no children. printer.pln(", false);"); } else if (hasList) { // A node with a dynamic number of children. printer.p(", "); // Emit the size expression. int scount = 0; boolean seenList = false; for (int i=0; i<size; i++) { Object o = n.get(i); if (o instanceof Node) { Node child = (Node)o; if (isPatternVariable(child) && isListType(getVariableType(child))) { if (seenList) { printer.p(" + "); } else { seenList = true; } printer.p(getVariableName(child)).p(".size()"); } else { scount++; } } else { scount++; } } if (0 < scount) { printer.p(" + ").p(scount); } printer.pln(").").indentMore(); // Emit the code to add the children. for (int i=0; i<size; i++) { Object o = n.get(i); printer.buffer(); if (o instanceof Node) { Node child = (Node)o; if (isPatternVariable(child) && isListType(getVariableType(child))) { printer.p("addAll("); } else { printer.p("add("); } } else { printer.p("add("); } printer.p(toLiteral(o, vars.get(i))).p(')'); if (i<size-1) { printer.p('.'); } else { printer.p(';'); } printer.fitMore(); } printer.pln(); } else { // A node with a static number of children. printer.p(", "); if (GNode.MAX_FIXED < size) printer.p(size).pln(").").indentMore(); for (int i=0; i<size; i++) { printer.buffer(); if (GNode.MAX_FIXED < size) printer.p("add("); printer.p(toLiteral(n.get(i), vars.get(i))); if (GNode.MAX_FIXED < size) printer.p(')'); if (i<size-1) { if (GNode.MAX_FIXED < size) { printer.p('.'); } else { printer.p(", "); } } else { if (GNode.MAX_FIXED < size) { printer.p(';'); } else { printer.p(");"); } } printer.fitMore(); } printer.pln(); } return result; } }