Playing LISP

Difference between version 14 and 15 - Previous - Next
[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], https://github.com/hoodiecrow/thtcl%|%[Thtcl%|%]
----
[Tcl and LISP] - [Arts and crafts of Tcl-Tk programming]