Git hosting
Clone
git clone https://depp.brause.cc/mal-candidates.git
Files
Size | Path |
---|---|
actionscript/ | |
aikido/ | |
algol-68/ | |
autohotkey/ | |
c/ | |
chapel/ | |
coldfusion/ | |
elisp/ | |
fortran/ | |
freebasic/ | |
icon/ | |
islisp/ | |
limbo/ | |
lush/ | |
modula-3/ | |
myrddin/ | |
newlisp/ | |
newtonscript/ | |
oberon-2/ | |
pike/ | |
reason/ | |
rebol/ | |
red/ | |
simula-67/ | |
slang/ | |
sml/ | |
spl/ | |
supercollider/ | |
txr-lisp/ | |
unicon/ | |
vala/ | |
wren/ | |
17108 | CANDIDATES.org |
17215 | LICENSE |
7532 | README.md |
README.md
About
I've contributed several implementations of MAL. Some of these were easier, some harder to pull off. It shouldn't be hard to test whether a language is suitable for this stunt, this repository collects candidates, test code and notes I've made during implementation.
Feature wish list for implementation candidates
- Reading from stdin, printing to stdout and reading files
- Accessing argv
- Loading code from other files or better, a module system
- Regex
- An usable type system or dynamic typing
- User-defined exceptions
- Closures
- Higher-order functions or at least function pointers (functors can work, but are yucky)
- Apply
- Extensible objects
- Records/structs/objects
- Numbers(!), arrays(!!), hash tables (can be faked with arrays)
- Measuring time, alternatively shelling out
- Linux support for the CI to work, alternatively Windows or OS X
- FFI or dynamically loadable modules to use GNU Readline
Hacks and workarounds for missing features
Stdin reading
- If you can read from files, try
/dev/stdin
(seesupercollider/util.sc
) - Consider writing some wrapper script that does this and somehow communicates with the language (like by using TCP sockets, OSC, a HTTP server with JSON as transport, …)
- Anything goes really, see https://github.com/kanaka/mal/blob/master/impls/plpgsql/io.sql
ARGV
- If there's no access to ARGV, but you can fetch environment
variables, modify
run
so that it exports aQX_ARGV
environment variable with all values joined by a separator (like NUL, bell, vertical tab, emoji, …) - Alternatively, have
run
write ARGV with all values joined by a separator to a file and read it from the program
Loading code from other files
- Careful, just loading code might be useless if the load isn't relative to the current file
- If there's no load-relative facility or a way to make one, consider concatenating all source files to one file to be executed by using the Makefile
- Alternatively, if loading multiple files is supported, write a runner script that loads them in the right order
- Note that you'll still have to either figure out dependencies or declare them by putting comments pointing to the required files there, see https://github.com/kanaka/mal/blob/master/impls/chuck/stepA_mal.ck
Regex
- It might be that your language doesn't use PCRE, but a more verbose or restricted dialect
- Writing a tokenizer without them isn't too hard, just translate the regex to code traversing the string, see https://github.com/kanaka/mal/blob/master/impls/picolisp/reader.l
Type system
- If your language only supports strings, things might get ugly
- See https://github.com/kanaka/mal/blob/master/impls/make/types.mk for an example where types are encoded as the first part of a string
Exceptions
- The guide suggests introducing an errno-like construct, but this is highly error-prone
- An easier alternative is creating an error type and returning error
objects, however you'll still have to check for it after every call
to
EVAL
or you'll run into saving errors into variables…
Closures
- As long as the language has call-by-reference you should be fine because all you need is one to the REPL environment
- See https://github.com/kanaka/mal/blob/master/impls/chuck/stepA_mal.ck
Higher-order functions
- Function pointers can suffice for this
- OOP languages typically offer a way to create a functor, that is, an
instance of a class with a
call
method - You still have to define a class for each known function, see https://github.com/kanaka/mal/tree/master/impls/chuck/types/subr
Apply
- Call the function with the array of arguments and destructure them in the function, see https://github.com/kanaka/mal/blob/master/impls/chuck/types/subr/MalAdd.ck
Objects
- You can fake them by using lists/arrays with a type tag as the first element and all further elements being slots
- Just define functions for the predicates and getters/setters, see https://github.com/kanaka/mal/blob/master/impls/elisp/mal/types.el
Numbers
- The classic solution is church numerals, see https://github.com/kanaka/mal/blob/master/impls/make/numbers.mk
- Arrowlisp is a purely symbolic Lisp dialect where there's no numbers, but symbols with a numeric name are allowed: https://github.com/wasamasa/arrowlisp/blob/master/nmath.l
Arrays
- Hopefully you'll be able to use a list/vector type instead
- Otherwise you'll have to define your own list type if you can create something akin to cons cells
- Or you could do it like TCL and embed them in strings…
Hash Tables
- Use a list/array of pairs instead
- If your hash tables have limitations, such as string keys only, that shouldn't be an issue because MAL only makes use of strings and keywords in maps (so the keyword hack with encoding them in a string should work)
- A more severe limitation is the objects stored in them being limited to integers, this would essentially force you to fake pointers (by coming up with a number for each known object and having an array where access to that numbered slot results in the object)
Measuring time
- If you can shell out, call
date
and read its output - If you can't read a shell command's output, redirect it to a file and read that file's contents, see https://github.com/kanaka/mal/blob/master/impls/chuck/types/subr/MalTimeMs.ck
Extensible objects
- The neatest way to implement MAL is by extending built-in types by the necessary fields (usually just the meta one as type/value should already be determinable) and defining extra types as needed (like for atoms and func objects)
- If your language doesn't support this, the easiest way out is to create custom MAL types and wrapping the native ones in them
- This is somewhat annoying, but makes debugging a lot less painful
Testing these features
Create a directory for the language you wish to test and the following
files, with .qx
being the canonical file extension of the language:
util.qx
Implement the following:
(read-words prompt)
Prompt for words until encountering EOF, return list(the-answer)
Calculate 42 in a convoluted way(make-greeter name)
Return a greeter function that returns a personalized greeting(hostname)
Read/etc/hostname
and return its contents(argv)
Return command-line arguments without the program name(fail-gracefully)
Throw and catch user-defined exceptions(splat op args)
Apply the operator to the list of arguments(tokenize input)
Tokenize parenthesized math (you may use regex)(keys dict)
Return keys of map(now)
Return a relative point in time in ms(rect size) (rect-size r) (rect-size-set! r)
OOP, alternatively do the last one functionally and return a new rect with different size
test.qx
- Import
util.qx
(relatively totest.qx
) - Measure starting time
- Test everything from
util.qx
- Measure end time
- Print spent time
run
- Execute
test.qx
from current and parent directory - Test with and without passed args
Makefile
- Optional, intended for languages requiring a compilation step
- May be used to auto-generate symlinks, configuration files and whatnot
repl.qx
- Optional, used to show off a working readline interface
repl
- A shell script executing
repl.qx
or a binary built fromrepl.qx