Home | History | Annotate | Download | only in compiler
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.clearsilver.jsilver.compiler;
     18 
     19 import com.google.clearsilver.jsilver.data.TypeConverter;
     20 
     21 import java.io.PrintWriter;
     22 import java.io.StringWriter;
     23 
     24 /**
     25  * Represents a node of a Java expression.
     26  *
     27  * This class contains static helper methods for common types of expressions, or you can just create
     28  * your own subclass.
     29  */
     30 public abstract class JavaExpression {
     31 
     32   /**
     33    * Simple type enumeration to allow us to compare the return types of expressions easily and cast
     34    * expressions nicely.
     35    */
     36   public enum Type {
     37     STRING("String") {
     38       @Override
     39       protected JavaExpression cast(JavaExpression expression) {
     40         if (expression.getType() == VAR_NAME) {
     41           expression = expression.cast(DATA);
     42         }
     43         return call(Type.STRING, "asString", expression);
     44       }
     45     },
     46     INT("int") {
     47       @Override
     48       protected JavaExpression cast(JavaExpression expression) {
     49         if (expression.getType() == VAR_NAME) {
     50           expression = expression.cast(DATA);
     51         }
     52         return call(Type.INT, "asInt", expression);
     53       }
     54     },
     55     BOOLEAN("boolean") {
     56       @Override
     57       protected JavaExpression cast(JavaExpression expression) {
     58         if (expression.getType() == VAR_NAME) {
     59           expression = expression.cast(DATA);
     60         }
     61         return call(Type.BOOLEAN, "asBoolean", expression);
     62       }
     63     },
     64     VALUE("Value") {
     65       @Override
     66       protected JavaExpression cast(JavaExpression expression) {
     67         if (expression.getType() == VAR_NAME) {
     68           return call(Type.VALUE, "asVariableValue", expression, TemplateTranslator.DATA_CONTEXT);
     69         } else {
     70           return call(Type.VALUE, "asValue", expression);
     71         }
     72       }
     73     },
     74     DATA("Data") {
     75       @Override
     76       protected JavaExpression cast(JavaExpression expression) {
     77         if (expression.getType() == VAR_NAME) {
     78           return callFindVariable(expression, false);
     79         } else {
     80           throw new JSilverCompilationException("Cannot cast to 'Data' for expression:\n"
     81               + expression.toString());
     82         }
     83       }
     84     },
     85     // This is a string that represents the name of a Data path.
     86     VAR_NAME("String") {
     87       @Override
     88       protected JavaExpression cast(JavaExpression expression) {
     89         final JavaExpression stringExpr = expression.cast(Type.STRING);
     90         return new JavaExpression(Type.VAR_NAME) {
     91           public void write(PrintWriter out) {
     92             stringExpr.write(out);
     93           }
     94         };
     95       }
     96     },
     97     // This is a special type because we only cast from DataContext, never to it.
     98     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