TIP 676: An "expr" alternative - "calc" command aliased to "="

Login
Author:         Colin Macleod <[email protected]>
State:          Draft
Type:           Project
Vote:           Pending
Tcl-Version:    9.1
Created:	23-Jun-2023

Abstract

This TIP proposes adding a new command calc which will allow numeric and boolean calculations to be written in a more compact form than with expr. The calc command will evaluate expressions compatibly with expr but in unbraced, presubstituted form.

It also proposes to make a standard predefined alias of = to calc which will permit numeric computations to be written within other commands in a more compact and natural form than at present, with no modification to Tcl's parsing rules.

Rationale

Most newcomers to Tcl, and some oldies, find expr awkward. The requirement to brace expressions for safety and performance leads to e.g. a canvas command with computed coordinates looking like:

.canvas addtag enclosed [expr {$x - 20}] [expr {$x + 20}] \
        [expr {$y - 20}] [expr {$y + 20}]

The wiki page https://wiki.tcl-lang.org/page/expr+shorthand+for+Tcl9 records many suggestions for a more compact syntax, one of which has also been proposed in TIP 672. This shows that the issue has been a concern for many years. However all of these proposals involve changing the basic Tcl parsing rules (the dodekalogue), which has a major impact in terms of extra complexity and backward compatibility. This TIP aims to allow such inline expressions to be as concise as possible without changing Tcl's parsing rules. The effect will be to allow the canvas command above to be written as:

.canvas addtag enclosed [= $x - 20] [= $x + 20] [= $y - 20] [= $y + 20]

Note that the values of variables (x and y in the example above) will have been substituted into the expression before calc is invoked, calc itself does no further substitutions of any kind.

There are some downsides to this method:

But many uses of expr are for simple numeric calculations where these restrictions do not matter, but brevity is desirable. The standard expr would still be available for use in the more demanding cases.

An alternative which already exists for inline calculations is to use operations from the mathop namespace in prefix form. However this is rather obscure to people who are not Tcl experts, and becomes awkward if several different operators need to be combined.

Specification

The calc command will have syntax:

It will evaluate expressions in a way which is compatible with expr with the following differences:

The following alias will be predefined:

interp alias {} = {} calc

Defining this as an alias will allow any existing code which defines an "=" command to continue working. However new code can use [= <expr>] as a compact way to make a calculation.

Options

Discussion

The first draft of this TIP proposed implementing this functionality as an option on the expr command so that the arguments would be concatenated and then tokenised as expr does. Peter Da Silva pointed out that this would allow arguments intended as single values to introduce new syntactic elements, potentially changing the entire meaning of an expression. E.g.

set b 3/0
...
calc $a - $b
=> divide by zero!

To avoid this I decided to require the arguments to be separate tokens, so that no substitution of values can introduce new syntactic elements. At the time I thought this might also enable supporting string and list values, but later realised that it's not that simple. E.g. if the parser sees "(" it has no way of telling whether this is the start of a parenthesised subexpression or just a string value that happens to contain "(".

So it then became clear that this functionality was sufficiently different from expr to make it a separate command. Also when I looked into the expr parsing code I realised that a separate implementation would be more practical.

At one stage I considered making invoking calc with a single argument a special case, just returning that argument with no parsing or processing. However this would prevent detecting what could be a common error case - passing the expression without spaces as can be done with expr - so I concluded it would be unwise.

Examples

Setting a variable:

    set bright [= $red * 0.3 + $green * 0.59 + $blue * 0.11]
    set x [= $radius * cos( $angle )]

Use with an image command:

    my_img put $shade -to [= $left + $i] $top [= $left + $i + 1] $bottom

Implementation

I have written a prototype of this functionality in Tcl, the code is at http://www.cmacleod.plus.com/tcl/calc.tcl. This code uses a simple "Pratt" parser and generates bytecode which is then run by tcl::unsupported::assemble . For the real implementation my intention would be to translate the same code into C.

I think byte-compilation of calc should be possible and worthwhile. For the normal case where all operators and parentheses are written as literals and only numeric or boolean values will be substituted at run-time, it should be possible to do the parsing at compile-time and generate reusable bytecode. However there would need to be a run-time check that the substituted values are actually numeric or boolean. E.g. the command

calc $a - 2

would be compiled assuming that $a has a numeric value and therefore the "-" is a infix subtraction. But if $a should have the value "-" we need to reparse this as two unary minus operators. Such cases can be expected to be rare, but do need to be handled. So the compiled bytecode needs to check that all substituted values are numeric or boolean and fall back to the uncompiled implementation if this does not hold. I'm not sure how to write this yet but think it should be possible. Perhaps it would be enough to just run the bytecode and if it returns an error then somehow fall back to the uncompiled implementation?

Copyright

This document has been placed in the public domain.