Target Language Compiler | ![]() ![]() |
Anatomy of a TLC Script
Let's dissect the script we just ran. Each "paragraph" of output from guide.tlc
is discussed in sequence in the following brief sections:
%assign
directive
Coding Conventions
These are some basic TLC syntax and coding conventions.
For further information, see TLC Coding Conventions.
File Header
%% File: read-guide.tlc (This line is a TLC Comment, and will not print) %% To execute this file, type: tlc -v -r guide.rtw read-guide.tlc %% Set format for displaying real values (default is "EXPONENTIAL") %realformat "CONCISE"
The first three lines are comments. All text on a line following the characters "%%
" is treated as a comment (ignored, not interpreted or output).
The fourth line, as explained in the third line, is the TLC directive (keyword) %realformat
, which controls how subsequent floating-point numbers will be formatted when output. Here we want to minimize the digits displayed.
Token Expansion
The first section of output is produced by the script lines:
Using TLC you can: * Directly access any field's value, e.g. %assign td = "%" + "<Top.Date>" %<td> -- evaluates to: "%<Top.Date>"
The first two lines (and any line that contains no TLC directives or tokens) are simply echoed to the output stream, including leading and trailing spaces.
The third and fourth lines will be explained momentarily.
The last line evaluates (expands) the record Top.Date
. More precisely, it evaluates the field called Date
existing in the scope called Top
. The syntax %<expr>
causes expression expr
(which can be a record, a variable, or a function) to be evaluated. This operation is sometimes referred to as an eval.
The third line creates a variable named td
and assigns a string value to it. The %assign
directive creates new and modifies existing variables. Its general syntax is:
The optional double colon prefix specifies that the variable being assigned to is a global variable. In its absence, a local variable in the current scope is created or modified.
In order to enable the TLC to print "%<Top.Date>
" without expanding it beforehand, the preceding line was needed to construct that string by pasting together two literals:
As discussed under "String Processing Plus" below, the plus operator concatenates strings as well as adds numbers.
General Assignment
The second section of output is produced by the script lines:
* Assign contents of a field to a variable, e.g. %assign worker = Top.Employee.FirstName "%assign worker = Top.Employee.FirstName" worker <- Top.Employee.FirstName = %<worker>
The first line is simply echoed to output. The second line is an assignment of a field called FirstName
in the Top.Employee
record scope to a new local variable called worker
.
As the second line does not produce output, it was necessary to add a line to show what it is. The third line, therefore, repeats the previous statement, making it visible by enclosing it in quotation marks.
The fourth line then indicates what the assignment caused to happen, and illustrates the token expansion that took place.
String Processing Plus
The next section of the script illustrates string concatenation, one of the uses of the overloaded "+
" operator:
* Concatenate string values, e.g. %assign worker = worker + " " + Top.Employee.LastName "%assign worker = worker + " " + Top.Employee.LastName" worker <- worker + " " + Top.Employee.LastName = "%<worker>"
The second line performs the concatenation, the third line echoes it, and the fourth line describes the operation, in which a variable is concatenated to a field separated by a space character. An alternative way to do this, without using the + operator, is
The alternative method uses evals of fields and is equally efficient.
The + operator, which is associative, also works for numeric types, vectors, matrices, and records:
Arithmetic Operations
The TLC provides a full complement of arithmetic operators for numeric data. In the next portion of our TLC script, two numeric fields are multiplied:
* Perform arithmetic operations, e.g. %assign wageCost = Top.Employee.PayRate * Top.Employee.Overhead "%assign wageCost = Top.Employee.PayRate * Top.Employee.Overhead" wageCost <- Top.Employee.PayRate * Top.Employee.Overhead... <- %<Top.Employee.PayRate> * %<Top.Employee.Overhead> = %<wageCost>
Again, the second line is the %assign
statement that computes the value, which is stored in local variable wageCost
, and the third line echoes the operation. Note that the fourth and fifth lines compose a single statement. The ellipsis ("...
", typed as three consecutive periods) signals that a statement is continued on the following line, but if the statement has output no line break will be inserted. To continue a statement and insert a line break, use a backslash ("\
") in place of "...
".
Modifying Records
Once read into memory, records in record files can be modified and manipulated just like variables created by assignment. The next segment of read-guide.tlc
replaces the value of record field Top.Employee.GrossRate
:
* Put variables into a field, e.g. %assign Top.Employee.GrossRate = wageCost "%assign Top.Employee.GrossRate = wageCost" Top.Employee.GrossRate <- wageCost = %<Top.Employee.GrossRate>
Such changes to records are nonpersistent (because record files are inputs to the TLC; other file types, such as C source code, are the outputs), but can be useful.
Several TLC directives beside %assign
may be used to modify records:
See Compiler Directives for further details on these related directives.
Traversing Lists
Record files can contain lists, or sequences of records having the same identifier. Our example contains a list of three records identified as Project
within the Top
scope. List references are indexed, numbered from 0 in the order in which they appear in the record file. Here is TLC code that compiles data from the Name
field of the Project
list:
* Traverse lists of values, e.g. %assign projects = Top.Project[0].Name + ", " + Top.Project[1].Name... + ", " + Top.Project[2].Name "%assign projects = Top.Project[0].Name + ", " + Top.Project[1].Name..." "+ ", " + Top.Project[2].Name" projects <- Top.Project[0].Name + ", " + Top.Project[1].Name + ", " + Top.Project[2].Name = %<projects>
The Scope.Record[n].Field
syntax is similar to that used in C to reference elements in an array of structs.
While explicit indexing such as the above is perfectly acceptable, it is often preferable to use a loop construct when traversing entire lists, as shown next.
Looping over Lists
By convention, the section of a record file that a list occupies is preceded by a record that indicates how many list elements are present. In model
.rtw
files such parameters are declared as NumIdent
, where Ident
is the identifier used for records in the list that follows. In guide.rtw
, the Project
list looks like this:
NumProject 3 # Indicates length of following list Project { # First list item, called Top.Project[0] Name "Tea" # Alpha field Name, Top.Project[0].Name Difficulty 3 # Numeric field Top.Project[0].Difficulty } # End of first list item Project { # Second list item, called Top.Project[1] Name "Gillian" # Alpha field Name, Top.Project[1].Name Difficulty 8 # Numeric field Top.Project[1].Difficulty } # End of second list item Project { # Third list item, called Top.Project[2] Name "Zaphod" # Alpha field Name, Top.Project[2].Name Difficulty 10 # Numeric field Top.Project[2].Difficulty } # End of third list item
Thus the value of NumProject
describes how many Project
records occur.
The last segment of read-guide.tlc
uses a %foreach
loop, controlled by the NumProject
parameter, to iterate the Project
list and manipulate its values. As you recall, the TLC output looks like this:
* Traverse and manipulate list data via loops, e.g. - At top of Loop, Project = Tea; Difficulty = 3 - Bottom of Loop, i = 0; diffSum = 3.0 - At top of Loop, Project = Gillian; Difficulty = 8 - Bottom of Loop, i = 1; diffSum = 11.0 - At top of Loop, Project = Zaphod; Difficulty = 10 - Bottom of Loop, i = 2; diffSum = 21.0 Average Project Difficulty <- diffSum / Top.NumProjects = 21.0 / 3 = 7.0
The TLC statements which generated this output are
* Traverse and manipulate list data via loops, e.g. %assign diffSum = 0.0 %foreach i = Top.NumProject - At top of Loop, Project = %<Top.Project[i].Name>; Difficulty =... %<Top.Project[i].Difficulty> %assign diffSum = DiffSum + Top.Project[i].Difficulty - Bottom of Loop, i = %<i>; diffSum = %<diffSum> %endforeach %assign avgDiff = diffSum / Top.NumProject Average Project Difficulty <- diffSum / Top.NumProject = %<diffSum>... / %<Top.NumProject> = %<avgDiff>
After initializing the summation variable diffSum
, a %foreach
loop is entered, with variable i
declared as the loop counter, iterating up to NumProject
. The scope of the loop is all statements encountered until the corresponding %endforeach
is reached (%foreach
loops may be nested).
Note Loop iterations implicitly start at zero and range to one less than the index that specifies the upper bound. The loop index is local to the loop body. |
![]() | Interpreting Records | Modify read-guide.tlc | ![]() |