- "TRAC language is characterized as being an interactive, macrogenerator, evaluative string language"
- "The language employs a single data type, namely the type 'string'"
- Extension with application packages "written in FORTRAN, COBOL, PL/I or in any other language" was at least discussed.
- "A collection of expressions or commands in TRAC language is called a 'script'"
if ![info exists Trace] {set Trace 0}
proc TN {} {set ::Trace 1}
proc TF {} {set ::Trace 0}
proc trac form {
while 1 {
set new [subst $form]
if {$new eq $form} break
if $::Trace {puts $form->$new}
set form $new
}
set form
}if 0 {To test this multiple substitution, here is an integer range generator that does not use recursion (because the inner call to itself is escaped with a backslash, and hence returned to its caller - who in turn re-substitutes it). Such iteration avoids the interp recursionlimit, yet allows to write quasi-recursive code.} proc range n {
if $n {return "\[range [expr $n-1]] $n"}
}if 0 {... And it works as expected: % range 5
[range 4] 5
% trac {foo [range 5] bar}
foo 1 2 3 4 5 barNote that foo is not a command here - TRAC leaves its input untouched, except for substitutions.TRAC is visually quite different from Tcl. The test above would, in real TRAC (classic T-64), look likefoo #(CL,range,5) bar'where CL is the built-in "primitive" to call a form, the command and its arguments are separated by commas, and enclosed in #(...) to indicate it shall be substituted, just like brackets in Tcl. The meta-character apostrophe (') terminates the input and has it evaluated.Below I play with some primitives that are named with two-letter uppercases as in TRAC, but the syntax is still Tcl-ish, so that I can use our parser - the TRAC assignment of a "form" to a variable, Define String,
#(DS,foo,42)is in this "Tractcl" dialect written as
[DS foo 42]and it's evident that an alias for set does the job for now:}
interp alias {} DS {} set
interp alias {} DD {} unsetif 0 {Forms are not only for variables, but for procedures as well. As discussed in half-lambda, I save the SS (Segment String) step by directly using numeric argument names ($1, $2, ...), so a single body string can be evaluated with arguments. (But see below why SS is still desirable...) The range generator comes out like this:} DS rg {[GR $1 0 "\[CL rg [SU $1 1]] $1" {}]}#-- using one of the two TRAC conditionals: proc GR {1 2 3 4} {
expr {$1 > $2?[trac $3]:[trac $4]}
}
proc EQ {1 2 3 4} {
expr {$1 eq $2?[trac $3]:[trac $4]}
}if 0 {The CalL primitive assigns the arguments, and a few extra ones, to numeric variables, and substitutes the named form:} proc CL {formVar args} {
if [info exists ::$formVar] {
set i 0
foreach a $args {set [incr i] $a}
foreach a {. . . .} {set [incr i] ""}
subst [set ::$formVar]
} ;# else do nothing
}if 0 {Here are TRAC's binary arithmetic (strictly Polish, as in Lisp - #(AD,5,10) is "5+10") as well as boolean operators (the latter used unmarked octals for both input and output), and I/O routines. TRAC-64 numbers were very special, as they allowed a non-digit prefix, which is ignored in computation, but added before the result, e.g.#(AD,cats 11,dogs-3)'cats 8A number with all prefix and no digits defaults to 0. I wrote a generic proc that takes the operator and two operands: }
proc trac-arith {op 1 2} {
set 1 [trac $1]
regexp {(.+\D)??([-+]?\d*)$} $1 -> pr 1
if {$1 eq ""} {set 1 0}
set 2 [trac $2]
regexp {(.+\D)??([-+]?\d*)$} $2 -> - 2
if {$2 eq ""} {set 2 0}
return $pr[expr $1 $op $2]
}#-- The specific "primitives" are just curried: foreach {prim op} {AD + SU - ML * DV /} {
interp alias {} $prim {} trac-arith $op
}#-- Testing arithmetics with factorial: DS fac {[GR $1 1 "\[ML $1 \[CL fac [SU $1 1]]]" 1]}#--- Boolean (bitwise) operations: proc BU {1 2} {
format %o [expr 0$1 | 0$2]
}#-- Input (RS, Read String) and output (PS, Print String) proc PS form {
if [info exists ::$form] {
puts [set ::$form]
} else {
puts [trac $form]
}
}
proc RS {} {gets stdin}if 0 {Re-reading the TRAC book convinced me that the SS primitive ("segment string") isn't so weird after all - besides argument numbering, it can also be used, together with CL, for substring substitution. So here it is - each argument is replaced with a Tcl variable reference ${1}, ${2}, ..., which are in turn re-substituted by CL - see the example at bottom:} proc SS {formVar args} {
upvar #0 $formVar form
set i 0
foreach a $args {
set form [string map [list $a "\${[incr i]}"] $form]
}
}#-- Testing: PS {foo [CL rg 5] bar}
PS [AD "cats 11" dogs-8]
PS [BU 403 1526]
PS [CL fac 5]
DS try {This is a bad example.}
SS try bad
puts $try ;# see it modified
PS [CL try good]if 0 {produces on stdout: foo 1 2 3 4 5 bar
cats 3
1527
120
This is a ${1} example.
This is a good example.Boy, I'm beginning to like TRAC - and Tcl even more, as it allows to experiment with "foreign languages" pretty effortlessly... But TRAC has its drawbacks, too:- forms are always global
- segmentation loses the original substrings
- often forms have to be referred to by name, not as pure values
Arts and crafts of Tcl-Tk programming }
