The Templet language was developed initially as a server-side scripting language for generating HTML. However, it is suitable for client-side scripting as well, although availability of the client-side plug-in will follow availability of the rest of the system.
As a standalone system, Templet is suitable for many other text generation and string processing tasks, including XML processing. It is, in fact, a complete general-purpose programming language.
Templet was designed to integrate well with HTML and derives its main syntactic features from HTML. The principal driving force for Templet 1.0 was to make it as easy as possible for HTML developers to initially use a small set of Templet features that mimicked HTML features. As Templet has evolved, it has grown somewhat more complex, but the essential principle of HTML similarity remains. In particular
Today is [today], [shortdate].
which uses the built-in Template functions
today and shortdate and
substitutes their results in the text, generating, for
example
Today is Monday, 10/26/98.
[sum 3 4] => 7
name
and /name. So does Templet, for example
I [if ([user] == Hillary)] <b>really</b> [/if] love you.
will display
unless the user is Hillary, in which case it displays
[foreach Sandi|Andee|Kathleen]
I loved [$].<br>
[/foreach]
which will display
A local variable could have been used to store the list of names, so the display above could also have been generated by
[do &names = Sandi|Andee|Kathleen]
[foreach &names]
I loved [$].<br>
[/foreach]
Adding optional clauses provides even more power:
[foreach Sandi|Andee|Kathleen][:trim][:separate ", and "]
I loved [$]
[/foreach]!
generates
I loved
<SELECT>
[foreach Sandi|Andee|Kathleen]
<OPTION VALUE="[$]">[$]
[/foreach]
</SELECT>
generates
Database extraction is almost as easy. The DB.SELECT
function uses standard SQL, except the SQL itself can be dynamically
generated in the same way that other text is. To display all
marriages recorded in the Marriages table
in the past year, write:
[do
&cutoff = [date];
&cutoff.year--;
]
[DB.SELECT husband, wife, marrydate
FROM Marriages
WHERE marrydate > [&cutoff]
]
[$husband] was married to [$wife] on [$marrydate].<br>
[/SELECT]
which could generate
<TABLE BORDER=1 CELLSPACING=1>
<TR>
[foreach Husband|Wife|Date]
<TD><B>[$]</B></TD>
[/foreach]
</TR>
[do
&cutoff = [date];
&cutoff.year--;
]
[DB.SELECT husband, wife, marrydate
FROM Marriages
WHERE marrydate > [&cutoff]
]
<TR>
[foreach [$husband]|[$wife]|[$marrydate]]
<TD>[$]</TD>
[/foreach]
</TR>
[/SELECT]
</TABLE>
could generate
| Husband | Wife | Date |
| Joe Johnson | Tillie Tillson | June 14, 1998 |
| Sam Samuels | Joan Jolson | August 19, 1998 |
| Pete Peters | Sandi Sanders | September 4, 1998 |
Templet's syntactic and semantic model borrows features from a somewhat eclectic set of programming languages including (in alphabetical order) Alphard, APL, Java, Javascript, Perl, Scheme (and other LISPs), and Smalltalk.
A history of Templet can be found at the end of this document.
The Templet 3.0 design is distinguished by the following features:
Templet comes with a wide variety of built-in and library classes, including Class, Object, String, List, Dictionary, SortedList, SortedDictionary, Date, Time, Timestamp, Function, Environment, Conductor, Promise, LazyList, Enumeration, Stream, PatternMatcher, Thread, Lock, Database and Query.
Templet also includes classes that provide significant server-side functionality and web support, including transparent construction of URLs for links, (frame & image) sources, and forms. Exercising these URLs invokes specified server-side Templet functions, passing along request-specific values and/or automatically generated session-id's, which are either encoded in the URL or provided via cookies. These classes also provided functions that simplify construction of input elements, especially those with dynamically-determined initial values.
As a web-driven back-end, Templet can either run as a Java servlet or as a separate Templet server, depending on a servlet, other server plug-in or CGI program to pass appropriate HTTP requests on to the Templet server; other configurations can be readily supported.
Templet's built-in functions are implemented in Java; user-defined functions can be implemented in Java as well. Public Java API's for all Templet object are available for use within user-defined functions.
There is no present support for a security model. Currently we assume that all code is server-side and open. Any access controls (e.g. private fields) are to support programming safety rather than security.
Contact Ellis S. Cohen (e.cohen@acm.org) for more information on availability or on how you can participate in the implementation.
All values are represented by objects. All objects, at least potentially, have fields, identified by a (1-based) index and/or by name. These fields can reference other objects. The indexed fields and named fields are (at least by default) separate, and do not overlap in any way. Consequently, objects can act (simultaneously) as both lists (which lookup by index) and dictionaries (which lookup by name). A large number of built-in functions operate on the fields of objects.
Every object has an associated class object. A class may specify that the indexed and/or named fields of its instances are missing, private or public to functions not associated with the class.
Most objects may be shared, that is, referenced from more than one field (in one or more objects). A class can specify whether its instances are shared; if they are not shared, then passing such an instance as a parameter, or storing it in a field makes a copy of it (although the system internally uses a copy-on-write mechanism, delaying the actual replication until required). Strings may not be shared. All other built-in objects may.
Every function is associated with some class. There are a number of kinds of functions, including ststaic and instance functions.
There is support for new classes defined using delegation, derivation, and extension of other classes. It is possible to test whether an object is of a class that extends (possibly transitively) some specified class, thereby guaranteeing that the object supports the instance functions defined by that class.
Instead of holding an object, a field can hold an alias, a non-object entity, which only indirectly references an object. Aliases can be user-defined (via an underlying object), however, the standard built-in alias simply identifies a field in some other object -- that is, the alias holds a reference to an object plus a name/index, and represents the named/indexed field within that object.
If a field holds an alias, then ordinarily retrieving an object from that field actually (and transparently) retrieves the object it indirectly references. In the case of a standard alias, it transparently retrieves the object from the field the alias identifies (and if that field itself holds an alias, then from the field it identifies, etc.).
If a field holds a standard alias, then ordinarily storing an object in that field actually stores the object in the field the alias identifies (unless that field itself currently holds an alias, in which case the object is stored in the field it identifies, etc.)
Aliases for strings can be used to achieve the effect of passing strings (or other objects which are automatically copied) without copying them.
A field can hold a standard alias that references another field in the same object. In fact, an object is said to have bound names if all named fields hold aliases to indexed fields in the same object. an object is said to have bound indices if all indexed fields hold aliases to named fields in the same object.
There are a wide variety of functions that operate on Strings, including search and replace functions that support pattern matching using nested regular expressions, including specific support for XML-elements. Strings also represent numbers, and the usual suite of functions on them are available.
Dictionaries are objects whose indexed fields are missing, and whose named fields are public. Consequently, they provide mappings from arbitrary strings (including from ones that would represent integers, since the indexed fields are missing) to arbitrary objects. There are built-in static functions that operate on any object with public named fields. Dictionaries have instance functions that correspond to each of these static functions.
Lists are objects whose named fields are missing, and whose indexed fields are public. There are built-in static functions that operate on any object with public indexed fields. Lists have instance functions that correspond to each of these static functions.
Strings themselves can be used to represent lists of
strings, with the | character used to separate
list elements. For example, the string
hello|there|bub can either be treated as a
single string (with the usual string functions applicable
to it) or as a list of three strings (with list-like
functions applicable to it). In fact, almost every List
instance function has a corresponding String instance
function that applies to strings treated as string lists.
A string that does not contain any | character
can be treated as a string list with a single element.
In APL-like manner, many operations are extended to work
on lists. For example the sum function takes an
arbitrary number of parameters. If these are all (strings
representing) numbers, then the result is the sum of those
numbers. But if any of the parameters is a list or string
list, then all the parameters are converted to lists which
are the length of the longest list (a single value is turned
into a list, all elements having that value; a shorter list
repeats its elements as needed), and the result is a list
whose i'th element is the sum of the i'th elements of each
of the parameter lists).
Functions execute with an Environment object many of whose fields (at least conceptually) include objects that act as naming contexts for the executing function. The naming contexts are generally created automatically by Templet, and are generally of class Object. The names and roles of these fields are:
In a server-side environment, other fields hold context objects for
Local and parameter variable names are prefixed by special prefix characters which identify the context. Templet has four prefix characters:
^ -- used to identify environment
variables. For example, ^parms names the
parameter context object of the current environment.
& -- used to specify local
variables. For example, &myvar identifies
the field named myvar in the local context of
the executing environment.
$ -- used to identify parameter
variables.
% -- used to identify instance
variables -- fields of the current instance object.
Local variables can be either named or indexed;
&ndx identifies the field named
ndx in the local context, while
&3 names the 3rd unnamed local
variable. Named and indexed local variables are different.
For convenience, &1 can be abbreviated as
&.
Parameter variables are always indexed. That is,
$1 (or just $) names the first
parameter, $2 names the second parameter, etc.
Parameter variables can be named as well, but (unlike local
variables), a named parameter variable (e.g.
$knt) always corresponds to some indexed
parameter variable. In other words, parameter contexts
always have bound names (if they have any named fields at
all) -- all named fields in a parameter context
contain aliases to indexed fields in the same context.
Instance variables are indexed and/or named depending on
the class of the instance object. % by itself
stands for the entire instance object; it is equivalent to
^this.
Variables without a special prefix (i.e. ones that start with a letter or an underscore) identify fields in the global context. In particular, the built-in and library class objects are available in the global context, as are many built-in (and some library) functions.
Note that any string can be used as a field name, however, not all fields can be directly named as variables -- in particular, field names that contains blanks, periods, colons, at signs, or hash marks cannot be used, since blanks are used as separators, and the other characters (as we shall see below) are used as naming operators. However, they can be enclosed in quotes as described below.
There are no variable declarations in Templet. Storing a value in an undeclared variable declares it. More generally, storing a value in an undefined field of an object defines it.
Accessing an undefined field just returns the special
value null. In many cases, a null acts like an
empty string, and both of them (as well as strings
containing only blanks -- i.e. spaces, tabs, newlines,
returns and formfeeds) act in many cases like (a string
representing) the number zero. In fact, these are exactly
the values that play the role of false in
boolean operations; built-in operations that return booleans
return a null for false and a 1 for true.
Functions, like other objects, are created at run-time.
A function contains a reference to the environment in which
it was defined. The class of that environment
is the class associated with the function. Instance functions
are invoked with an instance of their associated class.
When a function is invoked, the environment
newly created for that invocation has fields for both its
calling environment (^caller), and the
environment in which it was defined (^definer).
An existing function can be used to generate a new function
with the same code, but with a different defining environment.
Various fields in a new invocation's environment may be inherited from either the calling or defining environment, depending upon attributes of the function. For example
Because an environment contains a reference to the
calling environment, ordinary functions can act as
interpreters (though an eval function is
built-in). More generally, because functions, contexts and
environments are ordinary objects, many reflection
operations simply "fall out" rather than requiring a
separate reflection API.
The primary operation in Templet is application, which applies a target object to zero or more parameters, and which encompasses both accessing values and invoking functions.
If &s (i.e. the local variable named
s) holds a string, then [&s]
returns the string named by the variable, while if
&s holds a function, then
[&s] returns the result of invoking the
function with no parameters.
Similarly, if &v holds a list, then
[&v 3] returns the 3rd element of the
list, while if &v holds a function, then
[&v 3] holds the results of invoking the
function with 3 as its parameter.
Each object's class determines how it interprets
application, based on an optional apply
instance function in the class object.
Parameters themselves can be evaluated, so, if
&v holds a list, and if evaluating
[&s] generates 3, then evaluating
[&v [&s]] generates the 3rd element of
&v.
In fact, if &s is a string that holds 3,
evaluating [&v &s] generates the 3rd
element of &v as well, since List's apply
function uses the VAR parameter mechanism that
treats parameters beginning with one of the three variable
prefix characters as a variable, and passes the object it
references.
Functions can take multiple parameters, and both fixed
and variable numbers of parameters are supported. Multiple
parameters are separated by blanks (and are trimmed) so
[sum 3 4] evaluates to 7. If a function is
defined as having exactly a single parameter, then blanks do
not act as parameter separators, and (by default) the
parameter is not trimmed.
Templet has a number of parameter mechanisms that vary along a number of dimensions --
The parameter mechanisms available are:
VALBASE) evaluates its argument and
passes the result.
VAL) is like
VALBASE, but if the result is an alias, pass
the object it references instead.
VARBASE) is like VALBASE.
However, if the first non-blank character of the unevaluated
actual parameter is a prefix character (i.e. $,
&, or ^), the actual parameter
is trimmed, evaluated and then treated as a variable name,
and the object or alias it references is returned instead.
VAR) is like
VARBASE, but if the result is an alias, pass
the object it references instead. This is Templet's default
parameter mechanism.
REFBASE) trims then evaluates its argument. If
the result is an alias, the alias itself is passed. If the
result is a string (but the trimmed unevaluated parameter
was not a quoted string literal), it is treated as a
variable name, and the alias or object it holds is passed.
Otherwise, the resulting object itself is passed.
REF) is like
REFBASE, but if the result is an alias, pass
the object it references instead.
BASEALIAS)
trims then evaluates its argument. If the result is an`
alias, the alias itself is passed. If the result is a string
(but the trimmed unevaluated parameter was not a
quoted string literal), it is treated as a variable name,
and an alias for the named field is passed. Otherwise, the
resulting object itself is passed.
ALIAS) is
just like BASEALIAS. However, if an alias would
be passed, the last alias in the chain of aliases that leads
to an object is passed instead.
VALDEF) turns
the argument into the body of an anonymous bound function, which
is then passed
DEF) checks whether
the first non-blank character of the unevaluated actual
parameter is a prefix character. If so, it trims and
evaluates it and treats it like a variable name. The object
referenced is then passed. Otherwise an anonymous function
is defined whose body is the argument, and that is passed.
QUOTE) does not
evaluate its argument. It treats it as an uninterpreted
string, which is then passed
For QUOTE, DEF and
VALDEF parameters, the actual parameters must
be well-formed. That is, included square brackets, curly
braces, or parentheses must be balanced and properly nested,
or else null is passed instead.
For each parameter mechanism, Templet has a
corresponding built-in function that takes a single
parameter using the specified parameter mechanism and which
returns the argument ultimately passed. For example,
[ref sum] returns the object with the global
name sum -- i.e. the built-in sum
function.
Some special applicators relating to null are
[null] returns null
[null! $x] sets $x (an ALIAS parameter) to null
[null? $x] returns 1 if $x references a null, null otherwise
[nonnull? $x] return null if $x references a null, 1 otherwise
Similar applicators are available for space,
newline, tab, zero,
one, true,
and false.
The space, newline and
tab functions can take an integer parameter,
and the specified number of spaces, newlines, or tabs are generated.
Note that function which return booleans end (by
convention) with a question mark, while functions that set
variables or update the object held in a variable end (by
convention) with an exclamation mark. For example
[null! $x] is equivalent to [set! $x
[null]].
Functions that set or update variables, but which also
return some value (usually associated with the original
state) end (by convention) with an at-sign, so, for example
[set@ $x [null]] sets $x to null,
but returns the object previously referenced by
$x.
A string literal can be surrounded by either single or
double quotes. The standard Java backslash quoting
conventions are used within strings to represent special
characters. Inside a double-quoted string, \"
represents a double-quote. Inside a single-quoted string,
\' represents a single-quote. Of course,
\\ represents a single backslash.
Quote marks only quote string literals
when used as a non-QUOTE parameter
or when used following the prefix character
of a variable name.
A quoted string cannot be used as the target of an
application. Quoted strings have three uses:
If &d holds a dictionary, then either
[&d john] or [&d "john"]
returns the object indexed under the string
john. However, to obtain an entry indexed
under either the strings John Henry or
$nam, the unquoted form cannot be used.
Similarly, &"my var" refers to
the field named my var in the local context of
the executing environment.
The quote function also provides quoting.
It quotes its parameter and returns it, without treating any
characters, including quotes or backslash, as special.
However, the string must be well-formed.
Concatenation is an implicit operation in Templet. If
evaluating [foo 3] returns hello
and [foo 5] returns there, then
evaluating [foo 3] [foo 5]! results in
hello there!.
However, when a parameter consists of multiple
concatenated terms with top-level blanks, the blanks will
not be treated as characters, but as parameter separators if
the function can take multiple parameters. For example, if
$fn can take a variable number of
VAR (or VAL) parameters, then
evaluating [$fn [foo 3] [foo 5]!] will call
$fn with two arguments, hello and
there!.
There are two functions that can be used to avoid this
problem and pass the single string hello there!
to $fn -- cat and
val.
The concatenation function cat, which
concatenates an arbitrary number of arguments could be used,
with the example written as [$fn [cat [foo 3] " " [foo
5] "!"]]
The val function evaluates its single
VAL argument. So the example above could have
been written as [$fn [val [foo 3] [foo 5]!]].
The val function can also be used to quote
string literals, except that backslash is not treated
specially, and brackets do trigger application. So, If
&d holds a dictionary, then [&d
[val John Henry]] returns the object indexed under
the string John Henry.
The result of implicitly concatenating an alias or non-string object along with blanks (in either order) is the original object or alias -- the blanks are ignored. However, concatenating a non-blank string with an alias or non-string object converts the object or alias to a string which is then concatenated with the non-blank string.
Implicit string conversion uses the built-in
toString function. This first tries to use the
object class's toString instance function if
one is provided. The toString instance
function of all the built-in or library class evaluates the
detailed field of its thread-specific context
object (via ^threadclass).
If that evaluates to true, toString
generates a more detailed representation of the instance.
Variable names can be constructed dynamically. For
example, suppose that $from holds the string
a and that $to holds the string
b. Then [set! $[$to] $[$from]]
has the same effect as [set! $b $a] which sets
the field named by $b (corresponding to an
ALIAS parameter) to hold the value held by
$a (corresponding to a VAR
parameter).
However, if $from holds the string
$a and $to holds the string
$b. Then [set! [$to] [$from]]
will set the field named by $b to just hold the
string $a (not the object held by
$a).
Since the second parameter is a VAR parameter,
the object named by $a is passed only if the
unevaluated actual parameter itself begins with a prefix
character. It doesn't; [$from] begins with a
left bracket.
To obtain a reference to the object named by a
dynamically constructed variable, the function
ref can be used which has a single
REF parameter. Its argument is evaluated and
then treated as a variable name, and the object it names is
returned. So, in the example above, [set! [$to] [ref
[$from]]] will set $b to hold the value
held by $a.
The target of an applicator is automatically treated as
if it used a REF mechanism. If the result of
evaluating the target is a string, it is treated a variable
name, and the object it names is then taken to be the
target. So, if evaluating [&foo] returns
the string $baz and if $baz holds
a list, then [[&foo] 3] results in
evaluating [$baz 3] which returns the 3rd
element of the list.
get, put! and
set!. These functions treat aliases
transparently. However, there are other functions which
deal with aliases explicitly. This sections describes the
details of both of these kinds of functions.
[getBase object name/index]
returns the object or alias held in the named or indexed
field of the specified object. The first parameter is a
REF parameter, and the second is a VAR
parameter, so for example,
$x is an object with public named
fields, then [getBase $x foo] returns the
entity (i.e. object or alias) stored in the field named foo
of that object.
[$muck 9] returns an object
with public indexed fields, and $val holds 3,
then [getBase [$muck 9] $val] returns the
entity in the 3rd field of that object.
[putBase! object name/index
entity] places the provided entity in the
named or indexed field of the specified object. The first
two parameters are a REF and VAR
parameters. The last parameter is a VARBASE
parameter; this allows an alias to be passed;
if a VAR parameter was used, the alias would
first be resolved to the object it references, and that
would be stored instead. putBase@ is like
putBase! except that it also returns the entity
that was stored in the field.
A standard alias identifies a named or indexed field in
an object. The function [field object
name/index] returns an alias given the object
(a REF parameter)
and the name or index (a VAR parameter),
so, if $lst names a
list, then [field $lst 3] returns an
alias to the 3rd field of the list.
The related function classField returns an
alias to a field of the class object of a specified object.
So, for example, [classField $lst size] returns
an alias for the field named size in the list
class object (i.e. the field that holds the list size
function). [classField $obj $nam] is
equivalent to [field [getClass $obj] $nam].
Finally, the function threadField returns
an alias to a field of the thread-specific object associated
with the class of a specified object. [threadField
$obj $nam] is equivalent to [field
[getThreadContext [getClass $obj]] $nam]. For
example, evaluating [set! [threadField $obj detailed]
[true]] causes subsequent string conversions of all
objects of the same class as $obj in this
thread to be detailed (since by convention,
toString instance functions evaluate
[[field ^threadclass detailed]] to determine
whether to print a detailed representation of the instance.
Note that applying the result of the field function
has the following effect:
if the detailed field holds a function, that function is
evaluated; if it just holds a string, the value of that string
is returned.)
An alias itself directly references some entity -- either an object, or another alias. For example, a standard alias identifies a field in some object; that field holds an entity -- so a standard alias directly references the entity stored in the field it identifies.
[getDirect alias] returns the entity
directly referenced by the specified alias (except that if it is
passed an object, not an alias, the object itself
is returned). So, for example, [getDirect [field $lst
3]] returns the entity stored in the 3rd field of the
list -- it is equivalent to [getBase $lst 3].
getDirect has a REFBASE
parameter, so if $x holds an alias to the 3rd
field of $lst, then [getDirect $x]
will also return the entity stored in the 3rd field of the
list. getDirectAlias is similar to
getDirect, but it returns null if the alias
directly references an object.
[putDirect! alias entity] has
two parameters -- a REFBASE naming the alias,
and a VARBASE parameter identifying the entity
to be stored. It stores the entity through the the alias (so
it will now be directly referenced by it).
putDirect@ is like putDirect!
except that it also returns the entity that was directly
referenced by the alias.
If $lst names a list, and the 3rd element
of that list holds an alias, then [getBase $lst
3] returns that alias. However, [get $lst
3] (which has two VAR parameters) will
instead return the object referenced by that alias. If the
alias directly references another alias, get
will follow the chain of aliases until it reaches an object
(or null).
If the 3rd element of $lst holds an object,
then [put $lst 3 "Happy Days"] (with all 3
VAR parameters) will replace that object by the
string "Happy Days". However, if the 3rd element of
$lst holds an alias, then put
follows the chain of aliases (internally using
getDirectAlias) till it finds the final alias
in the chain (the one that directly references an object or
null). It then stores the string directly (using
putDirect!) through the alias. There is also a
put@ function.
The putEntity! function is just like
put! except that its 3rd parameter is a
VARBASE parameter as opposed to a
VAR parameter. If its 3rd parameter is an
alias, put! will resolve it to an object before
storing it; putEntity! will not.
putEntity@ is like putEntity!
except that it also returns the entity replaced by the
entity supplied.
For each parameter mechanism, there is a corresponding
function which returns whatever is passed to it as
transformed by the parameter mechanism. We now take a look
at how four of these functions -- refbase,
ref, basealias and
alias -- work in various situations. Suppose
that $lst and the field named lst
in $obj hold a list, while $ali
and the field named ali in $obj
hold an alias that references some other kind of object
refbase evaluates its argument:
getBase) and passed.
[refbase $lst] returns the list held by
$lst
[refbase $ali] returns the alias held by
$ali
[refbase [field $obj lst]] returns the
alias to the field lst in $obj
[refbase [field $obj ali]] returns the
alias to the field ali in $obj
ref is like refbase, but
if the result is an alias, it instead returns the object the
alias (directly or eventually) references. So,
[ref $lst] returns the list held by
$lst.
[ref $ali] returns the object referenced
by the alias held by $ali.
[ref [field $obj lst]] returns the list
held in the field lst in $obj.
[ref [field $obj ali]] returns the object
referenced by the alias held in the field ali
in $obj.
basealias is like refbase,
but if the result is a non-literal string, it is treated as
a variable name, and an alias for the named field is passed.
Remember that a variable name is just a shortcut for
identifying a field in a context, and so the alias returned
identfies the named field in the appropriate context object.
So
[basealias $lst] returns an alias for
$lst (that is, for the field named
lst in the current parameter context object).
[basealias $ali] returns an alias for
$ali
[basealias [field $obj lst]] returns the
alias to the field lst in $obj
[basealias [field $obj ali]] returns the
alias to the field ali in $obj
alias is just like
basealias. However, if an alias would be
passed, the last alias in the chain of aliases that leads to
an object is passed instead. So,
[alias $lst] returns an alias for
$lst.
[alias $ali] returns the alias held by
$ali
[alias [field $obj lst]] returns the alias
to the field lst in $obj
[alias [field $obj ali]] returns the
alias held by the field ali in
$obj
The set! function has two parameters -- the
second is a VAR parameter that resolves to the
object to be stored. The first is an ALIAS
parameter that determines where the object is stored.
For example, [field &x foo] returns an
alias for the field named foo in the object
named by &x, and [set! [field &x
foo] 20] sets that field to 20.
The setEntity! is like set!, except
that its 2nd parameter is a VARBASE parameter;
so an alias is not resolved to an object before being stored.
There are also set@ and setEntity@
functions.
freeze!, freeze@,
freezeBase!, freezeBase@,
freezeEntity! and freezeEntity@
have the same effect as the corresponding set
functions, but in addition
freezes the field that was changed
so that it cannot be subsequently changed (though the object
it holds can be).
[freeze! alias] and
[freezeBase! alias] freeze the field
with its current value.
Templet allows composite names to be constructed using
two infix naming operators (even in prefix mode) as
shortcuts for the field and
classField functions
The dot operator can be used as an infix alternative to
the field function whenever evaluation is being
done for any parameter mechanism that will treat the
evaluated result as a variable name. So [var
&x.foo] is equivalent to [var [field &x
foo]], which returns the object held in the field
named foo in the object named by
&x, but [val &x.foo] just
returns the string &x.foo
The related function classField and the
corresponding infix operator colon is used to return an
alias to a field of the class object of a specified object,
and can be used in the same circumstances. So, for example,
(in those circumstances), if $lst names a
list, then $lst:size returns an alias for the
field named size in the list class object
(i.e. the field that holds the list size function).
When the colon operator is used in the target of an
instance function application, it has an important side
effect -- the object which is determined by the left-hand
operand is stored in the instance field
of the environment created by invoking
the function. For example, evaluating
[$lst:length] invokes the length
function with ^this holding the list named by
$lst, which thereby returns the length of that
list. Also, using List's instance functions
get and put!, getting and putting
the 3rd element of $lst can be written as
[$lst:get 3] and [$lst:put! 3
$val].
The dot and colon operators are interpreted from left to
right, so $a.b:c stands for [classField
[field $a b] c].
Note that, interpreted as variable names:
&nam is short for ^locals.nam
$3 is short for ^parms.3
%foo is short for %.foo or
^this.foo
ident is short for ^global.ident
Application and concatenation are performed before field
access operators, so if [$foo] evaluates to the
string b, [$moo] evaluates to the
string az, and $baz names a
list, then $[$foo][$moo].3 names the 3rd
element of that list, as does $[$zoo] if
[$zoo] evaluates to baz.3.
Both infix and prefix modes are supported by Templet. A number of
built-in functions switch to evaluating their parameter(s)
in infix mode, including infix, which has a
single infix parameter whose value is returned, and
do which has multiple parameters (separated by
commas, since it is in infix mode), which are all evaluated,
but null is returned.
In addition, code enclosed in parentheses is evaluated
in infix mode, and is equivalent to passing the
parenthesized code to the infix function. The
following pairs of code fragments are the same:
[gt? &x 20] and (&x > 20)
[$lst 3] and ($lst(3))
[&foo $x "Bill Clinton"] and (&foo($x,"Bill Clinton")
A switch to prefix mode is triggered by using square
brackets within infix mode code, so prefix mode can be
intermixed with infix mode expressions. Thus the following
are all the same:
[gt? [sqr $x] 20], ([sqr $x] > 20) and (sqr($x) > 20)
In general, operators treat all their operands as
VAR parameters. There are two exceptions to
this.
++
and --, treat their operand as
an ALIAS parameter.
=, +=,
etc. treat their
left-hand parameter as an ALIAS parameter.
;) treats
both of its operands as VALDEF parameters. It
is evaluated by first inoking its left-hand operand and then
its right-hand operand (both without parameters), the latter
of which determines its result.
In infix mode, blanks or parentheses are used to delimit
values, variables and operators; operators and variable or
values must be separated by blanks to distinguish the
operators from parts of variable names. (Also,
& always refers to the bitwise-and
operator; &1 must be used to refer to the
first indexed local variable.) Otherwise, blanks, in
general are not retained in infix mode, except within quoted
strings, and within the actual parameter to a single
QUOTE-parameter function.
Implicit concatenation can only be used in limited cases
-- e.g. to concatenate strings that do not represent
variables, and even then, blanks need to be represented as
literals. In other cases, especially when variable names
are dynamically constructed, mixed modes or explicit
functions such as ref, or val are
required. Though not required, use of the concatenation
operator # is convenient in infix mode. For
example, the following are equivalent.
[val [$x][$y]], ( $x() $y() ), and ( $x() # $y() )
[val [$x] is [$y]], ( $x() " is " $y() ), ( $x() # " is " # $y() )
[sqr $[$nam]], ( sqr( $[$nam] )), and ( sqr( ref( "$" $nam )))
If parentheses simply enclose a string with no operators (including
implicit concatentation), then the string is used as a parameter to
the ref( $foo )
returns the object named by $foo, and evaluating
( sum ) returns the object named by sum --
i.e. the built-in sum function.
Classes whose objects are comparable will define a
cmp instance function which will return -1, 0 or 1,
depending upon whether the first parameter is less than,
equal to, or greater than the second parameter. If a
comparison operator is provided, it is the basis for the
infix comparison operators <,
<=, >, >=,
==, and !=, and both the built-in
static functions cmp, lt?,
le?,
eq?,
ne?,
gt?, and
ge?.
If no cmp function is provided, an
eq? instance function, if provided, becomes the basis
for the infix operators ==, and
!=, and for the the built-in
static functions eq?,
and ne?.
The cmp and eq? function for
strings first checks whether both arguments represent
numbers (with blank, empty, or null arguments representing
zero). If so, it performs numeric comparisons; otherwise it
performs lexicographic comparison.
The built-in static functions
numcmp, numlt?,
numle?,
numeq?,
numne?,
numgt?, and
numge?
always perform numeric comparisons, treating any
object that does not represent a number as zero.
The built-in static functions
strcmp, strlt?,
strle?,
streq?,
strne?,
strgt?, and
strge?, and
the corresponding infix operators
#<, #<=, #>,
#>=, #==, and #!=
perform lexicographic comparisons (based on a
locale-specific sort order which can be changed as needed).
Objects are converted to strings if necessary.
The built-in static functions
trimcmp, trimlt?,
trimle?,
trimeq?,
trimne?,
trimgt?, and
trimge?, and
the corresponding infix operators
#<#, #<=#,
#>#, #>=#,
#==#, and #!=#
perform
lexicographic comparisons on the trimmed strings (i.e. with
all blanks removed from the front and back)
The built-in function ident? and the
corresponding infix operator === (and
!==) test whether (or not) two objects are the
same. If no eq? or cmp function
is defined, then the operators ==, and
!= test for identity.
In infix mode, the = operator is used for
assignment, so the following are equivalent:
[set! $foo.x $val] and [do $foo.x = $val]
More generally, the = operator returns a
value which is that of the right hand operand, but which has
a side effect of performing an assignment. That is, (
$a + ( $b = 9 ) ) results in the sum of
$a and 9, and also results in assigning 9 to
$b.
Top-level parameter assignments are treated in a special way in infix mode for all parameters that are evaluated.
ALIAS parameters to yield aliases.
For example ( diff( $a, $b )) returns the
difference between $a and $b,
while ( diff( $b = $a, $a = $b )) returns the
same result, while at the same time swapping the contents of
$a and $b.
To just obtain the assignment side effects, the infix
mode do function can be used. That is,
[do $b = $a, $a = $b] simply swaps
$a and $b.
Functions can take multiple parameters, divided into groups (called clauses) some of which may be optional or repeated. For example,
[match &str][:pat/ x.*y]
finds (without overlap) all substrings matching the given
regular expression, and returns a list of them, while
[match &str]
[:replace +++ ---]
[:by ===]
[:subst]
[:limit/ 20]
or
[match &str]
[:limit 20]
[:replace +++ ---]
[:by ===]
[:subst/]
or
[match &str]
[:limit 20]
[:replace +++ ---]
[:by ===]
[:subst]
[/match]
starts with the string stored in &str,
finds at most 20 instances of the substring "===", replaces
them alternately with "+++" and "---", substituting them
back into the original string, which is then returned.
Typically there are many different clauses, many optional or alternates, and they can appear in any order. Note the use of the terminating slash or of the function name preceded by a slash to terminate the application.
This can also be represented in infix mode. Note that clauses which do not have parameters are not followed by parentheses.
( match(&str)
:by(===)
:subst
:replace(+++,---)
:limit/(20) )
Some functions are defined so that parameter groups may
repeat, for example, a multiple assignment version of the
set! function:
[set!][:! $a 9][:! $b 10][/set!]
which sets $a to 9 and $b to 10.
The first argument of each clause is an
ALIAS parameter; the second is a
VAR parameter. All parameters are computed and
passed before set! begins to execute and make
the assignments. Consequently, [set!][:! $a $b][:! $b
$a][/set!] swaps $a and $b.
Parameters can be embedded between clauses, such as in
[if (&x > 20)]
[$foo &x]
[:elsf (&y > 20)]
[$baz &y]
[:elsf (&x + &y > 20)]
[$glop &x &y]
[:else]
[$glop &y &x]
[/if]
An arbitrary number of parameters can be embedded between
clauses -- these are known as embedded parameters to
distinguish them from the direct parameters provided within
the clause. If there are multiple embedded parameters
between two clauses, then blanks are used as
separators, and the parameters are trimmed. If the function
is defined to only take a single embedded parameter, then
all blanks in it are (by default) retained. Embedded
parameters typically use the VALDEF parameter
mechanism, and are frequently used by functions (such as
if) that control the flow of execution.
Embedded parameters also can be used in infix mode, for example
[do
if( &x > 20 )
$foo( &x )
:elsf( &y > 20 )
$baz( &y )
:elsf( &x + &y > 20 )
$glop( &x, &y )
:else
$glop( &y &x )
/if]
If multiple embedded parameters are used in infix mode, they
must be separated by semicolons (as commas separate direct
parameters). For example, ifprog is like
if, but allows multiple embedded parameters;
when the enabling test is true, the parameters are each
evaluated in turn, with the result being the value returned
by the last one.
[do
ifprog( &x > 20 )
&x = &blotz( &x );
$foo( &x, &y );
&x + &y
/ifprog]
In this case,
[do
if( &x > 20 )
&x = &blotz( &x );
$foo( &x, &y );
&x + &y
/if]
has the same effect. if expects a single
embedded parameter. That parameter uses the operator
; (as opposed to the separator ;)
which evaluates both its left and right hand operands,
returning the value of the right one.
Some functions (e.g. macro and bound functions) can be defined so that they either accept parameters, or, if no parameters are provided, they inherit the parameters of either their defining or calling environment. Bound functions conditionally inherit parameters from their defining environment, so suppose some function is passed 678 as a parameter, and that function contains the following code:
[repeat 10]
[&blech $]
[/repeat]
The code fragment [&blech $] corresponds to a
VALDEF parameter to repeat, and so
becomes an anonymous bound function, which the repeat
function calls 10 times, but without any parameters. So,
within the anonymous function, $ refers to
the outer functions's parameter, 678, and so &blech
is called 10 times, each time passing 678 to it. But
suppose the function instead contains the following
code:
[iter 10]
[&blech $]
[/iter]
Unlike repeat, iter calls its
function with a parameter that, in this case, iterates from 1 to 10, so the
first call to &blech passes 1 as a parameter,
the second call passes 2, etc.
Now, suppose we wanted to pass to
&blech the sum of the iteration index and of the
parameter to the outer function (e.g. 678). This can be
written as
[set! &val $] [\ $ refers to the outer parameter]
[iter 10]
[&blech (&val + $)] [\ $ refers to the iteration index]
[/iter]
A more traditional form of iter is available
which names its control variable, avoiding the need to pass
a parameter to the embedded code. Thus, the following code
fragment has essentially the same effect.
[iter! &knt 10]
[&blech ($ + &knt)] [\ $ refers to the outer parameter]
[/iter!]
The body of an iterative control function may be executed
repeatedly. Each time it produces a result, which is used to construct
the overall result of the function.
Templet's built-in iterative control functions
(e.g. iter, for,
while, etc.) support a number of clauses that
determine how the overall result of the function is
constructed from the results of each iteration of the body.
In the simplest case, the results of each iteration are
concatenated together, so (using iteration over string lists)
[foreach Sandi|Andee|Kathleen]
<p>I loved [$].
[/foreach]
generates
<p>I loved Sandi.
<p>I loved Andee.
<p>I loved Kathleen.
while
[foreach Sandi|Andee|Kathleen][:trim][:separate ", "]
I loved [$]
[/foreach]!
generates
I loved Sandi, I loved Andee, I loved Kathleen!
and
[iter 10][:trim][:list]
[&plotz $]
[/iter]
produces a list of 10 values; the results of the calls to
&plotz with parameters 1 through 10.
The clauses that can be used with the built-in iterative control functions include:
[:void} -- the result is voided -- i.e.
always null; that is, the iteration is executed only for its
effect, not its value.
[:concat] -- the results of each
iteration are converted to strings and concatenated
together. This is the default.
[:concat separator] -- the results of
each iteration are converted to strings and concatenated
together separated by the specified separator.
[:strlst] -- the results of each iteration are
converted to strings and made successive elements of a
string list -- i.e. concatenated together with
| separating them.
[:list] -- the results of each iteration are
made successive elements of a list.
:xconcat, :xstrlst, :xlist -- these are
identical to :concat, :strlst, :list, except
that iterations that result in a null value are ignored (e.g.
not appended to a list, etc.)
:zconcat, :zstrlst, :zlist -- these are
identical to :concat, :strlst, :list, except
that iterations are ignored that result in either a null value or
a string that is empty or only contains blanks.
In addition, iteration functions can either use the
clauses [:trim] or [:notrim].
[:trim] indicates that within the body of the
iteration, all whitespace surrounding applicators is to be
discarded -- e.g. [$foo] and then [$baz]
is treated as [$foo]and then[$baz]. Specfiying
[:trim] or [:notrim] sets the
^trimming field of the environment. If not
explicitly set, its value is always inherited from its defining
environment.
(Note that in a trimmed environment, functions like
[space] and [newline] are the only
way to produce whitespace adjacent to an application.)
The eval
function evaluates a string, which is usually constructed
by concatenating quoted code
fragments together with values ordinarily evaluated from
other code fragments. It can be simpler to provide the
code as a string with the "quasi-quote" symbol '
used to mark applicators that needs to be evaluated first.
The quasiEval function quotes its argument,
then evaluates only quasi-quoted applicators. For example,
if [&args] evaluates to the string 3
4, then evaluating [quasiEval [sum
['&args]]] generates the string [sum 3
4]]
The code [eval [quasiEval [sum
['&args]]]] generates and then evaluates
[sum 3 4] with a final result of 7. This can
be short-circuited using the quasi-eval symbol
! to [!sum ['&args]].
Quasi-evaluation is also useful in deciding whether
to include an optional clause. For example:
[!match &str]
['if [&foo]]
[:limit &limit]
[/if]
[:replace "&"]
[:by "$"]
[:subst/]
In many cases, the apply function
(this is the built-in static apply function, not the instance
function used for defining application)
can be
used as an alternative to quasi-evaluation. There are three
variants of the built-in static apply function:
apply has 2
VAR parameters, and is used to call non-instance
functions. The first parameter specifies the target of an
applicator; the second provides an object
that can be used as a parameter context. It can be of any
class as long as it either has no named fields, or has
bound names.
apply function can also take 3
parameters and is used to call instance functions.
The final parameter still supplies a parameter
context; however the first two parameters supply the
instance object and the name of an instance
function in the instance's class object.
apply can take a single
parameter which supplies the parameter context. In
addition, it has an embedded (VALDEF)
parameter, to which the parameters are supplied
A variety of library functions assist in incrementally constructing, inspecting, and modifying parameter contexts, including those for functions with clauses and embedded parameters.
Templet provides a unified model for transfers of control that, in Java, are represented by return, break, continue, and throw. Since all forms of traditional statements in Templet are represented as functions, these various transfers of control all essentially dynamically unwind the stack looking for an appropriate function invocation (i.e. environment) to leave.
The leave function takes a Conductor as a
parameter (or creates one from provided values); it walks up
the stack, looking for an environment which matches the
conductor. When such an environment is found, it is exited
with a value provided either by the conductor or resulting
from executing a special clause defined as part of the
function of the matching environment.
It must be noted that Templet does not use Conductors to the same degree that Java, for example, uses exceptions. In Templet, many out-of-bound or exceptional conditions generally result in a null value. In particular, access to undefined fields, an application with null as a target both return null.
A conductor named fields are public. The fields below are defined when a conductor is created. Additional fields may be added in particular situations as needed.
class -- the class with which the conductor
is associated. By default, this is the class in which the
conductor is defined.
name -- the name of the conductor
match -- a value used to match the
conductor to a matching environment by leave.
value -- a value which is passed to the
matching environment, either for its use, or as its return
value.
reason -- A string describing the reason
for leaving.
[Conductor.new match value
name reason] creates a new conductor
using the specified fields in the following way:
Conductor.SelfValue, then its value will be
that of the conductor itself.
^class,
unless match is a Function, in which case
class is null.
[Conductor.new!] is similar to
[Conductor.new] except that it places the
conductor in the field of the current class named by
name and freezes the field.
The conductor instance functions like
and like! have the same parameters and effect
as new and new! except that any
fields not specifically provided are taken from the
instance.
The leave function can either
Conductor.new , in which case a conductor is
autonmatically created from the arguments.
Each environment also has a match field (i.e.
^match). Leave matches its conductor against
the match fields of the environments on the execution stack.
A leaving conductor matches a value if
Conductor.MatchClassName, and the
two conductors have the same class and the
same name.
Conductor.MatchClass, and the
two conductors have the same class.
Conductor.MatchAny.
label and try Functions Functions ordinarily place themselves in their
environment's match field, so that a conductor whose match
field contains a function can be used to return from that
function. Two special functions, label and
try, construct the environment's match field
differently.
When the label function is called, it places the
newly created environment itself in the environment's match
field. It takes (in addition to an embedded VALDEF parameter which
specifies the code to be executed) a direct
ALIAS parameter in which the environment is
stored as well. A conductor whose match field contains the
environment obtained through that alias can be used to break out of the
labeled code -- e.g.
[label &lbl]
[while 1]
[$foo &x]
[if [$tst]]
[leave &lbl &x] &x becomes the overall result of the label function
[/if]
[$baz]
[/while]
[/label]
A :label clause can also be used with a function
definition (when we get around to showing how
functions are defined).
[:label variable] specifies a variable
(interpreted within the context of the invoked function).
A reference to the invoked environment is placed in that variable as well
as in the match field of the environment (instead of just
a reference to the function). So leaving
with (a conductor based on) that variable leaves that specific
instance of the function -- which may be useful when functions
are called recursively.
The try function can take (in addition to an
embedded VALDEF parameter which specifies the
code to be executed) one or more parameters, which determine
the match field to be placed in its environment when invoked.
Conductor.Any
(see below) will be placed in the match field.
try:
[Conductor.matchAny] returns a conductor
(also available as Conductor.Any) that will
match any leaving conductor (i.e. its match field contains
Conductor.MatchAny)
[Conductor.matchClass class]
returns a conductor that will match any leaving conductor of
the specified class (i.e. its match field contains
Conductor.MatchClass).
The class defaults to the current
class if not provided.
[Conductor.matchClassName class
name] returns a conductor that will match any
leaving conductor of the same class and name. The class
defaults to the current class if not provided.
[Conductor.matchClassName! name] is
similar, but stores and freezes the conductor under the
specified name in the current class.
Function definitions, and
try and label functions can
specify a catch clause with an embedded
VALDEF parameter which is executed when and if
a conductor is caught. Its result becomes the result of the function
instead of the value of the conductor. The
actual conductor caught can be accessed via the
environment field ^conductor.
A finally clause with an embedded
VALDEF parameter can also be specified in the
same situations; it contains code that will be executed
immediately before execution exits the associated
function, whether
normally or via a leave. It does not affect the result
returned.
The built-in iterative control functions in Templet
(e.g. iter, for,
while, etc.) support a :label clause
that can be used to both continue and break with a number of
variations. In the simplest case, we find the following three
code fragments are equivalent:
[while 1]
[label &inner]
[$foo]
[if [$tst]]
[leave &inner val]
[/if]
[$baz]
[/label]
[/while]
[while 1][:label &inner]
[$foo]
[if [$tst]]
[leave &inner val]
[/if]
[$baz]
[/while]
[while 1][:label &inner]
[$foo]
[if [$tst]]
[continue &inner val]
[/if]
[$baz]
[/while]
There are a number of built-in functions -- like continue --
which construct specialized conductors and then leave with them.
All of these are caught by the matching control function.
The various specialized conductors have different names;
each one has a different effect on the flow of execution and on
the construction of the value ultimately returned by the iterative
control function.
[continue match val] is equivalent to
[leave [Control.makeContinue match val]],
where Control.makeContinue makes a conductor
with the specified match and value, and with a null name.
When caught by the control function, the value of the conductor is taken
to be the value of the executing the current iteration
This value is then used to build up the overall
value of the control function (as described earlier).
[skip match] is equivalent to
[leave [Control.makeSkip match]],
where Control.makeSkip makes a conductor
with the specified match, and with the name Control.Skip.
When caught by the control function, this iteration is skipped;
execution continues with the next iteration without changing
the current state of the overall control function value being constructed.
[break match val] is equivalent to
[leave [Control.makeBreak match val]],
where Control.makeBreak makes a conductor
with the specified match and value, and with the name Control.Break.
When caught by the control function, the control function is exited with
the value provided (ignoring the value already under construction).
[done match] is equivalent to
[leave [Control.makeDone match]],
where Control.makeDone makes a conductor
with the specified match, and with the name Control.Done.
When caught by the control function, the control function is exited with
the value constructed thus far.
[finish match val] is equivalent to
[leave [Control.makeFinish match val]],
where Control.makeFinish makes a conductor
with the specified match and value, and with the name Control.Finish.
When caught by the control function, the control function is exited with
the value constructed thus far, after incorporating the value passed
as the value of the final iteration.
The leave function does not cause a
non-local transfer of control by default. If leaving is not
enabled, then the result of calling leave (or
of a function that had leaving enabled and was left) is a
string representation of its conductor (which is null, if
[[field [getThreadContext Conductor] detailed]]
is false).
Why not enable leaving by default? Even though non-local transfers of control are useful, they conflict with a primary goal of Templet -- incremental text generation. The result of a top-level function call in Templet is generally the generation of text, possibly quite long, that can be stored in a file or sent out over the network. Unlike other languages, Templet need not make explicit calls to a write or append function to build up this text. Templet inherently concatenates the results of executing successive and iterative applications, and this concatentated result is itself the text to be generated.
Templet "knows", when a top-level function is called, where the resulting text is to be stored or sent. Templet does not wait until the top level function returns to store or send the generated text. Waiting would limit the size of text that could be generated. Instead, Templet writes out the text incrementally -- as it is being produced and concatenated -- even within nested function calls. That is if fn1's and fn'2 code are
[def! fn1]
Hello [fn2]. How are you?
[/def!]
[def! fn2]
my dear friends [fn3] and [fn4]
[/def!]
Then as fn1 executes, "Hello " can first be written, then as
fn2 executes, "my dear friends" cn be written, then the
results of fn3 (written out incrementally as fn3 executes),
etc.
Non-local transfers interfere with this model, since leaving a function generates its own value, ignoring whatever concatenated text has been incrementally produced by the function thus far.
Consequently, an environment must be explicitly enabled to support
conducting (i.e. non-local transfers) through the
[:conduct] clause, which may be used with iterative
control functions, and with function and class
definitions (below).
If an environment supports conducting, then it does not
perform incremental writes of concatentated text.
An environment supports conducting if either its calling
or its defining environment supports conducting.
Conducting is automatically enabled (and incremental writes are disabled) in a number of circumstances:
:void,
:list, :xlist, or :zlist,
conducting is enabled in the environment of the iterative function .
:xconcat, :zconcat,
:xstrlst, or :zstrlst,
conducting is enabled during each execution of the body
of the iterative function (but not
of the overall function itself).
def defines a new function -- by
default, a static function -- and returns it. For example,
the code below defines a function that takes a single
VAR parameter and doubles it, storing the
function in the local variable
&double.
[set! &double
[def][:parms VAR](2 * $)[/def] ]
Since the default parameter mechanism is VAR*,
which is consistent with VAR,
this can be written even more simply as
[set! &double [def (2 * $)]]
The more commonly used def! defines the
function (by default, an instance function),
and freezes it under that name in the class
object of the current environment. For example --
[class util.Math]
[def! double][:static][:trim][:parms VAR]
(2 * $)
[/def!]
[/class]
The class function creates an environment like the
current environment, but with the class context set to the (context)
object in util.Math (and with the instance field set to null).
Within that environment, it executes its embedded argument -- the
def! function invocation.
The def! invocation defines a static function
named double, and freezes the function under that name in
util.Math.
def and def! are identical to
Function.new
and Function.new!,
static functions in the Function class.
There are a number of common clauses used with function definitions that affect the result. They are:
:void -- it specifies that any result is
to be discarded; the function will always return null
:trim -- indicates that whitespace
within the function surrounding applicators
are to be discarded. This eliminates the blanks surrounding
(2 * $) in the example above, and ensures that
the result will be trimmed. The space,
newline and tab can be used to
generate whitespace if necessary.
:notrim -- indicates that whitespace
within the function surrounding applicators is to be retained.
If neither :trim nor :notrim is
specified, then when invoked, the environment field ^trimming
is instead inherited from its defining environment.
:prog -- indicates that multiple
embedded parameters are expected in the body of the
function. Executing the function will execute each of them
in turn, and the result will be the result of the final one.
:new -- indicates that a new instance of
the class (associated with the function) is created and
placed in the instance field when the function is invoked,
and is returned when the function exits.
:new! -- like new, except
that null is returned.
Both def functions can take a :label
clause which names a local variable in which a reference
to the function will be stored when the function is invoked.
This is useful in conjunction with leaving (i.e. returning from)
the function. A :conduct clause can also be used
which enables conduction (i.e. leaves) within invocations of
the function.
Built-in functions are implemented in Java; user-defined
functions can be implemented in Java as well by including an
:execute clause which specifies the fully
qualified name of the Java function which implements the
Templet function. When called, the Java function is passed
the calling context, the function object for the function
being called, and a parameter object (or null if there are
no parameters). These can be accessed and manipulated
via public Java API's
for the various classes of Templet objects.
A parameterless function is indicated by [:parms].
When there are a fixed number of parameters, the parameter
mechanisms of each can be listed -- for example
[:parms VAR VAL VAR].
Various suffixes can be used following the parameter to specify repeated or optional parameters. Specifically:
There can be at most one parameter suffixed with a
* or +. If there is more than
one suffixed parameters, then each optional parameter must
be prefixed with a unique number of exclamation marks that
indicates its priority. For example, consider
[:parms !!!VAR? VAL+ !REF? DEF !!REF?]
A minimum of two parameters are expected, a VAL
and a DEF parameter. If there are 3
parameters, the VAR has the highest priority,
so we have a VAR, followed by a
VAL followed by a DEF parameter.
The last REF has the next highest priority, so
4 parameters use the mechanisms VAR VAL DEF
REF. With 5 parameters, we get VAR VAL REF DEF
REF, and with 6 or more parameters, we can start
repeating the VAL parameter, so with 7
parameters, we expect VAR VAL VAL VAL REF DEF
REF.
The use of DEF indicates a call-by-def
parameter, but does not specify what parameters it expects.
This can be made explicit by specifying the parameters
within angle brackets, for example DEF<REF
VAL> indicates a function that has two parameters
-- a REF and a VAL parameter. The
default is DEF<VAR*>.
A class can have two or more completely separate definitions of
the same function so long as the number of possible parameters
for each definition do not overlap. The function #$
can be used to determine the number of parameters that were passed.
[:parms name:VAL VAR newval:VAR]
then the 1st parameter can be named as either
$1 (or just $) or as $name, and the 3rd
parameter can be named as either $3 or as
$newval. The second parameter is unnamed
and can be referred to only
as $2. Each named field in the parameter
context holds an alias to the corresponding indexed field.
Since VAR is the default direct parameter
mechanism, the name alone can be used for a VAR
parameter that has no suffixes (so long as the name is not the
same, including case, as one of the parameter mechanisms).
The example above could have been written as
[:parms name:VAL VAR newval]
Names are particularly useful when fields are optional or repeated. For example, in
[:parms foo:!!!VAR? baz:VAL+ ref:!REF? fn:DEF newref:!!REF?]
the variable $fn will always refer to the DEF
parameter, regardless of how many optional and repeated
parameters precede it. To find out the number of actual
repeated or optional parameters, the function
numparms can be used -- [numparms
baz] will indicate how many baz
parameters were passed. The code [parms baz]
will generate a new object with just the parameters
corresponding to baz.
The variable $baz can be used to refer to
the first one of the parameters associated with
baz; the # operator can be used to
refer to additional parameters. For example,
$baz#3 refers to the 3rd of the parameters.
The # operator can be used in conjunction with
the dot operator applied to any object that has bound fields
(keeping in mind that $name is a shortcut for
^parms.name). So obj.name#index is
short for [boundField obj name index].
Another useful function is boundIndex,
which also can be used with bound fields. [boundIndex
obj name] returns the index in obj to
which name is bound, so that
obj.name#index can also be written as
obj.( boundIndex(obj,name) + index - 1 )
Names can also be associated with the parameters
provided to a DEF parameter. For example, given the code
[set! &foo [def][:parms DEFThen][$ 123 4][/def]]
[&foo ($x + $y)] will result in 127.
A parameter group is defined by the clause
:clause. For example, suppose we wanted to
define a static void function setapply! (in the
current class) with clause places and
vals, that could, for example, be invoked as
[setapply! sum][:places &john &mary][:vals/ 40 50 60]
and that would add up the given values, and store the sum in
each of the named fields. We could define
[def! setapply!][:static][:void][:parms REF]
[:clause places][:parms placeparms:ALIAS+]
[:clause vals][:parms valparms:VAR+]
[set! & [apply $ [parms valparms]]]
[iter! &i [numparms placeparms]]
[set! $placeparms#[&i] &]
[/iter!]
[/def!]
It can also be defined without naming parameters as
[def! setapply!][:static][:void][:parms REF]
[:clause places][:parms ALIAS+]
[:clause vals][:parms VAR+]
[set! & [apply $ [parms @vals]]]
[iter! &i [numparms @places]]
[set! $@places#[&i] &]
[/iter!]
[/def!]
That is, [parms @vals] generates an object with
all the parameters provided to the vals
clause, and $@vals#3 names the 3rd such
parameter.
Note that [parms @] generates an object
with all the parameters provided to the main applicator, and
$@#3 names the 3rd such parameter, as, of course,
does $3.
Embedded parameters which are expected before or after a
particular defined clause are indicated by using
:before or :after following a
definition or clause. For example, a simple form
of the iter function could be defined (using
while) as
[def! iter][:static][:trim][:parms VAR][:after fn]
[set! &knt 1]
[while (&knt <= $)]
[$fn &knt]
[incr! &knt]
[/while]
[/def!]
The local variable &knt is used to count
from 1 up to the provided limit. On each iteration of the
loop, &knt is provided as a parameter to
the embedded code, named by
$fn (with the default embedded parameter
mechanism -- VALDEF<VAR*>).
Because :after is specified, but there are
no clauses defined, iter must be
terminated by using /iter.
When :before is used with the main
definition, it means that the corresponding embedded
parameter appears immediately before the terminating clause.
In fact, since the above definition of iter
only has a single embedded parameter, it could have been
written using [:before fn] instead of
[:after fn].
If a clause has an :after embedded
parameter, the parameter need not appear immediately after
its defining clause. If the following clause is defined
with [:skipAfter], then the embedded parameter
will be expected after it instead (unless the following
clause is also defined with
[:skipAfter]). :skipBefore works
the same way in conjunction with :before. As an
example, def! itself is defined with
[:skipAfter]. Part of its definition is
[def! def!][:void][:parms fnam fhold][:after code]
[:clause void][:parms][:skipAfter]
[:clause trim][:parms][:skipAfter]
...
[:clause parms][:parms parmDescription][:skipAfter]
...
[:clause catch][:after catchfn]
[:clause finally][:after finalfn]
[/def!]
That is, the code that defines the function does not appear
immediately after the main def! applicator,
but skips after the auxilliary clauses like :void
and :parms, but before :catch and/or
:finally.
The parameters associated with embedded parameters do
not need to be explicitly named. If embedded parameters are
specified after some clause foo, then
$+foo names the first such parameter (and
$+foo#3 names the 3rd such parameter).
Similarly, $-foo names the first embedded
parameter before foo, and $+ names
the initial embedded parameter, while $- names
the final embedded parameter.
So, the code for iter
can also be written as follows (keeping in mind that
[:after] implies the default embedded parameter
mechanism).
[def! iter][:static][:trim][:parms VAR][:after]
[set! &knt 1]
[while (&knt <= $)]
[$+ &knt]
[incr! &knt]
[/while]
[/def!]
Another simple example is the definition of the library
function with, which has both a direct and an
embedded parameter, and executes the embedded parameter
passing it the direct parameter:
[def! with][:trim][:parms VAR][:after]
[$+ $]
[/def!]
For example,
[with wobble]
I [$], I [$]d, I will [$].
[/with]
results in: I wobble, I wobbled, I will wobble.
quote function is defined with [:parms
QUOTE@] meaning that its single parameter can be
provided as either a direct or embedded parameter, so a
quote can be written as either of the following
[quote this is a long quote]
[quote]this is a long quote[/quote]
The built-in single-assignment set!
function has parameters defined as [:parms ALIAS
VAR@VAL] meaning that its second parameter can be
provided as either a direct VAR parameter or as
an embedded VAL parameter (but named in
either case as $), so that one could
write either of
[set! &x (some complicated expression)]
[set! &x]
(some complicated expression)
[/set!]
The comment and void functions are similar.
The comment function quotes and then ignores its argument,
and is defined as
[def! \][:parms QUOTE@][/def!].
The void function evaluates its function argument,
and throws away the result, and is defined as
[def! void][:void][:parms VAR@VAL][$][/def!]
In the examples above, parameter groups were optional
and could appear in any order. If order matters or if
parameter groups can be repeated, then the
:ordered clause must be specified as
part of the definition.
In ordered function definitions,
there are additional name bindings:
$ still refers to the first parameter
of the main applicator, and $+ and
$- still refer to the first initial and
terminating embedded parameter. If a parameter within this
groups is named foo, then $foo refers to the
named parameter as usual.
blotz, then $blotz refers to the
named parameter as usual. But if that clause appears multiple times,
then $blotz refers to that parameter in the last such
clause.
$@3 refers to the 3rd clause
group actually provided (and names its first parameter).
$@+3 refers to the embedded parameters after
the 3rd group, and $@-3 refers to the embedded
parameters before the 3rd group.
$@0 can also be used to refer to the
arguments of the main applicator,
and $@+0 and $@-0
can also be used to refer to the initial and terminating
embedded parameters.
foo, then that parameter can be named as
$@3$foo.
[tag 3]. It is also named by
$@3$.
[numclauses]. It is also named by
$@#
As an example, given a primitive cond
function which takes a DEF parameter indicating
a test, and an embedded VALDEF parameter
specifying a function to be evaluated if the test succeeds,
a simple if function can be coded as
[def! if][:static][:trim][:ordered]
[:parms DEF<>][:after VALDEF<>]
[:label &fn]
[:clause elsf][:parms DEF<>][:after VALDEF<>]
[:clause elsfnot][:parms DEF<>][:after VALDEF<>]
[:clause else][:parms][:after VALDEF<>]
[cond [$] [leave &fn [$+]]
[iter! &i 2 [numclauses]]
[cond
[select [tag &i]]
[:case elsf] [$@[&i]]
[:case elsfnot] [not [$@[&i]]]
[:case else] 1
[/select]]
[leave &fn [$@+[&i]]]
[/cond]
[/iter!]
[/def!]
The multiple-assignment set! function can be
defined in terms of the single-assignment version (the fact
that there are two definitions is ok -- the single
assignment versions takes either 1 or 2 parameters; this
version takes no parameters).
[def! set!][:static][:void][:ordered][:parms]
[:clause !][:parms left:ALIAS right]
[iter! &i [numclauses]]
[set! $@[&i]$left $@[&i]$right]
[/iter]
[/def!]
Function definitions that ue :ordered can
contain :clause clauses without a direct
parameter. If there is more than one such clause, their
associated :parms clauses must not conflict.
This allows invocations of the function to have arbitrarily
named clauses.
When a function is defined, it retains its defining environemnt --
the environment in which it was specified, or an alternate one
explicitly specified, using [:env environment].
When the function is invoked, the defining environment
and the calling environment together produce the invocation
environment, by setting ^class,
^this, ^parms and
^locals. There are five environment control
clauses that can be used to control the overall
setting of the of the invocation environment:
[:static]
[:instance]
[:dynamic]
[:bound]
[:macro]
In addition
:parms (as already described) is used to
specify that parameters are expected to be passed (overriding
the setting due to the default or specified
environment control clause).
:condparms can be used instead of
:parms. It indicates that if no actual
parameters are provided, then parameters are set based on
the default or specified environment control clause.
:locals can be specified to indicate that a
new local context is to be created instead of inheriting it.
If a list of names is provided, then the names of locals
will be limited to these.
When a function is defined by virtue of being passed as
a DEF or VALDEF parameter it
defaults to
[:bound][:condparms VAR*]
However, if it is being defined as the code of a
def function, then it defaults to
[:static], and if it is being defined as the
code of a def! function, then it defaults to
[:instance].
The redef function redefines a function,
using any of the environmental control clauses above.
In addition, [:env] can be used (without any
parameter) to change the environment of the function to the
environment from which redef is called; if
:env is not used (with or without parameters)
the returned function has the same environment as the
original function.
For example, consider the block
function. It has a single embedded parameter, and executes it
in a environment just like the current environment except
that it has a fresh local context. It can be defined in either
of the two ways below (the second of which is preferred):
[def! block][:macro][:trim][:locals][:parms][:after]
[[redef $+][:env/]]
[/def!]
[def! block][:trim][:parms][:after]
[[redef $+][:locals/]]
[/def!]
[chop $str 5] returns the string named by
$str with 5 characters chopped off at the end.
[chop! $str 5] returns nothing, but chops 5
characters off of the string referenced by $str.
[chop@ $str 5] also chops 5 characters off of
$str, and returns the 5 characters chopped off.
substring function, we can define
the 3 chop functions via a single function definition as
[def! chop][:trim][:parms str len]
[substring $str 1 (len + 1)]
[:make! str]
[:make@ str]
[substring $str (len + 1)]
[/def!]
[:make! str] indicates that
!
appended to the name (e.g. chop!)
[:void]; however if the original function is
specified with [:new], the new function is
specified with [:new!].
str) should become an ALIAS
parameter.
[:make!] (without any parameters) indicates
that the new function should have an additional
ALIAS parameter (to store the result) as its
first parameter.
[:make@ str] (or [make@])
can be applied to a function with or without a terminating !.
It
:make@
clause and remebers the result
! version of the function
newclass and newclass!
(also available as the static Class functions Class.new
and Class.new! create a new class, and execute
their embedded parameter within an environment in which that class
is the class context. For example, a Stack class can be defined
as
[newclass pkgs.util Stack]
[:noNames][:privateNdxs][:trim][:conduct]
[def! size][:parms]
[size %]
[/def!]
[def! clear!][:void][:parms]
[setSize! % 0]
[/def!]
[def! top][:parms]
[% -1]
[/def!]
[def! pop!][:void][:parms]
[removeBack! %]
[:make@]
[% -1]
[/def!]
[def! push!][:void][:parms VAR+]
[foreach ^parms]
[addBack! % $]
[/foreach]
[/def!]
[def! new][:new][:parms VAR*]
[if [#$]]
[apply % push! ^parms]
[/if]
[:make!]
[/def!]
[/newclass]
Notes:
[newclass pkgs.util Stack] causes the newly
created class object to be stored under the name
Stack in pkgs.util; that field is
then frozen.
pkgs is global.
[:noNames] indicates that instances of
Stack will not have named fields, while
[:privateNdxs] indicates that a Stack has
indexed fields, but they can only be accessed directly (i.e. using built-in
functions such as get and put!)
within functions whose class context is Stack.
:conduct and
:trim (or :notrim)
clauses can be associated with class definitions,
enabling conducting and trimming (or disabling trimming)
in the environment of the
class definition, where it is inherited by functions
dcefined within the environment.
size returns the number of defined indexed fields.
setSize sets the
number of defined indexed fields (accessing any field above the size
set will return null).
addBack! appends a new indexed field with the value
specified.
removeBack! removes the last indexed field.
[% -1] returns the object at the last
indexed field.
pop! just removes the top element of the
stack (returning null) while pop@
(automatically generated by [:make@] also returns
the element removed.
push!
with ^this continuing as the instance field
is like calling push! with those parameters.
new includes
[:make!], a new! function will be
automatically generated with an additional
ALIAS parameter (the first parameter) into
which the newly created stack is placed.
If a class does not provide an apply
instance function
then a default definition is used:
[def! apply][:trim][:parms VAR*]
[if [#$]]
[apply % get ^parms]
[:else]
(%)
[/if]
[/def!]
Fields of class objects may be marked as private, indicating
they can only be accessed within functions associated with
the class. Fields holding functions can be marked private
if the function has a [:private] clause.
Other fields can be marked private by listing them in
the markPrivate clause of newclass.
When a class is created, clauses are used to specify the disposition of the named and indexed fields of instances of the class.
[:noNdxs], [:noNames] --
specify that there should be no indexed or named fields.
[:publicNdxs],
[:publicNames] -- specify that there should be
indexed or named fields, ccessible through
built-in functions.
[:privateNdxs],
[:privateNames] -- specify that there should be
indexed or named fields, but they can only be accessed directly
(i.e. using
built-in function) within functions executing with the class
context of the class. These are the defaults.
[:virtualNdxs],
[:virtualNames] -- specify that there should
not be indexed or named fields, but when built-in
function are called that access the fields, the call is
redirected to specific instance functions which may be
defined in the class. If they are not defined, the
application is a noop.
[:mediatedNdxs],
[:mediatedNames] -- specify that there should
be indexed or named fields, but when accessed using built-in
functions called from functions external to the class, the call is
redirected to specific instance functions which may be
defined in the class. If they are not defined, the
application is a noop.
get
and/or getBase, for set and/or
setBase if fields can be updated, and for
remove if fields can be removed.
size
should be provided, and
insert may be provided to support inserting
fields.
elements may be
provided which enumerates the elements of the fields.
keys should be provided
which enumerates the keys available, and elements
may be provided.
A number of additional clauses may be used to optimize the implementation:
[:names names] -- in
each instance, reserves fields with the specified names
[:freezeNames names] -- like
:names, but guarantees that no other names can
be added.
[:recycleNames] -- indicates that names
may be arbitrarily added and removed; i.e. the
implementation should not reserve a place for each name ever
encountered
[:boundNames] -- guarantees that every
named field is either null or holds an alias to an indexed
field in the same instance.
[:boundNdxs] -- gurantees that every
indexed field is either null or holds an alias to a named
field in the same instance.
[:sized size] -- guarantees that
the number of indexed fields is fixed at the given size.
Templet facilitates extension of a class from other classes. The
:extends clause, which takes one or more classes,
can be used in conjunction with creating a new class.
Every
public instance function defined
in the named classes is redefined in the environment of
the new class. All the named classes must be
field-frozen,
otherwise newclass leaves with the
Class.NotFrozen conductor.
Any redefined function can be overridden by explicitly redefining
it in the body of the class definition. Any inherited functions
which are not overridden are
frozen in the new class except for those listed in
a :noFreeze clause. [:noFreeze]
indicates that none of the fields of the class should be
automatically frozen.
If :extends causes redefined functions with
the same name from different classes to have inconsistent
parameters, or if a redefined function is inconsistent with
a function that overrides it, then newclass!
leaves with the Class.InconsistentDerivation
conductor.
If functions from two or more extended classes have the same name and identical parameters (and are are not overridden), the one from the first class listed is redefined in the new class. If the parameters are consistent, but a function from a class listed later covers a wider range of parameters, then that function is also redefined, but only covering the number of parameters not covered by functions in earlier listed classes.
When :extends is used, the disposition of
instance fields (e.g. :privateNdxs)
is derived from the first class listed, but
can be overridden by explicitly providing the disposition
clauses.
[supports? object class] is
true if the object is an instance of the specified class, or
a class (possibly transitively) extended from it. It is a
shortcut for [[[getclass
object]:supportClasses]:contains?
class].
Similarly, [supports? object function] is
true if the specified function has parameters consistent with
an instance function of the specified class. It is a
shortcut for [[get [getclass
object] [function:getName]]:consistent?
function].
Extension is useful for "framework" classes in which some of the functions are expected to be replaced to vary the behavior of the class. For example, the SortedDictionary class is like an ordinary dictionary, except that it also (appears to) have bound indexed fields which order the dictionary entries -- i.e. getting the i'th indexed field gets the entry that is i'th in the ordering.
The SortedDictionary class uses a cmpElem
function which takes the names of two entries, and indicates
the relationship between the entries. The built-in
cmpElems function (passed the names of two entries)
sorts entries based on their
values. Derivation can be use to sort in some other way --
e.g. based on the names of the entries --
by overriding cmpElems:
[newclass! pkg.util EntrySortedDictionary]
[:extends pkg.util.SortedDictionary][:trim][:conduct]
[def! cmpElems][:parms VAR VAR]
[cmp $1 $2]
[/def!]
[/newclass!]
derive function. Derived functions are not
automatically frozen; a :freeze clause can be
use to specify which functions to freeze, or all of them
with :noFreeze additionally used to specify
which ones should not be frozen. When using derive,
every function (including private and non-instance functions)
is redefined for the new class, however an :include
or :exclude clause can be used to explicitly include
or exclude specific functions.
Thus, although the benefits of the "supports" relationship are lost, EntrySortedDictionary can also be defined as:
[newclass! pkg.util EntrySortedDictionary]
[:noNdxs][:mediateNames][:trim][:conduct]
[derive pkg.util.SortedDictionary][:exclude cmpElems][:freeze/]
[def! cmpElems][:parms VAR VAR]
[com $1 $2]
[/def!]
[/newclass!]
It is common when using :extends
to use derive to import
the static and private functions from the same class.
Also, when extending from multiple classes, derive
can be used to overide the default derived function with one
from a class listed later.
Delegation is typically used to define a new class that adds some orthognal functionality to an existing class or combines the orthogonal functionalities of two or more existing classes. For example, suppose a user had defined a Status class which maintained some complex notion of status along with functions to set and query the status. The user had also defined a Stack class, and now wanted to define a StackWithStatus class that provides the functions of both.
Using delegation, this class would be implemented so
that each instance would hold a Stack instance and a Status
instance. Stack-related function would be redirected to the
Stack instance, and Status-related function would be
redirected to the Status instance. This could be
implemented as
[newclass! &myclasses StackWithStatus]
[:noNdxs][:privateNames][:freezeNames stk stat]
[:extends pkgs.util.Stack &myclasses.Status]
[delegate/ pkgs.util.Stack stk]
[delegate/ &myclasses.Status stat]
[def! new][:new][:parms]
[pkgs.util.Stack.new! %stk]
[&myclasses.Status.new! %stat]
[make!]
[/def!]
[/newclass!]
The delegate function takes a class and the name of a field.
For each public instance function of the class, it defines a
public instance function in the current environment which
does nothing but call the corresponding function with the
same arguments accessed through the named field. Additional
clauses can be used to exclude certain functions, or
prevent them from being frozen.
For example, the code above will
define a push! function equivalent to:
[def! push!][:trim][:parms VAR+]
[apply %stk push ^parms]
[/def!]
hasNext? which indicates
whether more values are available in the sequence, and
next, which provides the next value.
For example, since lists have an elements
function which returns an enumeraion of its elements,
the following code calls foo on each element
of the list lst.
[set! &elems [&lst:elements]]
[while [&elems:hasNext?]]
[&foo [&elems:getNext]]
[/while]
In fact, that is equivalent to use of the foreach
function below, which works with any object that provides an
elements function to generate such an enumeration:
[foreach &lazylst]
[&foo $]
[/foreach]
In Java, Enumerations are an interface, and each different kind of
enumeration is a different class.
Because functions are objects in Templet, and
can be stored as instance fields (i.e. fields in the instance
context), for most uses, Templet only needs a single Enumeration class.
Enumeration's new function has a number of clauses
with embedded
parameters -- one to define initialization, and one each for
the various functions defined for
enumerations. All are optional -- if
they are missing, either their functionality
is synthesized from other provided clauses, or else the functionality
is not supported:
index which indicates the index of the enumeration;
it should be initially 0, since no value is initially available.
hasNext? which indicates whether a subsequent value
is available
move which takes an integer argument and moves forward
that many elements in the enumeration. Some enumerations may support
negative values for the argument.
getAlias
should be provided, which returns an alias for the field
currently referenced by the enumeration. Given getAlias,
code can be synthesized for getBase, get,
setBase, set, and setEntity
if not explicitly provided.
getNext function can be provided; otherwise it
will be synthesized from get and move.
Of course, get can be synthesized, if necessary,
from either getAlias or getBase.
Similary moveToEnd can be synthesized from
move and hasNext?.
insert! (which inserts an element at the index)
and/or remove! functions are provided.
insert! also supports the function
add! (which inserts an element before the index,
and is equivalent to an insert followed by a
move) as well as insertList! and
addList!.
move
accepts negative arguments. This supports the enumeration
functions hasPrev?, getPrev,
peekNext, peekPrev, and
moveToBeginning.
reverse clause to support the
reverse function. Reversing an enumeration
means that index counts from the end of the
enumeration, that passing a positive argument to move
actually moves backwards in the enumeration, and
getNext? returns false when at the first element
of the enumeration.
These functions provided with the clauses
are redefined by Enumeration as dynamic -- so that
when called by the Enumeration code, %
refers to the enumeration itself.
This allows the provided functions to use the instance
fields of the enumeration for its own
implementation needs. Part of Enumeration's definition
is shown below:
[newclass! pkgs.classes Enumeration]
[:privateNames][:privateNdxs][:trim][:conduct]
[def! index][:parms]
[%_index]
[/def!]
[\ ---------------------------------------]
[def! hasNext?][:parms]
[%_hasNext?]
[/def!]
[\ ---------------------------------------]
[def! move][:void][:parms]
[%_move]
[/def!]
[\ ---------------------------------------]
[def! getAlias][:parms]
[%_getAlias]
[/def!]
[\ ---------------------------------------]
[def! getBase][:parms]
[%_getBase]
[/def!]
[\ ---------------------------------------]
[def! get][:parms]
[%_get]
[/def!]
[\ ---------------------------------------]
[def! getNext][:parms]
[%_getNext]
[/def!]
[\ ---------------------------------------]
...
[\ ---------------------------------------]
[def! set!][:void][:parms VAR]
[%_set! $]
[/def!]
[\ ---------------------------------------]
...
[\ ---------------------------------------]
[def! new][:new][:parms VAR*][:after]
[:clause index][:parms][:after]
[:clause hasNext?][:parms][:after]
[:clause move][:parms][:after]
[:clause getAlias][:parms][:after]
[:clause getBase][:parms][:after]
[:clause get][:parms][:after]
[:clause getNext][:parms][:after]
[:clause getPrev][:parms][:after]
[:clause peekNext][:parms][:after]
[:clause peekPrev][:parms][:after]
[:clause moveToBeginning][:parms][:after]
[:clause moveToEnd][:parms][:after]
[:clause setBase!][:parms][:after]
[:clause setEntity!][:parms][:after]
[:clause set!][:parms][:after]
[:clause setBase@][:parms][:after]
[:clause setEntity@][:parms][:after]
[:clause set@][:parms][:after]
[:clause remove!][:parms][:after]
[:clause remove@][:parms][:after]
[:clause insert!][:parms][:after]
[:clause insertList!][:parms][:after]
[:clause add!][:parms][:after]
[:clause addList!][:parms][:after]
[:clause reverse][:parms][:after]
[set! %_index [redef $+index][:dynamic/]]
[set! %_hasNext? [redef $+hasNext?][:dynamic/]]
[set! %_move
[def][:instance][:parms]
[if [#$]]
[%_move $]
[:else]
[%_move 1]
[/if]
[/def]
[/set!]
[set! %_getAlias [redef $+getAlias][:dynamic/]]
[set! %_getBase]
[if ($+getBase)]
[redef $+getBase][:dynamic/]
[:else]
[def][:instance][:parms]
[getDirect [%:getAlias]]
[/def]
[/if]
[/set!]
[set! %_get]
[if ($+get)]
[redef $+get][:dynamic/]
[:elsf ($+getAlias)]
[def][:instance][:parms]
[val [%:getAlias]]
[/def]
[:else]
[def][:instance][:parms]
[val [%:getBase]]
[/def]
[/if]
[/set!]
[set! %_getNext]
[if ($+getNext)]
[redef $+getNext][:dynamic/]
[:else]
[def][:instance][:parms]
[%:move]
[%:get]
[/def]
[/if]
[/set!]
...
[set! %_set!]
[if ($+set!)]
[redef $+set!][:dynamic/]
[:else]
[def][:instance][:parms VAR]
[set! [%:getAlias] $]
[/def]
[/if]
[/set!]
...
[apply [redef $+][:dynamic/] ^parms] [/ initialization]
[make!]
[/def!]
[/newclass!]
So, for example, List could define its
elements function so that it is simple,
reasonably efficient, and complete, except for
reversability, as
[def! elements][:trim][:parms]
[Enumeration.new %] [\ % refers to the List instance]
[zero! %i]
[set! %lst $] [\ $ refers to the List instance]
[:index]
(%i)
[:hasNext?]
(%i < %lst:size())
[:move]
[sum! %i $]
[:moveToBegining]
[zero! %i]
[:moveToEnd]
[set! %i [%lst:size]]
[:getAlias]
[field %lst %i]
[/new]
[/def!]
A Stream extends an Enumeration, iterating through characters, and may be backed by either a string, a file, or a network connection. A stream backed by a string supports all enumeration functions with additional functions for scanning.
A stream backed by a file supports different sets of operations depending upon whether the file is open for input, output, or input and output. A stream backed by a file open for --
get and scanning
operations, but not operations that update the contents or
structure. All built-in functions that scan strings operate
on input-backed streams as well. Input-backed streams
support multiple simultaneous readers, can be configured to
allow backward scanning (for limited or unlimited amounts),
and can be associated with functions that provide integrated
prompting.
add but not
get and not other operations that update the
contents or structure. Most built-in operations that produce
strings via incremental appends have variations that can add
to both strings and out-backed streams.
get,
set, arbitrary moves, as well as
add, but only when at the end of the stream.
Each thread has a standard input and output stream
associated with it, accessed by [stdin]
(equivalent to [get [getThreadContext Stream]
stdin]) and [stdout].
The standard streams are initialized when a thread
is created, but it is possible to change
them to other streams.
The codestream (or
codestream!) function has a DEF
(or an embedded VALDEF) parameter, and produces
a stream based on the incremental output of the function
passed to it (executed in a separate thread).
Streams can be connected together dynamically. In addition, a variety of functions are available to connect computations via streams. In particular:
pipe takes two or more embedded
VALDEF parameters. Each is executed in a
separate thread, and the standard output stream of the
i'th thread is connected to the input stream of the
(i+1)'th thread.
codepipe also takes two or more
embedded VALDEF parameters, which are
executed in separate streams. The result of the i'th thread
is turned into an output stream (via codestream)
and connected to the input stream of the
(i+1)'th thread.
A promise delays evaluation of a function until needed,
but subsequent evaluation of the promise returns the same
result, and does not recompute it. If &p
holds the promise defined by [Promise.new][foo
$][/new], then the first evaluation of
[&p] will evaluate [foo $] (in
the environment in which it was defined); subsequent
evaluations of [&p] return the same value
returned previously without recomputing it. Templet's
VALDEF parameter mechanism, combined with the use
of class-defined application make Promise an easy library
class to implement as well.
[newclass! pkgs.classes Promise]
[:noNdxs][:freezeNames][:trim][:conduct]
[def! apply][:parms]
[if (%fn)]
[set! %val [%fn]]
[null! %fn]
[/if]
(%val)
[/def!]
[def! new][:new][:parms][:after VALDEF<>]
[set! %fn $+]
[:make!]
[/def!]
[/newclass]
A LazyList combines the functionality of Lists and
Promises. It is created with a
function that computes a needed element on demand given the
lazylist ($lst)
and the index of the requested element ($ndx),
leaving with the conductor
LazyList.NotAvailable if that element cannot
be provided.
LazyList also remembers the value computed under that
index, so it doesn't need to recompute it when subsequently
asked for the value at that index. This provides a simple
"memo function" capability. For example, to generate a
memoized version of a function &f, just
define &fmemo as
[LazyList.new! &fmemo]
[&f $ndx]
[/new!]
That is [&fmemo 12] will return the same result
as [&f 12], except that the &fmemo
version internally remembers the result for later use.
Heres a version that computes &f only for
indices from 1 to 100:
[LazyList.new! &fmemo100]
[if ($ndx < 100)]
[&f $ndx]
[:else]
[leave LazyList.NotAvailable]
[/if]
[/new!]
Memoization via LazyList is especially effective for
recursive functions, such as fib
[LazyList.new! &fib]
[if ($ndx < 2)]
1
[:else]
( $lst( $ndx - 1 ) + $lst( $ndx - 2 ) )
[/if]
[/new!]
Iterations over an ordinary List typically compare a
control variable to the size of the list. This does not
work for a LazyList, since the size is not known a-priori.
Instead of checking the size of a LazyList, the
avail? instance function is used to determine
whether a particular element can be made available. (This
instance function is also defined for lists and string lists,
with availability determined based on the
size of the list).
If a LazyList is known to be able to make all fields
available up to some (unknown) index, but none thereafter,
then the code below,
which works for both lazylists as well as lists and string lists,
can be used to call foo for
each available element of &lst.
[for [one! &i] [&lst:avail? &i] [incr! &i]]
[&foo [&lst &i]]
[/for]
or its infix equivalent
[dofor &i = 1, &lst:avail?(&i), &i ++ ]
&foo(&lst(&i))
[/dofor]
LazyList is defined so that it uses only a single named instance
field which holds the function passed to the new!
function. A (slightly simplied) definition of LazyList is:
[newclass! pkgs.classes LazyList]
[:freezeNames fn][:mediateNdxs][:trim][:conduct]
[Conductor.matchClassName! NotAvailable]
[freezePrivate! ^class.Undefined [Object.new]]
[\ ---------------------------------------]
[def! getElem][:macro][:private]
[growSize! % $ ^class.Undefined]
[if (%[$] === ^class.Undefined)]
[set! %[$]]
[try ^class.NotAvailable]
[%fn % $]
[/try]
[/set!]
[/if]
[if (%[$] === ^class.NotAvailable)]
[leave ^class.NotAvailable]
[:else]
(%[$])
[/if]
[/def!]
[\ ---------------------------------------]
[def! get][:parms VAR]
[^class.getElem]
[/def!]
[\ ---------------------------------------]
[def! put!][:parms ndx val]
[put! % $ndx $val]
[/def!]
[\ ---------------------------------------]
[def! avail?][:void][:parms VAR]
[try ^class.NotAvailable]
[^class.getElem]
[leave ^class.avail? [true]]
[/try]
[leave ^class.avail? [false]]
[/def!]
[\ ---------------------------------------]
[def! elements][:parms]
[Enumeration.new %] [\ % refers to the LazyList instance]
[zero! %i] [\ % refers to the Enumeration instance]
[set! %lst $] [\ $ refers to the LazyList instance]
[:index]
(%i)
[:hasNext?]
[%lst:avail? (%i + 1)]
[:move]
[sum! %i $]
[:moveToBegining] [\ note: moveToEnd not provided]
[zero! %i]
[:getAlias]
[field %lst %i]
[/new]
[/def!]
[\ ---------------------------------------]
[def! new][:new][:parms][:after VALDEF<lst:VAR ndx:VAR>]
[set! %fn $+]
[:make!]
[/def!]
[/newclass!]
Notes:
[Conductor.classNew! NotAvailable]
is a conductor that LazyList's get function leaves with
if the the requested element is not available. Because it is defined
using matchClassName, it can also be used (by try)
to catch itself.
getElem is a macro without parameters, so
$ refers to the parameter of its caller. In this
case, that is an index.
growSize increases the number of indexed fields,
unless there are already the specified number.
The third parameter (which is optional) specifies a value
(in this case, ^class.Undefined, an object created and stored
privately in the class)
to be placed in the newly added fields.
[try ^class.NotAvailable]
[%fn % $]
[/try]
calls the provided function passing it the
lazylist and the index. If the function leaves with
^class.NotAvailable, then the try
function catches it, and since there is no :catch
clause, the resulting value is the value of
^class.NotAvailable, which is the conductor itself.
This is then stored in the specified indexed field.
getElem computes and stores the
value if necessary. If the value at the specified
index (previously or just computed) is
^class.NotAvailable,
that is used to leave the function,
otherwise the value is returned.
[:mediateNdxs],
the built-in functions on indexed fields are
redirected to the provided instance functions
get and put.
When a file containing Templet code is loaded, the code is first parsed into an internal form, and then partially compiled. However, an application cannot be fully compiled unless the target is known (and frozen to the extent necessary) since that determines how its parameters are to be compiled.
To make compilation easier, (1) a function, once
defined, cannot be changed, and (2) the most common function
for defining a new function, def! takes the
location where the new function is to be placed, and freezes
that field (see below).
Still, a function (e.g. one of a mutually recursive group of functions) may not be even be defined when an application of it is first parsed. However, the targets of most applications in a function are often frozen by the time the function is first executed. So, if a function is not fully compiled when first executed, Templet will try to complete the compilation at that point.
Nonetheless, there may be applications whose targets are never frozen, and which may or may not change. For each such application, Templet essentially does the following:
If a function has multiple clauses, the function and all its clauses must be available when the function is first parsed, since it is otherwise impossible to match the clauses and ensure unambiguous parses. Since this is not always possible, casts can be used.
If the name of a multiple parameter group function is
constructed dynamically, or if the function is computed,
then a known function with the same signature must be
specified, using the cast operator '.
For example, if [$foo] computes a function
with the same signature as match, then an
example invocation would be
[[$foo]'match &str][:pat/ x.*y]
Multiple parameter group instance functions must always
use the cast operator to specify the class of the instance.
For example, if some class Zlot had an instance
function glom with a terminating clause
/glom, then if $zlot held a Zlot,
glom would be invoked on it by
[$zlot'Zlot:glom] ... [/glom]
If the function (or its name) were dynamically computed as well, then the name would have to be cast as well:
[$zlot'Zlot:[$foo]'glom] ... [/glom]
Casting at the invocation site could be avoided by allowing
fields to declare the class of objects they can hold.
This would trade off dispatch code for run-time checks
on assignments, though the latter could be ameliorated by
type-inferencing. In any case, such declarations are not
presently suppored.
Templet supports a number of different levels of freezing through built-in function, or through clauses associated with a function or class. Each of these allows optimization during compilation. These include:
Deeper freezes allow the Templet compiler to perform better optimization. The Templet compiler marks a function if it determines that applications of it can be optimized by pre-evaluating it if all of its parameters are frozen, and this is used when compiling an invocation of it.
User-defined functions that have only QUOTE
parameters can interpret those parameters any way
they want. Effectively, such functions have complete
control over the syntax they interpret. They can
delay this interpretation until they are invoked.
However, Templet offers another alternative.
A function with only QUOTE parameters can
specify that it is a transformer by using the :transform
clause.
In that case, when the
compiler encounters an application of the function, the
function's code is immediately invoked with its actual
parameters. The result of the invocation must itself be a
string, which is substituted for the original application,
and is compiled in its place.
The infix-mode functions (e.g. infix,
do, dofor, etc.) operate in this
way, converting infix-mode code into equivalent prefix-mode
code.
Any object class can act like an alias as long as it defines the following functions:
getDirect -- which returns the object or
alias directly referenced by this alias
getDirectAlias -- if the entity
directly referenced by this alias is also an alias, it is returned,
else null is returned.
setDirect! -- which replaces the object or
alias directly referenced by a new object or alias.
setDirect@ -- like
setDirect!, but also returns the old object or
alias directly referenced.
The function makeAlias can be used to turn
such an object into an alias. That is, if $obj
holds such an object, then [makeAlias $obj]
returns the corresponding alias. Operations on the alias
are passed on to the underlying object.
The standard alias class is defined as
[newclass! pkgs.alias StdAlias][:noNdxs]
[:freezeNames obj id][:trim][:conduct]
[def! getDirect][:parms]
[getBase %obj %id]
[/def!]
[\ ---------------------------------------]
[def! getDirectAlias][:parms]
[if [alias? %obj.%id]] [refbase %obj.%id] [/if]
[/def!]
[\ ---------------------------------------]
[def! setDirect!][:void][:parms VARBASE]
[putBase! %obj %id $]
[/def!]
[\ ---------------------------------------]
[def! setDirect@][:parms VARBASE]
[putBase@ %obj %id $]
[/def!]
[\-------------------------------]
[def! new][:make!][:new][:parms VAR VAR]
[putFreeze! %obj $1]
[putFreeze! %id $2]
[/def!]
[/newclass!]
This is a slightly different version of StdAlias
which always resolves aliases immediately
to the eventual object.
[newclass! pkgs.alias BarrierAlias][:noNdxs]
[:freezeNames obj id][:trim][:conduct]
[def! getDirect][:parms]
[get %obj %id]
[/def!]
[\ ---------------------------------------]
[def! getDirectAlias][:void][:parms]
[/def!]
[\ ---------------------------------------]
[def! setDirect!][:void][:parms VARBASE]
[put! %obj %id $]
[/def!]
[\ ---------------------------------------]
[def! setDirect@][:parms VARBASE]
[put@ %obj %id $]
[/def!]
[\-------------------------------]
[def! update!][:void][:parms VAR VAR]
[set! %obj $1]
[set! %id $2]
[/def!]
[\-------------------------------]
[def! new][:make!][:new][:parms VAR VAR]
[set! %obj $1]
[set! %id $2]
[/def!]
[/newclass!]
Finally, the following code describes a direct alias --
one that holds onto an object (or other
alias) directly, rather than pointing to a field where it is held.
[newclass! pkgs.alias DirectAlias][:noNdxs]
[:freezeNames hold][:trim][:conduct]
[def! getDirect][:parms]
[refbase %hold]
[/def!]
[\ ---------------------------------------]
[def! getDirectAlias][:parms]
[if [alias? %hold]] [refbase %hold] [/if]
[/def!]
[\ ---------------------------------------]
[def! setDirect!][:void][:parms VARBASE]
[setBase! %hold $]
[/def!]
[\ ---------------------------------------]
[def! setDirect@][:parms VARBASE]
[setBase@ %hold $]
[/def!]
[\-------------------------------]
[def! new][:make!][:new][:parms VARBASE]
[setBase! %hold $]
[/def!]
[/newclass!]
Aliases are simply a special case of mediators, special object-like entities that mediate or monitor access to object or to individual fields. For example,
maintain
expects an infix assignment expression as a parameter.
It places update hook mediators or all variables mentioned on
the right-hand side of the assignment
(or on the objects they hold, if not strings).
The mediator then arranges for the assignment to be re-executed
in order to maintain the constraint when an update hook mediator
is triggered.
Any object class can act like a field mediator and may optionally define the functions below. An alias is a field mediator, and can define these as well.
getBase -- called to monitor or change the
entity directly obtained from a field.
getVal -- called to monitor or change the
object ultimately obtained from a field.
setBase -- called to monitor or change the
entity directly placed in the field.
setEntity -- called to monitor or change
the entity stored via the field.
postSetBase -- called after an entity is
directly stored in the field.
postSetEntity -- called after an entity is
stored via the field.
onAssociate -- called when a mediator is
being associated with the field.
onDisassociate -- called when a mediator
is being disassociated with the field.
In general, these functions can do one of three things:
Mediator.Accept,
which signals that the mediator allows the operation, but
does not want to affect it (this is the default if no such
function is supplied).
Mediator.Reject,
which signals that the operation should not be performed.
The operation itself leaves with
Mediator.Reject.
Within Templet, all operations that obtain the entity
stored in a field ultimately execute either
getBase or get. When this happens
on a mediated field, Templet does the following:
getBase functions on all the
field mediators in order (starting with the alias directly
referenced by the field, if any) passing them the object and
name of the field, and (initially) the entity it holds.
get, and the field
itself holds an alias, the object it references it obtained.
getBase and it
returned an object (not an alias), or if the operation was
get,
invoke the getVal functions on all the
mediators in order (starting with the alias directly
referenced by the field, if any) passing them the object and
name of the field, and (initially) the object obtained.
Setting a new entity via a field works somewhat
similarly. Within Templet, all operations that store an entity in a
field ultimately execute either putBase!,
put!, or putEntity!.
When this happens on a mediated field, Templet does the following:
put! or putEntity! and
the field holds an alias, getBase is
called for all the mediators to obtain the initial alias.
setEntity on all the mediators in
reverse order passing them the object and name of the field,
and (initially) the entity to be stored.
setBase on all the mediators in reverse
order passing them the object and name of the field, and
(initially) the entity to be stored.
postSetEntity is called on all the mediators.
postSetBase is called on all the mediators.
putBase@, put@, or
putEntity@ precedes the putBase!,
put!, or putEntity! with a
getBase or get. If one of the
get functions results in a rejection, the
appropriate put! is still executed but the
overall result is null.
An object that can act like a field mediator becomes
associated with a field by the mediateField
function. This causes the onAssociate
functions of all the existing mediators of that field to be
invoked to accept or reject the new mediator. The
onDisassociate functions are called in response
to unmediateField.
Typically, an access hook mediator would leave all
functions with Mediator.Accept (after invoking
its hook function):
[\ The FieldHook mediator is created with an object
that defines the methods onGet, beforeSet and afterSet.
They are then called at the appropriate times. ]
[newclass! pkgs.mediators FieldHook]
[:noNdxs][:freezeNames hook][:trim][:conduct]
[def! getVal][:void][:parms VAR VAR VARBASE]
[%hook:onGet $1 $2 $3]
[/def!]
[\-------------------------------]
[def! setEntity][:void][:parms obj field]
[%hook:beforeSet $obj $field [$obj $field]]
[/def!]
[\-------------------------------]
[def! postsetEntity][:void][:parms obj field val]
[%hook:afterSet $obj $field $val]
[/def!]
[\-------------------------------]
[def! new][:make!][:new][:parms VAR]
[set! %hook $]
[/def!]
[/newclass!]
An access control mediator would
leave with Mediator.Accept if access is
allowed, and with Mediator.Reject if access is
denied. A non-alias field mediator would only return a
value from a function like getVal (instead of
leaving) if it wanted to transform the result in some way.
Below we show a class that can be used as an access control mediator (except that Templet provides built-in support for this one).
[\ ClassPrivate could be used to ensure that only a field
of an object is only accessed by methods whose class
is the same as the object's class ]
[newclass! pkgs.mediators ClassPrivate]
[:noNdxs][:noNames][:trim][:conduct]
[def! test][:macro][:void][:private]
[if (^caller.class == [$obj:getClass])]
[leave Mediator.Accept]]
[:else]
[leave Mediator.Reject]]
[/def!]
[\-------------------------------]
[def! getVal][:void][:parms obj]
[^class.test]
[/def!]
[\-------------------------------]
[def! setEntity][:void][:parms obj]
[^class.test]
[/def!]
[\-------------------------------]
[def! onAssociate][:void][:parms obj]
[^class.test]
[/def!]
[\-------------------------------]
[def! onDisassociate][:void][:parms obj]
[^class.test]
[/def!]
[\-------------------------------]
[def! new][:new][:parms]
[:make!]
[/def!]
[/newclass!]
An object that can act like an object mediator become
associated with an object by the mediateObject
function. This causes the onAssociate
functions of all the existing mediators of that object to be
invoked to accept or reject the new mediator. An object
mediator can optionally support the following functions:
onAccess -- takes the name and
parameters of the function being invoked on the object, and
either leaves with Mediator.Accept or
Mediator.Reject, or returns a function to be
invoked instead of the original function if the operation is
accepted.
postAccess -- similar, but called after
an instance function is invoked on the object.
onCall -- (for Function objects only)
called when the function is called (and is passed the newly
created environment). Otherwise similar to
onAccess
postCall -- (for Functions objects
only) it is called with the environment (which contains the
return value) immediately before the function exits,
allowing the return value to be changed.
onAssociate -- called when a mediator
is being associated with the object. It can either accept
or reject the association.
onDisassociate -- called when a
mediator is being disassociated with the object.
getClass -- if defined, it returns
the class of the object (can be used for masquerading).
It can also just leave with Mediator.Accept
or Mediator.Reject.
Templet is configured so that it can be used as a back-end server for arbitrary protocols, maintaining contexts specific to a request, and to a session. Depending on the protocol, sessions can be defined per-connection or via session-id's automatically maintained by Templet. In the case of HTTP, session id's are passed via cookies if the browser supports cookies; otherwise they are encoded in URLs. Protocol support is also planned for NNTP, IMAP4 and POP3.
The environment variable ^session
references a context in which session-specific variables may
be stored. As the server receives a request, it identifies
the session with which it is associated, and associates the
approriate session context with the initial environment used
to process the request.
For protocols like HTTP in which session lifetimes cannot be determined, Templet allows specification of the length of time that session objects will be retained without use before they are garbage collected. For long-term storage, Templet can be configured so that some or all strings stored as session variables are backed up in a database.
Templet can be configured to deal with multiple simultaneous requests in the same session, allowing
As a web-driven back-end, Templet can either run as a Java servlet or as a separate Templet server, depending on a servlet, other server plug-in or CGI program to pass appropriate HTTP requests on to the Templet server; other configurations can be readily supported.
Templet has a number of modules that can be used to determine the principal connecting to Templet. For HTTP, support is provided for automatic determination of the user via certificates, cookies, or basic authentication. For cases where developers want more control, Templet allows the explicit setting of a user id, and keeps sessions and users associated with one another. A variety of functions are available that assist in managing users-ids, including support for verifying e-mail addresses based on responses to registrations.
The environment variable ^user
references a context in which user-specific variables may
be stored. As with the session context,
Templet can be configured so that some or all strings
stored as user variables are backed up in a database.
Templet can be configured to control whether more than one session can simultaneously exist for the same user. Templet can
The environment variable ^request
references a context in which request-specific variables may
be stored. Templet supports inclusion of per-protocol
plug-in modules that parse requests into request variables.
In the case of HTTP:
^request.headers, an object whose fields hold
the headers of the request
^request.content.
In the case of a POST, the contents are parsed into
the object ^request.fields. The input names
each become fields in ^request.fields,
and the corresponding values become their values.
If a name has multiple values associated with it, then
the values are combined into a string list associated
with the name.
URLs meant for (and produced by) Templet can be specially encoded. In addition to containing a session id, it can encode other information which is automatically parsed by the HTTP protocol module. A Templet URL can encode:
^request.parms
^request.values.
The plug-in for a protocol determines which Templet function is invoked to process a request. In the case of HTTP, a simple URL (without additional encoded information) starts a new session and delivers some home or entry page. The function used to provide this page is based on the path portion of the URL and is configurable via an initialiation file. URLs on that page (i.e. for links and forms) that refer back to the same site are dynamically produced and encoded.
As noted above, a URL for Templet can encode the export name of
a function to be invoked, or of a file to be loaded and run, to process
the request.
The association between Templet files or functions and their
export names is established by the exportFile
or exportFunction
function, which is typically called by the Templet code that
initializes a site.
For RPC-like (i.e. synchronous) protocols like HTTP, each request produces a response, which is the result of the function called to process the request.
Some protocols, like HTTP, include a header, as well as
content, as part of their response. The contents of the fields of
^request.response are used to determine the
response headers. These must be filled in before enough content
is produced to fill the first
packet of the response.
As described above, URL's passed to Templet may encode
session id's, export function names, and request and parameter
values. These URL's need to be generated as part of documents
that Templet produces.
HTML.url, a static function in the HTML class, takes
#) and indicates where to scroll to
in the loaded document
[getSessionId]) and obtains the
request values from the current values of ^request.values.
Every field in ^request.values that contains a string
is included as a named request value of the URL. In effect,
^request.values holds values that are passed along
(and possibly changed along the way) from request to request.
Two additional HTML functions take the same parameters
as HTML.url:
The function HTML.link constructs an HTML a element,
whose href parameter is the constructed URL, and whose
embedded parameter is placed between the a element
and the closing /a element. The function
HTML.form similarly
creates an HTML form element.
Support is also provided via HTML.plainUrl,
HTML.plainLink, and HTML.plainForm
for producing plain URLs without encoded parts. However,
both methods do perform the "%"-style encoding used to transmit
special characters. These functions take
The function HTTP.getUrl is used to make a request
to a web server.
In addition to the VAR parameter for the URL,
it has two ALIAS parameters -- the first one
indicates where an object will be stored into which the headers of the
response will be parsed. The second indicates where a stream will be
stored through which the content can be accessed. If the second
parameter is not provided, the content will be returned instead
as the result of the function.
Similarly, HTTP.postUrl is used to post a request
to a web server. It is additionally passed an object whose fields
determine the input values passed.
Similar functionality is also planned for other HTTP operations (including support for WebDAV), for sending mail via SMTP, and for posting newsgroup articles via NNTP.
Templet includes a variety of functions for simplified
construction of various HTML controls, including lists
and groups of radio or check boxes with dynamically-determined
initial values. For example,
[HTML.radioGroup satisfaction]
[:labeldef * <b>[$]</b> *]
[:label very low|low|medium|high|very high]
[:init low]
[:separator/ <br>]
constructs a group of radio buttons. :labels
determines the labels of the radio buttons; each label in the list
is processed by the DEF parameter of
the :labeldef clause to determine its actual value.
Since there is no :values clause, the bare labels
are also the values of the radio buttons. :init
specifies the value of the radio button initially checked, and
:separator indicates how the radio buttons are separated.
The function invocation above produces the text
<input type=radio name=satisfaction value="very low">* <b>very low</b> *<br>
<input type=radio name=satisfaction value=low checked>* <b>low</b> *<br>
<input type=radio name=satisfaction value=medium>* <b>medium</b> *<br>
<input type=radio name=satisfaction value=high>* <b>high</b> *<br>
<input type=radio name=satisfaction value="very high">* <b>very high</b> *
When Templet becomes available for client-side scripting, new classes (e.g. Window, Document) will becomes available that correspond closely to the EcmaScript model. We also expect that many of these objects (supporting DHTML operations) will be available on the server-side as well.
Templet code files will be able to be explicitly loaded and run
on the client side, typically to define Templet functions
which run in response to client-side events. In addition,
the Templet script function will define Templet code that
will be embedded within HTML SCRIPT tags and run on the client.
Via an initial Server object, client-side code can retrieve
references to other server-side objects, whose functions can
then be called from client-side code.
[DB.SELECT husband, wife, date
FROM Marriages
WHERE [&cutoffDate] > date]
<p>[$husband] was married to [$wife] on [$date]
[/SELECT]
That is, DB.SELECT is a static function in the DB class
with a single
direct VAR parameter that (afer evaluation)
represents the remainder
of an SQL SELECT statement. Its embedded parameter is
repeatedly invoked for each selected row, with the results
concatenated together. The embedded parameter is invoked
in an environment in which the column names correspond to
parameters -- holding the value for the corresponding
column in the current row.
If this statement will be executed multiple times,
a Query can be constructed first, and separately
processed:
[DB.newquery! &query]
SELECT husband, wife, date
FROM Marriages
WHERE date < [&cutoffDate]
[/newquery!]
[&query'Query:execute]
<p>[$husband] was married to [$wife] on [$date]
[/execute]
However, [&cutoffDate] will be evaluated
once when the query is defined. If we want to recalculate
the cutoff date between queries, we can use parameterized
queries (where each parameter is cast to an SQL datatype)
[DB.newquery! &query][:sqlparms cutoff'DATE]
SELECT husband, wife, date
FROM Marriages
WHERE date < $cutoff
[/newquery!]
[&query:prepare [&cutoffDate]]
[&query'Query:execute]
<p>[$husband] was married to [$wife] on [$date]
[/execute]
The query parameter name appears (prefixed by
$) within the SQL statement. Preparing a query
provides the values which replace the query parameters prior
to processing the query. The same query can be prepared
multiple times with different values.
Templet 1.0 was designed as a macro language for server-side scripting of HTML pages, and introduced the key characteristics of the Templet languages:
While built-in functions had parameter groups and supported the primary parameter mechanisms of Templet 3.0. user-defined functions in Templet 1.0 only allowed call-by-val and did not support clauses. In addition, all variables were global, except for indexed parameters.
Templet 1.0 had only two classes of objects --
functions and strings.
Strings in Templet 1.0 always represented themselves;
parameters were never interpreted as variables, thus the
Templet 3.0 code [foo $] had to be written as
[foo [$]]. Quotes were not used,
and the string [] was used instead of blanks to
separate parameters.
A prototype implementation of Templet 1.0 was produced in 1996 proving the power of the approach, and indicating the need for additional functionality.
Templet 2.0 added some important language features
A large number of built-in functions were included in Templet 2.0. Streams were added as well, including support for substitution of streams for strings in scanning string operations.
Templet 2.0 also included significant server-side functionality and web support, including transparent construction of URLs with session ids, request values, and function names (as part of defining links and forms), and simplified construction of controls with dynamically-determined initial values.
Templet 2.0 was completed in mid-1997, and was used to implement a number of personal web sites. It was available briefly as shareware, but unfortunately is no longer available or supported.
Templet 3.0 is now in the final stage of design. The primary language changes and extensions to Templet 2.0 include
Contact Ellis S. Cohen (e.cohen@acm.org) for more information on availability or on how you can participate in the implementation.