HereTcl Language Experiment

HereTcl Language Experiment Peter Lewerin (2004-01-26): HereTcl is intended to be an unorthodox, radical, contrastandard, and hopefully interesting (or at least entertaining) variant of Tcl.

Concept

(2004-02-12)

The basic idea I had was to try using a widget-like object as a semantic building block. After discarding the initial prototype, I now have a small set of Snit types that contain the definitions necessary to allow a very simplified syntax to work as a language.

Simple types

  • Char: (=, pred, succ, <=>)
  • Float: (+, -, x, /, %, **, ceil, floor, round, abs, neg, <=>, toInteger)
  • Integer: (+, -, x, /, %, **, <=>, times, pred, succ, abs, neg, toChar, toFloat)
  • Nil: (not, and, or, xor, then, else, null)
  • Object: (not, and, or, xor, then, else, null)
  • Proc: (call)

Aggregate types

  • Array: (<=>, compact, remove, delete, fill, flatten, reverse, sort, uniq, each, clear, get, set, top, rest, end, append, size, empty)

Mixins

  • Comparator: (cmp, <, <=, ==, >=, >, !=, max, min, within)
  • Enumerator: (all, some, member, filter, map, first, max, min, fold)
  • Iterator: (begin, next, more, get, set)

The interpreter defines the objects

 Object t
 Nil nil

Evaluation

An expression, e.g.

 2 + 3 x 4

is evaluated by first replacing the first token with the name of a snit object which is initialized by the value of the token:

 ::Integer1 + 3 x 4

running [eval] on this calls the Snit dispatcher, which consumes the '+' token. The corresponding method consumes the '3' token and concatenates the resulting object to the rest:

 ::Integer3 x 4

running [eval] again leaves a single token in the line, at which point the evaluator returns with the embedded value of the final object.

The procedure that replaces the first token (the 'reader') looks like this:

        proc R {arg args} {
                if {$arg eq {}} {
                        return [concat nil $args]
                } elseif {[lsearch [info commands] [string trimleft $arg :]] >= 0} {
                        return [concat $arg $args]
                } elseif {[string is integer $arg]} {
                        return [concat [Integer %AUTO% -value $arg] $args]
                } elseif {[string is double $arg]} {
                        return [concat [Float %AUTO% -value $arg] $args]
                } elseif {[string length $arg] == 1} {
                        return [concat [Char %AUTO% -value $arg] $args]
                } else {
                        return [concat [Symbol %AUTO% -value $arg] $args]
                }
                return
        }

It is called by a 'reader-evaluator':

        proc RE {line} {
                while 1 {
                        set line [uplevel R $line]
                        if {[llength $line] < 2} break
                        set line [uplevel $line]
                }
                $line s
        }

A 'reader-evaluator-printer':

        proc REP {line} {
                if {$line eq {}} {
                        puts {}
                } else {
                        puts [uplevel RE [list $line]]
                }
        }

And finally a full 'read-eval-print' loop:

        proc REPL {script} {
                foreach line [scanScript $script] {
                        puts [uplevel RE [list $line]]
                }
        }

The R/RE/REP/REPL procedures (and a rudimentary scanScript procedure, not shown) make up the translation engine.


Notes

(2004-02-12): Stack levels... didn't think about them until I began having Proc objects as arguments. A few well-placed uplevels later, they work fine too. Updated translation engine above.

(2004-02-09): Keep It Simple, Swede-boy! (KISS) After writing and testing, I've realized that it's not at all necessary to convert all tokens in the input line to snit objects before evaluation; only the first one (and the first one in the resulting line after the first iteration, and so on).

(2004-02-02): HereTcl will not use postfix notation. Sorry. Seeing that experiments with postfix notation has already been done a number of times, most recently RPN again, there doesn't seem much point in doing it here.

(2004-01-29): I hadn't realized how many similar ideas were already posted on the Wiki. Having a look now.

(2004-01-28): I may have to abandon postfix notation, sigh. I'm almost done with simple expressions now, but need to write a better parser before I move on to the good stuff.


Discussion

(Please put questions or suggestions here)


RS: so binary operators work either infix or postfix (RPN), and the stack is returned, top at left?

(PL): Yes. The stack direction is simply the result of dumping a tcllib stack.

RS would be sorry if postfix notation was abandoned - the great breakthrough of Lukasiewicz, in creating Polish notation, was to do away with all unnecessary infix problems, but you had to read PN right to left. (Tcl is Polish, but remarkably intuitive though...) RPN (as in Forth) was the next step to make the notation more palatable for us left-to-righters. Both have the advantage for friends of FP that hardly any variables are ever needed, if you just keep the stack in mind. See Playing Joy or Minimal RPN. If I'd ever give up Tcl, it might well be for a powerful RPN language...

(PL): A strong reason for me to drop RPN, then. ;-) Or maybe I should branch a second paralanguage called Toast, both for "Tcl-on-a-stack" and for what I'll be when the other Tclers find out I've made you give up Tcl :-)

Seriously, I enjoy stack-based languages too. I'll see if I can't resurrect postfix, but I might have to change preferences: up to now the interpreter has taken operands from the token stream if available, and from the results stack if not. The problems I foresee now might be resolved by having the interpreter take the stack first and the stream second.


Examples

 2
 => 2

 2 + 3 x 8
 => 40

 nil then 2 else 3
 => 3
 1 then 2 else 3
 => 2

 A succ
 => B

 [Array a -value {3 7 1 2}] fold + 0
 => 13

 set q 1
 Array a -value {0 1 2 3}
 Proc p -value {n {RE "$n > $q"}}
 a first p
 => 2

 Array a -value {0 9 1 8 2 7 3 6 4 5}
 a member 8
 => t

 Array a -value {0 9 1 8 2 7 3 6 4 5}
 Proc p -value {n {RE "$n > 4"}}
 a filter p
 => 9 8 7 6 5

Notes:

  1. I had to use x as multiplication operator because * interferes with Snit delegation
  2. There are no precedence ordering of operators, hence the 'incorrect' result in #2
  3. Every value except nil evaluates as true

escargo 14 Apr 2005 - This reminds me of the talk I heard Alan Kay give on Smalltalk in 1974.

History

   Content migrated here from [HereTcl].