Playing LISP

Richard Suchenwirth 2001-12-19 - Here's a first approximation of making Tcl simulate LISP - there are subtle differences yet to solve, e.g. the $x formulation below - but build on it, and enjoy!

The wrapper proc is called, like in LISP, progn ("evaluate the following sequentially, and return the n-th(=last) result"). It takes care of paren-to-brace mapping, and then does that:

 proc progn body {
     regsub -all {;[^\n]*\n} $body \n body
     set body [string map {( \{ ) "\} "} $body]
     foreach cmd $body {
         set _ [uplevel 1 $cmd]
     }
     set _
 }

# Setting up Polish-style arithmetic prefix operators...

 foreach op {+ - * /} {
     proc $op args [string map [list @op@ $op] {expr [join $args @op@]}]
 }

# Some vocabulary exercises...

 interp alias {} defun {} proc
 interp alias {} setq  {} set

# Now testing...

 set res [progn {
     ;; LISPish comments, removed before bracing
     (defun sq (x) (* $x $x))
     (setq y 3)
     (sq $y)
 }]
 puts $res,y:$y

Disclaimer: This example happens to work, but to emulate LISP is still a long way to go. Lists in parens would have to be translated to stand in brackets (if not the outermost one), while quote-parened lists would have to be braced. Quoted strings should be distinguished from unquoted ones, which would have to get a dollar sign in front, and much more...

Furthermore Lisp requires to know if a date is an atom x or a list with one element (x) inside. As Tcl has no type it cannot make such decision, in both cases it is x. wdb


If execution of a list of commands is all you want, an easy way is to join them with newlines as separators, and eval them. The last result is returned as result of the eval:

 % lappend cmd {set foo 42}
 {set foo 42}
 % lappend cmd {puts foo:$foo}
 {set foo 42} {puts foo:$foo}
 % lappend cmd {if {$foo==42} {puts Yes!}}
 {set foo 42} {puts foo:$foo} {if {$foo==42} {puts Yes!}}
 % lappend cmd {set bar 2002}
 {set foo 42} {puts foo:$foo} {if {$foo==42} {puts Yes!}} {set bar 2002}

 % eval [join $cmd \n]
 foo:42
 Yes!
 2002

Revert a list in LISP style: Here is a dramatic, functional rewrite of lrevert from Additional list functions - no variable used except for the argument, repetition by recursion. The one-armed if returns an empty string = empty list if the condition does not hold. Note however that Tcl is not tail-call optimised, and this code will, with default recursion limit, fail on lists longer than 231 elements:

 proc car L {lindex $L 0}
 proc cdr L {lrange $L 1 end}
 proc lrevert L {if [llength $L] {concat [lrevert [cdr $L]] [list [car $L]]}}
 % lrevert {foo bar grill}
 grill bar foo

RS 2005-05-31: A late night experiment in Tclifying Lisp's c[ad]+r list accessors:

 proc lisp's {name res} {
    if ![regexp {c([ad]+)r} $name -> kernel] {error "bad name $name"}
    foreach letter [split $kernel ""] {
         switch $letter a {set res [lindex $res 0]} d {set res [lrange $res 1 end]}
    }
    set res
 }
 % lisp's car {a b c}
 a
 % lisp's cdar {a b c}
 b
 % lisp's cdr {a b c}
 b c

See also Playing Lisp again, Pils


Tcl and LISP - Arts and crafts of Tcl-Tk programming