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
17package com.google.clearsilver.jsilver.compiler;
18
19import com.google.clearsilver.jsilver.autoescape.EscapeMode;
20import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
21import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type;
22import static com.google.clearsilver.jsilver.compiler.JavaExpression.call;
23import static com.google.clearsilver.jsilver.compiler.JavaExpression.callFindVariable;
24import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
25import static com.google.clearsilver.jsilver.compiler.JavaExpression.declare;
26import static com.google.clearsilver.jsilver.compiler.JavaExpression.increment;
27import static com.google.clearsilver.jsilver.compiler.JavaExpression.infix;
28import static com.google.clearsilver.jsilver.compiler.JavaExpression.inlineIf;
29import static com.google.clearsilver.jsilver.compiler.JavaExpression.integer;
30import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal;
31import static com.google.clearsilver.jsilver.compiler.JavaExpression.macro;
32import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
33import static com.google.clearsilver.jsilver.compiler.JavaExpression.symbol;
34import com.google.clearsilver.jsilver.data.Data;
35import com.google.clearsilver.jsilver.data.DataContext;
36import com.google.clearsilver.jsilver.functions.Function;
37import com.google.clearsilver.jsilver.functions.FunctionExecutor;
38import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
39import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
40import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand;
41import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
42import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
43import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
44import com.google.clearsilver.jsilver.syntax.node.AEachCommand;
45import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
46import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
47import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
48import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
49import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
50import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
51import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
52import com.google.clearsilver.jsilver.syntax.node.ALoopCommand;
53import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand;
54import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand;
55import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
56import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
57import com.google.clearsilver.jsilver.syntax.node.ANoopCommand;
58import com.google.clearsilver.jsilver.syntax.node.ASetCommand;
59import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
60import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
61import com.google.clearsilver.jsilver.syntax.node.AWithCommand;
62import com.google.clearsilver.jsilver.syntax.node.PCommand;
63import com.google.clearsilver.jsilver.syntax.node.PExpression;
64import com.google.clearsilver.jsilver.syntax.node.PPosition;
65import com.google.clearsilver.jsilver.syntax.node.PVariable;
66import com.google.clearsilver.jsilver.syntax.node.Start;
67import com.google.clearsilver.jsilver.syntax.node.TCsOpen;
68import com.google.clearsilver.jsilver.syntax.node.TWord;
69import com.google.clearsilver.jsilver.template.Macro;
70import com.google.clearsilver.jsilver.template.RenderingContext;
71import com.google.clearsilver.jsilver.template.Template;
72import com.google.clearsilver.jsilver.values.Value;
73
74import java.io.IOException;
75import java.io.Writer;
76import java.lang.reflect.Method;
77import java.util.HashMap;
78import java.util.LinkedList;
79import java.util.Map;
80import 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 */
88public 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