exprstar

AMB:

Based on discussions on the pages Expanding the Expansion Prefix and expr*, I created a small Tcl package called "exprstar" that introduces a modified form of the expr command that returns multiple arguments, separated by commas in the same fashion as inputs to a Tcl math function.

Here is the Github repository: https://github.com/ambaker1/exprstar

The example from the page expr* that demonstrates how using expr can be awkward is as follows:

.canvas create rectangle [expr {$w-100}] [expr {$h-40}] [expr {$w+100}] [expr {$h+40}]

With the expr* command, you can do the following, which makes it a lot more readable.

.canvas create rectangle {*}[expr* {$w-100, $h-40, $w+100, $h+40}]

This also allows you to easily write procs that process arguments in comma-delimited fashion with the expr language.

proc sum {args} {
   ::tcl::mathop::+ {*}[uplevel 1 expr* $args]
}

set x 5.0
set y 10.0
sum {$x,$y,$x*$y}; # 65.0

Edit: I was trying out argparse, and realized that you can combine it with expr* to introduce a totally different way of using Tcl:

# func
package require exprstar
package require argparse

proc func {name args} {
    if {[llength $args] < 2} {
        return -code error "wrong # args: should be \"func name ?switches? argList body\""
    }
    set switches [lrange $args 0 end-2]
    if {[lindex $switches end] eq "--"} {
        set switches [lrange $switches 0 end-1]
    }
    if {"-inline" in $switches} {
        return -code error "-inline switch not supported"
    }
    set argList [lindex $args end-1]
    set body [lindex $args end]
    tailcall proc $name {args} "
    set args \[uplevel 1 expr* \$args\]
    argparse $switches -- {$argList}\n$body"
}

func perimeter -help {Perimeter of a rectangle} -validate {
    num {[string is double -strict $arg]}
} {
{-height= -validate num -default 3.0}
{-width= -validate num -default 4.0}
} {
    expr {2*($height + $width)}
}

set ft 12.0
puts [perimeter {"-h", 10*$ft, "-w", 20*$ft}]; # 720.0
perimeter {"-help"}

This prints out the following message:

 720.0
 Perimeter of a rectangle. Can accepts unambiguous prefixes instead of switches names. Accepts switches only before parameters.
 Switches:
     -height - Expects argument. Default value is 3.0.
     -width - Expects argument. Default value is 4.0.
     -help - Help switch, when provided, forces ignoring all other switches
         and parameters, prints the help message to stdout, and returns up to 2
         levels above the current level.

FM. Well, I would prefer to keep, as much as it's possible, the same syntax at the Tcl parser level, because it shall be very generic. I won't like, in a general fashion, to have to quote my arguments, to brace it, or to separate them with comma... The syntax of expr can be more specific, since this command is made for mathematic calculations. That justifiy a more strict syntax at this level.

But, let me continue the ideas I suggest on this matter in the « Expanding the Expansion Prefix » page for an illustration of what I mean :

# We register a "prefixed" annotation specifically on the '''second''' proc argument
# It will simply allow any prefix attach to an argument to be set as local variable whose value will be the argument value.
# Pseudo-code :
interp prefix register -command proc -argument 2 -prefix "prefixed" {args {
    foreach a $args {
        set [info annotation $a] $a
    }
}}

# Thereafter, we can use this specification in the definition of the perimeter proc

proc perimeter {prefixed}{height width} {
    if {[string is double $height] && [string is double $width]} {
        return {=}2*$height*$width
    } else {
        error "type mismatch"
    }
}
# Finaly we can use the proc, by composition of prefix
set ft 12
perimeter {{height}=}10*$ft {{width}=}20*$ft

Focus on prefix composition algorithm :

  1. Detection of the {=} prefix by the Tcl parser, which is known to it.
    It means : use expr to substitute the value of its suffix
    we will get the command => perimeter {height}120 {width}240
  2. Detection of {height} and {width} prefixes by the Tcl parser, but they are unknown to it.
    So, it means : just pass them to the proc command as annotations of their suffixes (as they resulted previously from the calculation, ie « 120 » and « 240 »)
    the perimeter proc will then recieve two arguments : 120 (annoted whith "height") and 240 (annoted with "width"). Think : Tcl_Obj Metadata
  3. Proc evaluation :
    1. Apply the registered command to parse arguments along the {prefixed} specification proviously recorded for this proc.
      • 1st arg :
        • lindex $args 0 => 120
        • info annotation [lindex $args 0] => height
        • result in : set height 120
      • 2nd arg
        • lindex $args 1 => 240
        • info annotation [lindex $args 1] => width
        • result in : set width 240
    2. Finaly : evaluate the body, and use the height and width variables