Skip to content
jappy edited this page Oct 15, 2011 · 9 revisions

Previous - Lexical Structure | Table of Contents | Next - Tea Functions

A Tea compilation unit consists of a single template. If templates are stored on a file system, then each template file must contain exactly one template definition. The name of the file must consist of the template name followed by the extension ".tea".

Every template must begin with a template declaration, which is defined in a code region (see section 3 Lexical Structure). Therefore, every template must begin with a code region. The template declaration defines the name of the template and any parameters that must be passed to the template.

Template ::= TemplateDeclaration  [StatementList]
TemplateDeclaration ::=  template Identifier ( [FormalParameterList] ) [SubstitutionParameter]   

The template's formal parameter list follows the template name in the declaration and must be delimited by parentheses. A comma separates each parameter in the list. Formal parameters require a class name followed by an identifier, which names it. Tea does not allow primitive types to be declared, only object types. Object type names must be fully qualified except for those defined in the java.lang and java.util packages. The type declaration may define an array (or an array of arrays) just like in Java, using '[' and ']' symbols.

FormalParameterList ::= FormalParameter |   
	                FormalParameterList, FormalParameter   
FormalParameter ::= Type Variable
Type ::= Name |    
         Type [ ]    

After the template declaration, a template can have text regions or code regions with any statements allowed by Tea. Statements can also appear inside the same code region that the template declaration was in.

Templates can invoke other templates, just like calling a function. Any template can be invoked by another template. Templates may also return a value to the caller. No special syntax is required in the template declaration to support callable templates or return types.

A template may require that it receive one substitution block parameter. This parameter is denoted by the ellipsis symbol '...'. With this parameter, any template invoking this template must pass in a block of code. A block of code begins with a left curly brace and ends with a right curly brace. The template that accepts the substitution parameter uses the substitution statement to insert the substitution.

Examples:

From SmallTemplate.tea:

<% template SmallTemplate(pkg.ContentObject obj) %>
<html><body>
This page was created on <% obj.date %>.
<br>
<%
user = obj.user
if (user != null and user.name != null) {
    'Hello ' user.name '!'
}
%>
</body></html>

From NewsPage.tea:

<% template NewsPage(org.news.NewsStory story)
// Pass a title and a substitution block to SimplePage 
call SimplePage(story.title, "#ffffff") {
    '<h1>' story.headline '</h1>'
    foreach (paragraph in story.body) {
        '<p>' paragraph.body
    }
}
%>

From SimplePage.tea, called from NewsPage:

<% template SimplePage(String title, String color) { ...  }
'<html><head><title>' title '</title></head>'
if (color == null) {
    '<body>'
}
else {
    '<body bgcolor="' & color & '">'
}
...
'</body></html>'
%>

4.1 - The Import Directive

The import reserved word provides an alternative to providing fully qualified Java class names and is analogous to the import statement in Java. It impacts the type names of the formal parameters that are passed into a template via the template keyword. It also can be used to qualify the type names referenced by the define and as reserved words. The import reserved word must appear before the template definition. An example of this follows:

<% 
import com.disney.vidio
template displayTitles()

define Title dvd = getRequest().attributes["dvd"]
...

In this example the Java data container class Title exists in the fully qualified Java class com.disney.video.Title. Note: Since the entire contents of a package is imported, if a class name exists in multiple packages then you must use the fully qualified names of the conflicting classes.

4.2 - Statements

Tea statements are used for variable assignments, controlling the flow of execution, performing looping, calling functions, and calling other templates. Statements are also used to output from a template and perform block substitutions. Statements need not terminate with a semi-colon, as they do in Java. Statements are not separated by new lines, but by a carefully designed language grammar. In very rare circumstances, a semi-colon is required to disambiguate statement separation.

Statement ::= EmptyStatement |
	      IfStatement |
	      ForeachStatement |
	      SubstitutionStatement |
	      AssignmentStatement |
	      CallStatement |
	      ExpressionStatement

The set of operations that can be performed by Tea statements is less than that provided by most application programming languages. This aids in enforcing a policy that keeps templates from performing complex operations that should be left to a hosting system.

4.2.1 - Semi-colons

Although Tea does not require the use of the semi-colon as a statement terminator, they can still be used. Java programmers are likely to use them out of habit or for stylistic reasons. Under certain special conditions, a semi-colon may be required to enforce statement separation. The following two statements

a  
-b

