Empty interpreter

An empty interpreter is a Tcl interpreter where there are neither commands nor variables.

The following creates an empty slave interp empty for you to experiment with:

 interp create -safe empty
 empty eval {namespace delete ::}

(The namespace delete trick here is by DGP, first published on the Braintwisters page.) empty now exhibits the following behaviour:

 % empty eval {info patchlevel}
 invalid command name "info"
 % empty eval {namespace exists ::}
 invalid command name "namespace"

and so on.

What good is such an interpreter? Well, it is still possible to create aliases in it:

 % empty alias list list {[list] in empty interp}    
 list
 % empty alias foo::bar::baz list {Alias in nontrivial namespace}    
 foo::bar::baz
 % empty eval {list a b c}
 {[list] in empty interp} a b c
 % empty eval foo::bar::foo
 invalid command name "foo::bar::foo"
 % empty eval foo::bar::baz
 {Alias in nontrivial namespace}

Hence empty interpreters can be used as config file parsers, for config files that follow Tcl syntax: For every allowed "config file command" you create that command as an alias to something in the main interpreter which actually does what that command is supposed to do. Then source or interp eval the config file in the empty interpreter, to have it safely processed. The code on Matthias Hoffmann - Tcl-Code-Snippets - Misc - Readprof has been using an empty interpreter in this way at least since 2004.

The following are features you get "for free" by using Tcl for config file syntax:

  • Comments
  • Character escapes, so you can express arbitrary data
  • Newlines separate commands — local changes have local effect
  • Error info (that as of Tcl 8.5 includes line numbers)

Character escapes is something you can have also with a list/dict-based syntax (e.g. the entire file is a dictionary, quite possibly nested), but comments are tricky in that case, and simple typo somewhere can throw off the entire key–value matching throughout the file.

How can I source the file when the source command is gone? you may ask? Well, as it turns out, the source command (and a few others) aren't really gone, since they were interp hidden from the namespace delete that emptied the interpreter:

 % empty hidden
 file socket open unload pwd glob exec encoding fconfigure load source exit cd
 % empty invokehidden source my-config-file
 couldn't read file "my-config-file": no such file or directory

Maybe some day someone will code up a full example of how to use these nice features of Tcl to allow the reading and creating of a config file, taking care of various safety issues. That would make a great example for people.

AMG: Wish granted! See Config file using slave interp.

Lars H: OK, here's a chatlog reaper rewritten to use an empty interpreter for parsing, rather than just blindly evaluating whatever code http://tclers.tk/ is sending. These chatlogs are supposed to only use the m command, but in an empty interpreter you can make sure that this is all there is to use.

#!/usr/bin/env tclsh
set usage {
   usage: today.tcl > today.html
   Retrieve today's chat log, format into HTML on stdout
}
set base http://tclers.tk/conferences/tcl/

if {$argv eq "-h"} {puts $usage; exit 1}

# Make empty interp [chatlog] for parsing
interp create -safe chatlog
chatlog eval {namespace delete ::}

#-- All chat entries invoke [m]
chatlog alias m html_m

proc html_m {time who what} {
   set tm [string range $time 9 13]
   if {$who eq "ijchain"} {
       set who [lindex [split $what] 0]
       if {$who eq "***"} {set who ""}
       set what [join [lrange [split $what] 1 end]]
   }
   set what [string map {\n <br/>} [html'escape $what]]
   if {$who eq ""} {                      #-- join/leave message
       puts "<br/>* <i>$what</i>"
   } elseif {[string match /me* $what]} { #-- "action"
       puts "<br/><i><b>[html'escape $who]</b> [string range $what 4 end]</i>"
   } else {                               #-- regular message
       if {$tm ne $::lasttime} {
           set who "$tm $who"
           set ::lasttime $tm
       }
       puts "<br/><b>[html'escape $who]:</b> $what"
   }
}

set map {}
foreach {char entity} {< lt > gt & amp} {
    lappend map $char &${entity}\;
}
interp alias {} html'escape {} string map $map


proc main argv {
   package require http
   set date [clock format [clock sec] -format %Y-%m-%d]
   #http::config -proxyhost proxy -proxyport 80
   set token [http::geturl $::base/$date.tcl -headers {Pragma no-cache}]

   puts "<html><head>Tclers' Chat of $date</head><body>"
   variable lasttime 00:00
   chatlog eval [http::data $token]
   puts <hr/>[clock format [clock sec]]</body></html>
}

main $argv

(Also, this has been slightly rewritten to collect more of the HTML handling in the m proc, to simplify supporting other output formats later.)


How do empty interps differ from safe interps? A safe interp cannot access the file system or do any networking, but it still provides a Turing-complete programming environment, which is overkill if the interpreter is meant to just parse data. If no commands for e.g. loops are available, then there is no way for a programming mistake to create an infinite loop.

Also, there is at least presently (July 2008) a bug (#1835292 ) affecting safe interpreters: ensemblification of core commands have made it difficult to interp hide their functionality. In an empty interpreter, the commands implementing these subcommands have been outright deleted.


A useful extension to DGP's namespace delete trick is to safeguard selected (top-level) commands from deletion using interp hide/interp expose. All credits to DGP on IRC. For example, protect expr, to obtain an expr-only interp:

 % interp create slave
 slave
 % interp hide slave expr expr
 % interp eval slave {namespace delete ::}
 % interp expose slave expr expr

This works because hidden commands are maintained outside the namespace tree. Useful also to reuse the expr parser, master procs interp alias'ed into ::tcl::mathfunc can be scripted using expr notation, rather than Tcl's.