In 2000, the programmatic browser differences were perplexing, so I made a tool to help manage the problem. I started with something similar to ecomcon
(see How JavaScript Works, Chapter 23). I would have special markers in the source that designated sections of code that would only be delivered to particular browsers.
<~ns4~layer.visibility = "hide";~> <~ie5~layer.style.visibility = "hidden";~>
The convention of combining angle brackets with special characters was popularized by PHP. I chose ~
tilde because it is rarely used in HTML, JavaScript, or English.
I then thought to add variables.
<~set~year~2000~> <~get~year~>
If a variable could contain a sequence of digits, I should be able to add them.
<~set~next year~<~add~<~get~year~>~1~>~>
At this point I should have noticed that this language was going to be inexcusably ugly, but astonishingly, I did not notice at the time. I kept pushing on, inspired by better languages like TRAC and LISP. I determined that this was the wrong approach for dealing with browser incompatibility, but I completed the language anyway. I named it Tilton after Robert Tilton, a television faith healer and speaker of tongues. I believe that Tilton is the ugliest programming language that was not intended to be an ugly programming language.
I do not expect anyone to use Tilton, but I use Tilton every day. The json.org
site is managed with Tilton. So is this one.
Tilton is a simple macro processor. It is small, portable, and Unicode compatible. It is an even uglier language than Lisp because it replaces the parens with angle brackets and tildes while lacking any useful data structures.
Macro expressions are completely contained within a set of macro brackets.
<~ ~>
Macro arguments are separated by tildes. Adjacency is concatenation.
Tilton only uses those three characters. All other characters are treated literally, even whitespace. This makes Tilton uncommonly good at dealing with textual content.
The character set used by Tilton is the UTF-8 form of Unicode.
Tilton's character functions (length
and substr
) deal
in whole Unicode characters, not in bytes.
In its easiest form, Tilton requires no programming. For example, the shell command
tilton deluxe bogus <base >result
will replace occurrences of <~1~>
in the file
base
with deluxe
and will replace occurrences
of <~2~>
with bogus
and store the result in the file
result
.
The shell command
tilton -s name "Carl Hollywood"
will replace all occurances of <~name~>
with Carl Hollywood
in the standard input.
Tilton has two functions that can be used to include additional files. These can be embedded in the input text.
<~read~
filename~>
The read
function reads a file and inserts its
contents.
<~include
~
filename~
parameters~>
The include
function reads a file, but evaluates it
before inserting it. It can take a set of parameters and do
substitutions with them.
These functions can be used together, so that the statement
<~include~macros<~3~>.tilton~>
will use the third command line argument to select the name of a
file to include. If the third argument is "mac
", then the
file included will be macrosmac.tilton
.
A macro expression is
<~
macroname~
firstArgument~
secondArgument...~>
If macroname matches the name of a known macro, then the macro is expanded with the arguments, and the macro expression is replaced with its result. Macro expressions can be nested to any depth.
Extra arguments are usually ignored. Missing arguments are usually treated as empty text. Some of the built-in macros report errors if the correct number of parameters is not provided.
Tilton does lazy evaluation. Each parameter of a macro is evaluated
zero or one times. A parameter is not evaluated until its value is
needed. That value is reused if the parameter needs to be evaluated
again. This makes it possible to write functions like min
without worrying about side-effects due to multiple evaluations of
the arguments. It is possible to force evaluation of arguments with
the mute
function.
There are no rules on macro names. You can call them anything you like, with any combination of characters. Names are case sensitive.
The terms macros and variables are used interchangeably. The values stored in them are strings.
Strings of digits can be treated as though they are integers.
The flexibility of naming can be used to simulate arrays.
<~set~subscript~18~><~set~myArray[<~subscript~>]~42~>
Because subscript
has a value of 18
, we will
assign 42
to a variable called myArray[18]
.
Note that the brackets are not special in Tilton.
The macro expressions <~0~>
thru
<~9~>
are used to access parameters. They can also be
used as local variables. <~1~>
gets the value of
the first argument. <~1
~
value~>
sets it to a new value.
Built-in macro | Description | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a single digit |
Digit macros are used to access arguments. They can also be used as local variables.
|
||||||||||||||
|
This function can take up to nine arguments. It produces the sum. If there are no arguments, it returns |
||||||||||||||
|
Each of the values is evaluated in turn, until a null value (or empty string) is found. If all of the arguments have values, then the result is the last argument. |
||||||||||||||
|
The values are evaluated and appended to the named variable. |
||||||||||||||
|
Same as set, except that the macrobody is not evaluated.
It is used to make user defined macros. It is equivalent to There are 9 macro expressions that can be placed inside of macrobodies that are replaced with parameters when the macro is evaluated:
|
||||||||||||||
|
If the named variable exists, it produces the trueValue. Otherwise, it produces the optional falseValue. |
||||||||||||||
|
The named variables and macros are deleted. If a named variable does not exist, no error. This is different from setting a variable to an empty value because it causes the variable to not exist.
is shorthand for
|
||||||||||||||
|
Both of the arguments must be numbers. The first value is divided by the second value. The result is the integer quotient. If the second value is zero, then nothing is produced. |
||||||||||||||
|
This is used for debugging. It prints everything that has been defined or set. |
||||||||||||||
|
Replace certain special characters with their HTML entity equivalents. This escapes characters for HTML.
|
||||||||||||||
|
The two arg strings are compared. If they are exactly the same, then the true value is evaluated. Otherwise, the false value is evaluated. The false value is optional. It can also be used as a case statement.
The result is the first value whose case matches the case value. |
||||||||||||||
|
Evaluate the string, using the values to replace numbered variables in the string. |
||||||||||||||
|
Search the variable for the delimiters. The result is the text before the delimiter. That text and the delimiter are deleted from the variable. |
||||||||||||||
|
The two arg strings are compared. If the first is greater than or equal to the second, then the true value is evaluated. Otherwise, the false value is evaluated. The false value is optional. If both arguments are numbers, then they are compared as numbers. Otherwise, they are compared as strings. |
||||||||||||||
|
This produces a four-digit sequence number. |
||||||||||||||
|
Get the value of a variable without evaluating it. Any number of variables may be named, so
is shorthand for
If the variable or macro cannot be found, then there is an error. |
||||||||||||||
|
This produces the |
||||||||||||||
|
The two arg strings are compared. If the first is greater than the second, then the true value is evaluated. Otherwise, the false value is evaluated. The false value is optional. If both arguments are numbers, then they are compared as numbers. Otherwise, they are compared as strings. |
||||||||||||||
|
Same as read, except that the contents of the file are
evaluated. The |
||||||||||||||
|
Search the variable for the last delimiter. The result is the text after the delimiter. That text and the delimiter are deleted from the variable. |
||||||||||||||
|
The number of Unicode characters encoded in the UTF-8 string is returned. |
||||||||||||||
|
The two arg strings are compared. If the first is less than or equal the second, then the true value is evaluated. Otherwise, the false value is evaluated. The false value is optional. If both arguments are numbers, then they are compared as numbers. Otherwise, they are compared as strings. |
||||||||||||||
|
This produces the string without evaluating it. |
||||||||||||||
|
Evaluate the condition. If it is not null (empty string) evaluate the body. Repeat. |
||||||||||||||
|
This produces the |
||||||||||||||
|
The two arg strings are compared. If the first is less than the second, then the true value is evaluated. Otherwise, the false value is evaluated. The false value is optional. If both arguments are numbers, then they are compared as numbers. Otherwise, they are compared as strings. |
||||||||||||||
|
Both of the arguments must be numbers. The first value is divided by the second value. The result is the integer remainder. If the second value is zero, then nothing is produced. |
||||||||||||||
|
All of the arguments must be numbers.
Any number of arguments can supplied. The result is the
product. If there are no arguments, it returns |
||||||||||||||
|
All of the arguments are evaluated, but their output is suppressed. This macro can be used when a macro is to be used only for its side-effects, or to provide annotation and whitespace to definitions without cluttering the output. |
||||||||||||||
|
The two arg strings are compared. If they are exactly the same, then the false value is evaluated. Otherwise, the true value is evaluated. The false value is optional. |
||||||||||||||
|
The null macro does not evaluate its arguments, and it does not produce a value. It can be used to insert comments into Tilton files. If the arguments contain macro brackets, they must balance. |
||||||||||||||
|
If the string argument is a number (containing one or more decimal digits with an optional leading minus sign), then the true value is evaluated. Otherwise, the optional false value is evaluated. |
||||||||||||||
|
Each of the values is evaluated in turn, until a non-null value is found. That value is the result. If none of the arguments have values, then there is no result. |
||||||||||||||
|
The value is evaluated and output to the alternate output channel. |
||||||||||||||
|
The named file is opened and read. It is similar to
|
||||||||||||||
|
Produce as many copies of the value as determined by the number. |
||||||||||||||
|
A variable is created with the name of the first
parameter and the value of the second parameter. If the
value parameter is omitted, then the variable will be
emptied. (See |
||||||||||||||
|
Insert |
||||||||||||||
|
This stops the program without writing the output. It is used to halt when an error is detected. |
||||||||||||||
|
Both of the arguments must be numbers or it is an error. The second value is subtracted from the first. The result is the difference. |
||||||||||||||
|
Both |
||||||||||||||
|
This produces the |
||||||||||||||
|
This produces the current version of the Tilton
Macro Processor. Currently it returns |
||||||||||||||
|
The result is a string in which the leading and trailing whitespace is removed, and all remaining runs of whitespace are replaced with single spaces. |
||||||||||||||
|
Convert the numbers to Unicode characters. |
||||||||||||||
|
The value is written to the named file, replacing its previous contents (if any). |
Using these functions, you can create your own macros. There are examples below.
User-defined macros can replace built-in macros.
Tilton interprets its command line argument list as a list of
parameters. It assigns them to <~0~>
thru
<~9~>
. <~0~>
is usually the name of the
program. Missing parameters are treated as empty strings. Extra
parameters are ignored. It then reads the standard input and
evaluates it. When it is finished, if there were no errors, it writes
the result to the standard output. <~print~>
,
<~dump~>
, <~stop~>
, and errors write to
stderr
.
Tilton also provides some command line options.
Command line argument | Description |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The next non-option item in the command line will go to
|
<~define~last, first~<~last name~>, <~first name~>~>
So, if <~set~first name~Carl~><~set~last name~Hollywood~>
then <~last, first~>
produces Hollywood, Carl
.
<~get~last, first~>
produces <~last name~>, <~first name~>
.
<~define~min~<~lt?~<~1~>~<~2~>~<~1~>~<~2~>~>~>
<~define~!~<~lt?~<~1~>~3~<~1~>~<~mult~<~!~<~sub~<~1~>~1~>~>~<~1~>~>~>~>
So <~!~5~>
produces 120
.
The numerical approach to absolute value is to take the larger of the number and the number subtracted from zero:
<~define~abs~<~9~<~sub~0~<~1~>~>~><~lt?~<~1~>~<~9~>~<~9~>~<~1~>~>~>
The textual approach is to notice if it starts with a minus sign, and if it does, remove it:
<~define~abs~<~eq?~<~substr~<~1~>~0~1~>~-~<~substr~<~1~>~1~>~<~1~>~>~>
If Tilton has a problem, it will print an error message and stop. The message will contain a header
sourceFile(
lineNumber,
columnNumber/
characterNumber):
The header will contain an entry for each level in Tilton's stack. I hope that this will provide enough information to identify the cause of the error.
The Tilton Macro Processor is named for the famous Christian Recreationalist, TV's Robert Tilton. He was selected for this honor on account of his name miraculously containing many of the same letters as the word "tilde", the most important character in this language. Robert Tilton is himself something of a linguist, being fluent in the speaking of tongues.
tilton | tilton.h |
tilton.cpp |
text | text.h |
text.cpp |
iter | iter.h |
iter.cpp |
context | context.h |
context.cpp |
node | node.h |
node.cpp |