Artifact f45acb38cf45342550592e51667b36c86f7a6451:
- Executable file
packages/proc/lib/step.tcl
— part of check-in
[7f8a8ea012]
at
2021-05-22 09:54:07
on branch trunk
— update various things to reflect changes in other packages
add new files and packages (user: pooryorick size: 24234)
#! /bin/env tclsh package require {ycl proc} package require {ycl chan diagnostic} [yclprefix] proc alias [yclprefix]::proc::alias [yclprefix] proc alias aliases [yclprefix] proc aliases aliases { {ycl list} { sl } {ycl ns ensemble} {} {ycl ns} { ensemble normalize powerimport } } # [history add] is auto-loaded, so leave it out of [subsume], and let # the user decide whether to wrap it. variable subsume { after catch {chan event} {dict {for map update with}} eval expr for foreach {interp eval} if lmap {namespace eval} source subst switch time try uplevel while } variable override { {info args body} lindex list set rename return string } # Bootstrap issue: Make aliases to the real commands so that they can be used # by the name that they would have if [subsume] were run, allowing the commands # below to function properly both before and after [subsume]. apply [list args { foreach cmdset $args { foreach name $cmdset { lassign $name[set name {}] name subcmds if {[catch { set ns [namespace ensemble configure ::$name -namespace] }]} { rename ::$name [namespace current]::$name interp alias {} ::$name {} [namespace current]::$name } else { ensemble duplicate ::$name $name tons [ namespace ensemble configure ::$name -namespace] } } } } [namespace current]] $subsume $override proc cmdhandler {infoname cmdidx ns args} { return $args } proc errhandler {infoname cmdidx ns cres copts} { # TO DO: fix all this variable procs catch { set body [uplevel 1 [list [namespace current]::info body] {[ lindex [info level 0] 0]}] set cmd1 [uplevel 1 [list [namespace which info] level 0]] set frame [uplevel [list [namespace which info] frame]] } set frameinfo [info frame -1] dict update copts -errorinfo2 -errorinfo2 -errorinfo -errorinfo { if {![info exists -errorinfo2]} { append -errorinfo2 "$cres\n while executing\n\"[ dict get $copts -errorstack INNER]\"\n" } append -errorinfo2 "\n invoked from within\n\"[ dict get $copts -errorstack CALL]\"\n" if {[dict exists $frameinfo proc]} { append -errorinfo2 "\n (proc \"[ dict get $frameinfo proc]\" line [dict get $frameinfo line])" } set -errorinfo ${-errorinfo2} } return [list return -options $copts $cres] } proc info_args name { variable procs if {![string match ::* $name]} { set name [uplevel 1 [list namespace which $name]] } if {[dict exists $procs $name wrapped]} { return [[namespace current]::info args [dict get $procs $name wrapped]] } tailcall info args $name } proc info_body name { variable procs if {![string match ::* $name]} { set name [uplevel 1 [list namespace which $name]] } if {[dict exists $procs $name]} { return [dict get $procs $name body] } tailcall info body $name } proc pre {} { upvar config config wrapped wrapped args args name name set config [uplevel 2 [list [namespace which stepconfig]] $args] if {![string match ::* $wrapped]} { set wrapped [ string trimright [uplevel 2 [ list [namespace which namespace] current]]::$wrapped] } if {![string match ::* $name]} { set name [string trimright [uplevel 2 [ list [namespace which namespace] current]]::$name] } } proc bluepill {} { variable matrix 1 } proc redpill {} { variable matrix 0 } proc matrix {} { variable matrix return $matrix } proc stepconfig args { variable interp variable errhandler_body variable cmdhandler_errhandler_body variable stepconfig_defaults set config [info cmdcount] set args [dict merge $stepconfig_defaults $args[set args {}]] dict update args {*}{cmdhandler cmdhandler cmdhandler_template cmdhandler_template errhandler errhandler catchcode catchcode catchres catchres catchopts catchopts} {} set ns [uplevel 1 [list [namespace which namespace] current]] foreach vname {catchcode catchres catchopts} { if {[info exists $vname]} { set $vname [normalize $vname $ns] } else { set $vname [normalize $vname] } } foreach cname {cmdhandler errhandler} { upvar 0 $cname var if {[info exists var]} { if {$var eq {}} { dict set args $cname {} } else { # In order to explicitly specify which [namespace] command, # don't use [namespace code] . set $cname [lreplace $var 0 0 [ uplevel 1 [list [namespace which namespace] which [ lindex $var[set var {}] 0]]]] dict set args $cname $var } } else { set $cname [list [namespace which $cname]] dict set args $cname $var } } if {$cmdhandler eq {}} { set cmdhandler_body1 @command@ } else { set cmdhandler_body1 [string map [ list @cmdhandler@ [list $cmdhandler]] $cmdhandler_template] } if {$errhandler eq {}} { set body1 "[list [namespace which if]] 1 $cmdhandler_body1" } else { set body1 [string map [list \ @cmdhandler_body@ [list $cmdhandler_body1] \ @errhandler@ $errhandler ] $cmdhandler_errhandler_body] } set body1 [string map [list \ @catchcode@ [list $catchcode] \ @catchres@ [list $catchres] \ @catchopts@ [list $catchopts] \ ] $body1] dict set args cmdhandler_body $body1 $interp eval [list set $config $args] return $config } proc subsume args { variable subsume variable subsumed if {$subsumed} { return } package require {ycl proc step builtins} foreach name $subsume { # temporary code for testing if {$name in {uplevel}} { continue } # ::$name has already been renamed and aliased at the # beginning of this script . if {[llength $name] == 1} { uplevel 1 [list [namespace which builtins] wrap_$name [ namespace current]::$name ::$name] $args } else { lassign $name[set name {}] name subcmds namespace eval w::$name " [list [namespace which namespace]] export * " if {[catch {set ns [ namespace ensemble configure ::$name -namespace]} copts cres] } { #not a true namespace ensemble . Turn it into one . # Need some place to stash the original subcommands . Note that # this creates a namespace that is not tied to the command by # the same name . set econfig {} foreach subcmd $subcmds { uplevel 1 [list [namespace which builtins] \ wrap_${name}_$subcmd $name] [ namespace current]::w::${name}::$subcmd $args dict set econfig -map $subcmd [ list [namespace current]::w::${name}::$subcmd] } dict set econfig -unknown [ list apply [list {wrapped cmd subcmd args} { return [list $wrapped $subcmd] } [namespace current]] [namespace current]::$name] } { set econfig [namespace ensemble configure ::$name] set map [dict get $econfig -map] foreach subcmd $subcmds { # Create the new proc before renaming the old proc # because functionality like [dict update] is needed # during proc creation. uplevel 1 [list [namespace which builtins] \ wrap_${name}_$subcmd ${ns}::$subcmd \ [namespace current]::w::${name}::$subcmd] $args dict set map $subcmd [ namespace current]::w::${name}::$subcmd } dict unset econfig -namespace } namespace eval w::$name " [list [namespace which namespace]] ensemble create -command [ list ::$name] -map [list $map] " namespace ensemble configure ::$name {*}$econfig } } rename ::proc proc uplevel 1 [list [namespace which stepproc] ::proc] $args set subsumed 1 } variable doc::stepproc { description { creates a a new proc-creating proc that implements step functionality. Returns the name of a variable holding configuration information for the new proc. To change the behaviour of The contents of this variable can be changed } args { catchcode { description { Name of a variable to hold the result of the [catch] command } default { [namespace current]::catchcode } } catchopts { description { Name to pass to [catch] as optionsVarName } default { [namespace current]::catchopts } } catchres { description { Name to pass to [catch] as resultVarName } default { [namespace current]::catchres } } cmdhandler { description { A command prefix to which is appended the namespace of the command and the words of the command itself. If it is the empty string, no command handler is installed. } default { The default command handler in this namespace } } cmdhandler_template { description { The template that the command handler is inserted into. } } errhandler { description { A command prefix to which is appended the name } default { The default error handler in this namespace } } subs { description { Boolean indicating whether to descend into command substitutions } default {lindex 1} } varsubs { description { A boolean indicating whether to process variable substitutions } default {lindex 1} } } } proc stepproc {name args} { variable interp variable procs set config [uplevel 1 [list [namespace which stepconfig] {*}$args]] uplevel 1 [list [namespace which proc] $name {name args body} [ string map [list \ @config@ [list $config] \ @list@ [list [namespace which list]] \ @namespace@ [list [namespace which namespace]] \ @proc@ [list [namespace which proc]] \ @procs@ [list [namespace which -var procs]] \ @stepscript@ [list [namespace which stepscript]] \ @uplevel@ [list [namespace which uplevel]] ] { lassign [@stepscript@ $body @config@] infoname newbody @uplevel@ 1 [@list@ @proc@ $name $args $newbody] set name [@uplevel@ 1 [list @namespace@ which $name]] dict set @procs@ $name body $body }]] return $config } proc steprename {oldname newname} { variable procs if {[string match ::* $oldname]} { set oldname [uplevel 1 [list namespace which $oldname]] } uplevel 1 [list [namespace which rename] $oldname $newname] if {[dict exists $procs $oldname]} { if {$newname ne {}} { set newname [uplevel 1 [list namespace which $newname]] dict set $procs $newname [dict get $procs $oldname] } dict unset procs $oldname } } proc stepscript args { variable interp lassign [$interp eval stepscript $args [list {}]] infoname body2 set body2 [string map [list \ @matrix@ [list [namespace which matrix]] \ @if@ [list [namespace which if]] \ @body@ [list [lindex $args 0]] \ @body2@ [list $body2] ] { @if@ {[@matrix@]} @body2@ else @body@ }] return [list $infoname $body2] } variable doc::wrap { description { Drop a stepproc in place of $name and return the new name of the replaced proc } args { indices { description { A list of variable names contain treated as Tcl scripts and transformed by stepproc. If the index is the special value, "args", the arguments, the values in $args (as seen by the wrapped proc) are joined with whitespace and then transformed by stepproc. } } args { As described for stepproc } } } proc wrap {wrapped name wrapargs args} { variable procs pre set script [wrapper $wrapped $wrapargs $config] uplevel 1 [list proc $name args $script] dict set procs $name wrapped $wrapped } proc wrapper {wrapped wrapargs config} { variable procs if {![string match ::* $wrapped]} { return -code error [list {name not normalized} $wrapped] } set wrapargs [dict merge [dict create indices {} eindices {}] $wrapargs[ set wrapargs {}]] dict update wrapargs body body indices indices eindices eindices {} set seen {} set checkseen { if {($idx eq {args} ? {end} : $idx) in $seen} { return -code error [list {index already wrapped} $idx] } } foreach idx $indices { eval $checkseen lappend seen [expr {$idx eq {args} ? {end} : $idx}] if {$idx eq {args}} { set t { lassign [@stepscript@ [join [ lindex $args end] { }] @config@] infoname newscript set args [lreplace $args end end $newscript] } } else { set t { lassign [@stepscript@ [lindex $args @idx@] @config@] \ infoname newscript set args [lreplace $args @idx@ @idx@ $newscript] } } append script \n[string map [list \ @idx@ [list $idx] \ @stepscript@ [list [namespace which stepscript]] \ @config@ [list $config] \ ] $t] } foreach idx $eindices { eval $checkseen if {$idx eq {args}} { set t { lassign [@stepexpr@ {} [ join [lindex $args[set args {}] end] { }] @config@ {}]] \ info newargs set args [lreplace $args end end $newargs] } } else { set t { lassign [@stepexpr@ {} [lindex $args @idx@] @config@ {}] \ info newargs set args [lreplace $args @idx@ @idx@ $newargs] } } append script \n[string map [list \ @idx@ [list $idx] \ @stepexpr@ [list [namespace which stepexpr]] \ @config@ [list $config] \ ] $t] } if {[info exists body]} { append script \n$body\n } append script { ::tailcall @wrapped@ {*}$args } set script [string map [list \ @config@ [list $config] \ @wrapped@ [list $wrapped] \ @stepscript@ [list [namespace which stepscript]] \ ] $script[set script {}]] } # stepuplevel was abandoned in favor of stepproc. To work, it would have to be # rewritten to the current implementation of the rest of this script. proc stepuplevel {name args body} { ::uplevel 1 [::list ::tcl::proc $name $args [::list apply [::list args [ ::string map [::list {${body}} [::list $body]] { ::foreach command [::scriptSplit ${body}] { set status [ ::catch [::list ::uplevel 1 [ ::cmdhandler $command]] cres copts] ::if {$status} { ::tailcall ::errhandler $status $cres $copts } } lindex $cres } ]] [uplevel 1 {namespace current}]]] } variable interp [interp create] $interp eval [list set auto_path $auto_path] $interp eval { package require {ycl parse tcl} namespace import [yclprefix]::parse::tcl::isvarsub namespace import [yclprefix]::parse::tcl::nocomments namespace import [yclprefix]::parse::tcl::words namespace import [yclprefix]::parse::tcl::wordparts namespace import [yclprefix]::parse::tcl::wordparts_info namespace import [yclprefix]::parse::tcl::varparts namespace import [yclprefix]::parse::tcl::varparts_info namespace import [yclprefix]::parse::tcl::exprlex namespace import [yclprefix]::parse::tcl::exprlex_info namespace import [yclprefix]::parse::tcl::funclex namespace import [yclprefix]::parse::tcl::exprval package require {ycl parse tcl commands} namespace import [yclprefix]::parse::tcl::commands::commands namespace import [yclprefix]::parse::tcl::commands::commands_info namespace import [yclprefix]::parse::tcl::words_info proc exprs args { variable exprs if {[llength $args] == 1} { return [dict get $exprs $arg] } elseif {[llength $args] == 0} { return $exprs } else { return -code error [list {wrong # args}] } } proc scommand {infoname command configvar state} { variable $configvar dict with $configvar {} set command [string map [list \ @infoname@ [list $infoname] \ @cmdidx@ [list [dict get $state cmdidx]] \ @command@ $command \ ] $cmdhandler_body] if {$errhandler ne {}} { set command [string map [list @errhandler@ $errhandler] $command] } return [list $command] } proc scriptinfo id { set [namespace current]::$id } proc stepexpr {infoname body configvar state} { variable exprs dict set exprs $body [info cmdcount] set ref [dict get $exprs $body] upvar 0 [namespace current]::$configvar config set state [dict merge [dict create \ cmdidx -1 col 0 offset 0 line 0 end [ expr {[string length $body] - 1}] ] $state[set state {}]] set res {} set info {} set body2 [string range $body [dict get $state offset] [ dict get $state end]] set idx -1 set parts [exprlex $body2] set report [exprlex_info] set ranges [dict get $report ranges] foreach part [exprlex $body2] { incr idx lassign [lindex $ranges $idx] offset end set newstate $state dict incr newstate offset $offset dict set newstate end [expr {[dict get $newstate offset] + [ string length $part] - 1}] switch [exprval $part] { script { dict incr newstate offset dict incr newstate end -1 lassign [stepscript $body $configvar $newstate] \ infoname res2 lappend res \[$res2\] } variable { lassign [wordtransform $infoname $body $part[ set part {}] 0 $configvar $newstate] info res2 lappend res $res2 } quoted { lassign [wordtransform $infoname $body $part[ set part {}] 0 $configvar $newstate] info res2 lappend res \"$res2\" } function { set fargs [lassign [funclex $part[set part {}]] name] foreach arg $fargs[set fargs {}] { lassign [stepexpr $infoname $arg $configvar $newstate] info res2 lappend fargs $res2 } lappend res $name([join $fargs ,]) } braced - default { lappend res $part } } } set res [join $res { }] dict set exprs $res $body set result [list $info $res] return $result } proc stepscript {body configvar state} { set infoname info_[info cmdcount] namespace upvar [namespace current] $infoname info if {[dict exists $state parent]} { dict set info parent [dict get $state parent] } upvar 0 [namespace current]::$configvar config set state [dict merge $state [dict create \ col 0 offset 0 line 0 end [expr {[string length $body] -1}] ] $state[set state {}]] dict set state cmdidx -1 dict set state widx -1 dict update state line line offset soffset {} dict set info body $body dict set info parts {} set body2 [string range $body [ dict get $state offset] [dict get $state end]] # Can't use [nocomments] because it throws off the index of the command # in $creport #set commands [nocomments [commands $body2]] set commands [commands $body2] set creport [commands_info] foreach command $commands report1 $creport { dict incr state cmdidx dict update state cmdidx idx {} dict update report1 line cmdline character cmdcol {} # No reason to adjust $col here set line [expr {$line + $cmdline}] set cmdinfo [dict create type cmd idx $idx line $line offset [ expr {$cmdcol + $soffset}]] dict set cmdinfo end [expr {[ string length $command] + [dict get $cmdinfo offset] - 1}] if {[string match #* $command]} { continue } if {[dict get $config subs]} { set words [words $command[set command {}]] set wreport [words_info] set widx -1 foreach word $words { incr widx lassign [lindex $wreport $widx] woffset wline set wordinfo [dict create \ offset [expr {[dict get $cmdinfo offset] + $woffset}] \ line $wline \ raw $word] set newstate [dict merge $state [dict create \ offset [dict get $wordinfo offset] \ line [expr {$line + $wline}]]] if {[string match {{[*]}*} $word]} { set argexpansion {{*}} dict incr state offset 3 set word [string range $word[set word {}] 3 end] } else { set argexpansion {} } if {[string match \{* $word]} { # To do: Should anything be done here? 2016-09-05 } else { set quoted [expr {[ string index $word 0] eq "\"" ? "\"" : {}}] lassign [ wordtransform $infoname $body $word[set word {}] \ 0 $configvar $newstate] newinfo word if {$word ne [dict get $wordinfo raw]} { dict set wordinfo parts $newinfo } } lappend command $argexpansion$quoted$word$quoted dict lappend cmdinfo parts $wordinfo } } dict lappend info parts $cmdinfo set newcmd [scommand $infoname [ join $command { }] $configvar $state] lappend newbody {*}$newcmd } set newbody [join $newbody[set newbody {}] \n] set result [list $infoname $newbody] dict set scripts $newbody $infoname return $result } proc wordtransform {infoname body word escapewhite configvar state args} { variable $configvar upvar 0 [namespace current]::$configvar config set res {} dict with config {} set info {} set parts [wordparts $word[set word {}]] set report [wordparts_info] set idx -1 foreach wordpart $parts { incr idx set newstate $state lassign [lindex $report $idx] poffset pline dict set newstate offset [ expr {[dict get $state offset] + $poffset}] dict set newstate line [expr {[dict get $state line] + $pline}] set parttype {} set partinfo {} if {[string match {[[]*]} $wordpart]} { dict incr newstate offset dict set newstate end [expr {[dict get $newstate offset] + [ string length $wordpart] - 3}] lassign [ stepscript $body $configvar $newstate] partinfo res2 set parttype script set wordpart \[$res2\] } elseif {[string match $* $wordpart] && $varsubs && [ isvarsub $wordpart]} { lassign [vartransform $infoname $body $wordpart \ $configvar $newstate] partinfo wordpart } elseif {[string match {\\*} $wordpart]} { #do nothing } elseif {$escapewhite} { # a regular word part # $word must be the ''index'' of a tcl array name, where the # whitespace is quoted by parenthesis, but otherwise not quoted regsub -all {(\s)} $wordpart[set wordpart {}] {\\\1} wordpart } lappend info $parttype $partinfo append res $wordpart } return [list $info $res] } proc vartransform {infoname body var configvar state args} { if {[regsub {^\${(.*)}$} $var {\1} var]} { set varbraced 1 } else { set var [string range $var[set var {}] 1 end] set varbraced 0 } set varparts [varparts $var] set report [varparts_info] lassign $varparts name index dict set varinfo raw $var if {[llength $varparts] == 2} { if {$varbraced} { if {[string first \} $name] >= 0} { return -code error [list \ {braced variable name contains a closing brace} \ $varparts] } if {[string first \} $index] >= 0} { return -code error [list \ {braced variable index contains a closing brace} \ $varparts] } set script "::set [list ${name}($index)]" } else { set newstate [dict merge $state [dict create offset [ expr {[dict get $state offset] + $report}]]] lassign [wordtransform $infoname $body $index \ 1 $configvar $newstate] indexinfo index dict set varinfo index $indexinfo set script "::set ${name}($index)" } return [list $varinfo "\[[ join [scommand $infoname $script $configvar $state] \n]\]"] } elseif {[llength $varparts] == 1} { set script "::set [list $name]" return [list $varinfo "\[[ join [scommand $infoname $script $configvar $state] \n]\]"] } elseif {[llength $varparts] == 0} { return [list $varinfo $var] } } variable exprs {} variable scripts {} } variable matrix 1 variable procs {} variable subsumed 0 interp alias {} [namespace current]::exprs $interp exprs interp alias {} [namespace current]::stepexpr $interp stepexpr interp alias {} [namespace current]::scriptinfo $interp scriptinfo interp alias {} ::rename {} [namespace current]::steprename apply [list args { set map [namespace ensemble configure ::info -map] dict set map args [namespace current]::info_args dict set map body [namespace current]::info_body namespace ensemble configure ::info -map $map } [namespace current]] variable cmdhandler_body [string map [list \ @if@ [list [namespace which if]] \ @namespace@ [list [namespace which namespace]] \ @matrix@ [list [namespace which -variable matrix]] \ ] {@if@ {[set @matrix@]} { @if@ 1 [{*}@cmdhandler@ @infoname@ @cmdidx@ [@namespace@ current] @command@] } else { @command@ }}] variable cmdhandler_body_simple { {*}@cmdhandler@ @command@ } #body must not begin with newline variable cmdhandler_errhandler_body [string map [list \ @if@ [list [namespace which if]] \ @try@ [list [namespace which try]] \ @namespace@ [list [namespace which namespace]] \ @set@ [list [namespace which set]] ] {@try@ @cmdhandler_body@ on error {@catchres@ @catchopts@} { @if@ 1 [{*}@errhandler@ @infoname@ @cmdidx@ [@namespace@ current] [@set@ @catchres@] [ @set@ @catchopts@]] }}] variable stepconfig_defaults [sl { cmdhandler [namespace which cmdhandler] cmdhandler_template [set [namespace current]::cmdhandler_body] subs 1 varsubs 1 }]