Home | History | Annotate | Download | only in interpreter
      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.interpreter;
     18 
     19 import com.google.clearsilver.jsilver.autoescape.EscapeMode;
     20 import com.google.clearsilver.jsilver.data.Data;
     21 import com.google.clearsilver.jsilver.data.DataContext;
     22 import com.google.clearsilver.jsilver.exceptions.ExceptionUtil;
     23 import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
     24 import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
     25 import com.google.clearsilver.jsilver.functions.FunctionExecutor;
     26 import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
     27 import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
     28 import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand;
     29 import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
     30 import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
     31 import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
     32 import com.google.clearsilver.jsilver.syntax.node.AEachCommand;
     33 import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
     34 import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
     35 import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
     36 import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
     37 import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
     38 import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
     39 import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
     40 import com.google.clearsilver.jsilver.syntax.node.ALoopCommand;
     41 import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand;
     42 import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand;
     43 import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
     44 import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
     45 import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
     46 import com.google.clearsilver.jsilver.syntax.node.ASetCommand;
     47 import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
     48 import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
     49 import com.google.clearsilver.jsilver.syntax.node.AWithCommand;
     50 import com.google.clearsilver.jsilver.syntax.node.PCommand;
     51 import com.google.clearsilver.jsilver.syntax.node.PExpression;
     52 import com.google.clearsilver.jsilver.syntax.node.PPosition;
     53 import com.google.clearsilver.jsilver.syntax.node.PVariable;
     54 import com.google.clearsilver.jsilver.syntax.node.TCsOpen;
     55 import com.google.clearsilver.jsilver.syntax.node.TWord;
     56 import com.google.clearsilver.jsilver.template.Macro;
     57 import com.google.clearsilver.jsilver.template.RenderingContext;
     58 import com.google.clearsilver.jsilver.template.Template;
     59 import com.google.clearsilver.jsilver.template.TemplateLoader;
     60 import com.google.clearsilver.jsilver.values.Value;
     61 import com.google.clearsilver.jsilver.values.VariableValue;
     62 
     63 import java.io.IOException;
     64 import java.util.Iterator;
     65 import java.util.LinkedList;
     66 
     67 /**
     68  * Main JSilver interpreter. This walks a template's AST and renders the result out.
     69  */
     70 public class TemplateInterpreter extends DepthFirstAdapter {
     71 
     72   private final Template template;
     73 
     74   private final ExpressionEvaluator expressionEvaluator;
     75   private final VariableLocator variableLocator;
     76   private final TemplateLoader templateLoader;
     77   private final RenderingContext context;
     78   private final DataContext dataContext;
     79 
     80   public TemplateInterpreter(Template template, TemplateLoader templateLoader,
     81       RenderingContext context, FunctionExecutor functionExecutor) {
     82     this.template = template;
     83     this.templateLoader = templateLoader;
     84     this.context = context;
     85     this.dataContext = context.getDataContext();
     86 
     87     expressionEvaluator = new ExpressionEvaluator(dataContext, functionExecutor);
     88     variableLocator = new VariableLocator(expressionEvaluator);
     89   }
     90 
     91   // ------------------------------------------------------------------------
     92   // COMMAND PROCESSING
     93 
     94   /**
     95    * Chunk of data (i.e. not a CS command).
     96    */
     97   @Override
     98   public void caseADataCommand(ADataCommand node) {
     99     context.writeUnescaped(node.getData().getText());
    100   }
    101 
    102   /**
    103    * <?cs var:blah > expression. Evaluate as string and write output, using default escaping.
    104    */
    105   @Override
    106   public void caseAVarCommand(AVarCommand node) {
    107     setLastPosition(node.getPosition());
    108 
    109     // Evaluate expression.
    110     Value value = expressionEvaluator.evaluate(node.getExpression());
    111     writeVariable(value);
    112   }
    113 
    114   /**
    115    * <?cs uvar:blah > expression. Evaluate as string and write output, but don't escape.
    116    */
    117   @Override
    118   public void caseAUvarCommand(AUvarCommand node) {
    119     setLastPosition(node.getPosition());
    120 
    121     // Evaluate expression.
    122     Value value = expressionEvaluator.evaluate(node.getExpression());
    123     context.writeUnescaped(value.asString());
    124   }
    125 
    126   /**
    127    * <?cs lvar:blah > command. Evaluate expression and execute commands within.
    128    */
    129   @Override
    130   public void caseALvarCommand(ALvarCommand node) {
    131     setLastPosition(node.getPosition());
    132     evaluateVariable(node.getExpression(), "[lvar expression]");
    133   }
    134 
    135   /**
    136    * <?cs evar:blah > command. Evaluate expression and execute commands within.
    137    */
    138   @Override
    139   public void caseAEvarCommand(AEvarCommand node) {
    140     setLastPosition(node.getPosition());
    141     evaluateVariable(node.getExpression(), "[evar expression]");
    142   }
    143 
    144   private void evaluateVariable(PExpression expression, String stackTraceDescription) {
    145     // Evaluate expression.
    146     Value value = expressionEvaluator.evaluate(expression);
    147 
    148     // Now parse result, into new mini template.
    149     Template template =
    150         templateLoader.createTemp(stackTraceDescription, value.asString(), context
    151             .getAutoEscapeMode());
    152 
    153     // Intepret new template.
    154     try {
    155       template.render(context);
    156     } catch (IOException e) {
    157       throw new JSilverInterpreterException(e.getMessage());
    158     }
    159   }
    160 
    161   /**
    162    * <?cs linclude!'somefile.cs' > command. Lazily includes another template (at render time).
    163    * Throw an error if file does not exist.
    164    */
    165   @Override
    166   public void caseAHardLincludeCommand(AHardLincludeCommand node) {
    167     setLastPosition(node.getPosition());
    168     include(node.getExpression(), false);
    169   }
    170 
    171   /**
    172    * <?cs linclude:'somefile.cs' > command. Lazily includes another template (at render time).
    173    * Silently ignore if the included file does not exist.
    174    */
    175   @Override
    176   public void caseALincludeCommand(ALincludeCommand node) {
    177     setLastPosition(node.getPosition());
    178     include(node.getExpression(), true);
    179   }
    180 
    181   /**
    182    * <?cs include!'somefile.cs' > command. Throw an error if file does not exist.
    183    */
    184   @Override
    185   public void caseAHardIncludeCommand(AHardIncludeCommand node) {
    186     setLastPosition(node.getPosition());
    187     include(node.getExpression(), false);
    188   }
    189 
    190   /**
    191    * <?cs include:'somefile.cs' > command. Silently ignore if the included file does not
    192    * exist.
    193    */
    194   @Override
    195   public void caseAIncludeCommand(AIncludeCommand node) {
    196     setLastPosition(node.getPosition());
    197     include(node.getExpression(), true);
    198   }
    199 
    200   /**
    201    * <?cs set:x='y' > command.
    202    */
    203   @Override
    204   public void caseASetCommand(ASetCommand node) {
    205     setLastPosition(node.getPosition());
    206     String variableName = variableLocator.getVariableName(node.getVariable());
    207 
    208     try {
    209       Data variable = dataContext.findVariable(variableName, true);
    210       Value value = expressionEvaluator.evaluate(node.getExpression());
    211       variable.setValue(value.asString());
    212       // TODO: what about nested structures?
    213       // "set" was used to set a variable to a constant or escaped value like
    214       // <?cs set: x = "<b>X</b>" ?> or <?cs set: y = html_escape(x) ?>
    215       // Keep track of this so autoescaping code can take it into account.
    216       variable.setEscapeMode(value.getEscapeMode());
    217     } catch (UnsupportedOperationException e) {
    218       // An error occurred - probably due to trying to modify an UnmodifiableData
    219       throw new UnsupportedOperationException(createUnsupportedOperationMessage(node, context
    220           .getIncludedTemplateNames()), e);
    221     }
    222   }
    223 
    224   /**
    225    * &lt;?cs name:blah &gt; command. Writes out the name of the original variable referred to by a
    226    * given node.
    227    */
    228   @Override
    229   public void caseANameCommand(ANameCommand node) {
    230     setLastPosition(node.getPosition());
    231     String variableName = variableLocator.getVariableName(node.getVariable());
    232     Data variable = dataContext.findVariable(variableName, false);
    233     if (variable != null) {
    234       context.writeEscaped(variable.getSymlink().getName());
    235     }
    236   }
    237 
    238   /**
    239    * &lt;?cs if:blah &gt; ... &lt;?cs else &gt; ... &lt;?cs /if &gt; command.
    240    */
    241   @Override
    242   public void caseAIfCommand(AIfCommand node) {
    243     setLastPosition(node.getPosition());
    244     Value value = expressionEvaluator.evaluate(node.getExpression());
    245     if (value.asBoolean()) {
    246       node.getBlock().apply(this);
    247     } else {
    248       node.getOtherwise().apply(this);
    249     }
    250   }
    251 
    252 
    253   /**
    254    * &lt;?cs escape:'html' &gt; command. Changes default escaping function.
    255    */
    256   @Override
    257   public void caseAEscapeCommand(AEscapeCommand node) {
    258     setLastPosition(node.getPosition());
    259     Value value = expressionEvaluator.evaluate(node.getExpression());
    260     String escapeStrategy = value.asString();
    261 
    262     context.pushEscapingFunction(escapeStrategy);
    263     node.getCommand().apply(this);
    264     context.popEscapingFunction();
    265   }
    266 
    267   /**
    268    * A fake command injected by AutoEscaper.
    269    *
    270    * AutoEscaper determines the html context in which an include or lvar or evar command is called
    271    * and stores this context in the AAutoescapeCommand node.
    272    */
    273   @Override
    274   public void caseAAutoescapeCommand(AAutoescapeCommand node) {
    275     setLastPosition(node.getPosition());
    276     Value value = expressionEvaluator.evaluate(node.getExpression());
    277     String escapeStrategy = value.asString();
    278 
    279     EscapeMode mode = EscapeMode.computeEscapeMode(escapeStrategy);
    280 
    281     context.pushAutoEscapeMode(mode);
    282     node.getCommand().apply(this);
    283     context.popAutoEscapeMode();
    284   }
    285 
    286   /**
    287    * &lt;?cs with:x=Something &gt; ... &lt;?cs /with &gt; command. Aliases a value within a specific
    288    * scope.
    289    */
    290   @Override
    291   public void caseAWithCommand(AWithCommand node) {
    292     setLastPosition(node.getPosition());
    293     VariableLocator variableLocator = new VariableLocator(expressionEvaluator);
    294     String withVar = variableLocator.getVariableName(node.getVariable());
    295     Value value = expressionEvaluator.evaluate(node.getExpression());
    296 
    297     if (value instanceof VariableValue) {
    298       if (((VariableValue) value).getReference() == null) {
    299         // With refers to a non-existent variable. Do nothing.
    300         return;
    301       }
    302     }
    303 
    304     dataContext.pushVariableScope();
    305     setTempVariable(withVar, value);
    306     node.getCommand().apply(this);
    307     dataContext.popVariableScope();
    308   }
    309 
    310   /**
    311    * &lt;?cs loop:10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, starting at
    312    * zero.
    313    */
    314   @Override
    315   public void caseALoopToCommand(ALoopToCommand node) {
    316     setLastPosition(node.getPosition());
    317     int end = expressionEvaluator.evaluate(node.getExpression()).asNumber();
    318 
    319     // Start is always zero, increment is always 1, so end < 0 is invalid.
    320     if (end < 0) {
    321       return; // Incrementing the wrong way. Avoid infinite loop.
    322     }
    323 
    324     loop(node.getVariable(), 0, end, 1, node.getCommand());
    325   }
    326 
    327   /**
    328    * &lt;?cs loop:0,10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers.
    329    */
    330   @Override
    331   public void caseALoopCommand(ALoopCommand node) {
    332     setLastPosition(node.getPosition());
    333     int start = expressionEvaluator.evaluate(node.getStart()).asNumber();
    334     int end = expressionEvaluator.evaluate(node.getEnd()).asNumber();
    335 
    336     // Start is always zero, increment is always 1, so end < 0 is invalid.
    337     if (end < start) {
    338       return; // Incrementing the wrong way. Avoid infinite loop.
    339     }
    340 
    341     loop(node.getVariable(), start, end, 1, node.getCommand());
    342   }
    343 
    344   /**
    345    * &lt;?cs loop:0,10,2 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, with a
    346    * specific increment.
    347    */
    348   @Override
    349   public void caseALoopIncCommand(ALoopIncCommand node) {
    350     setLastPosition(node.getPosition());
    351     int start = expressionEvaluator.evaluate(node.getStart()).asNumber();
    352     int end = expressionEvaluator.evaluate(node.getEnd()).asNumber();
    353     int incr = expressionEvaluator.evaluate(node.getIncrement()).asNumber();
    354 
    355     if (incr == 0) {
    356       return; // No increment. Avoid infinite loop.
    357     }
    358     if (incr > 0 && start > end) {
    359       return; // Incrementing the wrong way. Avoid infinite loop.
    360     }
    361     if (incr < 0 && start < end) {
    362       return; // Incrementing the wrong way. Avoid infinite loop.
    363     }
    364 
    365     loop(node.getVariable(), start, end, incr, node.getCommand());
    366   }
    367 
    368   /**
    369    * &lt;?cs each:x=Stuff &gt; ... &lt;?cs /each &gt; command. Loops over child items of a data
    370    * node.
    371    */
    372   @Override
    373   public void caseAEachCommand(AEachCommand node) {
    374     setLastPosition(node.getPosition());
    375     Value expression = expressionEvaluator.evaluate(node.getExpression());
    376 
    377     if (expression instanceof VariableValue) {
    378       VariableValue variableValue = (VariableValue) expression;
    379       Data parent = variableValue.getReference();
    380       if (parent != null) {
    381         each(node.getVariable(), variableValue.getName(), parent, node.getCommand());
    382       }
    383     }
    384   }
    385 
    386   /**
    387    * &lt;?cs alt:someValue &gt; ... &lt;?cs /alt &gt; command. If value exists, write it, otherwise
    388    * write the body of the command.
    389    */
    390   @Override
    391   public void caseAAltCommand(AAltCommand node) {
    392     setLastPosition(node.getPosition());
    393     Value value = expressionEvaluator.evaluate(node.getExpression());
    394     if (value.asBoolean()) {
    395       writeVariable(value);
    396     } else {
    397       node.getCommand().apply(this);
    398     }
    399   }
    400 
    401   private void writeVariable(Value value) {
    402     if (template.getEscapeMode().isAutoEscapingMode()) {
    403       autoEscapeAndWriteVariable(value);
    404     } else if (value.isPartiallyEscaped()) {
    405       context.writeUnescaped(value.asString());
    406     } else {
    407       context.writeEscaped(value.asString());
    408     }
    409   }
    410 
    411   private void autoEscapeAndWriteVariable(Value value) {
    412     if (isTrustedValue(value) || value.isPartiallyEscaped()) {
    413       context.writeUnescaped(value.asString());
    414     } else {
    415       context.writeEscaped(value.asString());
    416     }
    417   }
    418 
    419   private boolean isTrustedValue(Value value) {
    420     // True if PropagateEscapeStatus is enabled and value has either been
    421     // escaped or contains a constant string.
    422     return context.getAutoEscapeOptions().getPropagateEscapeStatus()
    423         && !value.getEscapeMode().equals(EscapeMode.ESCAPE_NONE);
    424   }
    425 
    426   // ------------------------------------------------------------------------
    427   // MACROS
    428 
    429   /**
    430    * &lt;?cs def:someMacro(x,y) &gt; ... &lt;?cs /def &gt; command. Define a macro (available for
    431    * the remainder of the interpreter context.
    432    */
    433   @Override
    434   public void caseADefCommand(ADefCommand node) {
    435     String macroName = makeWord(node.getMacro());
    436     LinkedList<PVariable> arguments = node.getArguments();
    437     String[] argumentNames = new String[arguments.size()];
    438     int i = 0;
    439     for (PVariable argument : arguments) {
    440       if (!(argument instanceof ANameVariable)) {
    441         throw new JSilverInterpreterException("Invalid name for macro '" + macroName
    442             + "' argument " + i + " : " + argument);
    443       }
    444       argumentNames[i++] = ((ANameVariable) argument).getWord().getText();
    445     }
    446     // TODO: Should we enforce that macro args can't repeat the same
    447     // name?
    448     context.registerMacro(macroName, new InterpretedMacro(node.getCommand(), template, macroName,
    449         argumentNames, this, context));
    450   }
    451 
    452   private String makeWord(LinkedList<TWord> words) {
    453     if (words.size() == 1) {
    454       return words.getFirst().getText();
    455     }
    456     StringBuilder result = new StringBuilder();
    457     for (TWord word : words) {
    458       if (result.length() > 0) {
    459         result.append('.');
    460       }
    461       result.append(word.getText());
    462     }
    463     return result.toString();
    464   }
    465 
    466   /**
    467    * &lt;?cs call:someMacro(x,y) command. Call a macro. Need to create a new variable scope to hold
    468    * the local variables defined by the parameters of the macro definition
    469    */
    470   @Override
    471   public void caseACallCommand(ACallCommand node) {
    472     String macroName = makeWord(node.getMacro());
    473     Macro macro = context.findMacro(macroName);
    474 
    475     // Make sure that the number of arguments passed to the macro match the
    476     // number expected.
    477     if (node.getArguments().size() != macro.getArgumentCount()) {
    478       throw new JSilverInterpreterException("Number of arguments to macro " + macroName + " ("
    479           + node.getArguments().size() + ") does not match " + "number of expected arguments ("
    480           + macro.getArgumentCount() + ")");
    481     }
    482 
    483     int numArgs = node.getArguments().size();
    484     if (numArgs > 0) {
    485       Value[] argValues = new Value[numArgs];
    486 
    487       // We must first evaluate the parameters we are passing or there could be
    488       // conflicts if new argument names match existing variables.
    489       Iterator<PExpression> argumentValues = node.getArguments().iterator();
    490       for (int i = 0; argumentValues.hasNext(); i++) {
    491         argValues[i] = expressionEvaluator.evaluate(argumentValues.next());
    492       }
    493 
    494       // No need to bother pushing and popping the variable scope stack
    495       // if there are no new local variables to declare.
    496       dataContext.pushVariableScope();
    497 
    498       for (int i = 0; i < argValues.length; i++) {
    499         setTempVariable(macro.getArgumentName(i), argValues[i]);
    500       }
    501     }
    502     try {
    503       macro.render(context);
    504     } catch (IOException e) {
    505       throw new JSilverIOException(e);
    506     }
    507     if (numArgs > 0) {
    508       // No need to bother pushing and popping the variable scope stack
    509       // if there are no new local variables to declare.
    510       dataContext.popVariableScope();
    511     }
    512   }
    513 
    514   // ------------------------------------------------------------------------
    515   // HELPERS
    516   //
    517   // Much of the functionality in this section could easily be inlined,
    518   // however it makes the rest of the interpreter much easier to understand
    519   // and refactor with them defined here.
    520 
    521   private void each(PVariable variable, String parentName, Data items, PCommand command) {
    522     // Since HDF variables are now passed to macro parameters by path name
    523     // we need to create a path for each child when generating the
    524     // VariableValue object.
    525     VariableLocator variableLocator = new VariableLocator(expressionEvaluator);
    526     String eachVar = variableLocator.getVariableName(variable);
    527     StringBuilder pathBuilder = new StringBuilder(parentName);
    528     pathBuilder.append('.');
    529     int length = pathBuilder.length();
    530     dataContext.pushVariableScope();
    531     for (Data child : items.getChildren()) {
    532       pathBuilder.delete(length, pathBuilder.length());
    533       pathBuilder.append(child.getName());
    534       setTempVariable(eachVar, Value.variableValue(pathBuilder.toString(), dataContext));
    535       command.apply(this);
    536     }
    537     dataContext.popVariableScope();
    538   }
    539 
    540   private void loop(PVariable loopVar, int start, int end, int incr, PCommand command) {
    541     VariableLocator variableLocator = new VariableLocator(expressionEvaluator);
    542     String varName = variableLocator.getVariableName(loopVar);
    543 
    544     dataContext.pushVariableScope();
    545     // Loop deals with counting forward or backwards.
    546     for (int index = start; incr > 0 ? index <= end : index >= end; index += incr) {
    547       // We reuse the same scope for efficiency and simply overwrite the
    548       // previous value of the loop variable.
    549       dataContext.createLocalVariableByValue(varName, String.valueOf(index), index == start,
    550           index == end);
    551 
    552       command.apply(this);
    553     }
    554     dataContext.popVariableScope();
    555   }
    556 
    557   /**
    558    * Code common to all three include commands.
    559    *
    560    * @param expression expression representing name of file to include.
    561    * @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template
    562    *        loader should be ignored, {@code false} otherwise.
    563    */
    564   private void include(PExpression expression, boolean ignoreMissingFile) {
    565     // Evaluate expression.
    566     Value path = expressionEvaluator.evaluate(expression);
    567 
    568     String templateName = path.asString();
    569     if (!context.pushIncludeStackEntry(templateName)) {
    570       throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context
    571           .getIncludedTemplateNames()));
    572     }
    573 
    574     loadAndRenderIncludedTemplate(templateName, ignoreMissingFile);
    575 
    576     if (!context.popIncludeStackEntry(templateName)) {
    577       // Include stack trace is corrupted
    578       throw new IllegalStateException("Unable to find on include stack: " + templateName);
    579     }
    580   }
    581 
    582   private String createIncludeLoopErrorMessage(String templateName, Iterable<String> includeStack) {
    583     StringBuilder message = new StringBuilder();
    584     message.append("File included twice: ");
    585     message.append(templateName);
    586 
    587     message.append(" Include stack:");
    588     for (String fileName : includeStack) {
    589       message.append("\n -> ");
    590       message.append(fileName);
    591     }
    592     message.append("\n -> ");
    593     message.append(templateName);
    594     return message.toString();
    595   }
    596 
    597   private String createUnsupportedOperationMessage(PCommand node, Iterable<String> includeStack) {
    598     StringBuilder message = new StringBuilder();
    599 
    600     message.append("exception thrown while parsing node: ");
    601     message.append(node.toString());
    602     message.append(" (class ").append(node.getClass().getSimpleName()).append(")");
    603     message.append("\nTemplate include stack: ");
    604 
    605     for (Iterator<String> iter = includeStack.iterator(); iter.hasNext();) {
    606       message.append(iter.next());
    607       if (iter.hasNext()) {
    608         message.append(" -> ");
    609       }
    610     }
    611     message.append("\n");
    612 
    613     return message.toString();
    614   }
    615 
    616   // This method should ONLY be called from include()
    617   private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile) {
    618     // Now load new template with given name.
    619     Template template = null;
    620     try {
    621       template =
    622           templateLoader.load(templateName, context.getResourceLoader(), context
    623               .getAutoEscapeMode());
    624     } catch (RuntimeException e) {
    625       if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) {
    626         return;
    627       } else {
    628         throw e;
    629       }
    630     }
    631 
    632     // Intepret loaded template.
    633     try {
    634       // TODO: Execute lincludes (but not includes) in a separate
    635       // context.
    636       template.render(context);
    637     } catch (IOException e) {
    638       throw new JSilverInterpreterException(e.getMessage());
    639     }
    640   }
    641 
    642   private void setLastPosition(PPosition position) {
    643     // Walks position node which will eventually result in calling
    644     // caseTCsOpen().
    645     position.apply(this);
    646   }
    647 
    648   /**
    649    * Every time a &lt;cs token is found, grab the line and position (for helpful error messages).
    650    */
    651   @Override
    652   public void caseTCsOpen(TCsOpen node) {
    653     int line = node.getLine();
    654     int column = node.getPos();
    655     context.setCurrentPosition(line, column);
    656   }
    657 
    658   private void setTempVariable(String variableName, Value value) {
    659     if (value instanceof VariableValue) {
    660       // If the value is a Data variable name, then we store a reference to its
    661       // name as discovered by the expression evaluator and resolve it each
    662       // time for correctness.
    663       dataContext.createLocalVariableByPath(variableName, ((VariableValue) value).getName());
    664     } else {
    665       dataContext.createLocalVariableByValue(variableName, value.asString(), value.getEscapeMode());
    666     }
    667   }
    668 
    669 }
    670