Version 2 of Let unknown know

Updated 2002-10-11 11:08:39

Richard Suchenwirth - To extend Tcl, i.e. to make it understand and do things that before raised an error, the easiest way is to write a proc. Any proc must however be called in compliance with Tcl's fundamental syntax: first word is the command name, then the arguments separated by whitespace. Deeper changes are possible with the unknown command, which is called if a command name is, well, unknown, and in the standard version tries to call executables, to auto-load scripts, or do other helpful things (see the file init.tcl). One could edit that file (not recommended), or rename unknown to something else and provide one's own unknown handler, that falls through to the original proc if unsuccessful, as shown in Radical language modification.

Here is a simpler way that allows to extend unknown "in place" and incrementally: We let unknown "know" what action it shall take under what conditions. The know command is called with a condition that should result in an integer when given to expr, and a body that will be executed if cond results in nonzero, returning the last result if not terminated with an explicit return. In both cond and body you may use the variable args that holds the problem command unknown was invoked with.

 proc know {cond body} {
    proc unknown {args} [string map [list @c@ $cond @b@ $body] {
        if {![catch {expr {@c@}} res] && $res} {
            return [eval {@b@}]
        }
    }][info body unknown]
 }

if 0 {Condition and body are wrapped together and prepended to the previous unknown body. This means that subsequent calls to know stack up, last condition being tried first, so if you have several conditions that fire on the same input, let them be "known" from generic to specific. Here's a little debugging helper, if "know" conditions don't fire: }

 proc know? {} {puts [string range [info body unknown] 0 511]}

if 0 {Now testing what new magic this handful of code allows us to do. This simple example invokes expr if the "command" is digestible for it:

 know {[expr $args] || 1} {expr $args}
 % 1+2 * 3
 7

The "||(or) 1" appended in the condition lets this fire even if the expression would result in zero... I started these experiments because I wanted to have an infix index vector constructor (cf. Playing Haskell), so [1..4] returns {1 2 3 4}:

 know {[regexp {^([0-9]+)\.\.([0-9]+)$} [lindex $args 0] -> from to]} {
    set res {}
    while {$from<=$to} {lappend res $from; incr from}
    set res
 }
 % puts [1..5]
 1 2 3 4 5

Here's a weird experiment with a "reverse Polish" command (see RPN in Tcl):

 % know {[lindex $args end]=="say"} {puts [lrange $args 0 end-1]}
 % hello world say
 hello world

And another variation on infix assignment:}

 know {[lindex $args 1] == "="} {
    set name  [lindex $args 0]
    set value [lrange $args 2 end]
    if [catch {uplevel 1 set $name [expr $value]} res] {
        uplevel 1 set $name [list $value]
    } else {set res}
 }
 puts [i = 17]
 puts [j = $i + 4]

But as in radical language modification, this works only for variable names that are unknown as commands...


unknown - Arts and crafts of Tcl-Tk programming