would not be interpreted as printing the value of variable "a" followed by the negative value of variable "b". Instead it would be

a – b   

which means print the value of "a" minus "b". This would not be a problem if the "-" token was not overloaded to mean both subtraction and negation. Here is the separated version:

a;
-b

Alternatively, parentheses can be used, but it is a clumsier notation:

(a)   
(-b)

In this example, if the parentheses were omitted from the first expression, the compiler would decide that "a" is a function receiving parameter "-b".

4.2.2 - Break Statement

The break statement can be used to exit from a loop. See 4.2.5 Looping.

Example:

foreach (count in 1..10) {
    count
    if (count > 4) {
        break
    }
}

Placing unreachable code after the break statement will result in an error at compile time.

4.2.3 - Continue Statement

The continue statement can be used to unconditionally branch to the top of a loop. See 4.2.5 Looping.

Example:

foreach (count in 1..10) {
    if (count % 2 == 0) {
        continue
    }
    count  // only odd numbers are printed  
}

Placing unreachable code after the continue statement will result in an error at compile time.

4.2.4 - If Statement

Tea's if statement is just like Java’s except in one way: braces are always required for its enclosed statements.

IfStatement ::= if ( Expression ) Block [ElseStatement]  
ElseStatement ::= else Block |
                  else IfStatement
Block ::= { [StatementList] }  

Examples:

if (fileName == null or fileName.length == 0) {
    // Assign a default filename if empty
    fileName = "default"
}

if (name == "Bob") {
    // Bob is welcome...
    "Hello Bob!"
}
else {
    // ...others are not
    "Go away, " & name & "."
}

if (count == -1) { count = 0 } else if (count >= 15) { count = count – 15 }

4.2.5 - Looping

The foreach statement is the only loop flow control statement in Tea. There is no way to modify the loop count variable from within the loop, but the break statement can be used to exit the loop. Foreach statements iterate over the values of an array, a collection or a range of values. When the optional reverse keyword is specified, the values are iterated in reverse order.

Like the if statement, blocks are required to be enclosed in braces.

ForeachStatement ::=
    foreach ( Variable in Expression ) Block   
    foreach ( Variable as Type in Expression ) Block   
    foreach ( Variable in Expression reverse )  Block   
    foreach ( Variable as Type in Expression reverse )  Block   
    foreach ( Variable in Expression ..  Expression  ) Block    
    foreach ( Variable in Expression ..  Expression  reverse ) Block   

The variable used in the foreach statement is re-assigned during the loop’s execution, and its type is redeclared to be the appropriate element type of the given expression (use the 'as' keyword to type collection and array iterators). If the loop variable was already declared outside of the loop, then the new declaration is promoted outside of the foreach scope. See also sections 4.2.7 Variable Assignment.

Example:

// Loop through and print every item in the page's list
foreach (item in page.list) {
    item
}

// Loop through in reverse
foreach (item in page.list reverse) {
    item
}

// Loop from 1 to 10, inclusive
foreach (count in 1..10) {
    "Count is " & count
}

// Print all the characters of a string
// NOTE: if the message length is zero, the range is 0..-1.
// Whenever the second value in the range is less than the
// first value, the foreach will loop zero times.  
message = "hello"
foreach (index in 0..message.length - 1) {
    "Letter at index " & index & " is: " & message[index] & "\n"
}

// Loop from 10 to 0, inclusive
foreach (count in 0..10 reverse) { ...  }

Unless looping through a range, the actual object looped through can be an array or a Collection (java.util.Collection). Objects that implement the Collection interface can be iterated in the reverse direction as well. Collection objects can implement the java.util.List, java.util.Set, and java.util.Map interfaces. When iterating java.util.Map, the loop variable will contain the map’s key, not the value. Use the key to dereference the value using normal associative array notation (i.e. value = myMap[key]). See 4.3.7 Array Creation.

If looping over a value of ranges, they can only be integers. Both the beginning and ending of the range is inclusive. If a number at the beginning or ending of a range is not an integer, it is rounded down to the nearest integer.

Because Java has no parameterized types, when a Collection is used in the foreach loop, Tea can only infer that its elements are Objects, even if they may be subclasses of Object (unless declaratively typed with ‘as’). If the template only needs to print the values of the elements, then this isn’t a problem, but if the template needs a specific property of the object, use the ‘as’ keyword to type the loop variable. Properties can be accessed as expected and the Tea compiler will perform the appropriate compile time type checks.

