/*
 * [The "BSD license"]
 *  Copyright (c) 2011 Terence Parr
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.stringtemplate.v4;

import org.stringtemplate.v4.compiler.*;
import org.stringtemplate.v4.compiler.Compiler;
import org.stringtemplate.v4.debug.*;
import org.stringtemplate.v4.gui.STViz;
import org.stringtemplate.v4.misc.*;

import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.*;

This class knows how to execute template bytecodes relative to a particular STGroup. To execute the byte codes, we need an output stream and a reference to an ST instance. That instance's ST.impl field points at a CompiledST, which contains all of the byte codes and other information relevant to execution.

This interpreter is a stack-based bytecode interpreter. All operands go onto an operand stack.

If debug set, we track interpreter events. For now, I am only tracking instance creation events. These are used by STViz to pair up output chunks with the template expressions that generate them.

We create a new interpreter for each invocation of ST.render, ST.inspect, or ST.getEvents.

/** * This class knows how to execute template bytecodes relative to a particular * {@link STGroup}. To execute the byte codes, we need an output stream and a * reference to an {@link ST} instance. That instance's {@link ST#impl} field * points at a {@link CompiledST}, which contains all of the byte codes and * other information relevant to execution. * <p> * This interpreter is a stack-based bytecode interpreter. All operands go onto * an operand stack.</p> * <p> * If {@link #debug} set, we track interpreter events. For now, I am only * tracking instance creation events. These are used by {@link STViz} to pair up * output chunks with the template expressions that generate them.</p> * <p> * We create a new interpreter for each invocation of * {@link ST#render}, {@link ST#inspect}, or {@link ST#getEvents}.</p> */
public class Interpreter { public enum Option { ANCHOR, FORMAT, NULL, SEPARATOR, WRAP } public static final int DEFAULT_OPERAND_STACK_SIZE = 100; public static final Set<String> predefinedAnonSubtemplateAttributes = new HashSet<String>() { { add("i"); add("i0"); } };
Operand stack, grows upwards.
/** Operand stack, grows upwards. */
Object[] operands = new Object[DEFAULT_OPERAND_STACK_SIZE];
Stack pointer register.
/** Stack pointer register. */
int sp = -1;
The number of characters written on this template line so far.
/** The number of characters written on this template line so far. */
int nwline = 0;
Render template with respect to this group. @see ST#groupThatCreatedThisInstance @see CompiledST#nativeGroup
/** Render template with respect to this group. * * @see ST#groupThatCreatedThisInstance * @see CompiledST#nativeGroup */
STGroup group;
For renderers, we have to pass in the locale.
/** For renderers, we have to pass in the locale. */
Locale locale; ErrorManager errMgr;
Dump bytecode instructions as they are executed. This field is mostly for StringTemplate development.
/** * Dump bytecode instructions as they are executed. This field is mostly for * StringTemplate development. */
public static boolean trace = false;
If trace is true, track trace here.
/** If {@link #trace} is {@code true}, track trace here. */
// TODO: track the pieces not a string and track what it contributes to output protected List<String> executeTrace;
When true, track events inside templates and in events.
/** When {@code true}, track events inside templates and in {@link #events}. */
public boolean debug = false;
Track everything happening in interpreter across all templates if debug. The last event in this field is the EvalTemplateEvent for the root template.
/** * Track everything happening in interpreter across all templates if * {@link #debug}. The last event in this field is the * {@link EvalTemplateEvent} for the root template. */
protected List<InterpEvent> events; public Interpreter(STGroup group, boolean debug) { this(group,Locale.getDefault(),group.errMgr, debug); } public Interpreter(STGroup group, Locale locale, boolean debug) { this(group, locale, group.errMgr, debug); } public Interpreter(STGroup group, ErrorManager errMgr, boolean debug) { this(group,Locale.getDefault(),errMgr, debug); } public Interpreter(STGroup group, Locale locale, ErrorManager errMgr, boolean debug) { this.group = group; this.locale = locale; this.errMgr = errMgr; this.debug = debug; if ( debug ) { events = new ArrayList<InterpEvent>(); executeTrace = new ArrayList<String>(); } } // public static int[] count = new int[Bytecode.MAX_BYTECODE+1]; // public static void dumpOpcodeFreq() { // System.out.println("#### instr freq:"); // for (int i=1; i<=Bytecode.MAX_BYTECODE; i++) { // System.out.println(count[i]+" "+Bytecode.instructions[i].name); // } // }
Execute template self and return how many characters it wrote to out.
Returns:the number of characters written to out
/** Execute template {@code self} and return how many characters it wrote to {@code out}. * * @return the number of characters written to {@code out} */
public int exec(STWriter out, InstanceScope scope) { final ST self = scope.st; if ( trace ) System.out.println("exec("+self.getName()+")"); try { setDefaultArguments(out, scope); return _exec(out, scope); } catch (Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); pw.flush(); errMgr.runTimeError(this, scope, ErrorType.INTERNAL_ERROR, "internal error: "+sw.toString()); return 0; } } protected int _exec(STWriter out, InstanceScope scope) { final ST self = scope.st; int start = out.index(); // track char we're about to write int prevOpcode = 0; int n = 0; // how many char we write out int nargs; int nameIndex; int addr; String name; Object o, left, right; ST st; Object[] options; byte[] code = self.impl.instrs; // which code block are we executing int ip = 0; while ( ip < self.impl.codeSize ) { if ( trace || debug ) trace(scope, ip); short opcode = code[ip]; //count[opcode]++; scope.ip = ip; ip++; //jump to next instruction or first byte of operand switch (opcode) { case Bytecode.INSTR_LOAD_STR : // just testing... load_str(self,ip); ip += Bytecode.OPND_SIZE_IN_BYTES; break; case Bytecode.INSTR_LOAD_ATTR : nameIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; name = self.impl.strings[nameIndex]; try { o = getAttribute(scope, name); if ( o==ST.EMPTY_ATTR ) o = null; } catch (STNoSuchAttributeException nsae) { errMgr.runTimeError(this, scope, ErrorType.NO_SUCH_ATTRIBUTE, name); o = null; } operands[++sp] = o; break; case Bytecode.INSTR_LOAD_LOCAL: int valueIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; o = self.locals[valueIndex]; if ( o==ST.EMPTY_ATTR ) o = null; operands[++sp] = o; break; case Bytecode.INSTR_LOAD_PROP : nameIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; o = operands[sp--]; name = self.impl.strings[nameIndex]; operands[++sp] = getObjectProperty(out, scope, o, name); break; case Bytecode.INSTR_LOAD_PROP_IND : Object propName = operands[sp--]; o = operands[sp]; operands[sp] = getObjectProperty(out, scope, o, propName); break; case Bytecode.INSTR_NEW : nameIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; name = self.impl.strings[nameIndex]; nargs = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; // look up in original hierarchy not enclosing template (variable group) // see TestSubtemplates.testEvalSTFromAnotherGroup() st = self.groupThatCreatedThisInstance.getEmbeddedInstanceOf(this, scope, name); // get n args and store into st's attr list storeArgs(scope, nargs, st); sp -= nargs; operands[++sp] = st; break; case Bytecode.INSTR_NEW_IND: nargs = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; name = (String)operands[sp-nargs]; st = self.groupThatCreatedThisInstance.getEmbeddedInstanceOf(this, scope, name); storeArgs(scope, nargs, st); sp -= nargs; sp--; // pop template name operands[++sp] = st; break; case Bytecode.INSTR_NEW_BOX_ARGS : nameIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; name = self.impl.strings[nameIndex]; Map<String, Object> attrs = (ArgumentsMap)operands[sp--]; // look up in original hierarchy not enclosing template (variable group) // see TestSubtemplates.testEvalSTFromAnotherGroup() st = self.groupThatCreatedThisInstance.getEmbeddedInstanceOf(this, scope, name); // get n args and store into st's attr list storeArgs(scope, attrs, st); operands[++sp] = st; break; case Bytecode.INSTR_SUPER_NEW : nameIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; name = self.impl.strings[nameIndex]; nargs = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; super_new(scope, name, nargs); break; case Bytecode.INSTR_SUPER_NEW_BOX_ARGS : nameIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; name = self.impl.strings[nameIndex]; attrs = (ArgumentsMap)operands[sp--]; super_new(scope, name, attrs); break; case Bytecode.INSTR_STORE_OPTION: int optionIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; o = operands[sp--]; // value to store options = (Object[])operands[sp]; // get options options[optionIndex] = o; // store value into options on stack break; case Bytecode.INSTR_STORE_ARG: nameIndex = getShort(code, ip); name = self.impl.strings[nameIndex]; ip += Bytecode.OPND_SIZE_IN_BYTES; o = operands[sp--]; attrs = (ArgumentsMap)operands[sp]; attrs.put(name, o); // leave attrs on stack break; case Bytecode.INSTR_WRITE : o = operands[sp--]; int n1 = writeObjectNoOptions(out, scope, o); n += n1; nwline += n1; break; case Bytecode.INSTR_WRITE_OPT : options = (Object[])operands[sp--]; // get options o = operands[sp--]; // get option to write int n2 = writeObjectWithOptions(out, scope, o, options); n += n2; nwline += n2; break; case Bytecode.INSTR_MAP : st = (ST)operands[sp--]; // get prototype off stack o = operands[sp--]; // get object to map prototype across map(scope,o,st); break; case Bytecode.INSTR_ROT_MAP : int nmaps = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; List<ST> templates = new ArrayList<ST>(); for (int i=nmaps-1; i>=0; i--) templates.add((ST)operands[sp-i]); sp -= nmaps; o = operands[sp--]; if ( o!=null ) rot_map(scope,o,templates); break; case Bytecode.INSTR_ZIP_MAP: st = (ST)operands[sp--]; nmaps = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; List<Object> exprs = new ObjectList(); for (int i=nmaps-1; i>=0; i--) exprs.add(operands[sp-i]); sp -= nmaps; operands[++sp] = zip_map(scope, exprs, st); break; case Bytecode.INSTR_BR : ip = getShort(code, ip); break; case Bytecode.INSTR_BRF : addr = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; o = operands[sp--]; // <if(expr)>...<endif> if ( !testAttributeTrue(o) ) ip = addr; // jump break; case Bytecode.INSTR_OPTIONS : operands[++sp] = new Object[Compiler.NUM_OPTIONS]; break; case Bytecode.INSTR_ARGS: operands[++sp] = new ArgumentsMap(); break; case Bytecode.INSTR_PASSTHRU : nameIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; name = self.impl.strings[nameIndex]; attrs = (ArgumentsMap)operands[sp]; passthru(scope, name, attrs); break; case Bytecode.INSTR_LIST : operands[++sp] = new ObjectList(); break; case Bytecode.INSTR_ADD : o = operands[sp--]; // pop value List<Object> list = (ObjectList)operands[sp]; // don't pop list addToList(scope, list, o); break; case Bytecode.INSTR_TOSTR : // replace with string value; early eval operands[sp] = toString(out, scope, operands[sp]); break; case Bytecode.INSTR_FIRST : operands[sp] = first(scope, operands[sp]); break; case Bytecode.INSTR_LAST : operands[sp] = last(scope, operands[sp]); break; case Bytecode.INSTR_REST : operands[sp] = rest(scope, operands[sp]); break; case Bytecode.INSTR_TRUNC : operands[sp] = trunc(scope, operands[sp]); break; case Bytecode.INSTR_STRIP : operands[sp] = strip(scope, operands[sp]); break; case Bytecode.INSTR_TRIM : o = operands[sp--]; if ( o.getClass() == String.class ) { operands[++sp] = ((String)o).trim(); } else { errMgr.runTimeError(this, scope, ErrorType.EXPECTING_STRING, "trim", o.getClass().getName()); operands[++sp] = o; } break; case Bytecode.INSTR_LENGTH : operands[sp] = length(operands[sp]); break; case Bytecode.INSTR_STRLEN : o = operands[sp--]; if ( o.getClass() == String.class ) { operands[++sp] = ((String)o).length(); } else { errMgr.runTimeError(this, scope, ErrorType.EXPECTING_STRING, "strlen", o.getClass().getName()); operands[++sp] = 0; } break; case Bytecode.INSTR_REVERSE : operands[sp] = reverse(scope, operands[sp]); break; case Bytecode.INSTR_NOT : operands[sp] = !testAttributeTrue(operands[sp]); break; case Bytecode.INSTR_OR : right = operands[sp--]; left = operands[sp--]; operands[++sp] = testAttributeTrue(left) || testAttributeTrue(right); break; case Bytecode.INSTR_AND : right = operands[sp--]; left = operands[sp--]; operands[++sp] = testAttributeTrue(left) && testAttributeTrue(right); break; case Bytecode.INSTR_INDENT : int strIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; indent(out, scope, strIndex); break; case Bytecode.INSTR_DEDENT : out.popIndentation(); break; case Bytecode.INSTR_NEWLINE : try { if ( (prevOpcode==0 && !self.isAnonSubtemplate() && !self.impl.isRegion) || prevOpcode==Bytecode.INSTR_NEWLINE || prevOpcode==Bytecode.INSTR_INDENT || nwline>0 ) { out.write(Misc.newline); } nwline = 0; } catch (IOException ioe) { errMgr.IOError(self, ErrorType.WRITE_IO_ERROR, ioe); } break; case Bytecode.INSTR_NOOP : break; case Bytecode.INSTR_POP : sp--; // throw away top of stack break; case Bytecode.INSTR_NULL : operands[++sp] = null; break; case Bytecode.INSTR_TRUE : operands[++sp] = true; break; case Bytecode.INSTR_FALSE : operands[++sp] = false; break; case Bytecode.INSTR_WRITE_STR : strIndex = getShort(code, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; o = self.impl.strings[strIndex]; n1 = writeObjectNoOptions(out, scope, o); n += n1; nwline += n1; break; // TODO: generate this optimization // case Bytecode.INSTR_WRITE_LOCAL: // valueIndex = getShort(code, ip); // ip += Bytecode.OPND_SIZE_IN_BYTES; // o = self.locals[valueIndex]; // if ( o==ST.EMPTY_ATTR ) o = null; // n1 = writeObjectNoOptions(out, self, o); // n += n1; // nwline += n1; // break; default : errMgr.internalError(self, "invalid bytecode @ "+(ip-1)+": "+opcode, null); self.impl.dump(); } prevOpcode = opcode; } if ( debug ) { int stop = out.index() - 1; EvalTemplateEvent e = new EvalTemplateEvent(scope, start, stop); trackDebugEvent(scope, e); } return n; } void load_str(ST self, int ip) { int strIndex = getShort(self.impl.instrs, ip); ip += Bytecode.OPND_SIZE_IN_BYTES; operands[++sp] = self.impl.strings[strIndex]; } // TODO: refactor to remove dup'd code void super_new(InstanceScope scope, String name, int nargs) { final ST self = scope.st; ST st = null; CompiledST imported = self.impl.nativeGroup.lookupImportedTemplate(name); if ( imported==null ) { errMgr.runTimeError(this, scope, ErrorType.NO_IMPORTED_TEMPLATE, name); st = self.groupThatCreatedThisInstance.createStringTemplateInternally(new CompiledST()); } else { st = imported.nativeGroup.getEmbeddedInstanceOf(this, scope, name); st.groupThatCreatedThisInstance = group; } // get n args and store into st's attr list storeArgs(scope, nargs, st); sp -= nargs; operands[++sp] = st; } void super_new(InstanceScope scope, String name, Map<String,Object> attrs) { final ST self = scope.st; ST st = null; CompiledST imported = self.impl.nativeGroup.lookupImportedTemplate(name); if ( imported==null ) { errMgr.runTimeError(this, scope, ErrorType.NO_IMPORTED_TEMPLATE, name); st = self.groupThatCreatedThisInstance.createStringTemplateInternally(new CompiledST()); } else { st = imported.nativeGroup.createStringTemplateInternally(imported); st.groupThatCreatedThisInstance = group; } // get n args and store into st's attr list storeArgs(scope, attrs, st); operands[++sp] = st; } void passthru(InstanceScope scope, String templateName, Map<String,Object> attrs) { CompiledST c = group.lookupTemplate(templateName); if ( c==null ) return; // will get error later if ( c.formalArguments==null ) return; for (FormalArgument arg : c.formalArguments.values()) { // if not already set by user, set to value from outer scope if ( !attrs.containsKey(arg.name) ) { //System.out.println("arg "+arg.name+" missing"); try { Object o = getAttribute(scope, arg.name); // If the attribute exists but there is no value and // the formal argument has no default value, make it null. if ( o==ST.EMPTY_ATTR && arg.defaultValueToken==null ) { attrs.put(arg.name, null); } // Else, the attribute has an existing value, set arg. else if ( o!=ST.EMPTY_ATTR ) { attrs.put(arg.name, o); } } catch (STNoSuchAttributeException nsae) { // if no such attribute exists for arg.name, set parameter // if no default value if ( arg.defaultValueToken==null ) { errMgr.runTimeError(this, scope, ErrorType.NO_SUCH_ATTRIBUTE_PASS_THROUGH, arg.name); attrs.put(arg.name, null); } } } } } void storeArgs(InstanceScope scope, Map<String,Object> attrs, ST st) { boolean noSuchAttributeReported = false; if (attrs != null) { for (Map.Entry<String, Object> argument : attrs.entrySet()) { if (!st.impl.hasFormalArgs) { if (st.impl.formalArguments == null || !st.impl.formalArguments.containsKey(argument.getKey())) { try { // we clone the CompiledST to prevent modifying the original // formalArguments map during interpretation. st.impl = st.impl.clone(); st.add(argument.getKey(), argument.getValue()); } catch (CloneNotSupportedException ex) { noSuchAttributeReported = true; errMgr.runTimeError(this, scope, ErrorType.NO_SUCH_ATTRIBUTE, argument.getKey()); } } else { st.rawSetAttribute(argument.getKey(), argument.getValue()); } } else { // don't let it throw an exception in rawSetAttribute if ( st.impl.formalArguments==null || !st.impl.formalArguments.containsKey(argument.getKey()) ) { noSuchAttributeReported = true; errMgr.runTimeError(this, scope, ErrorType.NO_SUCH_ATTRIBUTE, argument.getKey()); continue; } st.rawSetAttribute(argument.getKey(), argument.getValue()); } } } if (st.impl.hasFormalArgs) { boolean argumentCountMismatch = false; Map<String, FormalArgument> formalArguments = st.impl.formalArguments; if (formalArguments == null) { formalArguments = Collections.emptyMap(); } // first make sure that all non-default arguments are specified // ignore this check if a NO_SUCH_ATTRIBUTE error already occurred if (!noSuchAttributeReported) { for (Map.Entry<String, FormalArgument> formalArgument : formalArguments.entrySet()) { if (formalArgument.getValue().defaultValueToken != null || formalArgument.getValue().defaultValue != null) { // this argument has a default value, so it doesn't need to appear in attrs continue; } if (attrs == null || !attrs.containsKey(formalArgument.getKey())) { argumentCountMismatch = true; break; } } } // next make sure there aren't too many arguments. note that the names // of arguments are checked below as they are applied to the template // instance, so there's no need to do that here. if (attrs != null && attrs.size() > formalArguments.size()) { argumentCountMismatch = true; } if (argumentCountMismatch) { int nargs = attrs != null ? attrs.size() : 0; int nformalArgs = formalArguments.size(); errMgr.runTimeError(this, scope, ErrorType.ARGUMENT_COUNT_MISMATCH, nargs, st.impl.name, nformalArgs); } } } void storeArgs(InstanceScope scope, int nargs, ST st) { if ( nargs>0 && !st.impl.hasFormalArgs && st.impl.formalArguments==null ) { st.add(ST.IMPLICIT_ARG_NAME, null); // pretend we have "it" arg } int nformalArgs = 0; if ( st.impl.formalArguments!=null ) nformalArgs = st.impl.formalArguments.size(); int firstArg = sp-(nargs-1); int numToStore = Math.min(nargs, nformalArgs); if ( st.impl.isAnonSubtemplate ) nformalArgs -= predefinedAnonSubtemplateAttributes.size(); if ( nargs < (nformalArgs-st.impl.numberOfArgsWithDefaultValues) || nargs > nformalArgs ) { errMgr.runTimeError(this, scope, ErrorType.ARGUMENT_COUNT_MISMATCH, nargs, st.impl.name, nformalArgs); } if ( st.impl.formalArguments==null ) return; Iterator<String> argNames = st.impl.formalArguments.keySet().iterator(); for (int i=0; i<numToStore; i++) { Object o = operands[firstArg+i]; // value to store String argName = argNames.next(); st.rawSetAttribute(argName, o); } } protected void indent(STWriter out, InstanceScope scope, int strIndex) { String indent = scope.st.impl.strings[strIndex]; if ( debug ) { int start = out.index(); // track char we're about to write EvalExprEvent e = new IndentEvent(scope, start, start + indent.length() - 1, getExprStartChar(scope), getExprStopChar(scope)); trackDebugEvent(scope, e); } out.pushIndentation(indent); }
Write out an expression result that doesn't use expression options. E.g., <name>
/** Write out an expression result that doesn't use expression options. * E.g., {@code <name>} */
protected int writeObjectNoOptions(STWriter out, InstanceScope scope, Object o) { int start = out.index(); // track char we're about to write int n = writeObject(out, scope, o, null); if ( debug ) { EvalExprEvent e = new EvalExprEvent(scope, start, out.index() - 1, getExprStartChar(scope), getExprStopChar(scope)); trackDebugEvent(scope, e); } return n; }
Write out an expression result that uses expression options. E.g., <names; separator=", ">
/** Write out an expression result that uses expression options. * E.g., {@code <names; separator=", ">} */
protected int writeObjectWithOptions(STWriter out, InstanceScope scope, Object o, Object[] options) { int start = out.index(); // track char we're about to write // precompute all option values (render all the way to strings) String[] optionStrings = null; if ( options!=null ) { optionStrings = new String[options.length]; for (int i=0; i<Compiler.NUM_OPTIONS; i++) { optionStrings[i] = toString(out, scope, options[i]); } } if ( options!=null && options[Option.ANCHOR.ordinal()]!=null ) { out.pushAnchorPoint(); } int n = writeObject(out, scope, o, optionStrings); if ( options!=null && options[Option.ANCHOR.ordinal()]!=null ) { out.popAnchorPoint(); } if ( debug ) { EvalExprEvent e = new EvalExprEvent(scope, start, out.index() - 1, getExprStartChar(scope), getExprStopChar(scope)); trackDebugEvent(scope, e); } return n; }
Generic method to emit text for an object. It differentiates between templates, iterable objects, and plain old Java objects (POJOs)
/** Generic method to emit text for an object. It differentiates * between templates, iterable objects, and plain old Java objects (POJOs) */
protected int writeObject(STWriter out, InstanceScope scope, Object o, String[] options) { int n = 0; if ( o == null ) { if ( options!=null && options[Option.NULL.ordinal()]!=null ) { o = options[Option.NULL.ordinal()]; } else return 0; } if ( o instanceof ST ) { scope = new InstanceScope(scope, (ST)o); if ( options!=null && options[Option.WRAP.ordinal()]!=null ) { // if we have a wrap string, then inform writer it // might need to wrap try { out.writeWrap(options[Option.WRAP.ordinal()]); } catch (IOException ioe) { errMgr.IOError(scope.st, ErrorType.WRITE_IO_ERROR, ioe); } } n = exec(out, scope); } else { o = convertAnythingIteratableToIterator(scope, o); // normalize try { if ( o instanceof Iterator) n = writeIterator(out, scope, o, options); else n = writePOJO(out, scope, o, options); } catch (IOException ioe) { errMgr.IOError(scope.st, ErrorType.WRITE_IO_ERROR, ioe, o); } } return n; } protected int writeIterator(STWriter out, InstanceScope scope, Object o, String[] options) throws IOException { if ( o==null ) return 0; int n = 0; Iterator<?> it = (Iterator<?>)o; String separator = null; if ( options!=null ) separator = options[Option.SEPARATOR.ordinal()]; boolean seenAValue = false; while ( it.hasNext() ) { Object iterValue = it.next(); // Emit separator if we're beyond first value boolean needSeparator = seenAValue && separator!=null && // we have a separator and (iterValue!=null || // either we have a value options[Option.NULL.ordinal()]!=null); // or no value but null option if ( needSeparator ) n += out.writeSeparator(separator); int nw = writeObject(out, scope, iterValue, options); if ( nw > 0 ) seenAValue = true; n += nw; } return n; } protected int writePOJO(STWriter out, InstanceScope scope, Object o, String[] options) throws IOException { String formatString = null; if ( options!=null ) formatString = options[Option.FORMAT.ordinal()]; // ask the native group defining the surrounding template for the renderer AttributeRenderer r = scope.st.impl.nativeGroup.getAttributeRenderer(o.getClass()); String v; if ( r!=null ) v = r.toString(o, formatString, locale); else v = o.toString(); int n; if ( options!=null && options[Option.WRAP.ordinal()]!=null ) { n = out.write(v, options[Option.WRAP.ordinal()]); } else { n = out.write(v); } return n; } protected int getExprStartChar(InstanceScope scope) { Interval templateLocation = scope.st.impl.sourceMap[scope.ip]; if ( templateLocation!=null ) return templateLocation.a; return -1; } protected int getExprStopChar(InstanceScope scope) { Interval templateLocation = scope.st.impl.sourceMap[scope.ip]; if ( templateLocation!=null ) return templateLocation.b; return -1; } protected void map(InstanceScope scope, Object attr, final ST st) { rot_map(scope, attr, new ArrayList<ST>() {{add(st);}}); }
Renders expressions of the form <names:a()> or <names:a(),b()>.
/** * Renders expressions of the form {@code <names:a()>} or * {@code <names:a(),b()>}. */
protected void rot_map(InstanceScope scope, Object attr, List<ST> prototypes) { if ( attr==null ) { operands[++sp] = null; return; } attr = convertAnythingIteratableToIterator(scope, attr); if ( attr instanceof Iterator ) { List<ST> mapped = rot_map_iterator(scope, (Iterator) attr, prototypes); operands[++sp] = mapped; } else { // if only single value, just apply first template to sole value ST proto = prototypes.get(0); ST st = group.createStringTemplateInternally(proto); if ( st!=null ) { setFirstArgument(scope, st, attr); if ( st.impl.isAnonSubtemplate ) { st.rawSetAttribute("i0", 0); st.rawSetAttribute("i", 1); } operands[++sp] = st; } else { operands[++sp] = null; } } } protected List<ST> rot_map_iterator(InstanceScope scope, Iterator<?> attr, List<ST> prototypes) { List<ST> mapped = new ArrayList<ST>(); Iterator<?> iter = attr; int i0 = 0; int i = 1; int ti = 0; while ( iter.hasNext() ) { Object iterValue = iter.next(); if ( iterValue == null ) { mapped.add(null); continue; } int templateIndex = ti % prototypes.size(); // rotate through ti++; ST proto = prototypes.get(templateIndex); ST st = group.createStringTemplateInternally(proto); setFirstArgument(scope, st, iterValue); if ( st.impl.isAnonSubtemplate ) { st.rawSetAttribute("i0", i0); st.rawSetAttribute("i", i); } mapped.add(st); i0++; i++; } return mapped; }
Renders expressions of the form <names,phones:{n,p | ...}> or <a,b:t()>.
/** * Renders expressions of the form {@code <names,phones:{n,p | ...}>} or * {@code <a,b:t()>}. */
// todo: i, i0 not set unless mentioned? map:{k,v | ..}? protected ST.AttributeList zip_map(InstanceScope scope, List<Object> exprs, ST prototype) { if ( exprs==null || prototype==null || exprs.size()==0 ) { return null; // do not apply if missing templates or empty values } // make everything iterable for (int i = 0; i < exprs.size(); i++) { Object attr = exprs.get(i); if ( attr!=null ) exprs.set(i, convertAnythingToIterator(scope, attr)); } // ensure arguments line up int numExprs = exprs.size(); CompiledST code = prototype.impl; Map<String, FormalArgument> formalArguments = code.formalArguments; if ( !code.hasFormalArgs || formalArguments==null ) { errMgr.runTimeError(this, scope, ErrorType.MISSING_FORMAL_ARGUMENTS); return null; } // todo: track formal args not names for efficient filling of locals String[] formalArgumentNames = formalArguments.keySet().toArray(new String[formalArguments.size()]); int nformalArgs = formalArgumentNames.length; if ( prototype.isAnonSubtemplate() ) nformalArgs -= predefinedAnonSubtemplateAttributes.size(); if ( nformalArgs != numExprs ) { errMgr.runTimeError(this, scope, ErrorType.MAP_ARGUMENT_COUNT_MISMATCH, numExprs, nformalArgs); // TODO just fill first n // truncate arg list to match smaller size int shorterSize = Math.min(formalArgumentNames.length, numExprs); numExprs = shorterSize; String[] newFormalArgumentNames = new String[shorterSize]; System.arraycopy(formalArgumentNames, 0, newFormalArgumentNames, 0, shorterSize); formalArgumentNames = newFormalArgumentNames; } // keep walking while at least one attribute has values ST.AttributeList results = new ST.AttributeList(); int i = 0; // iteration number from 0 while ( true ) { // get a value for each attribute in list; put into ST instance int numEmpty = 0; ST embedded = group.createStringTemplateInternally(prototype); embedded.rawSetAttribute("i0", i); embedded.rawSetAttribute("i", i+1); for (int a = 0; a < numExprs; a++) { Iterator<?> it = (Iterator<?>) exprs.get(a); if ( it!=null && it.hasNext() ) { String argName = formalArgumentNames[a]; Object iteratedValue = it.next(); embedded.rawSetAttribute(argName, iteratedValue); } else { numEmpty++; } } if ( numEmpty==numExprs ) break; results.add(embedded); i++; } return results; } protected void setFirstArgument(InstanceScope scope, ST st, Object attr) { if ( !st.impl.hasFormalArgs ) { if ( st.impl.formalArguments==null ) { st.add(ST.IMPLICIT_ARG_NAME, attr); return; } // else fall thru to set locals[0] } if ( st.impl.formalArguments==null ) { errMgr.runTimeError(this, scope, ErrorType.ARGUMENT_COUNT_MISMATCH, 1, st.impl.name, 0); return; } st.locals[0] = attr; } protected void addToList(InstanceScope scope, List<Object> list, Object o) { o = convertAnythingIteratableToIterator(scope, o); if ( o instanceof Iterator ) { // copy of elements into our temp list Iterator<?> it = (Iterator<?>)o; while (it.hasNext()) list.add(it.next()); } else { list.add(o); } }
Return the first attribute if multi-valued, or the attribute itself if single-valued.

This method is used for rendering expressions of the form <names:first()>.

/** * Return the first attribute if multi-valued, or the attribute itself if * single-valued. * <p> * This method is used for rendering expressions of the form * {@code <names:first()>}.</p> */
public Object first(InstanceScope scope, Object v) { if ( v==null ) return null; Object r = v; v = convertAnythingIteratableToIterator(scope, v); if ( v instanceof Iterator ) { Iterator<?> it = (Iterator<?>)v; if ( it.hasNext() ) { r = it.next(); } } return r; }
Return the last attribute if multi-valued, or the attribute itself if single-valued. Unless it's a List or array, this is pretty slow as it iterates until the last element.

This method is used for rendering expressions of the form <names:last()>.

/** * Return the last attribute if multi-valued, or the attribute itself if * single-valued. Unless it's a {@link List} or array, this is pretty slow * as it iterates until the last element. * <p> * This method is used for rendering expressions of the form * {@code <names:last()>}.</p> */
public Object last(InstanceScope scope, Object v) { if ( v==null ) return null; if ( v instanceof List ) return ((List<?>)v).get(((List<?>)v).size()-1); else if ( v.getClass().isArray() ) { return Array.get(v, Array.getLength(v) - 1); } Object last = v; v = convertAnythingIteratableToIterator(scope, v); if ( v instanceof Iterator ) { Iterator<?> it = (Iterator<?>)v; while ( it.hasNext() ) { last = it.next(); } } return last; }
Return everything but the first attribute if multi-valued, or null if single-valued.
/** * Return everything but the first attribute if multi-valued, or * {@code null} if single-valued. */
public Object rest(InstanceScope scope, Object v) { if ( v == null ) return null; if ( v instanceof List ) { // optimize list case List<?> elems = (List<?>)v; if ( elems.size()<=1 ) return null; return elems.subList(1, elems.size()); } v = convertAnythingIteratableToIterator(scope, v); if ( v instanceof Iterator ) { List<Object> a = new ArrayList<Object>(); Iterator<?> it = (Iterator<?>)v; if ( !it.hasNext() ) return null; // if not even one value return null it.next(); // ignore first value while (it.hasNext()) { Object o = it.next(); a.add(o); } return a; } return null; // rest of single-valued attribute is null }
Return all but the last element. trunc(x)==null if x is single-valued.
/** Return all but the last element. <code>trunc(<i>x</i>)==null</code> if <code><i>x</i></code> is single-valued. */
public Object trunc(InstanceScope scope, Object v) { if ( v ==null ) return null; if ( v instanceof List ) { // optimize list case List<?> elems = (List<?>)v; if ( elems.size()<=1 ) return null; return elems.subList(0, elems.size()-1); } v = convertAnythingIteratableToIterator(scope, v); if ( v instanceof Iterator ) { List<Object> a = new ArrayList<Object>(); Iterator<?> it = (Iterator<?>) v; while (it.hasNext()) { Object o = it.next(); if ( it.hasNext() ) a.add(o); // only add if not last one } return a; } return null; // trunc(x)==null when x single-valued attribute }
Return a new list without null values.
/** Return a new list without {@code null} values. */
public Object strip(InstanceScope scope, Object v) { if ( v ==null ) return null; v = convertAnythingIteratableToIterator(scope, v); if ( v instanceof Iterator ) { List<Object> a = new ArrayList<Object>(); Iterator<?> it = (Iterator<?>) v; while (it.hasNext()) { Object o = it.next(); if ( o!=null ) a.add(o); } return a; } return v; // strip(x)==x when x single-valued attribute }
Return a list with the same elements as v but in reverse order.

Note that null values are not stripped out; use reverse(strip(v)) to do that.

/** * Return a list with the same elements as {@code v} but in reverse order. * <p> * Note that {@code null} values are <i>not</i> stripped out; use * {@code reverse(strip(v))} to do that.</p> */
public Object reverse(InstanceScope scope, Object v) { if ( v==null ) return null; v = convertAnythingIteratableToIterator(scope, v); if ( v instanceof Iterator ) { List<Object> a = new LinkedList<Object>(); Iterator<?> it = (Iterator<?>)v; while (it.hasNext()) a.add(0, it.next()); return a; } return v; }
Return the length of a multi-valued attribute or 1 if it is a single attribute. If v is null return 0.

The implementation treats several common collections and arrays as special cases for speed.

/** * Return the length of a multi-valued attribute or 1 if it is a single * attribute. If {@code v} is {@code null} return 0. * <p> * The implementation treats several common collections and arrays as * special cases for speed.</p> */
public Object length(Object v) { if ( v == null) return 0; int i = 1; // we have at least one of something. Iterator and arrays might be empty. if ( v instanceof Map ) i = ((Map<?, ?>)v).size(); else if ( v instanceof Collection ) i = ((Collection<?>)v).size(); else if ( v instanceof Object[] ) i = ((Object[])v).length; else if ( v.getClass().isArray() ) i = Array.getLength(v); else if ( v instanceof Iterable || v instanceof Iterator ) { Iterator<?> it = v instanceof Iterable ? ((Iterable<?>)v).iterator() : (Iterator<?>)v; i = 0; while ( it.hasNext() ) { it.next(); i++; } } return i; } protected String toString(STWriter out, InstanceScope scope, Object value) { if ( value!=null ) { if ( value.getClass()==String.class ) return (String)value; // if not string already, must evaluate it StringWriter sw = new StringWriter(); STWriter stw; try { Class<? extends STWriter> writerClass = out.getClass(); Constructor<? extends STWriter> ctor = writerClass.getConstructor(Writer.class); stw = ctor.newInstance(sw); } catch (Exception e) { stw = new AutoIndentWriter(sw); errMgr.runTimeError(this, scope, ErrorType.WRITER_CTOR_ISSUE, out.getClass().getSimpleName()); } if (debug && !scope.earlyEval) { scope = new InstanceScope(scope, scope.st); scope.earlyEval = true; } writeObjectNoOptions(stw, scope, value); return sw.toString(); } return null; } public Object convertAnythingIteratableToIterator(InstanceScope scope, Object o) { Iterator<?> iter = null; if ( o == null ) return null; if ( o instanceof Iterable ) iter = ((Iterable<?>)o).iterator(); else if ( o instanceof Object[] ) iter = Arrays.asList((Object[])o).iterator(); else if ( o.getClass().isArray() ) iter = new ArrayIterator(o); else if ( o instanceof Map ) { if (scope.st.groupThatCreatedThisInstance.iterateAcrossValues) { iter = ((Map<?, ?>)o).values().iterator(); } else { iter = ((Map<?, ?>)o).keySet().iterator(); } } //// this is implied by the following line //else if ( o instanceof Iterator ) { // iter = (Iterator<?>)o; //} if ( iter==null ) return o; return iter; } public Iterator<?> convertAnythingToIterator(InstanceScope scope, Object o) { o = convertAnythingIteratableToIterator(scope, o); if ( o instanceof Iterator ) return (Iterator<?>)o; List<Object> singleton = new ST.AttributeList(1); singleton.add(o); return singleton.iterator(); } protected boolean testAttributeTrue(Object a) { if ( a==null ) return false; if ( a instanceof Boolean ) return (Boolean)a; if ( a instanceof Collection ) return ((Collection<?>)a).size()>0; if ( a instanceof Map ) return ((Map<?, ?>)a).size()>0; if ( a instanceof Iterable ) { return ((Iterable<?>)a).iterator().hasNext(); } if ( a instanceof Iterator ) return ((Iterator<?>)a).hasNext(); return true; // any other non-null object, return true--it's present } protected Object getObjectProperty(STWriter out, InstanceScope scope, Object o, Object property) { if ( o==null ) { errMgr.runTimeError(this, scope, ErrorType.NO_SUCH_PROPERTY, "null." + property); return null; } try { final ST self = scope.st; ModelAdaptor adap = self.groupThatCreatedThisInstance.getModelAdaptor(o.getClass()); return adap.getProperty(this, self, o, property, toString(out,scope,property)); } catch (STNoSuchPropertyException e) { errMgr.runTimeError(this, scope, ErrorType.NO_SUCH_PROPERTY, e, o.getClass().getName()+"."+property); } return null; }
Find an attribute via dynamic scoping up enclosing scope chain. Only look for a dictionary definition if the attribute is not found, so attributes sent in to a template override dictionary names.

Return ST.EMPTY_ATTR if found definition but no value.

/** * Find an attribute via dynamic scoping up enclosing scope chain. Only look * for a dictionary definition if the attribute is not found, so attributes * sent in to a template override dictionary names. * <p> * Return {@link ST#EMPTY_ATTR} if found definition but no value.</p> */
public Object getAttribute(InstanceScope scope, String name) { InstanceScope current = scope; while ( current!=null ) { ST p = current.st; FormalArgument localArg = null; if ( p.impl.formalArguments!=null ) localArg = p.impl.formalArguments.get(name); if ( localArg!=null ) { Object o = p.locals[localArg.index]; return o; } current = current.parent; // look up enclosing scope chain } // got to root scope and no definition, try dictionaries in group and up final ST self = scope.st; STGroup g = self.impl.nativeGroup; Object o = getDictionary(g, name); if ( o!=null ) return o; // not found, report unknown attr throw new STNoSuchAttributeException(name, scope); } public Object getDictionary(STGroup g, String name) { if ( g.isDictionary(name) ) { return g.rawGetDictionary(name); } if ( g.imports!=null ) { for (STGroup sup : g.imports) { Object o = getDictionary(sup, name); if ( o!=null ) return o; } } return null; }
Set any default argument values that were not set by the invoking template or by ST.add directly. Note that the default values may be templates.

The evaluation context is the invokedST template itself so template default arguments can see other arguments.

/** * Set any default argument values that were not set by the invoking * template or by {@link ST#add} directly. Note that the default values may * be templates. * <p> * The evaluation context is the {@code invokedST} template itself so * template default arguments can see other arguments.</p> */
public void setDefaultArguments(STWriter out, InstanceScope scope) { final ST invokedST = scope.st; if ( invokedST.impl.formalArguments==null || invokedST.impl.numberOfArgsWithDefaultValues==0 ) { return; } for (FormalArgument arg : invokedST.impl.formalArguments.values()) { // if no value for attribute and default arg, inject default arg into self if ( invokedST.locals[arg.index]!=ST.EMPTY_ATTR || arg.defaultValueToken==null ) { continue; } //System.out.println("setting def arg "+arg.name+" to "+arg.defaultValueToken); if ( arg.defaultValueToken.getType()==GroupParser.ANONYMOUS_TEMPLATE ) { CompiledST code = arg.compiledDefaultValue; if (code == null) { code = new CompiledST(); } ST defaultArgST = group.createStringTemplateInternally(code); defaultArgST.groupThatCreatedThisInstance = group; // If default arg is template with single expression // wrapped in parens, x={<(...)>}, then eval to string // rather than setting x to the template for later // eval. String defArgTemplate = arg.defaultValueToken.getText(); if ( defArgTemplate.startsWith("{"+group.delimiterStartChar+"(") && defArgTemplate.endsWith(")"+group.delimiterStopChar+"}") ) { invokedST.rawSetAttribute(arg.name, toString(out, new InstanceScope(scope, invokedST), defaultArgST)); } else { invokedST.rawSetAttribute(arg.name, defaultArgST); } } else { invokedST.rawSetAttribute(arg.name, arg.defaultValue); } } }
If an instance of x is enclosed in a y which is in a z, return a String of these instance names in order from topmost to lowest; here that would be [z y x].
/** * If an instance of <i>x</i> is enclosed in a <i>y</i> which is in a * <i>z</i>, return a {@code String} of these instance names in order from * topmost to lowest; here that would be {@code [z y x]}. */
public static String getEnclosingInstanceStackString(InstanceScope scope) { List<ST> templates = getEnclosingInstanceStack(scope, true); StringBuilder buf = new StringBuilder(); int i = 0; for (ST st : templates) { if ( i>0 ) buf.append(" "); buf.append(st.getName()); i++; } return buf.toString(); } public static List<ST> getEnclosingInstanceStack(InstanceScope scope, boolean topdown) { List<ST> stack = new LinkedList<ST>(); InstanceScope p = scope; while ( p!=null ) { if ( topdown ) stack.add(0,p.st); else stack.add(p.st); p = p.parent; } return stack; } public static List<InstanceScope> getScopeStack(InstanceScope scope, boolean topdown) { List<InstanceScope> stack = new LinkedList<InstanceScope>(); InstanceScope p = scope; while ( p!=null ) { if ( topdown ) stack.add(0,p); else stack.add(p); p = p.parent; } return stack; } public static List<EvalTemplateEvent> getEvalTemplateEventStack(InstanceScope scope, boolean topdown) { List<EvalTemplateEvent> stack = new LinkedList<EvalTemplateEvent>(); InstanceScope p = scope; while ( p!=null ) { EvalTemplateEvent eval = (EvalTemplateEvent)p.events.get(p.events.size()-1); if ( topdown ) stack.add(0,eval); else stack.add(eval); p = p.parent; } return stack; } protected void trace(InstanceScope scope, int ip) { final ST self = scope.st; StringBuilder tr = new StringBuilder(); BytecodeDisassembler dis = new BytecodeDisassembler(self.impl); StringBuilder buf = new StringBuilder(); dis.disassembleInstruction(buf,ip); String name = self.impl.name+":"; if ( Misc.referenceEquals(self.impl.name, ST.UNKNOWN_NAME) ) name = ""; tr.append(String.format("%-40s",name+buf)); tr.append("\tstack=["); for (int i = 0; i <= sp; i++) { Object o = operands[i]; printForTrace(tr,scope,o); } tr.append(" ], calls="); tr.append(getEnclosingInstanceStackString(scope)); tr.append(", sp="+sp+", nw="+ nwline); String s = tr.toString(); if ( debug ) executeTrace.add(s); if ( trace ) System.out.println(s); } protected void printForTrace(StringBuilder tr, InstanceScope scope, Object o) { if ( o instanceof ST ) { if ( ((ST)o).impl ==null ) tr.append("bad-template()"); else tr.append(" "+((ST)o).impl.name+"()"); return; } o = convertAnythingIteratableToIterator(scope, o); if ( o instanceof Iterator ) { Iterator<?> it = (Iterator<?>)o; tr.append(" ["); while ( it.hasNext() ) { Object iterValue = it.next(); printForTrace(tr, scope, iterValue); } tr.append(" ]"); } else { tr.append(" "+o); } } public List<InterpEvent> getEvents() { return events; }
For every event, we track in overall events list and in self's InstanceScope.events list so that each template has a list of events used to create it. If e is an EvalTemplateEvent, store in parent's InstanceScope.childEvalTemplateEvents list for STViz tree view.
/** * For every event, we track in overall {@link #events} list and in * {@code self}'s {@link InstanceScope#events} list so that each template * has a list of events used to create it. If {@code e} is an * {@link EvalTemplateEvent}, store in parent's * {@link InstanceScope#childEvalTemplateEvents} list for {@link STViz} tree * view. */
protected void trackDebugEvent(InstanceScope scope, InterpEvent e) { // System.out.println(e); this.events.add(e); scope.events.add(e); if ( e instanceof EvalTemplateEvent ) { InstanceScope parent = scope.parent; if ( parent!=null ) { // System.out.println("add eval "+e.self.getName()+" to children of "+parent.getName()); scope.parent.childEvalTemplateEvents.add((EvalTemplateEvent)e); } } } public List<String> getExecutionTrace() { return executeTrace; } public static int getShort(byte[] memory, int index) { int b1 = memory[index]&0xFF; // mask off sign-extended bits int b2 = memory[index+1]&0xFF; return b1<<(8*1) | b2; } protected static class ObjectList extends ArrayList<Object> { } protected static class ArgumentsMap extends HashMap<String, Object> { } }