Version 12 of infix

Updated 2008-07-01 05:55:13 by LarrySmith

One of the styles for writing expressions, with operations in-between the operands, as contrasted to postfix (operations follow operands, as in RPN) and prefix (operations come before operands, as in Tcl in general). Infix notation for mathematical expressions is by far the most common, and is in Tcl supported by expr.


As of 2006-10-14, infix is also the name of a Tcl package (and command defined by this package) that lets programmers write sequences of mathematical formulae in the familiar infix form. A short example:

  package require infix 0.2
  ::infix::core::setup base numconst expr::ops expr::fun
  proc ngon_corner {num_sides radius} {
     infix {
        n <- num_sides
        r <- radius
     } {
        alpha = acos(-1) / n ;  # acos(-1) = pi
        r*cos(alpha), r*sin(alpha)
     }
  }
  ngon_corner 6 10 ; # Returns "8.66025403784 5.0" - a two element list

Larry Smith I just noticed that the infix command sports a piece of ambiguous syntax. "a<-5" could be read as either "a:=5" OR as "a < -5". Such an ambiguity is easy enough to work around by requiring spaces, but the rest of the little infix language does not require them. Now, C has the same kind of wart ("a/*b" - is it a divided by the value pointed to by b, or is it a, followed by the start of a comment) and, being C, it just defines one as "correct" (a and a comment) and charges on. Tcl should really be more elegant than this (and heaven knows that C sets a mighty low standard for elegance). The reverse op "a+b->c" does not have the same problem since ">" does not have a monadic interpretation. Of course, it could be argued that Algol, Pascal, Modula, Oberon, Component Pascal, ad Zonnon all got it right to begin with using "a:=b+c" and thereby avoiding the problem entirely. Or we could drop back to C and (eeewww) use "=" for assignment and "==" for equality under the theory that, well, we got it wrong up until now so we should stay with being wrong. After all, that's what C++ and Java did and look at how elegant they are.

A notable feature is that the little language implemented by this package is completely configurable (setting it up for expr-like operations is what the ::infix::core::setup command does), so you can define new operations, or define the usual ones to do something unusual. A setting that turns +, -, etc. into the operations of the math::bignum package is included with the infix package.

Sarnold: math::bignum is deprecated in Tcl 8.5, isn't it? But I see a real interest for complex numbers and math::bigfloat extension, for instance, to be infix'd. Nice and interesting work you've done! Lars H: Well, infix was written under 8.4, and (if memory serves) it wouldn't be too hard to get it running under 8.3 as well. But the main reson for doing math::bignum was as a proof-of-concept; demonstrating that infix didn't rely on having expr do the parsing or calculations. An extra module for math::complexnumber is fairly straightforward, but at the time I wrote the stuff I found math::bignum more appealing for a demo (maybe it was better documented, or had more features, or something). ;-) 2008-01-14: See below for implementing math::complexnumbers operations.

The code is available at

  http://abel.math.umu.se/~lars/tcl/infix.tar.gz

(requires tcllib, tcllib 1.8 is sufficient).

A paper on the package (which includes the entire source code, commented and explained) is available at

  http://abel.math.umu.se/~lars/tcl/infix.pdf

-- Lars H


Some kind of a user's manual (incomplete)

The user commands created by the package are

::infix::core::setup ?module ...?

Creates a command infix in the namespace it is called from, and loads the listed modules of settings for the little language of that infix command. See below for lists of defined modules and the syntax of the infix command.

::infix::core::opalias name type cmd ?arg ...?

Define a new operation name of type type that gets implemented by appending the operand(s) to the command prefix cmd ?arg ...?, as specified. Any previous meaning of the token name gets overwritten.

The possible types include:

binary priority
A binary, left-associative operation with priority as specified.
binary priority associativity
A binary operation with priority and associativity as specified. Possibilities for associativity include right-associative, non-associative, and n-ary.
prefix priority
A unary prefix operation with priority as specified.
postfix priority
A unary postfix operation with priority as specified.

The priorities should be Tcl numbers (non-integers are fine). Higher priority means tighter binding to the operands. In case of equal priority, the associativity setting is used to resolve which operation acts on which operands. The standard modules uses priorities in the range -2 (for ;) to 14 (for factorial), with + at 10, * at 11, and ** (right-associative) at 12.

Example (requires Tcl 8.5):

  ::infix::core::opalias ++ {binary 10} ::tcl::mathfunc::hypot

(MetaFont uses ++ for "Pythagorean addition": length of hypothenuse in right triangle where other sides have these lengths. C programmers no doubt find this very strange.)

::infix::core::funalias name numargs cmd ?arg ...?

Define a new function name with numargs arguments that gets implemented by appending the argument(s) to the command prefix cmd ?arg ...?, as specified. Any previous meaning of the token name gets overwritten. numargs may be any (in which case any number of arguments are accepted) or an integer.

The infix command

The infix command has the syntax

infix symlinks body

The body is where the actual expressions in the infix little language are written; the infix command returns the value of (the last statement in) the body. The symlinks argument links symbolic names appearing in the body to Tcl variables in the context from which infix was called.

The format of the symlinks is a list with a multiple of three elements. The first element in a triplet is the infix body symbolic name. The last element in a triplet is the external quantity to which the symbolic name is linked. The middle element of the triplet is an "arrow" that determines how the two are linked:

