250 likes | 378 Views
StringTemplate, created by Terence Parr, is a robust templating engine designed for generating structured text while maintaining a clear separation between logic and presentation. By adhering to the principles of model-view separation, StringTemplate minimizes complexity and enhances maintainability. It allows for the encapsulation of templates and their attributes, enabling developers to reuse components effectively. The philosophy behind StringTemplate promotes clarity, modular design, and facilitates easier updates to templates without altering the underlying codebase. Learn how to leverage its capabilities for efficient code generation.
E N D
StringTemplate Overview Terence Parr
Topics • Motivation and concepts • Feature summary • Model-view data transfer • Canonical operations • StringTemplate groups and inheritance • Templates and attribute expressions • Scoping • Experience
Why use StringTemplate for generated structured text? • Most text generators are unstructured blobs of generation logic and print statements (this includes tree walking code emitters) • Template engines arose to encourage separation of logic/display (model-view separation) • Fearing weakness, engines support entanglement • Nature of problem dictates solution • Output is not random; it’s a language (format) • Separation goal requires restricted template expressions • Proper formalism is output grammar • Grammars are proper means of describing languages • Restricted templates are equivalent to CF grammars • StringTemplate embodies this philosophy
Motivation for separation • Encapsulation; view and model isolated, unentangled • Clarity; Java and output not intertwined • Division of labor; templates, model developed in parallel • Component reuse; output factored into reusable templates • Single point-of-change; avoid errors from having multiple things to find, change • Maintenance; changing template is easier, safer than changing code • Interchangeable views; retarget code generators
Rules of Separation • the view cannot modify the model (technically toString() method could erase hard drive) • cannot perform computations upon dependent data values • cannot compare dependent data values (except boolean) • cannot make type assumptions (to be practical, can assume objects have properties) • data from model cannot contain display, layout information (unenforceable)
Equivalence to CFGs • Attributes = terminals, templates = rules • Can show grammar’s derivation tree for any sentence maps to a nested template tree structure Grammar Template prog : decl func ; decl : type ID ; func : type ID “()” “{“ body “}” … prog ::= “<decl()> <func()>” decl ::= “<type> <ID> ;” func : << <type> <ID>() { <body()> } >> …
What’s a template? • A sentence or fragment conforming to an output language with embedded expressions evaluating to text at rendering-time • Differs from code that generates text; template is an exemplar not computation • Overall output: nested tree of templates • Expressions are functions of incoming attributes: name/value pairs • All attributes are available prior to rendering
Getting started • Add stringtemplate-2.2.jar and antlr-2.7.5 to your path then compile and run. Output is QUERY: SELECT name FROM User; import org.antlr.stringtemplate.*; class Simple { public static void main(String[] args) { StringTemplate query = new StringTemplate("SELECT $column$ FROM $table$;"); query.setAttribute("column", "name"); query.setAttribute("table", "User"); System.out.println("QUERY: "+query.toString()); } }
Model-view data transfer • Each template has an attribute table mapping attribute name to value • Code pushes attributes into template; template does not pull from model • Attribute value can be anything including another template or list of values • Properties of attribute are getters or fields<a.foo> tries a.getFoo() then tries a.foo • Special case: if a is of type Map<a.foo> invokes a.get(“foo”)
Feature summary • Strictly enforces separation of model & view • Side-effect free expressions; no order of evaluation • Recursion (recall output structures are nested) • Dynamic attribute scoping • “Lazy-evaluation”; attributes resolve at toString time • Template inheritance/polymorphism • Output filters; e.g., auto-indent (the default) • Attribute renderer objects;e.g., how to render Date objects • Simple: No assignments, loops, arbitrary code, … • Small: 12,000 lines including generated parsers
Canonical Operations • Attribute reference:<type> • Template references (possibly recursive):<statementList()> • Apply template to multi-valued attribute:<decls:decl()>or with closure-like blocks<decls:{d | <d.type> <d.name>;}> • Conditional include:<if(superClass)>extends <superClass><endif>
StringTemplate groups • Set of mutually-referential templates • Either directory of template files or group file format with formal arguments: /** ANTLR Error Messages -- US english */ group en_US; DIR_NOT_FOUND(arg) ::= "directory not found: <arg>" OUTPUT_DIR_IS_FILE(arg) ::= "output directory is a file: <arg>” MISSING_RULE_ARGS(file,line,col,arg) ::= "<loc(…)>missing parameter(s) on rule reference: <arg>” loc(file,line,col) ::= "<file>:<line>:<col>: "
Code generation group example group javaTemplates; method(type,name,args,body) ::= << public <type> <name>( <args:arg(); separator=“,”> ) { <statements> } >> assign(lhs,expr) ::= “<lhs> = <expr>;” if(expr,stat) ::= “if (<expr>) <stat>” call(name,args) ::= << <name>( <args; separator=“,”> ); >> Note: controller creates assign, if, call templates and sets The statements attribute of method etc…
Template Polymorphism • Output: “y=1;” not “x=1;” because template instance’s group is subGroup group sup; slist() ::= “<assign()>” assign() ::= “x=1;” group sub; assign() ::= “y=1;” Late bind sub.setSuperGroup(sup); StringTemplate st = sub.getInstanceOf(”slist"); System.out.println(st.toString()); Group determines symbol resolution
Attribute Expressions • <attribute>, <a.property>, <a.(expr)> • <multi-valued-attribute; separator=“…”> • empty attributes yield no output UNREACHABLE_ALTS(file,line,alts) ::= << <file>:<line>:unreachable: <alts; separator=","> >>
Template inclusion • <template(args)>, <(expr)(args)> • Argument lists are parameter=expr pairs method(type,name,slist) ::= << public <type> <name>() { <debug(loc={enter method <name>})> <slist> }>> catch(exceptionName,slist) ::= << catch (<exceptionName>) { <debug(loc={catch <exceptionName>})> <slist> }>> debug(loc) ::= <<System.out.println(“at location <loc>”);>>
Applying template to attribute • A crucial distinguishing feature! • Obviates the need for loops • <attributeExpr:templateExpr> • <names:bold()>, <names:bold(item=it)> • <names:(templateName)()> • <names:italics():bold()> • <vars:{v | <v.type> <v.id>;<\n>}> • <varTypes,varIDs:{t,id | <t> <id>;<\n>}>
Translation maps • Want to translate string from language x to y • Must keep literals out of code so can’t have hashtable in code with mapping • ST 2.2 feature: <typeInitMap.int> is “0” also <typeInitMap.(typeName)> typeInitMap ::= [ "int":"0", "float":"0.0", "boolean":"false", default:"null" // anything else ]
Lists • Combines attributes into list:<[userDefined,implicitlyDefined]:decl()><[boys,girls,other]:{Attendee: <it>.<\n>}> • Operators: first, rest, last • ops on empty attributes yield an empty value • rest(a) is empty and tail(a) == first(a) if a is single valued <first(numbers):{ n | int sum = <n>;}> <rest(numbers):{ n | sum += <n>;}>
Recursively walking a list • Using rest and tail recursion, print out each element of x recurse(x) ::= “<x><rest(x):recurse()>”
Attribute Scoping • Dynamically not lexically scoped • Template can see all attributes from enclosing templates unless a formal argument hides an enclosing value • Look up a in template t: • 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
Best practice • Design templates top-down • Construct bottom-up • model = input token streamcontroller = parserview = template engine + template files • Controller extracts data from model, gives to view • Controller maps input constructs to output constructs such as assignment to assignment • Abstract concepts represented by one or more rules in the parser grammar and one or more templates in the template file. • View says what abstract concept looks like in target
Experience with ANTLR v3 • Tree walker (controller) collects data from AST (model), pushes data into templates (view) • Lazy evaluation decouples order of computation from order of output (this is huge) • Enforced separation guarantees easy retargeting, no code duplication, … • no code in template • no output strings in code generator • Previous code generator hopelessly entangled • Group file format (output grammar) is great! “Executable document”
Simplified ANTLR v3 Template parser(name, tokens, rules, DFAs) ::= << class <name> extends Parser { <tokens:{t | public static final int <t.name>=<t.type>;}> public <name>(TokenStream input) { super(input); } <rules; separator="\n"> <DFAs:{dfa | protected DFA<dfa.decisionNum> dfa<dfa.decisionNum> = new DFA<dfa.decisionNum>(); }> <DFAs> } >>