• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TemplateInterpreter(Template template, TemplateLoader templateLoader, RenderingContext context, FunctionExecutor functionExecutor)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
caseADataCommand(ADataCommand node)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
caseAVarCommand(AVarCommand node)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
caseAUvarCommand(AUvarCommand node)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
caseALvarCommand(ALvarCommand node)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
caseAEvarCommand(AEvarCommand node)139   public void caseAEvarCommand(AEvarCommand node) {
140     setLastPosition(node.getPosition());
141     evaluateVariable(node.getExpression(), "[evar expression]");
142   }
143 
evaluateVariable(PExpression expression, String stackTraceDescription)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
caseAHardLincludeCommand(AHardLincludeCommand node)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
caseALincludeCommand(ALincludeCommand node)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
caseAHardIncludeCommand(AHardIncludeCommand node)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
caseAIncludeCommand(AIncludeCommand node)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
caseASetCommand(ASetCommand node)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
caseANameCommand(ANameCommand node)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
caseAIfCommand(AIfCommand node)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
caseAEscapeCommand(AEscapeCommand node)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
caseAAutoescapeCommand(AAutoescapeCommand node)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
caseAWithCommand(AWithCommand node)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
caseALoopToCommand(ALoopToCommand node)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
caseALoopCommand(ALoopCommand node)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
caseALoopIncCommand(ALoopIncCommand node)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
caseAEachCommand(AEachCommand node)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
caseAAltCommand(AAltCommand node)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 
writeVariable(Value value)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 
autoEscapeAndWriteVariable(Value value)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 
isTrustedValue(Value value)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
caseADefCommand(ADefCommand node)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 
makeWord(LinkedList<TWord> words)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
caseACallCommand(ACallCommand node)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 
each(PVariable variable, String parentName, Data items, PCommand command)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 
loop(PVariable loopVar, int start, int end, int incr, PCommand command)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    */
include(PExpression expression, boolean ignoreMissingFile)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 
createIncludeLoopErrorMessage(String templateName, Iterable<String> includeStack)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 
createUnsupportedOperationMessage(PCommand node, Iterable<String> includeStack)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()
loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile)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 
setLastPosition(PPosition position)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
caseTCsOpen(TCsOpen node)652   public void caseTCsOpen(TCsOpen node) {
653     int line = node.getLine();
654     int column = node.getPos();
655     context.setCurrentPosition(line, column);
656   }
657 
setTempVariable(String variableName, Value value)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