Clo is a dynamically-typed programming language with garbage collection.
<comment> := "#" [^\n]* "\n"
<intrinsic> := "." [^\s]+
<binding> := <var> <expr>
<callee> := <intrinsic> | <expr>
<expr> := <int64_t-literal> | <ASCII-str-literal> | <var>
| lambda ( <var>* ) <expr>
| letrec ( <binding>* ) <expr>
| if <expr> <expr> <expr>
| { <expr>+ } // sequenced evaluation
| ( <callee> <expr>* )
| @ <var> <expr> // access var in closure's env (can simulate structs)
The distinguished feature of clo is serializing
the program state as a string.
The built-in function .forkstate
returns
a string encoding the current state,
and when the state is resumed using .eval
it starts
right after the .forkstate
call but with a return value of Void type.
This resembles Linux's fork
, Lisp's call/cc
, etc.
For example, the following program outputs 0, 1, 2, 2, 3 in order.
Note: .eval
works on both source code and serialized program state.
{
(.putstr "0\n")
(.putstr "1\n")
letrec (state (.forkstate)) {
(.putstr "2\n")
if (.= (.type state) 0) # if it is a void value
(.putstr "3\n")
(.eval state)
}
}
See test/ for more code examples (*.clo
).
The source code of the interpreter
is standard C++20 and thus can be compiled
by any C++20-conforming compiler.
The current Makefile
and CI.py
need clang++
with C++20 support (for sanitizer), make
, and python3
.
make -C src/ release
bin/clo <source-path>
python3 CI.py
(re-)builds the interpreter and runs all tests.