Notes on building:
-
With the Debian or Ubuntu packages
valacandlibreadline-devinstalled, 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.valaandcore.valaare shared between all thestepNmain 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
.csource files in the working directory. If that happens, you can runmake clean-cto 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
callmethod, which leads to a lot of boilerplate. -
To make
types.valawork in step 2, when theEnvtype doesn't exist yet, I had to use#ifto 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.Listand then mutating theGLib.Listcontained in it. But once they've finished and returned theMal.Listto their caller, that list is never mutated again, which means it's safe for the copying operation inwith-metato make a secondMal.Listsharing 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.ValorMal.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.