Let unknown know

Richard Suchenwirth 2001-12-17 - 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 @[email protected] $cond @[email protected] $body] {
        if {![catch {expr {@[email protected]}} res] && $res} {
            return [eval {@[email protected]}]
        }
    }][info body unknown]
}

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]
}

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 "|| 1" (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 [list ::set $name $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...


This is pure and utter evil. I like it!


I have wondered how hard it would be to connect unknown to a database or even a socket that would serve as the source for unknown procedures. I was thinking about how a local client can be customized to match a remote server.

In the two cases I am considering, the code that defines the procedures for editing data in a data base could be in the data base itself. When the client runs and needs to access a table that is defined (in the data base) but not previously accessed it would go to a data base table of tcl code (accessed using the table name as the key) and somehow get the code into the interpreter.

I was looking at the pkgIndex file and was wondering about what would happen if the code

[list source .....]

was replaced with something besides source that would load code. Then I wondered, what is the equivalent of source? That is, if I have a string containing tcl code that is equivalent to the contents of a file of tcl code, what is the proper way to load it?

In the second case, instead of having the source code in a data base, the source would reside on a remote system. I would want to dynamically load it through a socket. (This sounds similar to the interface between Erlang and Tk.) So somehow unknown would need to do these steps:

  1. Inquire through a socket whether the remote system had a definition for a proc.
  2. If it did, then transfer it through the socket (from the server to the client).
  3. If it was received successfully, load it and execute it (on the client).

Is this kind of behavior a solved problem somewhere?

escargo 5 Nov 2002

schlenk 17 Aug 2005 - something like this is done for PostgreSQL stored procedures in PL/Tcl [1 ]

escargo 17 Aug 2005 - Are stored procedures executed on the server or on the client? My question above had the client executing the downloaded code (and I've revised my earlier statements to make that more clear).

Rfox 18 Jan 2012 Here's some code that extends the package load system to load code from an sqlite database. The code is loaded from the table tclPackages which has the column package_name and code

package provide sqlite3load 1.0
package require sqlite3
 
namespace eval sqlite3load {
    namespace export addDatabase
    variable  serial 0
}
#----------------------------------------------------
#
#  Proc that does the actual load of a package from
#  an added database, intended to be internal.
#  All we need to do is to select the code from the
#  database and evaluate it at the global level.
#
# Parameters:
#   db      - handle open on the database.
#   package - Package name.
#
proc sqlite3load::loadPackage {db package} {
    $db eval \
        {select code from tclPackages where (package_name == $package)} {
            uplevel #0 eval $code
        }
}
#----------------------------------------------------
#
#  Add an sqlite3 data base to the package load system.
#  this is done by executing a package ifneeded
#  command for version 1.0 of all packages in the
#  tclPackages table of the database pointing
#  them at the sqlite3load::loadPackage proc.
#
# Parameters:
#    dbFile  - Path to database file which must have a
#              tclPackages table with package_name
#              and code columns.
# Returns:
#   The database handle.
#
proc sqlite3load::addDatabase dbFile {
     incr sqlite3load::serial 

    sqlite3 db_$sqlite3load::serial $dbFile
    db_$sqlite3load::serial eval {select package_name from tclPackages} {
        package ifneeded $package_name 1.0 \
            [list sqlite3load::loadPackage db_$sqlite3load::serial $package_name]
    }
    return db_$sqlite3load::serial
}

Be pretty simple to also add a version column and yuse that in the generated package if needed command ins sqlite3load::addDatabase


RS: To evaluate a string, use eval of course ;-) source $file does basically:

set fp [open $file]
set data [read $fp]
close $fp
eval $data

So if you get your code over a network, just eval it (but maybe in a safe interpreter...)

escargo: I was sure it was something simple like that, but eval is not mentioned on the source page (although evaluate is).

RS: Thanks for the hint, I put it there - but Wiki pages don't claim to be the authoritative reference, that would rather be the man pages.

escargo: The discussion on source is now much more detailed from my point of view, and helps clarify my options for the future. Thank you.

DKF: Source, because it is a C command, uses Tcl_Eval internally. Well, really it uses Tcl_EvalFile but that's just a trivial wrapper round Tcl_Eval. There's only one minor subtlety, and that is that it stores the name of the file in the interpreter for retrieval with [info script]. Luckily, this mechanism is also accessable to Tcl scripters, making the [source] command completely replaceable with the following (except for error handling, which is an exercise):

proc new_source {filename} {
    set fd [open $filename]
    set script [read $fd]
    close $fd
    info script $filename
    uplevel 1 $script
}

See Toot as toot can for a simpler variation of know:

proc know what {proc unknown args $what\n[info body unknown]}

DKF rightly pointed, out, on 2005-03-29 in the Tcl chatroom, that this is insecure if what is not a complete command (or sequence thereof) - the body of unknown is uncritically extended, but execution of unknown runs into an error and basically blocks the road. So, despite my love for one-liners, I now recommend the safer version

proc know what {
    if ![info complete $what] {error "incomplete command(s) $what"}
    proc unknown args $what\n[info body unknown]
} ;# RS

Usage example (let expr language be known):

% know {if {![catch {expr $args} res]} {return $res}}
% 3+4
7

CMcC Following from jenglish's comment: why learn to use a monster HTML-template library when you can, with a few lines of code, extend the base language so you can write things like [<A> href "/home.html" { ! "Home" }] ... I wrote the following to do just that using unknown.

know {[string match <*> [lindex $args 0]]} {
    set tag [string trim [lindex $args 0] "<>"]
    ::proc ::<$tag> {args} [string map [list @T $tag] {
        set result {}
        foreach {n v} [lrange $args 0 end-1] {
            set v [string map {& &amp; ' &apos; " &quot; < &lt; > &gt;} $v]
            lappend result "$n='$v'"
        }
        return "<@T [join ${result}]>[lindex $args end]</@T>"
    }]
    return [eval $args]
}

NEM TIP 181 [2 ] introduces a namespace unknown command into Tcl 8.5 that allows per-namespace unknown command handling. By default this works exactly as now, but it also allows a more controlled manner of dealing with unknown commands. For instance, here is how you would add RS's numeric ranges code:

% proc range {next args} {
    if {[regexp {^([0-9]+)\.\.([0-9]+)$} [lindex $args 0] -> from to]} {
        set res {}
        while {$from<=$to} { lappend res $from; incr from }
        return $res
    } else {
        uplevel 1 $next $args
    }
}
% # Install this behaviour for the current namespace
% namespace unknown [list range [namespace unknown]]
range ::unknown
% puts [1..5]
1 2 3 4 5

Notice how we can use any named proc as part of our unknown handler instead of having to redefine "::unknown". Also note that with the introspection we can handily capture the old unknown handler and pass it as an argument to our new handler, allowing for easy chaining of functionality. For instance, if I now add auto-expansion of leading word to command lookup, it is easy to accomplish:

% proc expand {next cmd args} {
    if {[llength $cmd] > 1} {
        uplevel 1 $cmd $args
    } else {
        uplevel 1 $next [linsert $args 0 $cmd]
    }
}
% namespace unknown [list expand [namespace unknown]]
expand {range ::unknown}
% puts [1..5]
1 2 3 4 5
% set chop {string range}
string range
% $chop "Hello, World!" 0 4
Hello
% foo
invalid command name "foo"

Here we have a chain of unknown handlers: expand -> range -> unknown (Tcl's default unknown handler).

Version of know proc that takes advantage of this feature is on namespace unknown page (under discussion).


RS 2006-08-10: Here's another thing to "know", bringing slice syntax to lists:

know {
    if {[regexp {(.+)\((.+)\)$} [lindex $args 0] -> _var ind]} {
        upvar 1 $_var var
        foreach {from to} [split $ind :] break
        if {$to eq ""} {set to $from}
        if {[lindex $args 1] eq "="} {
            return [set var [eval [list lreplace $var $from $to] [lrange $args 2 end]]]
        } else {return [lrange $var $from $to]}
    }
}
% set try {a b c d e f g}
a b c d e f g
% try(0)
a
% try(0:5)
a b c d e f
% try(end-2:end)
e f g
% try(2:3) = foo
a b foo e f g
% try(2:3) = foo bar
a b foo bar f g

hmm the know? helper above is a little nasty given that it hard codes the amount to output, might i suggest the following?

proc known {} {
    set val ""
    regexp {(.*)\#original} [info body unknown] -> val
    set val
}

proc unknown {args} "    #original unknown follows
[info body unknown]"