WALC /wɑlts/ compiles stand-alone modules in WebAssembly into pure untyped lambda expressions.
The input modules are only allowed to use custom WALC functions to input a byte, output a byte, and exit, see example programs written in Rust.
The output lambda expressions are in human-readable WALC format, which just uses square brackets instead of the lambda symbol. There is even a one-line script to convert it to the standard mathematical notation.
All lambda calculus semantics and purity is preserved. In order to perform I/O, the interpreter decodes the program as an I/O command, executes the command, supplies encoded user input if needed, and repeats again.
See example interpreters written in Lua and TypeScript in under 300 LOC that are optimized for running lambda calculus for a long time without speed and memory usage degradation.
You can run some example lambda expressions with:
interp/lambda.ts examples/walc/hello.walcYou might also utilize overview notes as a starting point for digging into the codebase.
Enjoy!
cargo run -- INPUT.wasm -o OUTPUT.walcor install it globally:
cargo install --path .
walc INPUT.wasm -o OUTPUT.walcExample Rust programs are here.
-
Install the WASM toolchain for Rust:
rustup target add wasm32v1-none
You can also experiment with the standard
wasm32-unknown-unknowntoolchain, even though its feature set is unstable and in the future it might extend beyond what WALC supports:rustup target add wasm32-unknown-unknown
-
Build for release. You can use the provided Makefile that will tell Cargo to also install all the
.wasmfiles into theexamples/rust/bindirectory:make -C examples/rust
Or, for the
wasm32-unknown-unknowntarget:make -C examples/rust TARGET=wasm32-unknown-unknown
Example:
mkdir bin
walc examples/rust/bin/mandelbrot.wasm -o bin/mandelbrot.walc
tools/lambda.ts bin/mandelbrot.walcOutput:
..............................:::::!?:!!:............
...............................:::::::!?@!:::::............
..............................:::::::?@@@@@@?!::::::...........
.............................::::::::::?@@@@@@@!:::::::::..........
..........................:::::??@!::@@??@@@@@@@??!@:::::@::.........
......................::::::::::::@@@@@@@@@@@@@@@@@@@@@?@@@@!::..........
..................:::::::::::::::?!@@@@@@@@@@@@@@@@@@@@@@@@@!::::..........
...............::!:::::::::::::::?@@@@@@@@@@@@@@@@@@@@@@@@@@@@::::...........
............::::::@!!!:!@!:::::!?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::...........
..........::::::::::?@@@@@@@@@?!?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@??::............
........::::::::::!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@?:::............
..:...:::::::::!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@?::::............
:?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@?!:::::............
..:...:::::::::!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@?::::............
........::::::::::!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@?:::............
..........::::::::::?@@@@@@@@@?!?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@??::............
............::::::@!!!:!@!:::::!?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::...........
...............::!:::::::::::::::?@@@@@@@@@@@@@@@@@@@@@@@@@@@@::::...........
..................:::::::::::::::?!@@@@@@@@@@@@@@@@@@@@@@@@@!::::..........
......................::::::::::::@@@@@@@@@@@@@@@@@@@@@?@@@@!::..........
..........................:::::??@!::@@??@@@@@@@??!@:::::@::.........
.............................::::::::::?@@@@@@@!:::::::::..........
..............................:::::::?@@@@@@?!::::::...........
...............................:::::::!?@!:::::............
Note that this particular example typically takes around 15 minutes or so to run, however, it does not require much memory.
WALC supports:
- WebAssembly 1.0 (pdf), the WWW standard released in 2019
- Linear Memory 1.0 extensions
WALC does not support:
-
Dynamic type checking and bounds checking.
Only division by zero and signed division overflow are checked. Other checks are ignored for efficiency, even though this is non-compliant behavior.
-
Floating-point arithmetic.
Given the scope of the project, there is simply no point in implementing this.
To avoid as much compilation problems as possible, floats are stored as integers. Reinterpreting conversions between floats and integers are replaced with nops and any other operations are replaced with traps. This behavior might be useful when you use a standard function like
printfthat can use floats internally, but your program never invokes it with any float values.