2022-04-17 20:44:43 +02:00
|
|
|
<img src="./doc/logo.svg" height="64">
|
|
|
|
|
2022-05-01 08:32:36 +02:00
|
|
|
# [WAForth](https://mko.re/waforth): Forth Interpreter+Compiler for WebAssembly
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2022-05-05 20:27:51 +02:00
|
|
|
[![Build](https://github.com/remko/waforth/actions/workflows/build.yml/badge.svg)](https://github.com/remko/waforth/actions/workflows/build.yml)
|
|
|
|
|
|
|
|
|
2022-04-17 20:44:43 +02:00
|
|
|
WAForth is a small bootstrapping Forth interpreter and dynamic compiler for
|
2018-05-23 19:47:23 +02:00
|
|
|
[WebAssembly](https://webassembly.org). You can see it in a demo
|
2022-05-01 08:32:36 +02:00
|
|
|
[here](https://mko.re/waforth/).
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2020-01-04 19:57:07 +01:00
|
|
|
It is [entirely written in (raw)
|
|
|
|
WebAssembly](https://github.com/remko/waforth/blob/master/src/waforth.wat), and
|
|
|
|
the compiler generates WebAssembly code on the fly. The only parts for which it
|
|
|
|
relies on external (JavaScript) code is to dynamically load modules (since
|
|
|
|
WebAssembly [doesn't support JIT
|
|
|
|
yet](https://webassembly.org/docs/future-features/#platform-independent-just-in-time-jit-compilation)),
|
2019-11-08 22:47:01 +01:00
|
|
|
and the I/O primitives to read and write a character to a screen.
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2022-04-17 20:44:43 +02:00
|
|
|
The WebAssembly module containing the interpreter, dynamic compiler, and
|
2022-05-01 08:32:36 +02:00
|
|
|
all built-in words comes down to 13k (6k gzipped), with an extra 7k (3k gzipped) for the JavaScript wrapper and web UI.
|
2022-04-17 20:44:43 +02:00
|
|
|
|
2019-03-15 13:09:02 +01:00
|
|
|
WAForth is still in an experimental stage. It implements most of the [ANS Core
|
|
|
|
Words](http://lars.nocrew.org/dpans/dpans6.htm#6.1), and passes most of the
|
2022-04-17 20:44:43 +02:00
|
|
|
[Forth 200x Test Suite](https://forth-standard.org/standard/testsuite)
|
2019-03-15 13:09:02 +01:00
|
|
|
core word tests.
|
|
|
|
|
2022-05-05 22:23:27 +02:00
|
|
|
![WAForth Console](doc/console.gif "WAForth Console")
|
2022-05-05 20:19:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
## Using WAForth in an application
|
|
|
|
|
|
|
|
You can embed WAForth in any JavaScript application.
|
|
|
|
|
|
|
|
A simple example to illustrate starting WAForth, and binding JavaScript functions:
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
import WAForth from "waforth";
|
|
|
|
|
|
|
|
(async () => {
|
|
|
|
// Create the UI
|
|
|
|
document.body.innerHTML = `<button>Go!</button><pre></pre>`;
|
|
|
|
const btn = document.querySelector("button");
|
|
|
|
const log = document.querySelector("pre");
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2022-05-05 20:19:03 +02:00
|
|
|
// Initialize WAForth
|
|
|
|
const forth = new WAForth();
|
|
|
|
forth.onEmit = (c) =>
|
|
|
|
log.appendChild(document.createTextNode(String.fromCharCode(c)));
|
|
|
|
await forth.load();
|
|
|
|
|
|
|
|
// Bind "prompt" call to a function that pops up a JavaScript prompt, and pushes the entered number back on the stack
|
|
|
|
forth.bind("prompt", (stack) => {
|
|
|
|
const message = stack.popString();
|
|
|
|
const result = window.prompt(message);
|
|
|
|
stack.push(parseInt(result));
|
|
|
|
});
|
|
|
|
|
|
|
|
// Load Forth code to bind the "prompt" call to a word, and call the word
|
|
|
|
forth.interpret(`
|
|
|
|
( Call "prompt" with the given string )
|
|
|
|
: PROMPT ( c-addr u -- n )
|
|
|
|
S" prompt" SCALL
|
|
|
|
;
|
|
|
|
|
|
|
|
( Prompt the user for a number, and write it to output )
|
|
|
|
: ASK-NUMBER ( -- )
|
|
|
|
S" Please enter a number" PROMPT
|
|
|
|
." The number was" SPACE .
|
|
|
|
;
|
|
|
|
`);
|
|
|
|
|
|
|
|
btn.addEventListener("click", () => {
|
|
|
|
forth.interpret("ASK-NUMBER");
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
```
|
|
|
|
|
|
|
|
## Development
|
|
|
|
|
|
|
|
### Install Dependencies
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2019-11-08 22:47:01 +01:00
|
|
|
The build uses the [WebAssembly Binary
|
|
|
|
Toolkit](https://github.com/WebAssembly/wabt) for converting raw WebAssembly
|
|
|
|
text format into the binary format, and [Yarn](https://yarnpkg.com) for
|
|
|
|
managing the dependencies of the shell.
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2019-11-08 22:47:01 +01:00
|
|
|
brew install wabt yarn
|
2018-05-23 19:47:23 +02:00
|
|
|
yarn
|
|
|
|
|
|
|
|
|
2022-05-05 20:19:03 +02:00
|
|
|
### Building & Running
|
2018-05-23 19:47:23 +02:00
|
|
|
|
|
|
|
To build everything:
|
|
|
|
|
|
|
|
make
|
|
|
|
|
|
|
|
To run the development server:
|
|
|
|
|
2022-04-13 17:02:46 +02:00
|
|
|
make dev
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2022-05-05 20:19:03 +02:00
|
|
|
### Testing
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2022-04-30 13:18:48 +02:00
|
|
|
The tests are served from `/waforth/tests` by the development server.
|
2019-03-10 10:37:01 +01:00
|
|
|
|
|
|
|
You can also run the tests in Node.JS by running
|
|
|
|
|
|
|
|
make check
|
2018-05-23 19:47:23 +02:00
|
|
|
|
|
|
|
## Design
|
|
|
|
|
2019-03-09 22:02:46 +01:00
|
|
|
The WAForth core is written as [a single
|
|
|
|
module](https://github.com/remko/waforth/blob/master/src/waforth.wat) in
|
|
|
|
WebAssembly's [text
|
2019-11-11 11:48:29 +01:00
|
|
|
format](https://webassembly.github.io/spec/core/text/index.html).
|
2018-05-23 19:47:23 +02:00
|
|
|
|
|
|
|
### The Interpreter
|
|
|
|
|
|
|
|
The interpreter runs a loop that processes commands, and switches to and from
|
|
|
|
compiler mode.
|
|
|
|
|
2019-03-09 22:02:46 +01:00
|
|
|
Contrary to some other Forth systems, this system doesn't use direct threading
|
|
|
|
for executing code. WebAssembly doesn't allow unstructured jumps, let alone
|
|
|
|
dynamic jumps. Instead, WAForth uses subroutine threading, where each word
|
|
|
|
is implemented as a single WebAssembly function, and the system uses calls
|
|
|
|
and indirect calls (see below) to execute words.
|
2018-05-23 19:47:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
### The Compiler
|
|
|
|
|
2019-03-09 22:02:46 +01:00
|
|
|
While in compile mode for a word, the compiler generates WebAssembly
|
|
|
|
instructions in binary format (since there is no assembler infrastructure in
|
|
|
|
the browser). Since WebAssembly [doesn't support JIT compilation
|
|
|
|
yet](https://webassembly.org/docs/future-features/#platform-independent-just-in-time-jit-compilation),
|
|
|
|
a finished word is bundled into a separate binary WebAssembly module, and sent
|
|
|
|
to the loader, which dynamically loads it and registers it with a shared
|
|
|
|
[function
|
|
|
|
table](https://webassembly.github.io/spec/core/valid/modules.html#tables) at
|
|
|
|
the next offset, which in turn is recorded in the word dictionary.
|
|
|
|
|
|
|
|
Because words reside in different modules, all calls to and from the words need
|
|
|
|
to happen as indirect `call_indirect` calls through the shared function table.
|
2019-11-08 22:47:01 +01:00
|
|
|
This of course introduces some overhead, although it appears limited.
|
2019-03-09 22:02:46 +01:00
|
|
|
|
|
|
|
As WebAssembly doesn't support unstructured jumps, control flow words
|
|
|
|
(`IF/ELSE/THEN`, `LOOP`, `REPEAT`, ...) can't be implemented in terms of more
|
|
|
|
basic words, unlike in jonesforth. However, since Forth only requires
|
|
|
|
structured jumps, the compiler can easily be implemented using the loop and
|
|
|
|
branch instructions available in WebAssembly.
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2018-05-31 21:11:04 +02:00
|
|
|
Finally, the compiler adds minimal debug information about the compiled word in
|
2019-03-09 22:02:46 +01:00
|
|
|
the [name
|
|
|
|
section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#name-section),
|
|
|
|
making it easier for doing some debugging in the browser.
|
2018-05-31 21:11:04 +02:00
|
|
|
|
2019-03-09 22:02:46 +01:00
|
|
|
![Debugger view of a compiled
|
2022-04-17 18:22:36 +02:00
|
|
|
word](doc/debugger.png "Debugger view of a
|
2019-03-09 22:02:46 +01:00
|
|
|
compiled word")
|
2018-05-31 21:11:04 +02:00
|
|
|
|
2018-05-23 19:47:23 +02:00
|
|
|
|
|
|
|
### The Loader
|
|
|
|
|
2019-03-09 22:02:46 +01:00
|
|
|
The loader is a small bit of JavaScript that uses the [WebAssembly JavaScript
|
|
|
|
API](https://webassembly.github.io/spec/js-api/index.html) to dynamically load
|
|
|
|
a compiled word (in the form of a WebAssembly module), and ensuring that the
|
|
|
|
shared function table is large enough for the module to register itself.
|
2018-05-23 19:47:23 +02:00
|
|
|
|
|
|
|
### The Shell
|
|
|
|
|
2019-03-09 22:02:46 +01:00
|
|
|
The shell is [a JavaScript
|
|
|
|
class](https://github.com/remko/waforth/blob/master/src/shell/WAForth.js) that
|
|
|
|
wraps the WebAssembly module, and loads it in the browser. It provides the I/O
|
|
|
|
primitives to the WebAssembly module to read and write characters to a
|
2022-05-05 20:19:03 +02:00
|
|
|
terminal, and externally provides a `interpret()` function to execute a fragment of
|
2019-03-09 22:02:46 +01:00
|
|
|
Forth code.
|
2018-05-23 19:47:23 +02:00
|
|
|
|
2019-03-09 22:02:46 +01:00
|
|
|
To tie everything together into an interactive system, there's a small
|
|
|
|
console-based interface around this shell to type Forth code, which you can see
|
2022-05-01 08:32:36 +02:00
|
|
|
in action [here](https://mko.re/waforth/).
|
2018-05-23 19:47:23 +02:00
|
|
|
|