fbfe6784d2
- Add a `vec` built-in function in step7 so that `quasiquote` does not require `apply` from step9. - Introduce quasiquoteexpand special in order to help debugging step7. This may also prepare newcomers to understand step8. - Add soft tests. - Do not quote numbers, strings and so on. Should ideally have been in separate commits: - elisp: simplify and fix (keyword :k) - factor: fix copy/paste error in let*/step7, simplify eval-ast. - guile: improve list/vector types - haskell: revert evaluation during quasiquote - logo, make: cosmetic issues |
||
---|---|---|
.. | ||
.gitignore | ||
core.vala | ||
Dockerfile | ||
env.vala | ||
gc.vala | ||
Makefile | ||
printer.vala | ||
reader.vala | ||
README.md | ||
run | ||
step0_repl.vala | ||
step1_read_print.vala | ||
step2_eval.vala | ||
step3_env.vala | ||
step4_if_fn_do.vala | ||
step5_tco.vala | ||
step6_file.vala | ||
step7_quote.vala | ||
step8_macros.vala | ||
step9_try.vala | ||
stepA_mal.vala | ||
types.vala |
Vala implementation
Notes on building:
-
With the Debian or Ubuntu packages
valac
andlibreadline-dev
installed, and GNU make, you should be able to build using the provided Makefile. -
The build will not be warning-clean, because the shared modules like
types.vala
andcore.vala
are shared between all thestepN
main programs, and not all the steps use all the functions in the shared modules, and the Vala compiler has no way to turn off the warning about unused pieces of source code. -
The Vala compiler works by translating the program to C and then compiling that. The C compilation stage can sometimes encounter an error, in which case the compiler will leave
.c
source files in the working directory. If that happens, you can runmake clean-c
to get rid of them.
Design notes on the implementation:
-
Vala has exceptions (which it calls 'error domains'), but they don't let you store an arbitrary data type: every exception subclass you make stores the same data, namely a string. So mal exceptions are implemented by storing a mal value in a static variable, and then throwing a particular Vala error whose semantics are 'check that variable when you catch me'.
-
Vala's bare function pointers are hard to use, especially if you want one to survive the scope it was created in. So all the core functions are implemented as classes with a
call
method, which leads to a lot of boilerplate. -
To make
types.vala
work in step 2, when theEnv
type doesn't exist yet, I had to use#if
to condition out the parts of the code that depend on that type. -
Mutability of objects at the Vala level is a bit informal. A lot of core functions construct a list by making an empty
Mal.List
and then mutating theGLib.List
contained in it. But once they've finished and returned theMal.List
to their caller, that list is never mutated again, which means it's safe for the copying operation inwith-meta
to make a secondMal.List
sharing the reference to the sameGLib.List
. -
Vala has a reference counting system built in to the language, but that's not enough to implement mal sensibly, because the common construction
(def! FUNC (fn* [ARGS] BODY))
causes a length-2 cycle of references: the environment captured inFUNC
's function object is the same one wheredef!
inserts the definition ofFUNC
, so the function and environment both link to each other. And either element of the cycle could end up being the last one referred to from elsewhere, so you can't break the link by just making the right one of those references weak. So instead there's a small garbage collector ingc.vala
, which works by being the only part of the program that keeps a non-weak reference to anyMal.Val
orMal.Env
: it links all GCable objects together into a list, and when the collector runs, it unlinks dead objects from that list and allows Vala's normal reference counting to free them.