I hope I got all the "smithisms" in this code - withOUT breaking it. I included the routines to implement a couple, notably the < > scheme for processing the args parameter. Also demonstrates that the distinction between vars and procs is not very useful with < and > to process args, so the < and > are just vars that are compiled invisibly to procs when first processed.
interp alias {} havevar {} info exists interp alias {} havecmd {} info commands # pulls in arguments as needed. A prefixed & creates an upvar set > { upvar args argl; upvar set ! if {[l# $argl]==0} {uplevel set #args -1;return} while {[l# $args]>0} { set arg [hd args] set val [hd argl] if {[string index $arg 0]eq"&"||[string index $val 0]eq"&"} { if {[string index $arg 0]eq"&"} {set arg [string range $arg 1 end]} if {[string index $val 0]eq"&"} {set val [string range $val 1 end]} set !($arg) $val uplevel upvar $val $arg } else { upvar $arg local set local $val } } uplevel set #args [l# $argl] } # reverse of >, puts args back in case we read too far in looking for options or overrides set < { upvar args argl; upvar set ! while {[l# $args]>0} { set arg [hd args] if {[exists !($arg)]} { set name [set !($arg)] unset !($arg) lv^ argl 0 $name } else { lv^ argl 0 [uplevel set $arg] } } } set strlast {set rtn [str@ [join $args " "] end];return $rtn} set hd {> &var;set rtn {};catch {set rtn [lindex $var 0];set var [lrange $var 1 end]};return $rtn} set l+ {> l;set l [lsort -unique [concat {*}$l {*}$args]];return [lsearch -all -inline -not -exact $l {}]} set l- {l args} {> l;each arg {*}$args {set rtn [ls? -all -inline -not -exact $l $arg]};return $rtn} set l^ {> l idx;set rtn [linsert $l $idx {*}$args]; return $rtn} set lv+ {> &l;lappend l {*}$args;return [lsort -unique [lsearch -all -inline -not -exact $l {}]]} set lv++ {> &rtn pos val mod;ifno val 1 mod 0;set val [lindex $var $pos];++ val $amt $mod;!set $rtn $pos $val;return $rtn} set lv- {> &rtn;each arg $args {set rtn [ls? -all -inline -not -exact $rtn $arg]};return $rtn} set lv-- {> &rtn pos val mod;ifno val 1 mod 0;set val [lindex $var $pos];-- val $amt $mod;!set $rtn $pos $val;return $rtn} set lv^ {> &rtn idx;set rtn [linsert $rtn $idx {*}$args]; return $rtn}
The real reason for the above is my version of "unknown" which serves a couple purposes. First, it recognizes expressions using the := operator: "[a := 2+7]" turns into "[set a [expr {2+7}]]]". It will also recognize when an expr is present so [sin($x)/cos($x)] will turn into the appropriate expr. Another feature is conforming dict and array access. [dictorarrayname@dictorarrayindex] returns the value of the dict or array with that index. You can ask for any number, and you can also do assignments: "dictname@index1: arrayname@index2" will assign $name(index2) to dictname [index1 foobar]. $ are not needed, it knows one is required if you are trying to assign. Any number of assignments can be done. In addition to assignments, you can use +: or -: to add or remove elements in a list.
rename unknown _unknown # This version of "unknown" recognizes expressions and evaluates # them, returning the result. It recognizes the assignment op := # and generates appropriate code to implement it. It also implements # Rebol-style assignments using : - "i: 0" sets i to 0. Since it joins # the args beyond the varname to be assigned with space, "i: 1 2 3" sets # i to "1 2 3" rather than complaining about too many args to set. +: and # -: will add or remove the following elements from the list. This will # shimmer to a normal list if thereafter treated as one. Finally, it can # set and get array members and dict keys. foo@bar is replaced by either # foo(bar) or by [dg $foo $bar] in the enclosing scope according to # the type of "foo". This also works for assignment, foo@bar: foo@grill # will set foo@bar (whichever it is, dict or array) to foo@grill (same # deal). Any number can play, assignments and retrieves can be mixed in # one line - the result is a list of the retrieved items - if you want # to both assign in-line AND retrieve, you need to replicate one side # or the other. proc unknown args { # since most of my programming just uses "args" as a parameter and uses > and < # access them, the distinction between vars and procs is not very useful. This # section checks to see if the 1st passed argument (the name of the unknown proc) # is present as a var, to make the associated proc. It also traces the var so if # it is reassigned it will be again turning into a proc. Aside from making tcl a # bit more orthogonal, it permits more brevity. ! pname [l@ $args 0] if {[havevar ::$pname] && ([havecmd $pname] eq "")} { # we have a proc in a var, compile to proc proc ::$pname args [! ::$pname] # add trace to var so if it is rewritten the proc is deleted and recreated trace add variable ::$pname write {rename ::$pname ""} # call it tailcall ::$pname {*}[lspan $args 1 end] } # allow assignments in expr ! i [strpos ":=" $args] if {$i!=-1} { ^ [^^ "! [strcpy $args 0 $i-1] [= [strcpy $args $i+2 end]]"] } else { # otherwise if it doesn't start w/cmd eval as expr ! cmd [l@ $args 0] if {[l# [cmds $cmd]]==0 && [regexp {^[0-9+\\-]} $cmd]} { ^ [% $args] } } # fancy addressing - foo@bar can refer to either $foo(bar) OR dict get foo bar # also handling special ops to add or remove items to list - all triggered by # a "@" sign in first arg. ! arg [hd args] ! rtn {} if {[strpos "@" $arg]!=-1} { ! prev "" while {($arg ne $prev) && [strposany $arg \$ \[]!=-1} {! prev $arg; ! arg [^^ subst $arg]} while {[! idx [strpos "@" $arg]]!=-1} { if {[strlast $arg] in {: =}} { # doing an assignment ! arg [str-1e $arg] ! ch [strlast $arg] if {$ch eq "+" || $ch eq "-"} { ! arg [str-1e $arg] } else {! ch ":"} ! arrname [strcpy $arg 0 $idx-1] ! index [strcpy $arg $idx+1 end] & $arrname anarray ! val [hd args] ! prev "" while {($val ne $prev) && [strposany $val \$ \[]!=-1} {! prev $val; ! val [^^ subst $val]} # if val is also a reference to # a dict or array evaluate it if {[! idx [strpos "@" $val]]!=-1} { ! valarr [strcpy $val 0 $idx-1] ! index2 [strcpy $val $idx+1 end] & $valarr valarray ! val {} if {[catch {! val $valarray($index2)}]} { catch {! val [dg $valarray $index2]} } } # val is now whatever we want to assign # to the variable we processed above. # assign it. if {[^^ array exists $arrname]} { case $ch { : {! anarray($index) $val} + {lv+ anarray($index) $val} - {lv- anarray($index) $val} } } else { # weird issue with dict set, so treat dict as list ! idx [ls? $anarray $index] if {$idx == -1} { # didn't find it, so add it to the end. lv+ anarray $index $val } else { ++ idx ;# point to slot where value is & do it. case $ch { : {! anarray [lreplace $anarray $idx $idx $val]} + {lv+ anarray $val} - {lv- anarray $val} } } } ! arg [hd args] } else { ! arrname [strcpy $arg 0 $idx-1] ! index [strcpy $arg $idx+1 end] & $arrname anarray if {[catch {lv+ rtn $anarray($index)}]} { catch {lv+ rtn [dg $anarray $index]} } ! arg [hd args] } } ^ $rtn } elseif {[strlast $arg] eq ":"} { & [str-1e $arg] var ! var [join $args " "] ^ $var } else {eval _unknown $args} } # test and demo if 0 { foo(bar): now foo(bar2): it foo(grill): works set dict1 [dict create foo now foo2 it foo3 works] puts "should be 'now it works' = [foo@bar foo@bar2 foo@grill]" puts [dict1@foo dict1@foo2 dict1@foo3] dict1@foo2: really foo@bar2: really foo@urble: fobby dict1@urble+: foo@urble puts [foo@bar foo@bar2 foo@grill foo@urble] puts [dict1@foo dict1@foo2 dict1@foo3 dict1@urble] puts "$dict1" }