[AMG]: This procedure converts a Tcl script to a list of commands. '''[DGP]''': How does it compare to [cmdSplit] ? [AMG]: For the most part, it's the same thing, done a different way. But the difference is that substitutions are performed. Also it is possible to enable certain Tcl commands or custom procedures which can affect the results. For example, if there's an [if] command, only the portion of the script inside the chosen branch will be echoed to the result. Looping commands can cause multiple copies of the script to be emitted, with variations due to variable substitution. Backslashes work. And so on. ---- While there are many possible uses for this functionality, I think one of its most frequently useful practical applications is for reading configuration or database files. The configuration file is written according to the [Dodekalogue], so comments, quoting, whitespace, semicolon, and other goodies can be used to spruce up the formatting. More interesting possibilities arise when variables and commands are thrown into the mix, but the basic functionality is quite useful all by itself. ====== package require Tcl 8.6 proc parse {script} { set int [interp create -safe] try { $int eval {unset {*}[info vars]} $int eval {rename ::tcl::info::frame infoframe~} foreach command [$int eval {info commands}] {$int hide $command} $int invokehidden namespace delete\ {*}[$int invokehidden namespace children] $int alias unknown apply {{int args} { upvar 1 result result lappend result [dict get [$int invokehidden infoframe~ 0] cmd] list }} $int set result {} $int eval $script return $result } finally { interp delete $int } } ====== Also, here is a version that's much simpler, but it strips out the first word of the command line if that word is "unknown". It also doesn't permit exposing Tcl commands that do things other than get echoed to the output, for example [if] or [foreach]. ====== proc parse {script} { set int [interp create -safe] try { $int eval {namespace delete ::} $int alias unknown apply {args {upvar 1 r r; lappend r $args; list}} set r {} $int eval $script return $r } finally { interp delete $int } } ====== The only validation this code does is to make sure that its input satisfies the [Dodekalogue]. It doesn't attempt to interpret the meaning of its input. That is left to the code that calls [[parse]]. When the data has a nested structure, [[parse]] can be called repeatedly to pull it apart. For example, if the configuration file has a "command" that takes an argument which itself is structured like a Tcl script, just call [[parse]] on its argument to turn it into an easily-processed list. This code works very well with a templating system, so you can write templates with variables that are filled in with data from user-supplied Tcl-like configuration files. I do this at work, with great results. I made a "compiler.tcl" script which (repeatedly) calls [[parse]], analyzes the results, checks syntax (e.g. invalid commands), then prints a [dict] that is accepted by the '''-file''' argument of my [Templates and subst] script. ---- This alternate formation of '''unknown''' changes the output to be a [dict]-like alternating list of commands and argument lists, such as is described above. Merge this into the above script to make it capable of producing output that is directly compatible with the '''-file''' argument of my [Templates and subst] script. ====== $int alias unknown apply {{int args} { upvar 1 result result set args [lassign [dict get [$int invokehidden infoframe~ 0] cmd] name] lappend result $name $args list } ====== Or instead use code similar to the following to format and print the output of [[parse]]: ====== foreach line [parse [read stdin]] { puts [list [lindex $line 0] [lrange $line 1 end]] } ====== ---- One interesting possibility is to selectively expose Tcl commands (or custom [proc]s) to the child interpreter so that the configuration files can use them. For example, if [[[foreach]]] is exposed, then this configuration file: ====== # First specify the foo. foo bar bas # Use lots of quux! foreach a {1 2 3} b {7 8 9} {c d} {p q r s t u} { quux $a$b $c$d } ====== "compiles" into this list: ====== {foo bar bas} {quux 17 pq} {quux 28 rs} {quux 39 tu} ====== ---- A drawback of my approach is that anything can be put into square brackets with nonsensical results: ====== [] [foo [bar]] [bas] ====== results in the following: ====== {bar} {foo ""} {bas} {"" "" ""} ====== At least this clearly demonstrates Tcl's substitution order rules. :^) Plus I bet you didn't know that [[]] is yet another way to obtain the empty string. ---- [AMG]: Here's a Tcl 8.4-compatible version of the "simpler" variation above. (The more advanced variation isn't possible in Tcl 8.4 due to lack of [[[info frame]]].) The [[[try]]] command makes things so much easier... :^( ====== proc parse_helper {args} { upvar 1 r r lappend r $args list } proc parse {script} { set int [interp create -safe] set r {} set code [catch { $int eval {namespace delete ::} $int alias unknown parse_helper $int eval $script } result] interp delete $int if {$code} { return -code $code $result } else { return $r } } ====== ---- **See also** * [TDL] <> File | Parsing