[PYK] 2015-11-08: '''procstep''' is a drop-in replacement for `[proc]` that evaluates one command of a procedure body at at time. ** Description ** `procstep` provides one hook to inspect and manipulate the command before it is evaluated, and another to react to `[return]` codes. It requires [cmdSplit%|%scriptSplit] and [cmdSplit%|%nocomments]. These commands are also available as `[ycl%|%ycl proc tcl step]`. ====== ::tcl::proc proc {name args body} { set newbody {} ::foreach command [nocomments [scriptSplit $body]] { ::append newbody [string map [list {${command}} [list $command]] { set ::tcl::ccode [ ::catch {if 1 [::cmdhandler [namespace current] \ ${command}]} ::tcl::cres ::tcl::copts] ::if {$::tcl::ccode} { if 1 [ ::errorhandler [namespace current] \ $::tcl::ccode $::tcl::cres $::tcl::copts] } else { ::lindex $::tcl::cres } }] } ::uplevel [::list ::tcl::proc $name $args $newbody] } ====== Here is a variant that improves runtime performance quite a bit by rewriting each command of the body when the procedure is created. This leaves no private space for `[catch]` to create its output variables in, so it stuffs them into `$::tcl::cres` and `::tcl::copts` instead instead: ====== ::tcl::proc proc {name args body} { set newbody {} ::foreach command [nocomments [scriptSplit $body]] { ::append newbody [string map [list {${command}} [list $command]] { set ::tcl::ccode [ ::catch {if 1 {{*}[::cmdhandler [namespace current] \ ${command}]}} ::tcl::cres ::tcl::copts] ::if {$::tcl::ccode} { if 1 [ ::errorhandler [namespace current] \ $::tcl::ccode $::tcl::cres $::tcl::copts] } else { ::lindex $::tcl::cres } }] } ::uplevel [::list ::tcl::proc $name $args $newbody] } ====== Here are some example handlers that don't do much of anything: ====== ::tcl::proc cmdhandler {namespace command} { return $command } ::tcl::proc errorhandler {namespace code cres copts} { if {$code == 2} { tailcall return {*}$copts $cres } tailcall return {*}$copts -code $code $cres } ====== Here is a monitor that disallows `[lindex]`: ====== ::tcl::proc cmdhandler {namespace command} { if {[namespace eval $namespace [ list namespace which [lindex $command 0]]] eq {::lindex}} { return {error "lindex not allowed"} } ::puts stderr [::list {now executing} $command] return $command } ====== This monitor doesn't catch commands in command substitutions, so it wouldn't catch something like `list [lindex ...]`. `[ycl] proc step`, however, does. ** `[ycl] proc step` ** `[ycl] proc step` is a more fully-featured variant of `stepproc`. It could be used as the engine for the [Sugar] macro system, with the difference that it sould behave in a more [LISP%|%LISPY] way, expanding each command macro at the beginning of the command, rather that all at once when the [proc%|%procedured] is defined. This defeats one of the stated objects of [Sugar], namely performance, but brings the advantage of being more dynamic, allowing macros to consider the current state of execution. In contrast, `[ycl] proc step` would not be useful as an engine to drive [knit] because in knit macro procedures are explicitly defined and called directly as commands. A macro system build on `[ycl] proc step` would, like [Sugar], want to provide an interface for integrating it with [Many ways to eval%|%commands that evaluate some of their arguments as scripts]. Another potential application for `[ycl] proc step` would be a condition system like that of [LISP] that allowed restarts. This was actually the motivation for `[ycl] proc step`. An interesting feature of `[ycl] proc step` is that it would allow restarts even in [dodekalogue%|%command substitutions]. I think that a `[tailcall]` for Tcl versions prior to [Changes in Tcl/Tk 8.6%|%8.6] could also be written usiing `[ycl] proc step`. to be robust, though, all commands that evaluate some of their arguments as scripts or expressions would have to be wrapped. ** Using `[ycl] proc step` ** ====== #! /bin/env tclsh package require {ycl proc step} namespace import [yclprefix]::proc::step::bluepill namespace import [yclprefix]::proc::step::redpill proc cmdhandler {ns args} { # Take the redpill to step out of the matrix in order to manage it. redpill #puts [list executing $args] if {[lindex $args 0] eq {p2}} { set return [list intrusion {*}$args] } else { set return $args } # Re-enter the matrix bluepill return $return } proc intrusion args { return -code error [list \ {I'm sorry, Dave; I'm afraid I can't let you do} "$args" ] } [yclprefix] proc step subsume cmdhandler cmdhandler bluepill # Within the the matrix, every script and expression is filtered through the # handlers. proc p1 {} { p2 {} } proc p2 x { return {restricted content} } p1 ====== <> debugging | macro | ycl component