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"), dating back to about 1964, 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 [trampoline] effect 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 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 bar Note that foo is not a command here - TRAC leaves its input untouched, except for [subst]itutions. TRAC is visually 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 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 {} unset if 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 8 A 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] }