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.autoescape.EscapeMode;
     20 import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
     21 import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type;
     22 import static com.google.clearsilver.jsilver.compiler.JavaExpression.call;
     23 import static com.google.clearsilver.jsilver.compiler.JavaExpression.callFindVariable;
     24 import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
     25 import static com.google.clearsilver.jsilver.compiler.JavaExpression.declare;
     26 import static com.google.clearsilver.jsilver.compiler.JavaExpression.increment;
     27 import static com.google.clearsilver.jsilver.compiler.JavaExpression.infix;
     28 import static com.google.clearsilver.jsilver.compiler.JavaExpression.inlineIf;
     29 import static com.google.clearsilver.jsilver.compiler.JavaExpression.integer;
     30 import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal;
     31 import static com.google.clearsilver.jsilver.compiler.JavaExpression.macro;
     32 import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
     33 import static com.google.clearsilver.jsilver.compiler.JavaExpression.symbol;
     34 import com.google.clearsilver.jsilver.data.Data;
     35 import com.google.clearsilver.jsilver.data.DataContext;
     36 import com.google.clearsilver.jsilver.functions.Function;
     37 import com.google.clearsilver.jsilver.functions.FunctionExecutor;
     38 import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
     39 import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
     40 import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand;
     41 import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
     42 import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
     43 import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
     44 import com.google.clearsilver.jsilver.syntax.node.AEachCommand;
     45 import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
     46 import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
     47 import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
     48 import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
     49 import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
     50 import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
     51 import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
     52 import com.google.clearsilver.jsilver.syntax.node.ALoopCommand;
     53 import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand;
     54 import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand;
     55 import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
     56 import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
     57 import com.google.clearsilver.jsilver.syntax.node.ANoopCommand;
     58 import com.google.clearsilver.jsilver.syntax.node.ASetCommand;
     59 import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
     60 import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
     61 import com.google.clearsilver.jsilver.syntax.node.AWithCommand;
     62 import com.google.clearsilver.jsilver.syntax.node.PCommand;
     63 import com.google.clearsilver.jsilver.syntax.node.PExpression;
     64 import com.google.clearsilver.jsilver.syntax.node.PPosition;
     65 import com.google.clearsilver.jsilver.syntax.node.PVariable;
     66 import com.google.clearsilver.jsilver.syntax.node.Start;
     67 import com.google.clearsilver.jsilver.syntax.node.TCsOpen;
     68 import com.google.clearsilver.jsilver.syntax.node.TWord;
     69 import com.google.clearsilver.jsilver.template.Macro;
     70 import com.google.clearsilver.jsilver.template.RenderingContext;
     71 import com.google.clearsilver.jsilver.template.Template;
     72 import com.google.clearsilver.jsilver.values.Value;
     73 
     74 import java.io.IOException;
     75 import java.io.Writer;
     76 import java.lang.reflect.Method;
     77 import java.util.HashMap;
     78 import java.util.LinkedList;
     79 import java.util.Map;
     80 import java.util.Queue;
     81 
     82 /**
     83  * Translates a JSilver AST into compilable Java code. This executes much faster than the
     84  * interpreter.
     85  *
     86  * @see TemplateCompiler
     87  */
     88 public class TemplateTranslator extends DepthFirstAdapter {
     89 
     90   // Root data
     91   public static final JavaExpression DATA = symbol(Type.DATA, "data");
     92   // RenderingContext
     93   public static final JavaExpression CONTEXT = symbol("context");
     94   // DataContext
     95   public static final JavaExpression DATA_CONTEXT = symbol(Type.DATA_CONTEXT, "dataContext");
     96   public static final JavaExpression NULL = symbol("null");
     97   // Accessed from macros as well.
     98   public static final JavaExpression RESOURCE_LOADER = callOn(CONTEXT, "getResourceLoader");
     99   public static final JavaExpression TEMPLATE_LOADER = symbol("getTemplateLoader()");
    100   public static final JavaExpression THIS_TEMPLATE = symbol("this");
    101 
    102   private final JavaSourceWriter java;
    103 
    104   private final String packageName;
    105   private final String className;
    106 
    107   private final ExpressionTranslator expressionTranslator = new ExpressionTranslator();
    108   private final VariableTranslator variableTranslator =
    109       new VariableTranslator(expressionTranslator);
    110   private final EscapingEvaluator escapingEvaluator = new EscapingEvaluator(variableTranslator);
    111 
    112   private static final Method RENDER_METHOD;
    113 
    114   private int tempVariable = 0;
    115   /**
    116    * Used to determine the escaping to apply before displaying a variable. If propagateEscapeStatus
    117    * is enabled, string and numeric literals are not escaped, nor is the output of an escaping
    118    * function. If not, any expression that contains an escaping function is not escaped. This
    119    * maintains compatibility with the way ClearSilver works.
    120    */
    121   private boolean propagateEscapeStatus;
    122 
    123   /**
    124    * Holds Macro information used while generating code.
    125    */
    126   private static class MacroInfo {
    127     /**
    128      * JavaExpression used for outputting the static Macro variable name.
    129      */
    130     JavaExpression symbol;
    131 
    132     /**
    133      * Parser node for the definition. Stored for evaluation after main render method is output.
    134      */
    135     ADefCommand defNode;
    136   }
    137 
    138   /**
    139    * Map of macro names to definition nodes and java expressions used to refer to them.
    140    */
    141   private final Map<String, MacroInfo> macroMap = new HashMap<String, MacroInfo>();
    142 
    143   /**
    144    * Used to iterate through list of macros. We can't rely on Map's iterator because we may be
    145    * adding to the map as we iterate through the values() list and that would throw a
    146    * ConcurrentModificationException.
    147    */
    148   private final Queue<MacroInfo> macroQueue = new LinkedList<MacroInfo>();
    149 
    150   /**
    151    * Creates a MacroInfo object and adds it to the data structures. Also outputs statement to
    152    * register the macro.
    153    *
    154    * @param name name of the macro as defined in the template.
    155    * @param symbol static variable name of the macro definition.
    156    * @param defNode parser node holding the macro definition to be evaluated later.
    157    */
    158   private void addMacro(String name, JavaExpression symbol, ADefCommand defNode) {
    159     if (macroMap.get(name) != null) {
    160       // TODO: This macro is already defined. Should throw an error.
    161     }
    162     MacroInfo info = new MacroInfo();
    163     info.symbol = symbol;
    164     info.defNode = defNode;
    165     macroMap.put(name, info);
    166     macroQueue.add(info);
    167 
    168     // Register the macro.
    169     java.writeStatement(callOn(CONTEXT, "registerMacro", string(name), symbol));
    170   }
    171 
    172   static {
    173     try {
    174       RENDER_METHOD = Template.class.getMethod("render", RenderingContext.class);
    175     } catch (NoSuchMethodException e) {
    176       throw new Error("Cannot find CompiledTemplate.render() method! " + "Has signature changed?",
    177           e);
    178     }
    179   }
    180 
    181   public TemplateTranslator(String packageName, String className, Writer output,
    182       boolean propagateEscapeStatus) {
    183     this.packageName = packageName;
    184     this.className = className;
    185     java = new JavaSourceWriter(output);
    186     this.propagateEscapeStatus = propagateEscapeStatus;
    187   }
    188 
    189   @Override
    190   public void caseStart(Start node) {
    191     java.writeComment("This class is autogenerated by JSilver. Do not edit.");
    192     java.writePackage(packageName);
    193     java.writeImports(BaseCompiledTemplate.class, Template.class, Macro.class,
    194         RenderingContext.class, Data.class, DataContext.class, Function.class,
    195         FunctionExecutor.class, Value.class, EscapeMode.class, IOException.class);
    196     java.startClass(className, BaseCompiledTemplate.class.getSimpleName());
    197 
    198     // Implement render() method.
    199     java.startMethod(RENDER_METHOD, "context");
    200     java
    201         .writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT, "getDataContext")));
    202     java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE));
    203     super.caseStart(node); // Walk template AST.
    204     java.writeStatement(callOn(CONTEXT, "popExecutionContext"));
    205     java.endMethod();
    206 
    207     // The macros have to be defined outside of the render method.
    208     // (Well actually they *could* be defined inline as anon classes, but it
    209     // would make the generated code quite hard to understand).
    210     MacroTransformer macroTransformer = new MacroTransformer();
    211 
    212     while (!macroQueue.isEmpty()) {
    213       MacroInfo curr = macroQueue.remove();
    214       macroTransformer.parseDefNode(curr.symbol, curr.defNode);
    215     }
    216 
    217     java.endClass();
    218   }
    219 
    220   /**
    221    * Chunk of data (i.e. not a CS command).
    222    */
    223   @Override
    224   public void caseADataCommand(ADataCommand node) {
    225     String content = node.getData().getText();
    226     java.writeStatement(callOn(CONTEXT, "writeUnescaped", string(content)));
    227   }
    228 
    229   /**
    230    * &lt;?cs var:blah &gt; expression. Evaluate as string and write output, using default escaping.
    231    */
    232   @Override
    233   public void caseAVarCommand(AVarCommand node) {
    234     capturePosition(node.getPosition());
    235 
    236     String tempVariableName = generateTempVariable("result");
    237     JavaExpression result = symbol(Type.STRING, tempVariableName);
    238     java.writeStatement(declare(Type.STRING, tempVariableName, expressionTranslator
    239         .translateToString(node.getExpression())));
    240 
    241     JavaExpression escaping =
    242         escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus);
    243     writeVariable(result, escaping);
    244   }
    245 
    246   /**
    247    * &lt;?cs uvar:blah &gt; expression. Evaluate as string and write output, but don't escape.
    248    */
    249   @Override
    250   public void caseAUvarCommand(AUvarCommand node) {
    251     capturePosition(node.getPosition());
    252     java.writeStatement(callOn(CONTEXT, "writeUnescaped", expressionTranslator
    253         .translateToString(node.getExpression())));
    254   }
    255 
    256   /**
    257    * &lt;?cs set:x='y' &gt; command.
    258    */
    259   @Override
    260   public void caseASetCommand(ASetCommand node) {
    261     capturePosition(node.getPosition());
    262     String tempVariableName = generateTempVariable("setNode");
    263 
    264     // Data setNode1 = dataContext.findVariable("x", true);
    265     JavaExpression setNode = symbol(Type.DATA, tempVariableName);
    266     java.writeStatement(declare(Type.DATA, tempVariableName, callFindVariable(variableTranslator
    267         .translate(node.getVariable()), true)));
    268     // setNode1.setValue("hello");
    269     java.writeStatement(callOn(setNode, "setValue", expressionTranslator.translateToString(node
    270         .getExpression())));
    271 
    272     if (propagateEscapeStatus) {
    273       // setNode1.setEscapeMode(EscapeMode.ESCAPE_IS_CONSTANT);
    274       java.writeStatement(callOn(setNode, "setEscapeMode", escapingEvaluator.computeEscaping(node
    275           .getExpression(), propagateEscapeStatus)));
    276     }
    277   }
    278 
    279   /**
    280    * &lt;?cs name:blah &gt; command. Writes out the name of the original variable referred to by a
    281    * given node.
    282    */
    283   @Override
    284   public void caseANameCommand(ANameCommand node) {
    285     capturePosition(node.getPosition());
    286     JavaExpression readNode =
    287         callFindVariable(variableTranslator.translate(node.getVariable()), false);
    288     java.writeStatement(callOn(CONTEXT, "writeEscaped", call("getNodeName", readNode)));
    289   }
    290 
    291   /**
    292    * &lt;?cs if:blah &gt; ... &lt;?cs else &gt; ... &lt;?cs /if &gt; command.
    293    */
    294   @Override
    295   public void caseAIfCommand(AIfCommand node) {
    296     capturePosition(node.getPosition());
    297 
    298     java.startIfBlock(expressionTranslator.translateToBoolean(node.getExpression()));
    299     node.getBlock().apply(this);
    300     if (!(node.getOtherwise() instanceof ANoopCommand)) {
    301       java.endIfStartElseBlock();
    302       node.getOtherwise().apply(this);
    303     }
    304     java.endIfBlock();
    305   }
    306 
    307   /**
    308    * &lt;?cs each:x=Stuff &gt; ... &lt;?cs /each &gt; command. Loops over child items of a data
    309    * node.
    310    */
    311   @Override
    312   public void caseAEachCommand(AEachCommand node) {
    313     capturePosition(node.getPosition());
    314 
    315     JavaExpression parent = expressionTranslator.translateToData(node.getExpression());
    316     writeEach(node.getVariable(), parent, node.getCommand());
    317   }
    318 
    319   /**
    320    * &lt;?cs with:x=Something &gt; ... &lt;?cs /with &gt; command. Aliases a value within a specific
    321    * scope.
    322    */
    323   @Override
    324   public void caseAWithCommand(AWithCommand node) {
    325     capturePosition(node.getPosition());
    326 
    327     java.startScopedBlock();
    328     java.writeComment("with:");
    329 
    330     // Extract the value first in case the temp variable has the same name.
    331     JavaExpression value = expressionTranslator.translateUntyped(node.getExpression());
    332     String methodName = null;
    333     if (value.getType() == Type.VAR_NAME) {
    334       String withValueName = generateTempVariable("withValue");
    335       java.writeStatement(declare(Type.STRING, withValueName, value));
    336       value = symbol(Type.VAR_NAME, withValueName);
    337       methodName = "createLocalVariableByPath";
    338 
    339       // We need to check if the variable exists. If not, we skip the with
    340       // call.
    341       java.startIfBlock(JavaExpression.infix(Type.BOOLEAN, "!=", value.cast(Type.DATA), literal(
    342           Type.DATA, "null")));
    343     } else {
    344       // Cast to string so we support numeric or boolean values as well.
    345       value = value.cast(Type.STRING);
    346       methodName = "createLocalVariableByValue";
    347     }
    348 
    349     JavaExpression itemKey = variableTranslator.translate(node.getVariable());
    350 
    351     // Push a new local variable scope for the with local variable
    352     java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
    353 
    354     java.writeStatement(callOn(DATA_CONTEXT, methodName, itemKey, value));
    355     node.getCommand().apply(this);
    356 
    357     // Release the variable scope used by the with statement
    358     java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
    359 
    360     if (value.getType() == Type.VAR_NAME) {
    361       // End of if block that checks that the Data node exists.
    362       java.endIfBlock();
    363     }
    364 
    365     java.endScopedBlock();
    366   }
    367 
    368   /**
    369    * &lt;?cs loop:10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, starting at
    370    * zero.
    371    */
    372   @Override
    373   public void caseALoopToCommand(ALoopToCommand node) {
    374     capturePosition(node.getPosition());
    375 
    376     JavaExpression start = integer(0);
    377     JavaExpression end = expressionTranslator.translateToNumber(node.getExpression());
    378     JavaExpression incr = integer(1);
    379     writeLoop(node.getVariable(), start, end, incr, node.getCommand());
    380   }
    381 
    382   /**
    383    * &lt;?cs loop:0,10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers.
    384    */
    385   @Override
    386   public void caseALoopCommand(ALoopCommand node) {
    387     capturePosition(node.getPosition());
    388 
    389     JavaExpression start = expressionTranslator.translateToNumber(node.getStart());
    390     JavaExpression end = expressionTranslator.translateToNumber(node.getEnd());
    391     JavaExpression incr = integer(1);
    392     writeLoop(node.getVariable(), start, end, incr, node.getCommand());
    393   }
    394 
    395   /**
    396    * &lt;?cs loop:0,10,2 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, with a
    397    * specific increment.
    398    */
    399   @Override
    400   public void caseALoopIncCommand(ALoopIncCommand node) {
    401     capturePosition(node.getPosition());
    402 
    403     JavaExpression start = expressionTranslator.translateToNumber(node.getStart());
    404     JavaExpression end = expressionTranslator.translateToNumber(node.getEnd());
    405     JavaExpression incr = expressionTranslator.translateToNumber(node.getIncrement());
    406     writeLoop(node.getVariable(), start, end, incr, node.getCommand());
    407   }
    408 
    409   private void writeLoop(PVariable itemVariable, JavaExpression start, JavaExpression end,
    410       JavaExpression incr, PCommand command) {
    411 
    412     java.startScopedBlock();
    413 
    414     String startVarName = generateTempVariable("start");
    415     java.writeStatement(declare(Type.INT, startVarName, start));
    416     JavaExpression startVar = symbol(Type.INT, startVarName);
    417 
    418     String endVarName = generateTempVariable("end");
    419     java.writeStatement(declare(Type.INT, endVarName, end));
    420     JavaExpression endVar = symbol(Type.INT, endVarName);
    421 
    422     String incrVarName = generateTempVariable("incr");
    423     java.writeStatement(declare(Type.INT, incrVarName, incr));
    424     JavaExpression incrVar = symbol(Type.INT, incrVarName);
    425 
    426     // TODO: Test correctness of values.
    427     java.startIfBlock(call(Type.BOOLEAN, "validateLoopArgs", startVar, endVar, incrVar));
    428 
    429     JavaExpression itemKey = variableTranslator.translate(itemVariable);
    430 
    431     // Push a new local variable scope for the loop local variable
    432     java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
    433 
    434     String loopVariable = generateTempVariable("loop");
    435     JavaExpression loopVar = symbol(Type.INT, loopVariable);
    436     JavaExpression ifStart = declare(Type.INT, loopVariable, startVar);
    437     JavaExpression ifEnd =
    438         inlineIf(Type.BOOLEAN, infix(Type.BOOLEAN, ">=", incrVar, integer(0)), infix(Type.BOOLEAN,
    439             "<=", loopVar, endVar), infix(Type.BOOLEAN, ">=", loopVar, endVar));
    440     java.startForLoop(ifStart, ifEnd, increment(Type.INT, loopVar, incrVar));
    441 
    442     java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByValue", itemKey, symbol(
    443         loopVariable).cast(Type.STRING), infix(Type.BOOLEAN, "==", symbol(loopVariable), startVar),
    444         infix(Type.BOOLEAN, "==", symbol(loopVariable), endVar)));
    445     command.apply(this);
    446 
    447     java.endLoop();
    448 
    449     // Release the variable scope used by the loop statement
    450     java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
    451 
    452     java.endIfBlock();
    453     java.endScopedBlock();
    454   }
    455 
    456   private void writeEach(PVariable itemVariable, JavaExpression parentData, PCommand command) {
    457 
    458     JavaExpression itemKey = variableTranslator.translate(itemVariable);
    459 
    460     // Push a new local variable scope for the each local variable
    461     java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
    462 
    463     String childDataVariable = generateTempVariable("child");
    464     java.startIterableForLoop("Data", childDataVariable, call("getChildren", parentData));
    465 
    466     java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByPath", itemKey, callOn(
    467         Type.STRING, symbol(childDataVariable), "getFullPath")));
    468     command.apply(this);
    469 
    470     java.endLoop();
    471 
    472     // Release the variable scope used by the each statement
    473     java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
    474   }
    475 
    476   /**
    477    * &lt;?cs alt:someValue &gt; ... &lt;?cs /alt &gt; command. If value exists, write it, otherwise
    478    * write the body of the command.
    479    */
    480   @Override
    481   public void caseAAltCommand(AAltCommand node) {
    482     capturePosition(node.getPosition());
    483     String tempVariableName = generateTempVariable("altVar");
    484 
    485     JavaExpression declaration =
    486         expressionTranslator.declareAsVariable(tempVariableName, node.getExpression());
    487     JavaExpression reference = symbol(declaration.getType(), tempVariableName);
    488     java.writeStatement(declaration);
    489     java.startIfBlock(reference.cast(Type.BOOLEAN));
    490 
    491     JavaExpression escaping =
    492         escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus);
    493     writeVariable(reference, escaping);
    494     java.endIfStartElseBlock();
    495     node.getCommand().apply(this);
    496     java.endIfBlock();
    497   }
    498 
    499   /*
    500    * Generates a statement that will write out a variable expression, after determining whether the
    501    * variable expression should be exempted from any global escaping that may currently be in
    502    * effect. We try to make this determination during translation if possible, and if we cannot, we
    503    * output an if/else statement to check the escaping status of the expression at run time.
    504    *
    505    * Currently, unless the expression contains a function call, we know at translation tmie that it
    506    * does not need to be exempted.
    507    */
    508   private void writeVariable(JavaExpression result, JavaExpression escapingExpression) {
    509 
    510     if (escapingExpression instanceof BooleanLiteralExpression) {
    511       BooleanLiteralExpression expr = (BooleanLiteralExpression) escapingExpression;
    512       if (expr.getValue()) {
    513         java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING)));
    514       } else {
    515         java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING)));
    516       }
    517 
    518     } else {
    519       java.startIfBlock(escapingExpression);
    520       java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING)));
    521       java.endIfStartElseBlock();
    522       java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING)));
    523       java.endIfBlock();
    524     }
    525   }
    526 
    527   /**
    528    * &lt;?cs escape:'html' &gt; command. Changes default escaping function.
    529    */
    530   @Override
    531   public void caseAEscapeCommand(AEscapeCommand node) {
    532     capturePosition(node.getPosition());
    533     java.writeStatement(callOn(CONTEXT, "pushEscapingFunction", expressionTranslator
    534         .translateToString(node.getExpression())));
    535     node.getCommand().apply(this);
    536     java.writeStatement(callOn(CONTEXT, "popEscapingFunction"));
    537   }
    538 
    539   /**
    540    * A fake command injected by AutoEscaper.
    541    *
    542    * AutoEscaper determines the html context in which an include or lvar or evar command is called
    543    * and stores this context in the AAutoescapeCommand node. This function loads the include or lvar
    544    * template in this stored context.
    545    */
    546   @Override
    547   public void caseAAutoescapeCommand(AAutoescapeCommand node) {
    548     capturePosition(node.getPosition());
    549 
    550     java.writeStatement(callOn(CONTEXT, "pushAutoEscapeMode", callOn(symbol("EscapeMode"),
    551         "computeEscapeMode", expressionTranslator.translateToString(node.getExpression()))));
    552     node.getCommand().apply(this);
    553     java.writeStatement(callOn(CONTEXT, "popAutoEscapeMode"));
    554 
    555   }
    556 
    557   /**
    558    * &lt;?cs linclude:'somefile.cs' &gt; command. Lazily includes another template (at render time).
    559    * Throw an error if file does not exist.
    560    */
    561   @Override
    562   public void caseAHardLincludeCommand(AHardLincludeCommand node) {
    563     capturePosition(node.getPosition());
    564     java.writeStatement(call("include", expressionTranslator
    565         .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT));
    566   }
    567 
    568   /**
    569    * &lt;?cs linclude:'somefile.cs' &gt; command. Lazily includes another template (at render time).
    570    * Silently ignore if the included file does not exist.
    571    */
    572   @Override
    573   public void caseALincludeCommand(ALincludeCommand node) {
    574     capturePosition(node.getPosition());
    575     java.writeStatement(call("include", expressionTranslator
    576         .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT));
    577   }
    578 
    579   /**
    580    * &lt;?cs include!'somefile.cs' &gt; command. Throw an error if file does not exist.
    581    */
    582   @Override
    583   public void caseAHardIncludeCommand(AHardIncludeCommand node) {
    584     capturePosition(node.getPosition());
    585     java.writeStatement(call("include", expressionTranslator
    586         .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT));
    587   }
    588 
    589   /**
    590    * &lt;?cs include:'somefile.cs' &gt; command. Silently ignore if the included file does not
    591    * exist.
    592    */
    593   @Override
    594   public void caseAIncludeCommand(AIncludeCommand node) {
    595     capturePosition(node.getPosition());
    596     java.writeStatement(call("include", expressionTranslator
    597         .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT));
    598   }
    599 
    600   /**
    601    * &lt;?cs lvar:blah &gt; command. Evaluate expression and execute commands within.
    602    */
    603   @Override
    604   public void caseALvarCommand(ALvarCommand node) {
    605     capturePosition(node.getPosition());
    606     evaluateVariable(node.getExpression(), "[lvar expression]");
    607   }
    608 
    609   /**
    610    * &lt;?cs evar:blah &gt; command. Evaluate expression and execute commands within.
    611    */
    612   @Override
    613   public void caseAEvarCommand(AEvarCommand node) {
    614     capturePosition(node.getPosition());
    615     evaluateVariable(node.getExpression(), "[evar expression]");
    616   }
    617 
    618   private void evaluateVariable(PExpression expression, String stackTraceDescription) {
    619     java.writeStatement(callOn(callOn(TEMPLATE_LOADER, "createTemp", string(stackTraceDescription),
    620         expressionTranslator.translateToString(expression), callOn(CONTEXT, "getAutoEscapeMode")),
    621         "render", CONTEXT));
    622   }
    623 
    624   /**
    625    * &lt;?cs def:someMacro(x,y) &gt; ... &lt;?cs /def &gt; command. Define a macro (available for
    626    * the remainder of the context).
    627    */
    628   @Override
    629   public void caseADefCommand(ADefCommand node) {
    630     capturePosition(node.getPosition());
    631 
    632     // This doesn't actually define the macro body yet, it just calls:
    633     // registerMacro("someMacroName", someReference);
    634     // where someReference is defined as a field later on (with the body).
    635     String name = makeWord(node.getMacro());
    636     if (macroMap.containsKey(name)) {
    637       // this is a duplicated definition.
    638       // TODO: Handle duplicates correctly.
    639     }
    640     // Keep track of the macro so we can generate the body later.
    641     // See MacroTransformer.
    642     addMacro(name, macro("macro" + macroMap.size()), node);
    643   }
    644 
    645   /**
    646    * This is a special tree walker that's called after the render() method has been generated to
    647    * create the macro definitions and their bodies.
    648    *
    649    * It basically generates fields that look like this:
    650    *
    651    * private final Macro macro1 = new CompiledMacro("myMacro", "arg1", "arg2"...) { public void
    652    * render(Data data, RenderingContext context) { // macro body. } };
    653    */
    654   private class MacroTransformer {
    655 
    656     public void parseDefNode(JavaExpression macroName, ADefCommand node) {
    657       java.startField("Macro", macroName);
    658 
    659       // Parameters passed to constructor. First is name of macro, the rest
    660       // are the name of the arguments.
    661       // e.g. cs def:doStuff(person, cheese)
    662       // -> new CompiledMacro("doStuff", "person", "cheese") { .. }.
    663       int i = 0;
    664       JavaExpression[] args = new JavaExpression[1 + node.getArguments().size()];
    665       args[i++] = string(makeWord(node.getMacro()));
    666       for (PVariable argName : node.getArguments()) {
    667         args[i++] = variableTranslator.translate(argName);
    668       }
    669       java.startAnonymousClass("CompiledMacro", args);
    670 
    671       java.startMethod(RENDER_METHOD, "context");
    672       java.writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT,
    673           "getDataContext")));
    674       java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE));
    675       // If !context.isRuntimeAutoEscaping(), enable runtime autoescaping for macro call.
    676       String tempVariableName = generateTempVariable("doRuntimeAutoEscaping");
    677       JavaExpression value =
    678           JavaExpression.prefix(Type.BOOLEAN, "!", callOn(CONTEXT, "isRuntimeAutoEscaping"));
    679       JavaExpression stmt = declare(Type.BOOLEAN, tempVariableName, value);
    680       java.writeStatement(stmt);
    681 
    682       JavaExpression doRuntimeAutoEscaping = symbol(Type.BOOLEAN, tempVariableName);
    683       java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN));
    684       java.writeStatement(callOn(CONTEXT, "startRuntimeAutoEscaping"));
    685       java.endIfBlock();
    686 
    687       node.getCommand().apply(TemplateTranslator.this);
    688 
    689       java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN));
    690       java.writeStatement(callOn(CONTEXT, "stopRuntimeAutoEscaping"));
    691       java.endIfBlock();
    692       java.writeStatement(callOn(CONTEXT, "popExecutionContext"));
    693       java.endMethod();
    694       java.endAnonymousClass();
    695       java.endField();
    696     }
    697   }
    698 
    699   private String makeWord(LinkedList<TWord> words) {
    700     if (words.size() == 1) {
    701       return words.getFirst().getText();
    702     }
    703     StringBuilder result = new StringBuilder();
    704     for (TWord word : words) {
    705       if (result.length() > 0) {
    706         result.append('.');
    707       }
    708       result.append(word.getText());
    709     }
    710     return result.toString();
    711   }
    712 
    713   /**
    714    * &lt;?cs call:someMacro(x,y) command. Call a macro.
    715    */
    716   @Override
    717   public void caseACallCommand(ACallCommand node) {
    718     capturePosition(node.getPosition());
    719 
    720     String name = makeWord(node.getMacro());
    721 
    722     java.startScopedBlock();
    723     java.writeComment("call:" + name);
    724 
    725     // Lookup macro.
    726     // The expression used for the macro will either be the name of the
    727     // static Macro object representing the macro (if we can statically
    728     // determine it), or will be a temporary Macro variable (named
    729     // 'macroCall###') that gets the result of findMacro at evaluation time.
    730     JavaExpression macroCalled;
    731     MacroInfo macroInfo = macroMap.get(name);
    732     if (macroInfo == null) {
    733       // We never saw the definition of the macro. Assume it might come in an
    734       // included file and look it up at render time.
    735       String macroCall = generateTempVariable("macroCall");
    736       java
    737           .writeStatement(declare(Type.MACRO, macroCall, callOn(CONTEXT, "findMacro", string(name))));
    738 
    739       macroCalled = macro(macroCall);
    740     } else {
    741       macroCalled = macroInfo.symbol;
    742     }
    743 
    744     int numArgs = node.getArguments().size();
    745     if (numArgs > 0) {
    746 
    747       // TODO: Add check that number of arguments passed in equals the
    748       // number expected by the macro. This should be done at translation
    749       // time in a future CL.
    750 
    751       JavaExpression[] argValues = new JavaExpression[numArgs];
    752       JavaExpression[] argStatus = new JavaExpression[numArgs];
    753 
    754       // Read the values first in case the new local variables shares the same
    755       // name as a variable (or variable expansion) being passed in to the macro.
    756       int i = 0;
    757       for (PExpression argNode : node.getArguments()) {
    758         JavaExpression value = expressionTranslator.translateUntyped(argNode);
    759         if (value.getType() != Type.VAR_NAME) {
    760           value = value.cast(Type.STRING);
    761         }
    762         String valueName = generateTempVariable("argValue");
    763         java.writeStatement(declare(Type.STRING, valueName, value));
    764         argValues[i] = JavaExpression.symbol(value.getType(), valueName);
    765         if (propagateEscapeStatus) {
    766           argStatus[i] = escapingEvaluator.computeEscaping(argNode, propagateEscapeStatus);
    767         } else {
    768           argStatus[i] = JavaExpression.symbol("EscapeMode.ESCAPE_NONE");
    769         }
    770 
    771         i++;
    772       }
    773 
    774       // Push a new local variable scope for this macro execution.
    775       java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
    776 
    777       // Create the local variables for each argument.
    778       for (i = 0; i < argValues.length; i++) {
    779         JavaExpression value = argValues[i];
    780         JavaExpression tempVar = callOn(macroCalled, "getArgumentName", integer(i));
    781         String methodName;
    782         if (value.getType() == Type.VAR_NAME) {
    783           methodName = "createLocalVariableByPath";
    784           java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value));
    785         } else {
    786           // Must be String as we cast it above.
    787           methodName = "createLocalVariableByValue";
    788           java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value, argStatus[i]));
    789         }
    790       }
    791     }
    792 
    793     // Render macro.
    794     java.writeStatement(callOn(macroCalled, "render", CONTEXT));
    795 
    796     if (numArgs > 0) {
    797       // Release the variable scope used by the macro call
    798       java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
    799     }
    800 
    801     java.endScopedBlock();
    802   }
    803 
    804   /**
    805    * Walks the PPosition tree, which calls {@link #caseTCsOpen(TCsOpen)} below. This is simply to
    806    * capture the position of the node in the original template file, to help developers diagnose
    807    * errors.
    808    */
    809   private void capturePosition(PPosition position) {
    810     position.apply(this);
    811   }
    812 
    813   /**
    814    * Every time a &lt;cs token is found, grab the line and column and call
    815    * context.setCurrentPosition() so this is captured for stack traces.
    816    */
    817   @Override
    818   public void caseTCsOpen(TCsOpen node) {
    819     int line = node.getLine();
    820     int column = node.getPos();
    821     java.writeStatement(callOn(CONTEXT, "setCurrentPosition", JavaExpression.integer(line),
    822         JavaExpression.integer(column)));
    823   }
    824 
    825   private String generateTempVariable(String prefix) {
    826     return prefix + tempVariable++;
    827   }
    828 }
    829