The code is split into files anticipating that some optional enhancements are orthogonal and pertain to just one file.

The recursive routine compile (c[1|2].c) compiles an expression into a coded form called Mcode. It is your typical ugly ad-hoc parser. It cannot be expanded much beyond its current state. It is, however, small and does not depend on external state of the art of parsing. compile uses stack space proportional to the maximum depth of the input.

The recursive routine eval (a.c) evaluates a coded expression. Its two arguments are:

cp x
which locates the code compiled from the corresponding expression,
env * e
which provides the environment which gives values to all of the free variables within the code body.
eval uses stack space proportional to the number functions dynamically called. There is no garbage collection and you can exhaust the working space of worksize (=100000) environ units or stack space. The compiled code must fit in codesize (=5000) bytes. Lexical scoping may not exceed a depth of md (=100).

b.c also provides the routine eval but eschews C’s stack mechanism and is thus much harder to read. It constructs and frees continuations explicitly just like as stack frames. It paves the was to garbage collection.

On the Mac the library routine getchar() returns the next 8 bit byte from a utf-8 coded stream of unicode characters, which might be coming from a file or from terminal input such as a paste operation.


Many combinations of C sources in this directory may be combined to build different things. This header file is necessary in each case. The compiler option “fnested-functions” is unlikely to be needed except on Mac OS X. The pragma __dead2 in h.h may be omitted if your compiler objects.

gcc -g -fnested-functions -Wall g.c a.c c1.c m.c -o repl1 builds the Read-Eval-Print-Loop file repl1 which assumes the original unabbreviated syntax.

gcc -g -fnested-functions -Wall g.c a.c c2.c m.c -o repl2 builds the Read-Eval-Print-Loop file repl2 which assumes the more flexible syntax which requires dots.

gcc -g -fnested-functions -Wall g.c s.c c1.c m.c -o cnvSch1 builds a program cnvSch1 with the original input syntax whose output is a Scheme program with the same meaning.

gcc -g -fnested-functions -Wall g.c s.c c2.c m.c -o cnvSch2 builds a program cnvSch2 like cnvSch1 above but with the flexible input syntax.

The file makesx builds 6 versions at once. The versions ending in x use the bounded stack logic.

Here is a directory where h.h, a.c and m.c are modified to do reference counting to reclaim storage.


repl1 runs these elementary tests as repl2 runs these which were about enough to find the last bugs. These larger programs (too) illustrate some simple code.

Bugs

((λx.xxx)(λx.xxx)) blows the stack. I don't know if this is preventable in general. The bounded stack implementation avoids this; it exhausts and reports exhaustion of workspace instead.
tcflush seems not to work as I understand it and this results in continuing to report errors after the first error of an expression. This is seldom useful.
The 2 byte field that holds the length of the function expression in an application expression of Mcode is unaligned. See this. That fails on some processors.

Embarrasements

A numeral is compiled to an ‘inline constant’ which produces a new value upon each evaluation. It should presumably produce a value at compile time whether or not we do garbage collection. Perhaps such compile time constants could join the initial environ. This is as if ( ... 7 ... ) were transformed before compiling into ((λx ( ... x ... )) 7) which will evaluate 7 just once. On the other hand a manual or automatic transformation such as the above achieves the same end without burdening either the compiler or run-time.