You can exit a loop by using the break statement (see 4.2.2 Break Statement).

You can unconditionally branch to the top of a loop by using the continue statement (see 4.2.3 Continue Statement).

4.2.6 - Substitution Statement

The substitution statement is only allowed in templates that declare that they receive a block substitution parameter. Templates can declare at most one block substitution parameter. The substitution statement is used to substitute the code for the block passed in at the statement’s location. The substitution statement is simply the ellipsis (...).

SubstitutionStatement ::=  ...

A substitution statement can appear in multiple locations in a template and even in foreach and if statements. The most common use for a substitution is for creating “shell” templates, from the example earlier:

<% template SimplePage(String title, String color) { ...  }
'<html><head><title>' title '</title></head>'
if (color == null) {
    '<body>'
}
else {
    '<body bgcolor="' & color & '">'
}
...
'</body></html>'
%>

A substitution can also be passed to a context-defined function, using the special com.go.tea.runtime.Substitution class. This example defines a simple looping function:

public void loop(int count, Substitution s) throws Exception {
    while (--count >= 0) {
        s.substitute();
    }
}

The template might invoke this function as:

loop (100) {
    "This message is printed 100 times\n"
}

4.2.7 - Variable Assignment/Declaration

With the exception of passed-in parameters, Tea variables are normally not declared. Rather, their type is inferred based on assignment. If necessary, a variables type can be declared using the define or as reserved words. Unless specifically declared using these reserved words, a variable's type is dynamic, and can change after another assignment. Passed-in parameters behave like ordinary local variables in that they can be re-assigned, and they can change type. Variables can also be assigned by a foreach statement (see 4.2.5 Looping).

AssignmentStatement:

Variable = Expression

Assignments can only be made to variables. Array elements cannot be assigned values, like they can in Java. Assignments of this form are not allowed: a[i] = x; This restriction makes it possible to pass collections to templates without fear of the template modifying it.

Examples:

message = "Hello"		// Assign the string literal “Hello” to message

result = 50			// Assign the integer literal 50 to result

amount = result + 20		// Assign the calculated sum of result and 20

message = amount		// Re-assign message with the integer amount

Unlike Java, assignment statements cannot be expressions. This means that assignments cannot be chained together like they can in Java: a = b = c = 2.

Tea variables can also be declaratively typed as follows:

define Integer amount

or

amount = result + 20 as Integer

A variable’s type can also be a Java class. For example, a JavaBean class called Title that exists in the hypothetical com.disney.videos could be decared in this way:

define com.disney.videos.Title video

When used in conjunction with the import reserved word, this can be shortened to:

define Title video

4.2.8 - Expression Statement

All expressions can be statements (see 4.2 Expressions). When an expression takes on the form of a statement, the return value is sent directly to the template’s execution context. The execution context will typically take this value, convert it to a string, and write it to an output stream. This is the main way in which templates output data.

ExpressionStatement:

Expression   

Any expression, which is not part of another statement, is automatically passed to the built-in print function as if it were done directly. The following two statements are equivalent:

"Hello world"
print("Hello world")

Calling the print function directly in templates is not preferred, and tools that display execution context functions should hide the print function.

If the last statement in a template is an expression statement, the expression results (complete with type information) are passed back to the calling template. The caller can then use the call statement as a call expression (see 5.1 Calling Functions and Templates). If the caller does not do anything special, then the call expression is treated as an expression statement, and the caller automatically prints out the results.

4.3 - Expressions

An expression is any program fragment that produces some kind of value. Expressions are evaluated in a specific order, and parentheses can be used to override that ordering. A sub-expression bounded by parentheses is evaluated first within a sub-expression. Otherwise, the order of operations is the same as that for Java, wherever applicable. See section 7 Grammar Summary for the order of operations.

4.3.1 - Accessing Properties

All objects have properties, and these properties are accessible using the JavaBeans Introspector. Essentially, any object passed to or used in a template is a JavaBean. JavaBeans defines two kinds of properties, non-indexed and indexed. Tea can access all non-indexed properties on a JavaBean, but can only access a certain kind of indexed property.

Defining a non-indexed property in a Java class is really simple. The method must return something, must take no parameters, and its name must start with "get". The part of the method name that follows "get" defines the name of the property. Capitalization rules are applied so that the method getSizeOfThing defines the property "sizeOfThing" and the method getURL defines the property "URL".

