-
Notifications
You must be signed in to change notification settings - Fork 0
Installation
This article explains how to get a copy of the PHANTASM Assembler, and how to use it to assemble a PHANTASM source file into an executable WebAssembly module.
Each PHANTASM source file corresponds to (exactly) one WebAssembly module. Programs with multiple source files are assembled as simple collections of individual modules, and must rely on the host resolving any imports and exports to combine modules into larger programs.
Note: WebAssembly binaries can be disassembled to WAT using wasm2wat
(part the WebAssembly Binary Toolkit). This automates conversion of PHANTASM source to WAT source, which can be useful when learning PHANTASM.
The project uses static, vanilla JavaScript (with native imports and exports). There are no build steps or packages. You simply copy the source (the assembler directory) into your project, then import an entrypoint function that takes your source and returns the corresponding binary.
The PHANTASM Project has not published any tooling yet, so you will need to write a few lines of JavaScript to import the entrypoint function, pass your source to it, and use the result (there is some example code at the end of this document that provides a starting point).
The PHANTASM repo contains a directory named assembler which contains the four files that implement the assembler. They are helpers.js
, lexer.js
, parser.js
and compiler.js
, and they are all ES6 modules.
The helpers.js
file contains a few generic helper functions, while lexer.js
, parser.js
and compiler.js
each implement one of the three components of the assembly pipeline, respectively corresponding to each of the three stages of the assembly process: lex, parse, compile.
The three main files, lexer.js
, parser.js
and compiler.js
, each contain an entrypoint function, respectively named lex
, parse
and compile
. These functions are exported explicitly (under their own names) and implicitly (as the default export for their respective module).
All three of the entrypoint functions (lex
, parse
and compile
) can be invoked directly, and they all accept the same argument (the argument to any of the entrypoint functions is passed down the pipeline, to the others, internally). The argument is a simple hash of parameters. There are currently only two parameters, but more will be added later (to implement feature flags):
-
source
: The PHANTASM source to assemble, as a required string. -
url
: The URL for the source file, as an optional string that defaults to"undefined"
.
The entrypoint functions are actually generator functions, each yielding its respective output as individual tokens, statements or bytes. This is exploited internally to ensure that syntax errors are handled lexically (if a file contains more than one syntax error, the error that raises the exception will always be the one that occurs closest to the start of the source).
The compile function yields the bytes of the compiled module (as Number
type values, each between 0
and 255
, inclusive). These bytes must be copied to a Uint8Array
, before being passed to WebAssembly.instantiate
.
The following example code demonstrates the essential steps that are required to execute PHANTASM source in the browser:
import compile from "/assembler/compiler.js";
const source = `
define type $binop as i32, i32 -> i32
define $sum of type $binop thus get 0, get 1, add i32
export "sum" as function $sum
`;
const config = {source, url: "/main.phantasm"};
const binary = new Uint8Array(compile(config));
const module = await WebAssembly.instantiate(binary, {});
console.log(module.instance.exports.sum(1, 2)); // => 3
Early adopters will need to adapt this example to their individual needs.