Radical Language Modification

Richard Suchenwirth 1999-07-14 - My very first Wiki page was introduced by DKF:

You can do some mind-bogglingly fancy stuff with modifications to the [unknown] command (the handler for everything that Tcl doesn't know what to do with). This page includes some of the more fun ones....

As always, additions, comments and corrections are utterly welcome. - DKF

Description

And, as you're interested in alternatives to expr, here's my unknown proc, introducing infix notation for assignment (a = b+c) into the strictly Polish Tcl language:

  • Infix Notation
if {[info proc _unknown]==""} {rename unknown _unknown} ;# keep the original
proc unknown {args} {
    if [regexp (.+):$ [lindex $args 0] -> name] {
        set args [lreplace $args 0 0 $name =]
    } ;# allow REBOL-style assignments (foo: bar; bar: 17+4)
    if {[lindex $args 1]=="="} {       # maybe an assignment like "x = 3+4" ? (Blanks matter!)
        upvar [lindex $args 0] _x
        set rest [lrange $args 2 end]
        if [llength [info commands [lindex $args 2]]] {
            return [set _x [uplevel eval $rest]]
        }
        set _x $rest                ;# this should always work...
        catch {set _x [expr $rest]} ;# ...but maybe expr is happy
        return $_x
    } elseif {[regexp {^([^ ]+)\+\+$} $args -> vname]} {
        uplevel [list incr $vname]  ;# allow things like "i++" ...
    } elseif {[regexp {^([^ ]+)--$} $args -> vname]} {
        uplevel [list incr $vname -1]          ;# ... or "j--"
    } elseif {[regexp {^[-+/\*\.0-9 ()]+$} $args]} {
        return [expr $args]         ;# pure expression? "(17+4)/3"
    } else {uplevel 1 _unknown $args}    ;# let old "unknown" do it
}

For calls like the following sample usages (from an imaginary session):

% v = info tclversion ;# maybe assign a proc result (no []!!)
8.1
% s = this is a string ;# default: shrink-wrapped string (no ""!) 
this is a string
% j = sqrt(2)*3        ;# if expr agrees, the result (no [expr..]!)
4.24264068712

Yes, this is still Tcl, and no, it's not like in the book. This kind of extreme flexibility led RS to later nickname Tcl "The Chameleon Language".

(Adapted from an article [L1 ]


Known problem: The unknown handler is (of course) only called for something unknown. So, if you want to assign to a variable that happens to be also the name of a command/proc, "unknown" would not get called:

info = Something to tell
bad option "=": must be args, body, cmdcount, commands, ...

and this will not be too rare because of many common-sense command/proc names in Tcl. This is where the REBOL style ("i: 5", blank after but not before the colon) comes in handy.

info: Something to tell

would only miss the unknown if you had a proc named "info:", which may not occur that often. BTW: how's

for {i: 0} {$i<$max} {i++} {...}

For a cleaner, unknown-free approach to "natural assignments" see Gadgets.


Another use of unknown, now for indexing lists and arrays in half-natural syntax:

if {![llength [info commands _unknown]]} {rename unknown _unknown}
proc unknown {args} {
    if {[lindex $args 1]=="(" && [lindex $args 3]==")"} {
        upvar [lindex $args 0] _x
        if [array exists _x] {return $_x([lindex $args 2])} ;##
        return [lindex $_x [lindex $args 2]]
    } else {
        eval _unknown $args     
}

This again only works if no proc has the same name as the list (or array). The line marked with ## shields the distinction between list and array, so you can say

set foo {a b c d e f}; foo ( 2 ) => c
array set bar {cat Katze dog Hund}; bar ( cat ) => Katze

Blanks must however be written round the brackets (this is a potential error reason). This indexing handler can be combined with the infix-expression handler above. Richard Suchenwirth

Tcl is the best invention since even before sliced bread !-)

LES on 27 Sep 2005: But do we really need those parentheses? Why not make even shorter syntax?

  • Array Access 1
if {![llength [info commands _unknown]]} {rename unknown _unknown}
proc unknown {args} {
    if {[llength $args] == 2 && [uplevel 1 info exists [lindex $args 0]]}  {
        upvar [lindex $args 0] _x
        if [array exists _x] {return $_x([lindex $args 1])}
        return [lindex $_x [lindex $args 1]]
    } else {
        eval _unknown $args     
}

Now we can say:

% set foo {a b c d e f}
% foo 2
c
% array set bar {cat Katze dog Hund}
% bar dog
Hund
% if {[bar cat] == "Katze"} {puts meow}
meow

But I find this one more elegant:

  • Array Access 2
if {![llength [info commands _unknown]]} {rename unknown _unknown}
proc unknown {args} {
    set _rgx {([0-9A-Za-z_]+):([0-9A-Za-z_]+)$}
    if  [regexp $_rgx [lindex $args 0] => foo _bar]  {
        upvar $foo _foo
        # if it is array...
        if  [array exists _foo]  {return $_foo($_bar)}
        # and if it is a list...
        return [lindex $_foo $_bar]
    }
}

Testing:

% set foo {a b c d e f}
% foo:2
c
% array set bar {cat Katze dog Hund}
% bar:dog
Hund
% if {[bar:cat]=="Katze"} {puts meow}
meow

  • Integer Range

Bryan Oakley proposed (without recommending) another use, so you can generate a list of integers by just putting .. between them, e.g.

set i [5 .. 12] ==> {5 6 7 8 9 10 11 12}:
if {![llength [info commands _unknown]]} {rename unknown _unknown}
proc unknown args {
    if {[lindex $args 1]==".."} {
        set res {}
        for {set i [lindex $args 0]} {$i<=[lindex $args 2]} {incr i} {
            lappend res $i
        }
        return $res
    } else {
        eval _unknown $args
    }
}

RS - See also Let unknown know

Larry Smith: You can get arbitrarily fancy with run.


FW: This hack allows for you to perform expr math operations "freeform" without actually needing to call expr. Similar to RS's expr functionality in infix notation for assignment at the top of the page. Don't try anything with strings, though - this is only for numbers.

Since it captures all procedure names that contain a number or an operator, don't go naming any procedures things like "go!" and you'll be fine. Nor can you use the conditional (?:) operator.

if {[info proc _unknown] == ""} {rename unknown _unknown}

proc unknown {args} {
    if {[regexp {[0-9+-~!*/%<>|&().]} [lindex $args 0]]} {
        return [expr $args]
    } else {
        eval _unknown $args
    }
} ;# FW

Example:

puts [sqrt(2) * 3]

LES: no, it doesn't work for me

MG Would something along these lines be easier? This works for all valid expr expressions.

if {[info proc _unknown] == ""} {rename unknown _unknown}
proc unknown {args} {
    if { [catch {expr $args} ans] } {
        return [eval _unknown $args]
    }
    return $ans;
}

LES on 2005-Feb-27: This second idea works fine for me, except for one strange side effect. I load that new "unknown" proc in my custom config in Tkcon and have this problem:

$ parray env
"env" isn't an array

But the output always is correct in the second attempt. Tkcon's fault? Certainly not. Put that "unknown" proc in a script and finish the script with this command:

puts [ parray env ]

You will get an error. Now try this:

catch { puts [ parray env ] }
puts [ parray env ]

We get correct output again. Now I wonder what other commands are affected by that custom curry "unknown" proc.

Note to RS: I really had to insert my comment here. But now "Very much so" below becomes totally meaningless.


RS Very much so - except that five years later, I prefer to just augment the unknown body, see Let unknown know - with a reusable know command:

proc know what {proc unknown args $what\n[info body unknown]}
know {if ![catch {expr $args} res] {return $res}}

Note however that this cuts off part of the history mechanism, which is also handled by unknown, so e.g.

!42

is intercepted as a valid expression, and no longer taken to mean "repeat command number 42".


NiC I would propose the following evolution to the first version of the unknown function by Richard Suchenwirth (top of this page)

  • Short Hand for Expr
if {[info proc _unknown]==""} {rename unknown _unknown} ;# keep the original
proc unknown {args} {
   if [regexp (.+):$ [lindex $args 0] -> name] {
      set args [lreplace $args 0 0 $name =]
   } ;# allow REBOL-style assignments (foo: bar; bar: 17+4)
   if {[lindex $args 1]=="="} {       # maybe an assignment like "x = 3+4" ? (Blanks matter!)
      upvar [lindex $args 0] _x
      set rest [lrange $args 2 end]
      
      # [NiC] Prepare _x with the simplest possible result
      set _x $rest
      # [NiC] NOW, try to eval the rest, if possible
      if { [catch {set _x [uplevel eval $rest]} ] } {
          # [NiC] If error, try to eval the rest as a valid expr expression
          catch {set _x [expr $rest]} ;# ...but maybe expr is happy
      }
      return $_x

   } elseif {[regexp {^([^ ]+)\+\+$} $args -> vname]} {
       uplevel [list incr $vname]  ;# allow things like "i++" ...
   } elseif {[regexp {^([^ ]+)--$} $args -> vname]} {
       uplevel [list incr $vname -1]          ;# ... or "j--"
   } elseif {[regexp {^[-+/\*\.0-9 ()]+$} $args]} {
       return [expr $args]         ;# pure expression? "(17+4)/3"
   } else {uplevel 1 _unknown $args}    ;# let old "unknown" do it
}

With this code, it becomes possible to execute things such as :

% a = b = c = int(2+sqrt(4))
4
% a = b++
5

What do you think ? Since I am very new to both this Wiki and Tcl, please, correct my possible mistake or remove this proposition in case it is not in a correct place !


AndyDent - 2010-11-29 20:57:16 (putting back the content I inadvertently destroyed above, the "Add a Comment" has weird behaviour, at least on this page, wiping all previous content.)

I'm pretty new to TCL but it seems like there's a bug in the first line that has been propagated throughout this page

if {[info proc _unknown]==""} {rename unknown _unknown} ;# keep the original

shouldn't the name of the command being tested in the first expression be unknown which would make the correct line

if {[info proc unknown]==""} {rename unknown _unknown} ;# keep the original

2010 Dec 02 rgf ... no, the original is correct. The code might be described as

  ''if {the unknown proc hasn't already been renamed} then {rename the unknown proc}''

Of course, this doesn't guarantee that the _unknown proc isn't something else - it just checks whether or not it exists.


For a system allowing infix expressions in Tcl code, see also xsource. See also:


milarepa - 2015-03-20 07:23:38

Tcl syntax is very simple and consistent because of its command / arguments structure. Sometimes I miss the dot notation of other languages like ruby. In ruby you can write something like this:

"It's a nice day".length
=> 15

How we can do that in Tcl with the unknown command?

EMJ All the uses of unknown here amount to:

  1. recognize a pattern
  2. do the right thing

and in this case the pattern is "ends in .length", so it should be easy. When using it of course, you will want something like

set howlong ["It's a nice day".length]

See Also

arrexpr, by dbohdan
Another foray into overloading array notation for maths.

SchrodingerTcl, by Setok: Quantum computing. Sort of.