Arrays, Collections and strings also have a special property named "length" which isn’t defined by a get method. For arrays and Collections, length is the number of elements they contain. For strings, it is the number of characters they contain. JavaBeans defines several different forms for indexed properties, but Tea only understands one kind: unnamed indexed properties. A method that returns something, takes a single parameter and is named "get" follows the design rules for unnamed indexed properties. Java's Map and List interfaces follow these rules, and Tea accesses elements from them via this design pattern.

LookupExpression:

Factor  Lookupopt

Lookup:

Lookupopt  .  Identifier   
Lookupopt  [ Expression ]   

A property access is called a lookup, and Tea has different syntax for its two supported kinds of lookups. A lookup can be applied to any expression, but it is usually applied to variables or chained to other lookups. Non-indexed properties are looked up from an expression using a dot (.) followed by the property name. Indexed properties are looked up using an expression bounded by square brackets.

If the type of an indexed property is Object, (like it is in Map and List) and an element type is defined for that class (by extending Map or List or the use of 'as'), then Tea will cast the object to that element type. An element type for a class that has an indexed property is defined the same as for Collections described by the foreach statement (see 4.1.4 Looping).

The Java signature of the field is

public static final Class ELEMENT_TYPE

Like its use in foreach, if an element is returned that can’t be cast to the specified element type, a ClassCastException is thrown at runtime.

Examples:

// Access the name object from user
name = user.name
// Access the first name
f = name.first
// Access the last name by chaining
last = user.name.last

// Access an element from the users array and get the age
"Age is " & users[10].age

// Lookup a team object by code
team = allTeams[“SEA”]

// Get the last character from a string
str[str.length - 1]

4.3.2 - Arithmetic

Tea can perform arithmetic on both integers and floating-point numbers. Supported operations are addition, subtraction, multiplication, division, division remainder and negation. The operators are +, -, *, /, %, and - respectively. In complex arithmetic expressions, negate is performed first, left-to-right. Multiplication, division, and remainder are performed next, followed by addition and subtraction. This order of operations matches Java's.

AdditiveExpression:

MultiplicativeExpression   
AdditiveExpression + MultiplicativeExpression   
AdditiveExpression – MultiplicativeExpression   

MultiplicativeExpression:

UnaryExpression   
MultiplicativeExpression * UnaryExpression   
MultiplicativeExpression / UnaryExpression   
MultiplicativeExpression % UnaryExpression   

Examples:

// Simple addition
x = a + b

// Increment a count
count = count + 1

// More complex example
result = -((value – 10.0/y) * 50) % 2

In order to perform a division on integer values, but produce a fractional result, adding 0.0 to at least one of the values forces a floating point division. The addition by zero is optimized away and is not actually performed.

x = 3
y = 2
// Integer division prints 1
x / y
"<br>"
// Floating point division prints 1.5
(x + 0.0) / y
"<br>"

4.3.3 - String Concatenation

String concatenation in Java is performed with the plus operator. Tea uses the ampersand (&) operator instead because of the way the template language determines type information. Overloading the plus operator would create typing ambiguities.

ConcatenateExpression:
AdditiveExpression
ConcatenateExpression & AdditiveExpression

Examples:

// Equivalent to text = "Hello World"
text = "Hello" & " " & 'World!'

y = 5
'498234 divided by "y" is ' & 498234 / y

Output:
498234 divided by "y" is 99646

Both the left and right values of a concatenation are converted to strings (if they are not already strings) using the hidden toString function. Any formatting settings are applied when performing the string conversion.

String concatenation can also be used to force an object to be converted to a string. Concatenating the empty string forces a string conversion, but the actual concatenation operation is optimized away.

x = 43554
x = x & ""
// x is now a string, and 5 is printed out
x.length

4.3.4 - Relational Tests

Tea supports expressions for performing relational tests. They are used most often in the context of an if statement condition, although their result can be assigned to a variable. The resulting type of a relational test is always boolean. Six operators are supported, and they fall into two categories: equality tests and comparison tests.

Equality tests are equals (==) and not-equals (!=), and they work for any kind of data type. When an equality test is performed against a string, the other operand is also converted to a string. The following test is legal and performs a string equality test: 5 == "5".

Whenever an equality test is performed on objects, the "equals" method is invoked is possible. If either operand is null, the Tea compiler ensures that no NullPointerException is ever generated, and it may not call the equals method at all if a test is being made against null. The following test will never invoke the equals method: x == null.

