Template and attribute lookup rules
Template lookup
When you request a named template via StringTemplateGroup.getInstanceOf()
or within a template, there is a specific sequence used to locate the template.
If a template, t, references another template and t is not specifically associated with any group, t is implicitly associated with a default group whose root directory is ".
", the current directory. The referenced template will be looked up in the current directory.
If a template t is associated with a group, but was not defined via a group file format, lookup a referenced template in the group's template table. If not there, look for it on the disk under the group's root dir. If not found, recursively look at any supergroup of the group. If not found at all, record this fact and don't look again on the disk until refresh interval.
If the template's associated group was defined via a group file, then that group is searched first. If not found, the template is looked up in any supergroup. The refresh interval is not used for group files because the group file is considered complete and enduring.
Attribute scoping rules
A StringTemplate
is a list of chunks, text literals and attribute expressions, and an attributes table. To render a template to string, the chunks are written out in order; the expressions are evaluated only when asked to during rendering. Attributes referenced in expressions are looked up using a very specific sequence similar to an inheritance mechanism.
When you nest a template within another, such as when a page
template references a searchbox
template, the nested template may see any attributes of the enclosing instance or its enclosing instances. This mechanism is called dynamic scoping. Contrast this with lexical scoping used in most programming languages like C# and Java where a method may not see the variables defined in invoking methods. Dynamic scoping is very natural for templates. For example, if page
has an attribute/value pair font
/Times
then searchbox
could reference $font$
when nested within a page
instance.
Reference to attribute a in template t is resolved as follows:
- Look in t's attribute table
- Look in t's arguments
- Look recursively up t's enclosing template instance chain
- Look recursively up t's group / supergroup chain for a map
This process is recursively executed until a is found or there are no more enclosing template instances or super groups.
When using a group file format to specify templates, you must specify the formal arguments for that template. If you try to access an attribute that is not formally defined in that template or an enclosing template, you will get a InvalidOperationException
.
When building code generators with StringTemplate
, large heavily nested template tree structures are commonplace and, due to dynamic attribute scoping, a nested template could inadvertently use an attribute from an enclosing scope. This could lead to infinite recursion during rendering and other surprises. To prevent this, formal arguments on template t hide any attribute value with that name in any enclosing scope. Here is a test case that illustrates the
point.
Java |
String templates = "group test;" +newline+ "block(stats) ::= \"{$stats$}\"" ; StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates)); StringTemplate b = group.getInstanceOf("block"); b.setAttribute("stats", group.getInstanceOf("block")); String expecting ="{{}}"; |
---|---|
C# |
string templates = "group test;" +newline+ "block(stats) ::= \"{$stats$}\"" ; StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates)); StringTemplate b = group.GetInstanceOf("block"); b.SetAttribute("stats", group.GetInstanceOf("block")); string expecting ="{{}}"; |
Python |
templates = ( "group test;" + os.linesep + "block(stats) ::= \"{$stats$}\"" ) group = stringtemplate3.StringTemplateGroup(file=StringIO(templates), lexer='default') b = group.getInstanceOf("block") b["stats"] = group.getInstanceOf("block") expecting ="{{}}" |
Even though block
has a stats
value that refers to itself, there is no recursion because each instance of block
hides the stats
value from above since stats
is a formal argument.
Sometimes self-recursive (hence infinitely recursive) structures occur through programming error and they are nasty to track down. If you turn on "lint mode", StringTemplate
will attempt to find cases where a template instance is being evaluated during the evaluation of itself. For example, here is a test case that causes and traps infinite recursion.
Java |
String templates = "group test;" +newline+ "block(stats) ::= \"$stats$\"" + "ifstat(stats) ::= \"IF true then $stats$\"\n" ; StringTemplate.setLintMode(true); StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates)); StringTemplate b = group.getInstanceOf("block"); StringTemplate ifstat = group.getInstanceOf("ifstat"); b.setAttribute("stats", ifstat); // block has if stat ifstat.setAttribute("stats", b); // but make the "if" contain block try { String result = b.toString(); } catch (IllegalStateException ise) { ... } |
---|---|
C# |
string templates = "group test;" +newline+ "block(stats) ::= \"$stats$\"" + "ifstat(stats) ::= \"IF true then $stats$\"\n" ; StringTemplate.SetLintMode(true); StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates)); StringTemplate b = group.GetInstanceOf("block"); StringTemplate ifstat = group.GetInstanceOf("ifstat"); b.SetAttribute("stats", ifstat); // block has if stat ifstat.SetAttribute("stats", b); // but make the "if" contain block try { string result = b.ToString(); } catch (InvalidOperationException ise) { ... } |
Python |
templates = ( "group test;" + os.linesep + "block(stats) ::= \"$stats$\"" + os.linesep + "ifstat(stats) ::= \"IF true then $stats$\"\n" ) stringtemplate3.lintMode = True group = stringtemplate3.StringTemplateGroup(file=StringIO(templates), lexer="default") b = group.getInstanceOf("block") ifstat = group.getInstanceOf("ifstat") b["stats"] = ifstat # block has if stat ifstat["stats"] = b # but make the "if" contain block try: result = str(b) except stringtemplate3.language.ASTExpr.IllegalStateException, exc: # do something |
The nested template stack trace from exception object will be similar to:
infinite recursion to <ifstat([stats])@4> referenced in <block([stats])@3>; stack trace: <ifstat([stats])@4>, attributes=[stats=<block()@3>]> <block([stats])@3>, attributes=[stats=<ifstat()@4>], references=[stats]> <ifstat([stats])@4> (start of recursive cycle) ...