001: /*
002: * xtc - The eXTensible Compiler
003: * Copyright (C) 2007 Robert Grimm
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License
007: * version 2 as published by the Free Software Foundation.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
017: * USA.
018: */
019: package xtc.tree;
020:
021: import java.util.ArrayList;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import xtc.util.Utilities;
028:
029: /**
030: * Visitor to convert trees of generic nodes into methods that
031: * programmatically create the trees. Trees may contain generic nodes
032: * that represent pattern variables. During transduction, pattern
033: * variable are replaced with method arguments of the same names. In
034: * general, a variable represents a single child of a newly created
035: * generic node. However, if the variable's type is
036: * <code>List<T></code>, all of the list's elements are directly
037: * added as children to the newly created generic node. Any node
038: * representing a pattern variable must have exactly one child that is
039: * a string representing the variable's name.
040: *
041: * @author Robert Grimm
042: * @version $Revision: 1.5 $
043: */
044: public class Transducer extends Visitor {
045:
046: /** The printer. */
047: protected final Printer printer;
048:
049: /** The mapping from pattern variable names to their types. */
050: protected final Map<String, String> variables;
051:
052: /** The temporary variable count. */
053: protected int varcount;
054:
055: /**
056: * Create a new transducer. The newly created transducer treats all
057: * trees as literals, without pattern variables.
058: *
059: * @param printer The printer.
060: */
061: public Transducer(Printer printer) {
062: this (printer, new HashMap<String, String>());
063: }
064:
065: /**
066: * Create a new transducer. The mapping from pattern variable names
067: * to types may be empty (but not <code>null</code>) to indicate
068: * that all tree nodes are literal, i.e., do not contain any "holes"
069: * that are filled during creation.
070: *
071: * @param printer The printer.
072: * @param variables The mapping from node names representing pattern
073: * variables to their values' types.
074: */
075: public Transducer(Printer printer, Map<String, String> variables) {
076: this .printer = printer;
077: this .variables = variables;
078: }
079:
080: /**
081: * Determine whether the specified node represents a pattern variable.
082: *
083: * @param n The node.
084: * @return <code>true</code> if it represents a pattern variable.
085: */
086: public boolean isPatternVariable(Node n) {
087: n = n.strip();
088: return null == n ? false : n.isGeneric()
089: && variables.containsKey(n.getName());
090: }
091:
092: /**
093: * Ensure that the specified node is a well-formed pattern variable.
094: *
095: * @param n The node.
096: * @return The node as a pattern variable.
097: * @throws IllegalArgumentException Signals that the specified node
098: * is not a pattern variable or is a malformed pattern variable.
099: */
100: public GNode toPatternVariable(Node n) {
101: n = n.strip();
102: if ((null == n) || (!n.isGeneric())
103: || (!variables.containsKey(n.getName()))) {
104: throw new IllegalArgumentException(
105: "Not a pattern variable: " + n);
106: } else if ((1 != n.size()) || (!Token.test(n.get(0)))) {
107: throw new IllegalArgumentException(
108: "Malformed pattern variable: " + n);
109: }
110: return (GNode) n;
111: }
112:
113: /**
114: * Get the pattern variable's name.
115: *
116: * @param n The node.
117: * @return The corresponding variable name.
118: * @throws IllegalArgumentException Signals that the node does not
119: * represent a pattern variable or is malformed.
120: */
121: public String getVariableName(Node n) {
122: return Token.cast(toPatternVariable(n).get(0));
123: }
124:
125: /**
126: * Get the pattern variable's type.
127: *
128: * @param n The node.
129: * @return The corresponding variable type.
130: * @throws IllegalArgumentException Signals that the node does not
131: * represent a pattern variable or is malformed.
132: */
133: public String getVariableType(Node n) {
134: return variables.get(toPatternVariable(n).getName());
135: }
136:
137: /**
138: * Determine whether the specified type is a list type.
139: *
140: * @param t The type.
141: * @return <code>true</code> if the specified type is a list type.
142: */
143: public boolean isListType(String t) {
144: return t.equals("List") || t.startsWith("List<");
145: }
146:
147: /**
148: * Convert the specified object to a literal. If the specified
149: * variable name is <code>null</code> and the object is a node, the
150: * object must be a pattern variable.
151: *
152: * @param o The object.
153: * @param var The variable name for nodes that are not pattern
154: * variables.
155: * @return The corresponding literal.
156: * @throws IllegalArgumentException Signals that the specified object
157: * is not recognized.
158: */
159: public String toLiteral(Object o, String var) {
160: if (null == o) {
161: return "null";
162: } else if (Token.test(o)) {
163: return '"' + Utilities.escape(Token.cast(o),
164: Utilities.JAVA_ESCAPES) + '"';
165: } else if (o instanceof Node) {
166: return null == var ? getVariableName((Node) o) : var;
167: } else if (o instanceof Boolean) {
168: return ((Boolean) o).booleanValue() ? "true" : "false";
169: } else if (o instanceof Double) {
170: return ((Double) o).doubleValue() + "D";
171: } else if (o instanceof Float) {
172: return ((Float) o).floatValue() + "F";
173: } else if (o instanceof Long) {
174: return ((Long) o).longValue() + "L";
175: } else if (o instanceof Integer) {
176: return o.toString();
177: } else if (o instanceof Short) {
178: return "Short.valueOf(" + ((Short) o).shortValue() + ')';
179: } else if (o instanceof Byte) {
180: return "Byte.valueOf(" + ((Byte) o).byteValue() + ')';
181: } else if (o instanceof Character) {
182: return "'" + ((Character) o).charValue() + "'";
183: } else {
184: throw new IllegalArgumentException("Unrecognized value: "
185: + o);
186: }
187: }
188:
189: /**
190: * Process the specified node.
191: *
192: * @param method The method name.
193: * @param n The node.
194: * @throws IllegalArgumentException Signals that the specified is
195: * not a generic node or that it is a pattern variable.
196: */
197: public void process(String method, Node n) {
198: // Perform consistency checks: neither node nor method may be null.
199: if (null == method) {
200: throw new NullPointerException("Null method name");
201: } else if (null == n) {
202: throw new NullPointerException("Null node");
203: }
204:
205: n = n.strip();
206: if (!GNode.test(n)) {
207: throw new IllegalArgumentException(
208: "Not an (annotated) generic node: " + n);
209: } else if (isPatternVariable(n)) {
210: throw new IllegalArgumentException("Pattern variable: " + n);
211: }
212:
213: // Reset this visitor's state.
214: varcount = 0;
215:
216: // Declare the list of pattern holes and their types.
217: final List<String> holes = new ArrayList<String>();
218: final List<String> types = new ArrayList<String>();
219:
220: // Fill in the list of pattern holes and their types.
221: new Visitor() {
222: @SuppressWarnings("unused")
223: public void visit(GNode n) {
224: if (isPatternVariable(n)) {
225: String name = getVariableName(n);
226: String type = getVariableType(n);
227: int idx = holes.indexOf(name);
228: if (-1 == idx) {
229: holes.add(name);
230: types.add(type);
231: } else if (!types.get(idx).equals(type)) {
232: types.set(idx, "Object");
233: }
234: } else {
235: for (Object o : n) {
236: if (o instanceof Node) {
237: dispatch((Node) o);
238: }
239: }
240: }
241: }
242: }.dispatch(n);
243:
244: // Emit the method header.
245: String desc = Utilities.split(n.getName(), ' ');
246: printer.indent().pln("/**");
247: printer.indent().p(" * Create ").p(Utilities.toArticle(desc))
248: .p(' ').p(desc).pln('.');
249: printer.indent().pln(" *");
250: for (String h : holes) {
251: printer.indent().p(" * @param ").p(h).p(" The ").p(h).pln(
252: '.');
253: }
254: printer.indent().pln(" * @return The generic node.");
255: printer.indent().pln(" */");
256: printer.indent().p("public Node ").p(method).p('(');
257: final int align = printer.column();
258: boolean first = true;
259: Iterator<String> iterH = holes.iterator();
260: Iterator<String> iterT = types.iterator();
261: while (iterH.hasNext()) {
262: if (!first) {
263: printer.buffer();
264: }
265:
266: printer.p(iterT.next()).p(' ').p(iterH.next());
267:
268: if (iterH.hasNext()) {
269: printer.p(", ");
270: }
271:
272: if (first) {
273: first = false;
274: } else {
275: printer.fit(align);
276: }
277: }
278: printer.pln(") {").incr();
279:
280: // Emit the method body.
281: String result = (String) dispatch(n);
282: if (null == result)
283: result = getVariableName(n);
284: printer.indent().p("return ").p(result).pln(';');
285:
286: // Close the method body.
287: printer.decr().indent().pln('}');
288: }
289:
290: /** Visit the specified generic node. */
291: public String visit(GNode n) {
292: // Pattern variables do not require any code.
293: if (isPatternVariable(n))
294: return null;
295:
296: // Iterate over the children, recursively processing any nodes.
297: // Also, determine whether any of the pattern variables used for
298: // this node has a list type.
299: final int size = n.size();
300: List<String> vars = null;
301: boolean hasList = false;
302:
303: if (0 < size)
304: vars = new ArrayList<String>(size);
305:
306: for (Object o : n) {
307: if (o instanceof Node) {
308: Node child = (Node) o;
309:
310: if (isPatternVariable(child)) {
311: vars.add(null);
312: if (isListType(getVariableType(child))) {
313: hasList = true;
314: }
315:
316: } else {
317: vars.add((String) dispatch(child));
318: }
319:
320: } else {
321: vars.add(null);
322: }
323: }
324:
325: // Emit code for declaring and creating the node.
326: String result = "v$" + (++varcount);
327:
328: printer.indent().p("Node ").p(result).p(" = GNode.create(\"")
329: .p(n.getName()).p("\"");
330:
331: // Emit the code for the node's children.
332: if (0 == size) {
333: // A node with no children.
334: printer.pln(", false);");
335:
336: } else if (hasList) {
337: // A node with a dynamic number of children.
338: printer.p(", ");
339:
340: // Emit the size expression.
341: int scount = 0;
342: boolean seenList = false;
343:
344: for (int i = 0; i < size; i++) {
345: Object o = n.get(i);
346:
347: if (o instanceof Node) {
348: Node child = (Node) o;
349:
350: if (isPatternVariable(child)
351: && isListType(getVariableType(child))) {
352:
353: if (seenList) {
354: printer.p(" + ");
355: } else {
356: seenList = true;
357: }
358: printer.p(getVariableName(child)).p(".size()");
359:
360: } else {
361: scount++;
362: }
363:
364: } else {
365: scount++;
366: }
367: }
368:
369: if (0 < scount) {
370: printer.p(" + ").p(scount);
371: }
372: printer.pln(").").indentMore();
373:
374: // Emit the code to add the children.
375: for (int i = 0; i < size; i++) {
376: Object o = n.get(i);
377:
378: printer.buffer();
379: if (o instanceof Node) {
380: Node child = (Node) o;
381:
382: if (isPatternVariable(child)
383: && isListType(getVariableType(child))) {
384: printer.p("addAll(");
385: } else {
386: printer.p("add(");
387: }
388: } else {
389: printer.p("add(");
390: }
391: printer.p(toLiteral(o, vars.get(i))).p(')');
392: if (i < size - 1) {
393: printer.p('.');
394: } else {
395: printer.p(';');
396: }
397: printer.fitMore();
398: }
399: printer.pln();
400:
401: } else {
402: // A node with a static number of children.
403: printer.p(", ");
404: if (GNode.MAX_FIXED < size)
405: printer.p(size).pln(").").indentMore();
406:
407: for (int i = 0; i < size; i++) {
408: printer.buffer();
409: if (GNode.MAX_FIXED < size)
410: printer.p("add(");
411: printer.p(toLiteral(n.get(i), vars.get(i)));
412: if (GNode.MAX_FIXED < size)
413: printer.p(')');
414:
415: if (i < size - 1) {
416: if (GNode.MAX_FIXED < size) {
417: printer.p('.');
418: } else {
419: printer.p(", ");
420: }
421: } else {
422: if (GNode.MAX_FIXED < size) {
423: printer.p(';');
424: } else {
425: printer.p(");");
426: }
427: }
428: printer.fitMore();
429: }
430:
431: printer.pln();
432: }
433:
434: return result;
435: }
436:
437: }
|