This is a rough draft of the documentation that will describe the expression language at the heart of TT3. It is incomplete and incorrect in places.
The core syntax of the terms in the TT3 language:
Constants
10 # integer 3.142 # float 10e32 # scientific 0xDEADBEEF # hex
Strings
'single' # single quote literal text 'Mr O\'Brien' # backslash escape ' "double" # double quoted text "it was 10\" long" # backslash escape " "newline\nand so on" # all the usual escape sequences "double $var" # variables are interpolated "double ${foo}" # explicit scoping "double ${foo.bar}" # explicit scoping - see variable dotops "double \$var" # backslash escape $ to prevent interpolation
Lists
[10, 20] ['foo','bar'] ['foo',10,'bar',20]
Hash arrays
{a=10,b=20} {a=>10,b=>20} # '=>' is same as '=' in hash defs
Variables
foo # implicit variable $foo # explicit $ variable sigil (usually same as above) var:foo # explicit var: variable namespace
Variable dotops
foo.bar foo # dotops can have whitespace before them .bar .baz
Literal dotops
'foo'.length [10,20].size {pi=3.14,e=2.718}.
Interpolated dotops
foo.$bar foo.${bar.baz} [10.20].$n
Variable arguments (functions and object methods)
foo(10,20) foo(a=10) # TT params foo(a=>10) # regular Perl fat comma, same as foo('a',10) foo(10).bar(20)
Variable assignment
foo = 10 # set variable foo.bar = 20 # set dotted variable a = b = c = 10 # assignments can be chained, right associative
List expansion
a = [10,20] b = [30,40] c = [@a,@b] # same as: c = [10,20,30,40] @a # same as: 10, 20 @a = @b # update contents of a with contents of b @d = @b # creates new list reference, same as: d = [@b] d = @b # @b auto-folded to list reference, same as: d = [@b]
Hash expansion
a = { x=10, y=20 } b = { p=19, q=23 } c = { %a, %b } # same as: c = { x=10, y=20, p=10, q=23 } # I *think* it should be OK to allow @hash expansion d = [@a] # same as: d = ['x', 10, 'y', 20] c = { @a, @b } # same as: c = { %a, %b } # not sure about this... it kinda makes sense but could be weird %a # export contents of a, same as: x=10, y=20
Namespaces
# terms can be prefixed with a namespace, either to clue the parser # up or to access external resources var:foo # explicit variable, same as: $foo template:header # reference a template file:example.txt # reference a file ...plus others and your own custom namespaces...
Quote Ops (using namespaces) - note that only some of this is working
# q: is for single quoted strings q:/Single Quoted Text/ # These all do *exactly* the same thing. q:[Single Quoted Text] # Note in particular that q:"foo $bar" is q:{Single Quoted Text} # a *single* quoted string because of the q: q:# prefix. It just happens to be using double q:'Single Quoted Text' # quotes as delimiters. The variable embedded q:"Single Quoted Text" # in the string is *not* interpolated # qq: is for double quoted strings qq:/Blah $blah/ # same as "Blah $blah" qq:[Blah $blah] qq:[Blah "$blah"] # same as "Blah \"$blah\"" ...and all the same delimiters as above # qr: is for regexen. It can have flags on the end qr:/^foo\s+/ qr:/ ^ foo \s+ /sx qr:[^foo\s+] qr:[ ^ foo \s+ ]sx ...etc... # if a quotelike namespace is specified without delimiters then it # consumes everything up to the next whitespace q:foo # same as 'foo', q:'foo', q:[foo] and so on q:foo.bar # same as 'foo.bar', etc. # this ties in with other namespaces that don't expect delimiters file:example.txt # filenames don't need to be quoted template:header.html # nor do template names http://foo.com/query?x=10&y=20 # nor do URIs / URLs # NOTE: quoting delimiters are namespace dependent. For example, # file:, template:, http: and similar accept "" and '' but not [] # // and so on. file:'my file.html' file:/slashes_are/part_of/the_path.html file:[blah] # same as file:'[blah]' # BEWARE! # The default namespace depends on context. In most places a word # is treated as a variable: foo # these are both the same var:foo # In other places, a template name is expected fill example.html # fill (nee INCLUDE) expects template: fill template:example.html # same as above # you can over-ride the default fill var:foo.bar # template name is in foo.bar var fill $foo.bar # short-hand sigil form, same as above # same goes for words following a dotop foo.bar # 'bar' is a bareword foo.$bar # 'bar' is a variable foo.var:bar # same as above (but clumsy) # blank namespace defaults to q: q:foo # same as: 'foo' :foo # same as above
All directives become expressions that can yield none or more values. That include things we traditionally think of as "statement", like "if" and "for" blocks.
Consider the following example:
Hello [% name %]
This contains two expressions. The first is a text chunk that always yields the static value 'Hello '. The second is a variable expression that yields whatever value the 'name' variable has at runtime.
A slightly more complex example:
Hello [% name or 'World' %]
We still have two expressions, but the second is now a logical 'or' expression that yields either the value of the expression on its left (the 'name' variable expression) or that of the expression on its right (the "'World'" string).
TT3 understands operator precedence.
[% a = b + c * d %]
This is parsed as:
[% a = (b + (c * d)) %]
As you would expect, you can use parens if you want different precedence.
[% a = (b + c) * d %]
Remember that everything in TT3 is an expression. For example, you can use the 'fill' command anywhere you can use a variable.
[% a = fill header %] # fill header template and save output in 'a' [% foo(fill header) %] # fill header and pass output to foo()
Everything is an expression, even things like 'if' blocks.
[% a = if b; c; else; d; end %]
One thing that fall naturally out of this is generator expressions. For example, if you want a list of the numbers from 1 to 10 you can write:
1..10
Or if you prefer:
1 to 10
But what if you want the numbers 10, 20, etc., up to 100? That's easy:
for x in 1 to 10; x * 10; end
The 'for' directive yields the values generated by its block. So if you want to capture the values and save them in a list, you can write this:
numbers = [ for x in 1 to 10; x * 10; end ]
Or if you prefer you can use braces to delimit blocks.
numbers = [ for x in 1 to 10 { x * 10; } ]
Because there's only a single expression in the 'for' block you can actually do away with the braces.
numbers = [ for x in 1 to 10 x * 10 ]
With a single expression you can also write it in side-effect form:
numbers = [ x * 10 for x in 1 to 10 ]
In this example the 'for' command is being interpreted as an infix operator (as are '*', 'in' and 'to'). You can chain block expressions indefinitely in either direction:
numbers = [ for x in 1 to 10 { # this example has stupid logic if x < 5 { # but it illustrates the point x * 10 } } ]
Or without the braces:
numbers = [ for x in 1 to 10 if x < 5 x * 10 ]
Or in side effect form:
numbers = [ x * 10 if x < 5 for x in 1 to 10 ]
All squished up:
numbers = [ x * 10 if x < 5 for x in 1 to 10 ]
Remember you can use any commands as expressions. Here are some examples that all do the same thing: they generate a list containing the output generated from processing three separate templates.
# explicit output = [ fill site/header fill site/content fill site/footer ] # generate from list output = [ fill "site/$chunk" for chunk in ['header', 'content', 'body'] ] # generate from split string output = [ fill "site/$chunk" for chunk in 'header content body'.split ]
Andy Wardley http://wardley.org/