implement a minimalistic REPL
... and tell about its shortcomings
git: commit
Good news first: I implemented a REPL 1 for my machine. If you have a
Linux machine with libreadline.so
and its headers set up, then
use make repl && build/repl/repl
to try it out.
REPL is built as a combination of a very simple launcher, a couple of
additional builtins that I define in the repl.c
, and the main loop
implemented in LISP main.lispm
.
The way one extends builtins in LISPM is by using LISPM_BUILTINS_EXT
macro to define an array of LispmBuiltin
objects. The repl.c
defines
three extensions: #io:readline
, #io:print
and #eval
. The first one
is a wrapper around GNU readline()
call, and the latter two wrap
lispm_print_short()
and lispm_eval0()
correspondingly.
I am somewhat unhappy about the macro, as it currently enforces the symbol to go into a particularly named section of the object file. Later I use a GNU linker script to glue all these sections together into a contiguous memory array. The upside of this solution is that it uniquely maps a builtin definition to a small integer number – its offset in the builtins table. The downside is that it is outside of standard C, it is not portable, and already prevents me from playing with LISPM on my Macbook. I have an idea how to factor this magic out and provide an alternative solution in standard C, but so far I was kicking this can down the road.
I reconsidered what #symbols
are, again. They can still be only
defined by a VM extension, but are not obliged to be self-referencing
keywords. Of course, #t
and #err!
still evaluate to themselves, but
#io:readline
is just a regular extension function, and the symbol
resolves to the corresponding LispmBuiltin
object.
I know how to implement (define
but decided to leave it out for now.
The least pleasant dirty secret of the current implementation is that
ln
does not carry the value of the input line, but only a boolean
flag “are we at eof?” The actual value of the line is stored in a static
variable. Hence, the program only works because #io:readline
is
immediately followed by #eval
.
The underlying deficiency of the VM is its inability to refer to a
native value from its own values stack: even though I can somehow map
the pointer returned from readline()
to an integer value, I cannot
tell from repl.c
when this value is safe to discard – to do that I
would need a hint from garbage collector.
verbose branch logs
-
[e0ce9663] API cleanup
provide non-throwing alternatives to both main API methods
-
[c984f3d4] reduce bit-shifting in gc0 and list_reverse_inplace
-
[b4c5249a] get rid of the limit on the builtin name length
-
[d1b59e8e] clean up htable_ensure
Instead of operating on the returned entry on the caller side, I now provide the initial value directly. Besides cleaner interface, this also saved some 30 bytes of binary size.
-
[20db1850] an extremely bare to the bone REPL
The REPL works, but keeps the state split between the LISP world and the native environment. For instance, the result of
#io:readline
is not a genuine value, and the result of#eval ln
depends only on the internal state obtained duringio_readline
. I miss some capabilities for proper implementation, mainly a natural way to bind a VM value to a behind-the-scene state. -
[f61a6297] retire launcher
-
[693a9295] don’t forget to free the previous input!