## 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` (see `supercollider/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 ### ARGV - If there's no access to ARGV, but you can fetch environment variables, modify `run` so that it exports a `QX_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 ### 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 ### Type system - If your language only supports strings, things might get ugly - See 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 ### 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 ### Apply - Call the function with the array of arguments and destructure them in the function, see ### 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 ### Numbers - The classic solution is church numerals, see - Arrowlisp is a purely symbolic Lisp dialect where there's no numbers, but symbols with a numeric name are allowed: ### 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 ### 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 to `test.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 from `repl.qx` [MAL]: https://github.com/kanaka/mal/