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"
}
[Category Programming Unknown] |