/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver.compiler; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression; import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type; import static com.google.clearsilver.jsilver.compiler.JavaExpression.call; import static com.google.clearsilver.jsilver.compiler.JavaExpression.callFindVariable; import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn; import static com.google.clearsilver.jsilver.compiler.JavaExpression.declare; import static com.google.clearsilver.jsilver.compiler.JavaExpression.increment; import static com.google.clearsilver.jsilver.compiler.JavaExpression.infix; import static com.google.clearsilver.jsilver.compiler.JavaExpression.inlineIf; import static com.google.clearsilver.jsilver.compiler.JavaExpression.integer; import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal; import static com.google.clearsilver.jsilver.compiler.JavaExpression.macro; import static com.google.clearsilver.jsilver.compiler.JavaExpression.string; import static com.google.clearsilver.jsilver.compiler.JavaExpression.symbol; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataContext; import com.google.clearsilver.jsilver.functions.Function; import com.google.clearsilver.jsilver.functions.FunctionExecutor; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAltCommand; import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand; import com.google.clearsilver.jsilver.syntax.node.ACallCommand; import com.google.clearsilver.jsilver.syntax.node.ADataCommand; import com.google.clearsilver.jsilver.syntax.node.ADefCommand; import com.google.clearsilver.jsilver.syntax.node.AEachCommand; import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand; import com.google.clearsilver.jsilver.syntax.node.AEvarCommand; import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand; import com.google.clearsilver.jsilver.syntax.node.AIfCommand; import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand; import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand; import com.google.clearsilver.jsilver.syntax.node.ALvarCommand; import com.google.clearsilver.jsilver.syntax.node.ANameCommand; import com.google.clearsilver.jsilver.syntax.node.ANoopCommand; import com.google.clearsilver.jsilver.syntax.node.ASetCommand; import com.google.clearsilver.jsilver.syntax.node.AUvarCommand; import com.google.clearsilver.jsilver.syntax.node.AVarCommand; import com.google.clearsilver.jsilver.syntax.node.AWithCommand; import com.google.clearsilver.jsilver.syntax.node.PCommand; import com.google.clearsilver.jsilver.syntax.node.PExpression; import com.google.clearsilver.jsilver.syntax.node.PPosition; import com.google.clearsilver.jsilver.syntax.node.PVariable; import com.google.clearsilver.jsilver.syntax.node.Start; import com.google.clearsilver.jsilver.syntax.node.TCsOpen; import com.google.clearsilver.jsilver.syntax.node.TWord; import com.google.clearsilver.jsilver.template.Macro; import com.google.clearsilver.jsilver.template.RenderingContext; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.values.Value; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; /** * Translates a JSilver AST into compilable Java code. This executes much faster than the * interpreter. * * @see TemplateCompiler */ public class TemplateTranslator extends DepthFirstAdapter { // Root data public static final JavaExpression DATA = symbol(Type.DATA, "data"); // RenderingContext public static final JavaExpression CONTEXT = symbol("context"); // DataContext public static final JavaExpression DATA_CONTEXT = symbol(Type.DATA_CONTEXT, "dataContext"); public static final JavaExpression NULL = symbol("null"); // Accessed from macros as well. public static final JavaExpression RESOURCE_LOADER = callOn(CONTEXT, "getResourceLoader"); public static final JavaExpression TEMPLATE_LOADER = symbol("getTemplateLoader()"); public static final JavaExpression THIS_TEMPLATE = symbol("this"); private final JavaSourceWriter java; private final String packageName; private final String className; private final ExpressionTranslator expressionTranslator = new ExpressionTranslator(); private final VariableTranslator variableTranslator = new VariableTranslator(expressionTranslator); private final EscapingEvaluator escapingEvaluator = new EscapingEvaluator(variableTranslator); private static final Method RENDER_METHOD; private int tempVariable = 0; /** * Used to determine the escaping to apply before displaying a variable. If propagateEscapeStatus * is enabled, string and numeric literals are not escaped, nor is the output of an escaping * function. If not, any expression that contains an escaping function is not escaped. This * maintains compatibility with the way ClearSilver works. */ private boolean propagateEscapeStatus; /** * Holds Macro information used while generating code. */ private static class MacroInfo { /** * JavaExpression used for outputting the static Macro variable name. */ JavaExpression symbol; /** * Parser node for the definition. Stored for evaluation after main render method is output. */ ADefCommand defNode; } /** * Map of macro names to definition nodes and java expressions used to refer to them. */ private final Map macroMap = new HashMap(); /** * Used to iterate through list of macros. We can't rely on Map's iterator because we may be * adding to the map as we iterate through the values() list and that would throw a * ConcurrentModificationException. */ private final Queue macroQueue = new LinkedList(); /** * Creates a MacroInfo object and adds it to the data structures. Also outputs statement to * register the macro. * * @param name name of the macro as defined in the template. * @param symbol static variable name of the macro definition. * @param defNode parser node holding the macro definition to be evaluated later. */ private void addMacro(String name, JavaExpression symbol, ADefCommand defNode) { if (macroMap.get(name) != null) { // TODO: This macro is already defined. Should throw an error. } MacroInfo info = new MacroInfo(); info.symbol = symbol; info.defNode = defNode; macroMap.put(name, info); macroQueue.add(info); // Register the macro. java.writeStatement(callOn(CONTEXT, "registerMacro", string(name), symbol)); } static { try { RENDER_METHOD = Template.class.getMethod("render", RenderingContext.class); } catch (NoSuchMethodException e) { throw new Error("Cannot find CompiledTemplate.render() method! " + "Has signature changed?", e); } } public TemplateTranslator(String packageName, String className, Writer output, boolean propagateEscapeStatus) { this.packageName = packageName; this.className = className; java = new JavaSourceWriter(output); this.propagateEscapeStatus = propagateEscapeStatus; } @Override public void caseStart(Start node) { java.writeComment("This class is autogenerated by JSilver. Do not edit."); java.writePackage(packageName); java.writeImports(BaseCompiledTemplate.class, Template.class, Macro.class, RenderingContext.class, Data.class, DataContext.class, Function.class, FunctionExecutor.class, Value.class, EscapeMode.class, IOException.class); java.startClass(className, BaseCompiledTemplate.class.getSimpleName()); // Implement render() method. java.startMethod(RENDER_METHOD, "context"); java .writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT, "getDataContext"))); java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE)); super.caseStart(node); // Walk template AST. java.writeStatement(callOn(CONTEXT, "popExecutionContext")); java.endMethod(); // The macros have to be defined outside of the render method. // (Well actually they *could* be defined inline as anon classes, but it // would make the generated code quite hard to understand). MacroTransformer macroTransformer = new MacroTransformer(); while (!macroQueue.isEmpty()) { MacroInfo curr = macroQueue.remove(); macroTransformer.parseDefNode(curr.symbol, curr.defNode); } java.endClass(); } /** * Chunk of data (i.e. not a CS command). */ @Override public void caseADataCommand(ADataCommand node) { String content = node.getData().getText(); java.writeStatement(callOn(CONTEXT, "writeUnescaped", string(content))); } /** * <?cs var:blah > expression. Evaluate as string and write output, using default escaping. */ @Override public void caseAVarCommand(AVarCommand node) { capturePosition(node.getPosition()); String tempVariableName = generateTempVariable("result"); JavaExpression result = symbol(Type.STRING, tempVariableName); java.writeStatement(declare(Type.STRING, tempVariableName, expressionTranslator .translateToString(node.getExpression()))); JavaExpression escaping = escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus); writeVariable(result, escaping); } /** * <?cs uvar:blah > expression. Evaluate as string and write output, but don't escape. */ @Override public void caseAUvarCommand(AUvarCommand node) { capturePosition(node.getPosition()); java.writeStatement(callOn(CONTEXT, "writeUnescaped", expressionTranslator .translateToString(node.getExpression()))); } /** * <?cs set:x='y' > command. */ @Override public void caseASetCommand(ASetCommand node) { capturePosition(node.getPosition()); String tempVariableName = generateTempVariable("setNode"); // Data setNode1 = dataContext.findVariable("x", true); JavaExpression setNode = symbol(Type.DATA, tempVariableName); java.writeStatement(declare(Type.DATA, tempVariableName, callFindVariable(variableTranslator .translate(node.getVariable()), true))); // setNode1.setValue("hello"); java.writeStatement(callOn(setNode, "setValue", expressionTranslator.translateToString(node .getExpression()))); if (propagateEscapeStatus) { // setNode1.setEscapeMode(EscapeMode.ESCAPE_IS_CONSTANT); java.writeStatement(callOn(setNode, "setEscapeMode", escapingEvaluator.computeEscaping(node .getExpression(), propagateEscapeStatus))); } } /** * <?cs name:blah > command. Writes out the name of the original variable referred to by a * given node. */ @Override public void caseANameCommand(ANameCommand node) { capturePosition(node.getPosition()); JavaExpression readNode = callFindVariable(variableTranslator.translate(node.getVariable()), false); java.writeStatement(callOn(CONTEXT, "writeEscaped", call("getNodeName", readNode))); } /** * <?cs if:blah > ... <?cs else > ... <?cs /if > command. */ @Override public void caseAIfCommand(AIfCommand node) { capturePosition(node.getPosition()); java.startIfBlock(expressionTranslator.translateToBoolean(node.getExpression())); node.getBlock().apply(this); if (!(node.getOtherwise() instanceof ANoopCommand)) { java.endIfStartElseBlock(); node.getOtherwise().apply(this); } java.endIfBlock(); } /** * <?cs each:x=Stuff > ... <?cs /each > command. Loops over child items of a data * node. */ @Override public void caseAEachCommand(AEachCommand node) { capturePosition(node.getPosition()); JavaExpression parent = expressionTranslator.translateToData(node.getExpression()); writeEach(node.getVariable(), parent, node.getCommand()); } /** * <?cs with:x=Something > ... <?cs /with > command. Aliases a value within a specific * scope. */ @Override public void caseAWithCommand(AWithCommand node) { capturePosition(node.getPosition()); java.startScopedBlock(); java.writeComment("with:"); // Extract the value first in case the temp variable has the same name. JavaExpression value = expressionTranslator.translateUntyped(node.getExpression()); String methodName = null; if (value.getType() == Type.VAR_NAME) { String withValueName = generateTempVariable("withValue"); java.writeStatement(declare(Type.STRING, withValueName, value)); value = symbol(Type.VAR_NAME, withValueName); methodName = "createLocalVariableByPath"; // We need to check if the variable exists. If not, we skip the with // call. java.startIfBlock(JavaExpression.infix(Type.BOOLEAN, "!=", value.cast(Type.DATA), literal( Type.DATA, "null"))); } else { // Cast to string so we support numeric or boolean values as well. value = value.cast(Type.STRING); methodName = "createLocalVariableByValue"; } JavaExpression itemKey = variableTranslator.translate(node.getVariable()); // Push a new local variable scope for the with local variable java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); java.writeStatement(callOn(DATA_CONTEXT, methodName, itemKey, value)); node.getCommand().apply(this); // Release the variable scope used by the with statement java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); if (value.getType() == Type.VAR_NAME) { // End of if block that checks that the Data node exists. java.endIfBlock(); } java.endScopedBlock(); } /** * <?cs loop:10 > ... <?cs /loop > command. Loops over a range of numbers, starting at * zero. */ @Override public void caseALoopToCommand(ALoopToCommand node) { capturePosition(node.getPosition()); JavaExpression start = integer(0); JavaExpression end = expressionTranslator.translateToNumber(node.getExpression()); JavaExpression incr = integer(1); writeLoop(node.getVariable(), start, end, incr, node.getCommand()); } /** * <?cs loop:0,10 > ... <?cs /loop > command. Loops over a range of numbers. */ @Override public void caseALoopCommand(ALoopCommand node) { capturePosition(node.getPosition()); JavaExpression start = expressionTranslator.translateToNumber(node.getStart()); JavaExpression end = expressionTranslator.translateToNumber(node.getEnd()); JavaExpression incr = integer(1); writeLoop(node.getVariable(), start, end, incr, node.getCommand()); } /** * <?cs loop:0,10,2 > ... <?cs /loop > command. Loops over a range of numbers, with a * specific increment. */ @Override public void caseALoopIncCommand(ALoopIncCommand node) { capturePosition(node.getPosition()); JavaExpression start = expressionTranslator.translateToNumber(node.getStart()); JavaExpression end = expressionTranslator.translateToNumber(node.getEnd()); JavaExpression incr = expressionTranslator.translateToNumber(node.getIncrement()); writeLoop(node.getVariable(), start, end, incr, node.getCommand()); } private void writeLoop(PVariable itemVariable, JavaExpression start, JavaExpression end, JavaExpression incr, PCommand command) { java.startScopedBlock(); String startVarName = generateTempVariable("start"); java.writeStatement(declare(Type.INT, startVarName, start)); JavaExpression startVar = symbol(Type.INT, startVarName); String endVarName = generateTempVariable("end"); java.writeStatement(declare(Type.INT, endVarName, end)); JavaExpression endVar = symbol(Type.INT, endVarName); String incrVarName = generateTempVariable("incr"); java.writeStatement(declare(Type.INT, incrVarName, incr)); JavaExpression incrVar = symbol(Type.INT, incrVarName); // TODO: Test correctness of values. java.startIfBlock(call(Type.BOOLEAN, "validateLoopArgs", startVar, endVar, incrVar)); JavaExpression itemKey = variableTranslator.translate(itemVariable); // Push a new local variable scope for the loop local variable java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); String loopVariable = generateTempVariable("loop"); JavaExpression loopVar = symbol(Type.INT, loopVariable); JavaExpression ifStart = declare(Type.INT, loopVariable, startVar); JavaExpression ifEnd = inlineIf(Type.BOOLEAN, infix(Type.BOOLEAN, ">=", incrVar, integer(0)), infix(Type.BOOLEAN, "<=", loopVar, endVar), infix(Type.BOOLEAN, ">=", loopVar, endVar)); java.startForLoop(ifStart, ifEnd, increment(Type.INT, loopVar, incrVar)); java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByValue", itemKey, symbol( loopVariable).cast(Type.STRING), infix(Type.BOOLEAN, "==", symbol(loopVariable), startVar), infix(Type.BOOLEAN, "==", symbol(loopVariable), endVar))); command.apply(this); java.endLoop(); // Release the variable scope used by the loop statement java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); java.endIfBlock(); java.endScopedBlock(); } private void writeEach(PVariable itemVariable, JavaExpression parentData, PCommand command) { JavaExpression itemKey = variableTranslator.translate(itemVariable); // Push a new local variable scope for the each local variable java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); String childDataVariable = generateTempVariable("child"); java.startIterableForLoop("Data", childDataVariable, call("getChildren", parentData)); java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByPath", itemKey, callOn( Type.STRING, symbol(childDataVariable), "getFullPath"))); command.apply(this); java.endLoop(); // Release the variable scope used by the each statement java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); } /** * <?cs alt:someValue > ... <?cs /alt > command. If value exists, write it, otherwise * write the body of the command. */ @Override public void caseAAltCommand(AAltCommand node) { capturePosition(node.getPosition()); String tempVariableName = generateTempVariable("altVar"); JavaExpression declaration = expressionTranslator.declareAsVariable(tempVariableName, node.getExpression()); JavaExpression reference = symbol(declaration.getType(), tempVariableName); java.writeStatement(declaration); java.startIfBlock(reference.cast(Type.BOOLEAN)); JavaExpression escaping = escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus); writeVariable(reference, escaping); java.endIfStartElseBlock(); node.getCommand().apply(this); java.endIfBlock(); } /* * Generates a statement that will write out a variable expression, after determining whether the * variable expression should be exempted from any global escaping that may currently be in * effect. We try to make this determination during translation if possible, and if we cannot, we * output an if/else statement to check the escaping status of the expression at run time. * * Currently, unless the expression contains a function call, we know at translation tmie that it * does not need to be exempted. */ private void writeVariable(JavaExpression result, JavaExpression escapingExpression) { if (escapingExpression instanceof BooleanLiteralExpression) { BooleanLiteralExpression expr = (BooleanLiteralExpression) escapingExpression; if (expr.getValue()) { java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING))); } else { java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING))); } } else { java.startIfBlock(escapingExpression); java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING))); java.endIfStartElseBlock(); java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING))); java.endIfBlock(); } } /** * <?cs escape:'html' > command. Changes default escaping function. */ @Override public void caseAEscapeCommand(AEscapeCommand node) { capturePosition(node.getPosition()); java.writeStatement(callOn(CONTEXT, "pushEscapingFunction", expressionTranslator .translateToString(node.getExpression()))); node.getCommand().apply(this); java.writeStatement(callOn(CONTEXT, "popEscapingFunction")); } /** * A fake command injected by AutoEscaper. * * AutoEscaper determines the html context in which an include or lvar or evar command is called * and stores this context in the AAutoescapeCommand node. This function loads the include or lvar * template in this stored context. */ @Override public void caseAAutoescapeCommand(AAutoescapeCommand node) { capturePosition(node.getPosition()); java.writeStatement(callOn(CONTEXT, "pushAutoEscapeMode", callOn(symbol("EscapeMode"), "computeEscapeMode", expressionTranslator.translateToString(node.getExpression())))); node.getCommand().apply(this); java.writeStatement(callOn(CONTEXT, "popAutoEscapeMode")); } /** * <?cs linclude:'somefile.cs' > command. Lazily includes another template (at render time). * Throw an error if file does not exist. */ @Override public void caseAHardLincludeCommand(AHardLincludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT)); } /** * <?cs linclude:'somefile.cs' > command. Lazily includes another template (at render time). * Silently ignore if the included file does not exist. */ @Override public void caseALincludeCommand(ALincludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT)); } /** * <?cs include!'somefile.cs' > command. Throw an error if file does not exist. */ @Override public void caseAHardIncludeCommand(AHardIncludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT)); } /** * <?cs include:'somefile.cs' > command. Silently ignore if the included file does not * exist. */ @Override public void caseAIncludeCommand(AIncludeCommand node) { capturePosition(node.getPosition()); java.writeStatement(call("include", expressionTranslator .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT)); } /** * <?cs lvar:blah > command. Evaluate expression and execute commands within. */ @Override public void caseALvarCommand(ALvarCommand node) { capturePosition(node.getPosition()); evaluateVariable(node.getExpression(), "[lvar expression]"); } /** * <?cs evar:blah > command. Evaluate expression and execute commands within. */ @Override public void caseAEvarCommand(AEvarCommand node) { capturePosition(node.getPosition()); evaluateVariable(node.getExpression(), "[evar expression]"); } private void evaluateVariable(PExpression expression, String stackTraceDescription) { java.writeStatement(callOn(callOn(TEMPLATE_LOADER, "createTemp", string(stackTraceDescription), expressionTranslator.translateToString(expression), callOn(CONTEXT, "getAutoEscapeMode")), "render", CONTEXT)); } /** * <?cs def:someMacro(x,y) > ... <?cs /def > command. Define a macro (available for * the remainder of the context). */ @Override public void caseADefCommand(ADefCommand node) { capturePosition(node.getPosition()); // This doesn't actually define the macro body yet, it just calls: // registerMacro("someMacroName", someReference); // where someReference is defined as a field later on (with the body). String name = makeWord(node.getMacro()); if (macroMap.containsKey(name)) { // this is a duplicated definition. // TODO: Handle duplicates correctly. } // Keep track of the macro so we can generate the body later. // See MacroTransformer. addMacro(name, macro("macro" + macroMap.size()), node); } /** * This is a special tree walker that's called after the render() method has been generated to * create the macro definitions and their bodies. * * It basically generates fields that look like this: * * private final Macro macro1 = new CompiledMacro("myMacro", "arg1", "arg2"...) { public void * render(Data data, RenderingContext context) { // macro body. } }; */ private class MacroTransformer { public void parseDefNode(JavaExpression macroName, ADefCommand node) { java.startField("Macro", macroName); // Parameters passed to constructor. First is name of macro, the rest // are the name of the arguments. // e.g. cs def:doStuff(person, cheese) // -> new CompiledMacro("doStuff", "person", "cheese") { .. }. int i = 0; JavaExpression[] args = new JavaExpression[1 + node.getArguments().size()]; args[i++] = string(makeWord(node.getMacro())); for (PVariable argName : node.getArguments()) { args[i++] = variableTranslator.translate(argName); } java.startAnonymousClass("CompiledMacro", args); java.startMethod(RENDER_METHOD, "context"); java.writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT, "getDataContext"))); java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE)); // If !context.isRuntimeAutoEscaping(), enable runtime autoescaping for macro call. String tempVariableName = generateTempVariable("doRuntimeAutoEscaping"); JavaExpression value = JavaExpression.prefix(Type.BOOLEAN, "!", callOn(CONTEXT, "isRuntimeAutoEscaping")); JavaExpression stmt = declare(Type.BOOLEAN, tempVariableName, value); java.writeStatement(stmt); JavaExpression doRuntimeAutoEscaping = symbol(Type.BOOLEAN, tempVariableName); java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN)); java.writeStatement(callOn(CONTEXT, "startRuntimeAutoEscaping")); java.endIfBlock(); node.getCommand().apply(TemplateTranslator.this); java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN)); java.writeStatement(callOn(CONTEXT, "stopRuntimeAutoEscaping")); java.endIfBlock(); java.writeStatement(callOn(CONTEXT, "popExecutionContext")); java.endMethod(); java.endAnonymousClass(); java.endField(); } } private String makeWord(LinkedList words) { if (words.size() == 1) { return words.getFirst().getText(); } StringBuilder result = new StringBuilder(); for (TWord word : words) { if (result.length() > 0) { result.append('.'); } result.append(word.getText()); } return result.toString(); } /** * <?cs call:someMacro(x,y) command. Call a macro. */ @Override public void caseACallCommand(ACallCommand node) { capturePosition(node.getPosition()); String name = makeWord(node.getMacro()); java.startScopedBlock(); java.writeComment("call:" + name); // Lookup macro. // The expression used for the macro will either be the name of the // static Macro object representing the macro (if we can statically // determine it), or will be a temporary Macro variable (named // 'macroCall###') that gets the result of findMacro at evaluation time. JavaExpression macroCalled; MacroInfo macroInfo = macroMap.get(name); if (macroInfo == null) { // We never saw the definition of the macro. Assume it might come in an // included file and look it up at render time. String macroCall = generateTempVariable("macroCall"); java .writeStatement(declare(Type.MACRO, macroCall, callOn(CONTEXT, "findMacro", string(name)))); macroCalled = macro(macroCall); } else { macroCalled = macroInfo.symbol; } int numArgs = node.getArguments().size(); if (numArgs > 0) { // TODO: Add check that number of arguments passed in equals the // number expected by the macro. This should be done at translation // time in a future CL. JavaExpression[] argValues = new JavaExpression[numArgs]; JavaExpression[] argStatus = new JavaExpression[numArgs]; // Read the values first in case the new local variables shares the same // name as a variable (or variable expansion) being passed in to the macro. int i = 0; for (PExpression argNode : node.getArguments()) { JavaExpression value = expressionTranslator.translateUntyped(argNode); if (value.getType() != Type.VAR_NAME) { value = value.cast(Type.STRING); } String valueName = generateTempVariable("argValue"); java.writeStatement(declare(Type.STRING, valueName, value)); argValues[i] = JavaExpression.symbol(value.getType(), valueName); if (propagateEscapeStatus) { argStatus[i] = escapingEvaluator.computeEscaping(argNode, propagateEscapeStatus); } else { argStatus[i] = JavaExpression.symbol("EscapeMode.ESCAPE_NONE"); } i++; } // Push a new local variable scope for this macro execution. java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope")); // Create the local variables for each argument. for (i = 0; i < argValues.length; i++) { JavaExpression value = argValues[i]; JavaExpression tempVar = callOn(macroCalled, "getArgumentName", integer(i)); String methodName; if (value.getType() == Type.VAR_NAME) { methodName = "createLocalVariableByPath"; java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value)); } else { // Must be String as we cast it above. methodName = "createLocalVariableByValue"; java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value, argStatus[i])); } } } // Render macro. java.writeStatement(callOn(macroCalled, "render", CONTEXT)); if (numArgs > 0) { // Release the variable scope used by the macro call java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope")); } java.endScopedBlock(); } /** * Walks the PPosition tree, which calls {@link #caseTCsOpen(TCsOpen)} below. This is simply to * capture the position of the node in the original template file, to help developers diagnose * errors. */ private void capturePosition(PPosition position) { position.apply(this); } /** * Every time a <cs token is found, grab the line and column and call * context.setCurrentPosition() so this is captured for stack traces. */ @Override public void caseTCsOpen(TCsOpen node) { int line = node.getLine(); int column = node.getPos(); java.writeStatement(callOn(CONTEXT, "setCurrentPosition", JavaExpression.integer(line), JavaExpression.integer(column))); } private String generateTempVariable(String prefix) { return prefix + tempVariable++; } }