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:

  1. Look in t's attribute table
  2. Look in t's arguments
  3. Look recursively up t's enclosing template instance chain
  4. 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)
...