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
, ormysTiCaL
. Perhaps somebody else can write their own version of this project with one of those names!
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 built you can execute the application, supplying the path to a TCL script which you wish to execute. For example:
$ ./critical examples/prime.tcl
0
1
2 is prime
3 is prime
..
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
Generally the point of a scripting language is you can embed it inside a (host) application of your own - exporting project-specific variables and functions.
You'll find an example of doing that beneath the embedded/ directory:
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]
continue
}
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:
$ ./critical ./picol.tcl
I can compute that 1*1 = 1
I can compute that 2*2 = 4
..
Additional TCL-code can be found beneath examples/.
The following commands are available, and work as you'd expect:
append
,break
,continue
,decr
,env
,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
.- See the later examples, or examine code such as examples/prime.tcl.
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 - see #19 for details of what would be required to implement this support.
Our code has 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
This repository was put together after experimenting with a scripting language, an evaluation engine, putting together a FORTH-like scripting language, writing a BASIC interpreter and creating yet another lisp..
I've also played around with a couple of compilers which might be interesting to refer to:
- Brainfuck compiler:
- A math-compiler:
Please feel free to open a new issue with your example included so I can see how to fix it.
Steve