GoDoc Go Report Card license

criTiCaL – A TCL interpreter written in golang

After re-reading TCL the Misunderstood, I decided to create a simple TCL evaluator of my own. This project is the result, and it has feature-parity with the two existing “small TCL” projects, written in C, which I examined:

There is a simple introduction to this project, and TCL syntax, on my blog here:

The name of this project was generated by looking for words containing the letters “T”, “C”, and “L”, in order. I almost chose arTiCLe, TreaCLe, myThiCaL, or mysTiCaL. Perhaps somebody else can write their own version of this project with one of those names!

Building & Usage

This repository contains a TCL-like interpreter, along with a sample driver.

You can build both in the way you’d expect for golang applications:

$ go build .

Once build you can execute the application, supplying the path to a TCL script which you wish to execute. For example:

    $ ./critical input.tcl
    A is set to: 4.000000
    Hello World

The interpreter contains an embedded “standard-library”, which you can view at stdlib/stdlib.tcl, which is loaded along with any file that you specify.

To disable the use of the standard library run:

   $ ./critical -no-stdlib path/to/file.tcl

It is expected that you might prefer to embed this interpreter within your own (host) application(s), so you’ll find an example of that beneath the embedded/ directory:

Note that the embedded example does not load the standard-library, but of course it could be updated to do so.


The following is a simple example program which shows what the code here looks like:

// Fibonacci sequence, written in the naive/recursive fashion.
proc fib {x} {
    if { <= $x 1 } {
        return 1
    } else {
        return [expr [fib [expr $x - 1]] + [fib [expr $x - 2]]]

// Lets run this in a loop
set i 0
set max 20

while {<= $i $max } {
   puts "Fib $i is [fib $i]"
   incr i

Another example is the test-code which @antirez posted with his picol writeup which looks like this:

proc square {x} {
    * $x $x

set a 1
while {<= $a 10} {
    if {== $a 5} {
        puts {Missing five!}
        set a [+ $a 1]
    puts "I can compute that $a*$a = [square $a]"
    set a [+ $a 1]

This example is contained within this repository as picol.tcl, so you can run it directly:

go build . && ./critical ./picol.tcl
I can compute that 1*1 = 1
I can compute that 2*2 = 4

Additional examples can be found beneath examples/.

Available Commands

The following commands are available, and work as you’d expect:

  • append, break, continue, decr, eval, exit, expr, for, if, incr, proc, puts, regexp, return, set, while.

The complete list of standard TCL commands will almost certainly never be implemented, but pull-request to add omissions you need will be applied with thanks.


Read the file input.tcl to get a feel for the language, but in-brief you’ve got the following facilities available:

  • Floating-point mathematical operations for expr
    • + - / * %.
  • Comparison operations for expr
    • < > <= >=, ==, !=, eq, ne
  • Output to STDOUT via puts.
  • Inline command expansion, for example puts [* 3 4]
  • Inline variable expansion, for example puts "$$name is $name".
  • The ability to define procedures, via proc.

Missing Features

The biggest missing feature is the complete absence of support for lists of any kind. This is common in the more minimal-TCL interpreters I examined.

The other obvious missing feature is support for the upvalue command, which means we’re always a little at risk of scope-related issues.

Adding upvalue would be possible, but adding list-processing would be more work than I’d prefer to carry out at this time.


Our code has near 100% test-coverage, which you can exercise via the standard golang facilities:

$ go test ./...

There are also fuzz-based testers supplied for the lexer and parser packages, to run these run one of the following two sets of commands:

cd parser
go test -fuzztime=300s -parallel=1 -fuzz=FuzzParser -v

cd lexer
go test -fuzztime=300s -parallel=1 -fuzz=FuzzLexer -v


Please feel free to open a new issue with your example included so I can see how to fix it.



View Github