<-
Input-only value; the external quantity is the name of a variable whose value is copied to the symbol.
->
Output-only value; the external quantity is the name of a variable which is set to the final value of the symbol.
<->
Input&output value; combines <- and ->.
<=
Input-only constant; the external quantity becomes the value of the symbol. Useful for constants that don't fit into the infix body syntax.
<e
The external quantity is evaluated as a script, and the symbol is set to the result of that script. (This is somewhat like using command substitution in expr expressions.)

To increment variable $a by $b one could do

  infix {
     a <-> a
     b <-  b
  } { a := a + b }

but also

  infix {
     a <- a
     b <- b
     c -> a
  } { c = a + b }

To Be Continued...

List of modules

Each module module is implemented by the package infix::module, so anyone can define new modules. The following are those that come with the infix package itself.

baseBasic definitions: parentheses for grouping, = for definition, := for assignment, semicolon as separator, and comma as list constructor.
expr::opsThe unary and binary operations of expr.
expr::funThe expr built-in functions.
expr::ternaryThe expr ternary ?: operation. (See ifthen for an alternative.)
softsemicolonA more forgiving statement seprator. The base semicolons may only appear between expressions, but this relaxes the syntax so that a semicolon is effectively ignored if there is no expression after it.
ifthenImplements expression choices of the form if condition then expression else expression fi (also allowing elseif clauses and omitting the else clause). Unlike ?:, this can have semicolons and the like in the expressions without a need to wrap them up in parentheses.
numconstMakes symbols that look like numeric constants be interpreted as such. (Without it, e.g. 0, 1, and 3.5 behave just like x and y.) Underscore is a substitute for minus sign in exponents: 3.2e_1 is 3.2*10**-1.
bignumOperations as for expr::ops, but implemented using the math::bignum commands, with their representation for values. Numeric constants are supported, as are the postfix operations ! (factorial) and !! (semifactorial), and functions sqrt, powm, fromstr, and tostr. Compatible with the ifthen module.
TeX::semiChanges the tokenizer, so that TeX-style control sequences such \alpha and \cdot count as tokens.
expr::delimSome expr-functions written as delimiters.
listbracketBrackets for list construction and indexing.

Example: complex numbers

In want of a module for this, operations on complex numbers (as implemened using the math::complexnumbers package) are fairly easy to set up using opalias and funalias. First we need the basic package requires:

 package require infix
 infix::core::setup base
 package require math::complexnumbers

Then we can define the operations +, -, *, /, and **:

 infix::core::opalias + {binary 10} math::complexnumbers::+
 infix::core::opalias - {try {binary 10} - {prefix 13} -} math::complexnumbers::-
 infix::core::opalias * {binary 11} math::complexnumbers::*
 infix::core::opalias / {binary 11} math::complexnumbers::/
 infix::core::opalias ** {binary 12 right-associative} math::complexnumbers::pow

The try part in the definition of - is because there are two common operations denoted by minus: subtraction (binary) and negation (unary prefix). Both interpretations are tried, in that order.

Defining the functions should be equally straightforward, but as it turns out version 0.2 of infix has a bug in the definition of infix::core::funalias, so we need to fix that first:

 proc ::infix::core::funalias {name numargs cmd args} {
    set ns [uplevel 1 {::namespace eval infix {::namespace current}}]
    set fcmd [uplevel 1 [list ::namespace which -command $cmd]]
    if {$fcmd eq ""} then {
       return -code error "Undefined command: $cmd"
    }
    set ${ns}::tokentype($name) function
    set ${ns}::function($name) [list $numargs byvalue\
      [list ::concat [linsert $args 0 $fcmd]]]
 }

After that, the complex-valued functions are trivial.

 foreach fun {exp log conj sqrt sin cos tan} {
    infix::core::funalias $fun 1 math::complexnumbers::$fun
 }

There are however also some real-valued functions which make things trickier, as their results cannot be used as arguments to any of the operations provided by the package. A solution in this case is to define companion commands which return complex numbers (that however has imaginary part 0) and let the infix functions refer to these instead.

 foreach fun {real imag mod arg} {
    proc ::math::complexnumbers::c${fun} {z} [format {
       complex [%s $z] 0
    } $fun]
 }
 foreach fun {real imag mod arg} {
    infix::core::funalias $fun 1 math::complexnumbers::c${fun}
 }

(Here I'm putting the companions, e.g. cimag of imag, in the math::complexnumbers namespace too. A well-behaved complexnumbers module would rather put such auxilliary commands in its own private namespace ::infix::complexnumbers.)

Some examples:

 % infix {z <= {2 0}} {-z*z+z}
 -2.0 0.0
 % infix {z <= {2 1}} {conj(-z)}
 -2.0 1.0
 % infix {z <= {2 1}} {exp(-z)*z+z}
 2.26012464526 0.845360537469
 % infix {z <= {2 1}} {log(-z)*z+z}
 6.28738295702 -3.55117113296
 % infix {z <= {3 4}} {mod(z)}
 5.0 0
 % infix {z <= {3 4}} {arg(z)}
 0.927295218002 0
 % infix {z <= {3 0}} {z**z**z}
 7.62559748499e+12 0.0

Next step: Modify the parser so that complex constants can be inlined into the expressions.