if 0 {[Richard Suchenwirth] 2004-04-25 - More programming language archeology: here are some experiments to re-live aspects of the venerable TRAC language ("Text Reckoning and Compilation") in Tcl. Some quotes from "The Beginner's Manual for TRAC Language" (1972): * "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'" One big difference to Tcl is that substitutions of "forms" (the TRAC unification for both variables and commands) happen not only once, but as long as substitution changes something. This can be had in Tcl with calling [subst] in a loop - I added some trace support:} 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 is not recursive (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 bar Note that foo is not a command here - TRAC leaves its input untouched, except for substitutions. TRAC looks quite different from Tcl. The test above would, in real TRAC (classic T-64), look like foo #(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 named with two-letter uppercases as in TRAC, but the syntax is still Tcl-ish, so I can use our parser - the TRAC assignment of a "form" to a variable, Define String, #(DS,foo,42) is in this "Tractcl" 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 {} unset if 0 {Forms are not only for variables, but for procedures as well. As discussed in [half-lambda], I save the SS 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 8 A number with all prefix and no digits defaults to 0: } 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] } 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] } 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 {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] }