1
1
mirror of https://github.com/kanaka/mal.git synced 2024-08-18 02:00:40 +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

2.9 KiB

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.