Comparison tests are less than (<), greater than (>), less than or equal (<=), and greater than or equal (>=). Comparison tests can be performed on numbers, strings, or any object that implements the Comparable interface. Both values in the comparison must be of the same type. Unlike equality tests against strings, comparisons against strings never force a string conversion.

A comparison between two numbers will perform a numerical comparison, and comparisons between all other objects is performed using the "compareTo" method defined in the Comparable interface.

EqualityExpression:
RelationalExpression
EqualityExpression == RelationalExpression
EqualityExpression != RelationalExpression

RelationalExpression:
ConcatenateExpression
RelationalExpression < ConcatenateExpression
RelationalExpression > ConcatenateExpression
RelationalExpression <= ConcatenateExpression
RelationalExpression >= ConcatenateExpression
RelationalExpression isa Type

Examples:

// Check that the items are not null before looping through them
if (items != null) {
    foreach(item in items) {
        "Item is " & item
    }
}

// Do names A-M followed by names N-Z
"Everyone from A-M<br>"
foreach (name in allNames) {
    if (name < "N") {
        name & "<br>"
    }
}
"<p>Everyone from N-Z<br>"
foreach (name in allNames) {
    if (name >= "N") {
        name & "<br>"
    }
}

4.3.5 - "isa" Operator

The "isa" operator is a special operator that works both like Java's instanceof operator and Java's cast expressions. The left side of isa must be a simple variable expression. When used with an if statement, the tested variable is automatically cast to the given type, within the scope of the if statement’s then or else blocks.

Examples:

if (content isa app.pkg.News) {
    "Story is: " content.story
}

if (item isa app.pkg.Feature and content isa app.pkg.News) {
    "Info is: " item.info
    "Story is: " content.story
}
else if (item isa Object[]) {
    foreach (element in item) {
        element “<br>”
    }
}

4.3.6 - Logical Operators

Logical operations are often used in conjunction with relational tests because they only operate on booleans. Like Java, logical operations will short circuit. Tea's logical operators are "or", "and" and "not". This differs from Java in that the symbols ||, &&, and ! are used to denote these operations.

Examples:

notitle = (title == null or title.sub == null)
	
if (section == "Sports" and sport == "MLB") {
    "Major League Baseball"
}

value = not (x and y)

4.3.7 - Array Creation

Tea templates can define read-only, pre-initialized arrays. Arrays created by this expression can be indexed (zero-based) or associative (a Map). The array values can have any data type, and they can be composed of either constants or any expression. Associative arrays can have any type used for their keys, but usually strings or numbers will be used.

Based on the type of elements defined in the array, a common type is inferred. For any object retrieved from the array, the only properties that are accessible are those defined for the common type.

Arrays defined in templates can be used in the same way as an array passed to a template. This means that elements are accessed using array property syntax (i.e. x[i]). In the case of indexed arrays, they have a length property and can be used in a foreach statement.

The array creation expression is prefixed with a single hash (#) to denote an indexed array, and a double hash (##) to denote an associative array. The list of elements is comma-delimited (the Perl like "=>" can be used as an alternative to comma for associative arrays) and is contained inside parentheses. Associative arrays must have an even number of elements in the list, and list pattern is key, value, key, value, etc. Associative arrays are backed by an instance of java.util.LinkedHashMap. This preserves the item ordering when iterating associative arrays (with foreach) defined in this way.

NewArrayExpression:
	# ( Listopt )
        ## ( Listopt )

Examples:

// An indexed array containing some letters
vowels = #("a", "e", "i", "o", "u")
// Access the 3rd vowel
letter = vowels[2]

// Loop through some words
foreach (word in #("The", "quick", "brown", "fox", "jumped")) {
    "Word: " & word & "<br>"
}

// Iterate over some special values
foreach (value in #(2,3,5,7,11,13,17,19,23,29)) {
    "The value is " & cardinal(value) & "<br>"
}

// Map abbreviations to descriptions
map = ##("MLB" => "Major League Baseball", 
         "NBA" => "National Basketball Association",
         "NHL" => "National Hockey League",
         "NFL" => "National Football League")
"<title>" & map[sport.abbrev] & "</title>"

New indexed arrays are always implemented as ordinary Java arrays, and can be passed to functions and other templates in the usual fashion. Associative arrays will implement the Map interface, and in the current implementation, LinkedHashMap is used. When associative arrays are passed to and from other templates, the element type information is preserved.

Previous - Lexical Structure | Table of Contents | Next - Tea Functions