1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-11 21:57:38 +03:00
mal/impls/vala/README.md
Joel Martin 8a19f60386 Move implementations into impls/ dir
- Reorder README to have implementation list after "learning tool"
  bullet.

- This also moves tests/ and libs/ into impls. It would be preferrable
  to have these directories at the top level.  However, this causes
  difficulties with the wasm implementations which need pre-open
  directories and have trouble with paths starting with "../../". So
  in lieu of that, symlink those directories to the top-level.

- Move the run_argv_test.sh script into the tests directory for
  general hygiene.
2020-02-10 23:50:16 -06:00

61 lines
2.9 KiB
Markdown

# Vala implementation
Notes on building:
* With the Debian or Ubuntu packages `valac` and `libreadline-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` and `core.vala` are shared between all the `stepN` 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 run `make 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 the `Env` 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 the `GLib.List` contained in it. But once they've
finished and returned the `Mal.List` to their caller, that list is
never mutated again, which means it's safe for the copying operation
in `with-meta` to make a second `Mal.List` sharing the reference to
the same `GLib.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 in `FUNC`'s function object
is the same one where `def!` inserts the definition of `FUNC`, 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 in `gc.vala`, which works by being the only part of the
program that keeps a non-weak reference to any `Mal.Val` or
`Mal.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.