JavaExpression.java revision 56ed4167b942ec265f9cee70ac4d71d10b3835ce
103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)/*
203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * Copyright (C) 2010 Google Inc.
303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *
403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * Licensed under the Apache License, Version 2.0 (the "License");
503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * you may not use this file except in compliance with the License.
603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * You may obtain a copy of the License at
703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *
803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * http://www.apache.org/licenses/LICENSE-2.0
903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *
1003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * Unless required by applicable law or agreed to in writing, software
1103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS,
1203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * See the License for the specific language governing permissions and
1403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * limitations under the License.
1503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) */
1603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
1703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)package com.google.clearsilver.jsilver.compiler;
1803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
1903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)import com.google.clearsilver.jsilver.data.TypeConverter;
2003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
2103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)import java.io.PrintWriter;
2203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)import java.io.StringWriter;
2303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
2403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)/**
2503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * Represents a node of a Java expression.
2603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *
2703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * This class contains static helper methods for common types of expressions, or you can just create
2803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * your own subclass.
2903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) */
3003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)public abstract class JavaExpression {
3103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
3203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  /**
3303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)   * Simple type enumeration to allow us to compare the return types of expressions easily and cast
3403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)   * expressions nicely.
3503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)   */
3603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  public enum Type {
3703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    STRING("String") {
3803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      @Override
3903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      protected JavaExpression cast(JavaExpression expression) {
4003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        if (expression.getType() == VAR_NAME) {
4103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          expression = expression.cast(DATA);
4203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        }
4303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        return call(Type.STRING, "asString", expression);
4403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      }
4503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    },
4603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    INT("int") {
4703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      @Override
4803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      protected JavaExpression cast(JavaExpression expression) {
4903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        if (expression.getType() == VAR_NAME) {
5003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          expression = expression.cast(DATA);
5103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        }
5203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        return call(Type.INT, "asInt", expression);
5303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      }
5403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    },
5503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    BOOLEAN("boolean") {
5603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      @Override
5703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      protected JavaExpression cast(JavaExpression expression) {
5803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        if (expression.getType() == VAR_NAME) {
5903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          expression = expression.cast(DATA);
6003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        }
6103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        return call(Type.BOOLEAN, "asBoolean", expression);
6203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      }
6303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    },
6403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    VALUE("Value") {
6503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      @Override
6603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      protected JavaExpression cast(JavaExpression expression) {
6703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        if (expression.getType() == VAR_NAME) {
6803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          return call(Type.VALUE, "asVariableValue", expression, TemplateTranslator.DATA_CONTEXT);
6903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        } else {
7003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          return call(Type.VALUE, "asValue", expression);
7103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        }
7203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      }
7303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    },
7403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    DATA("Data") {
7503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      @Override
7603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      protected JavaExpression cast(JavaExpression expression) {
7703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        if (expression.getType() == VAR_NAME) {
7803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          return callFindVariable(expression, false);
7903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        } else {
8003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          throw new JSilverCompilationException("Cannot cast to 'Data' for expression:\n"
8103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)              + expression.toString());
8203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        }
8303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      }
8403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    },
8503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    // This is a string that represents the name of a Data path.
8603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    VAR_NAME("String") {
8703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      @Override
8803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      protected JavaExpression cast(JavaExpression expression) {
8903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        final JavaExpression stringExpr = expression.cast(Type.STRING);
9003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        return new JavaExpression(Type.VAR_NAME) {
9103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          public void write(PrintWriter out) {
9203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)            stringExpr.write(out);
9303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          }
9403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        };
9503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      }
9603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    },
9703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    // This is a special type because we only cast from DataContext, never to it.
9803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    DATA_CONTEXT("DataContext") {
99      @Override
100      protected JavaExpression cast(JavaExpression expression) {
101        throw new JSilverCompilationException("Cannot cast to 'DataContext' for expression:\n"
102            + expression.toString());
103      }
104    },
105    // This is a special type because we only cast from Data, never to it.
106    MACRO("Macro") {
107      @Override
108      protected JavaExpression cast(JavaExpression expression) {
109        throw new JSilverCompilationException("Cannot cast to 'Macro' for expression:\n"
110            + expression.toString());
111      }
112    },
113    // Use this type for JavaExpressions that have no type (such as method
114    // calls with no return value). Wraps the input expression with a
115    // JavaExpression of Type VOID.
116    VOID("Void") {
117      @Override
118      protected JavaExpression cast(final JavaExpression expression) {
119        return new JavaExpression(Type.VOID) {
120          @Override
121          public void write(PrintWriter out) {
122            expression.write(out);
123          }
124        };
125      }
126    };
127
128    /** Useful constant for unknown types */
129    public static final Type UNKNOWN = null;
130
131    /**
132     * The Java literal representing the type (e.g. "int", "boolean", "Value")
133     */
134    public final String symbol;
135
136    /**
137     * Unconditionally casts the given expression to the type. This should only be called after it
138     * has been determined that the destination type is not the same as the expression type.
139     */
140    protected abstract JavaExpression cast(JavaExpression expression);
141
142    private Type(String symbol) {
143      this.symbol = symbol;
144    }
145  }
146
147  private final Type type;
148
149  /**
150   * Creates a typed expression. Typed expressions allow for greater optimization by avoiding
151   * unnecessary casting operations.
152   *
153   * @param type the Type of the expression. Must be from the enum above and represent a primitive
154   *        or a Class name or void.
155   */
156  public JavaExpression(Type type) {
157    this.type = type;
158  }
159
160  /**
161   * Cast this expression to the destination type (possibly a no-op)
162   */
163  public JavaExpression cast(Type destType) {
164    return (type != destType) ? destType.cast(this) : this;
165  }
166
167  /**
168   * Gets the type of this expression (or {@code null} if unknown).
169   */
170  public Type getType() {
171    return type;
172  }
173
174  /**
175   * Implementations use this to output the expression as Java code.
176   */
177  public abstract void write(PrintWriter out);
178
179  @Override
180  public String toString() {
181    StringWriter out = new StringWriter();
182    write(new PrintWriter(out));
183    return out.toString();
184  }
185
186  /**
187   * An untyped method call (e.g. doStuff(x, "y")).
188   */
189  public static JavaExpression call(final String method, final JavaExpression... params) {
190    return call(null, method, params);
191  }
192
193  /**
194   * A typed method call (e.g. doStuff(x, "y")).
195   */
196  public static JavaExpression call(Type type, final String method, final JavaExpression... params) {
197    return new JavaExpression(type) {
198      @Override
199      public void write(PrintWriter out) {
200        JavaSourceWriter.writeJavaSymbol(out, method);
201        out.append('(');
202        boolean seenAnyParams = false;
203        for (JavaExpression param : params) {
204          if (seenAnyParams) {
205            out.append(", ");
206          } else {
207            seenAnyParams = true;
208          }
209          param.write(out);
210        }
211        out.append(')');
212      }
213    };
214  }
215
216  /**
217   * An untyped method call on an instance (e.g. thingy.doStuff(x, "y")). We assume it returns VOID
218   * and thus there is no return value.
219   */
220  public static JavaExpression callOn(final JavaExpression instance, final String method,
221      final JavaExpression... params) {
222    return callOn(Type.VOID, instance, method, params);
223  }
224
225  /**
226   * A typed method call on an instance (e.g. thingy.doStuff(x, "y")).
227   */
228  public static JavaExpression callOn(Type type, final JavaExpression instance,
229      final String method, final JavaExpression... params) {
230    return new JavaExpression(type) {
231      @Override
232      public void write(PrintWriter out) {
233        instance.write(out);
234        out.append('.');
235        call(method, params).write(out);
236      }
237    };
238  }
239
240  /**
241   * A Java string (e.g. "hello\nworld").
242   */
243  public static JavaExpression string(String value) {
244    return new StringExpression(value);
245  }
246
247  public static class StringExpression extends JavaExpression {
248
249    private final String value;
250
251    public StringExpression(String value) {
252      super(Type.STRING);
253      this.value = value;
254    }
255
256    public String getValue() {
257      return value;
258    }
259
260    @Override
261    public void write(PrintWriter out) {
262      // TODO: This is not production ready yet - needs more
263      // thorough escaping mechanism.
264      out.append('"');
265      char[] chars = value.toCharArray();
266      for (char c : chars) {
267        switch (c) {
268          // Single quote (') does not need to be escaped as it's in a
269          // double-quoted (") string.
270          case '\n':
271            out.append("\\n");
272            break;
273          case '\r':
274            out.append("\\r");
275            break;
276          case '\t':
277            out.append("\\t");
278            break;
279          case '\\':
280            out.append("\\\\");
281            break;
282          case '"':
283            out.append("\\\"");
284            break;
285          case '\b':
286            out.append("\\b");
287            break;
288          case '\f':
289            out.append("\\f");
290            break;
291          default:
292            out.append(c);
293        }
294      }
295      out.append('"');
296    }
297  }
298
299  /**
300   * A JavaExpression to represent boolean literal values ('true' or 'false').
301   */
302  public static class BooleanLiteralExpression extends JavaExpression {
303
304    private final boolean value;
305
306    public static final BooleanLiteralExpression FALSE = new BooleanLiteralExpression(false);
307    public static final BooleanLiteralExpression TRUE = new BooleanLiteralExpression(true);
308
309    private BooleanLiteralExpression(boolean value) {
310      super(Type.BOOLEAN);
311      this.value = value;
312    }
313
314    public boolean getValue() {
315      return value;
316    }
317
318    @Override
319    public void write(PrintWriter out) {
320      out.append(String.valueOf(value));
321    }
322  }
323
324  /**
325   * An integer.
326   */
327  public static JavaExpression integer(String value) {
328    // Just parse it to to check that it is valid
329    TypeConverter.parseNumber(value);
330    return literal(Type.INT, value);
331  }
332
333  /**
334   * An integer.
335   */
336  public static JavaExpression integer(int value) {
337    return literal(Type.INT, String.valueOf(value));
338  }
339
340  /**
341   * A boolean
342   */
343  public static JavaExpression bool(boolean value) {
344    return literal(Type.BOOLEAN, value ? "true" : "false");
345  }
346
347  /**
348   * An untyped symbol (e.g. myVariable).
349   */
350  public static JavaExpression symbol(final String value) {
351    return new JavaExpression(Type.UNKNOWN) {
352      @Override
353      public void write(PrintWriter out) {
354        JavaSourceWriter.writeJavaSymbol(out, value);
355      }
356    };
357  }
358
359  /**
360   * A typed symbol (e.g. myVariable).
361   */
362  public static JavaExpression symbol(Type type, final String value) {
363    return new JavaExpression(type) {
364      @Override
365      public void write(PrintWriter out) {
366        JavaSourceWriter.writeJavaSymbol(out, value);
367      }
368    };
369  }
370
371  public static JavaExpression macro(final String value) {
372    return symbol(Type.MACRO, value);
373  }
374
375  /**
376   * A typed assignment (e.g. stuff = doSomething()).
377   */
378  public static JavaExpression assign(Type type, final String name, final JavaExpression value) {
379    return new JavaExpression(type) {
380      @Override
381      public void write(PrintWriter out) {
382        JavaSourceWriter.writeJavaSymbol(out, name);
383        out.append(" = ");
384        value.write(out);
385      }
386    };
387  }
388
389  /**
390   * A typed assignment with declaration (e.g. String stuff = doSomething()). Use this in preference
391   * when declaring variables from typed expressions.
392   */
393  public static JavaExpression declare(final Type type, final String name,
394      final JavaExpression value) {
395    return new JavaExpression(type) {
396      @Override
397      public void write(PrintWriter out) {
398        JavaSourceWriter.writeJavaSymbol(out, type.symbol);
399        out.append(' ');
400        assign(type, name, value).write(out);
401      }
402    };
403  }
404
405  /**
406   * An infix expression (e.g. (a + b) ).
407   */
408  public static JavaExpression infix(Type type, final String operator, final JavaExpression left,
409      final JavaExpression right) {
410    return new JavaExpression(type) {
411      @Override
412      public void write(PrintWriter out) {
413        out.append("(");
414        left.write(out);
415        out.append(" ").append(operator).append(" ");
416        right.write(out);
417        out.append(")");
418      }
419    };
420  }
421
422  /**
423   * An prefix expression (e.g. (-a) ).
424   */
425  public static JavaExpression prefix(Type type, final String operator,
426      final JavaExpression expression) {
427    return new JavaExpression(type) {
428      @Override
429      public void write(PrintWriter out) {
430        out.append("(").append(operator);
431        expression.write(out);
432        out.append(")");
433      }
434    };
435  }
436
437  /**
438   * A three term inline if expression (e.g. (a ? b : c) ).
439   */
440  public static JavaExpression inlineIf(Type type, final JavaExpression query,
441      final JavaExpression trueExp, final JavaExpression falseExp) {
442    if (query.getType() != Type.BOOLEAN) {
443      throw new IllegalArgumentException("Expect BOOLEAN expression");
444    }
445    return new JavaExpression(type) {
446      @Override
447      public void write(PrintWriter out) {
448        out.append("(");
449        query.write(out);
450        out.append(" ? ");
451        trueExp.write(out);
452        out.append(" : ");
453        falseExp.write(out);
454        out.append(")");
455      }
456    };
457  }
458
459  /**
460   * An increment statement (e.g. a += b). The difference with infix is that this does not wrap the
461   * expression in parentheses as that is not a valid statement.
462   */
463  public static JavaExpression increment(Type type, final JavaExpression accumulator,
464      final JavaExpression incr) {
465    return new JavaExpression(type) {
466      @Override
467      public void write(PrintWriter out) {
468        accumulator.write(out);
469        out.append(" += ");
470        incr.write(out);
471      }
472    };
473  }
474
475  /**
476   * A literal expression (e.g. anything!). This method injects whatever string it is given into the
477   * Java code - use only in cases where there can be no ambiguity about how the string could be
478   * interpreted by the compiler.
479   */
480  public static JavaExpression literal(Type type, final String value) {
481    return new JavaExpression(type) {
482      @Override
483      public void write(PrintWriter out) {
484        out.append(value);
485      }
486    };
487  }
488
489  public static JavaExpression callFindVariable(JavaExpression expression, boolean create) {
490    if (expression.getType() != Type.VAR_NAME) {
491      throw new IllegalArgumentException("Expect VAR_NAME expression");
492    }
493    return callOn(Type.DATA, TemplateTranslator.DATA_CONTEXT, "findVariable", expression,
494        JavaExpression.bool(create));
495  }
496}
497