waforth/README.md

222 lines
10 KiB
Markdown
Raw Normal View History

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-06-10 20:29:02 +02:00
WAForth is a small but complete bootstrapping Forth interpreter and dynamic compiler for
2022-05-07 21:02:28 +02:00
[WebAssembly](https://webassembly.org). You can see it in action
2022-05-08 18:46:50 +02:00
[in an interactive Forth console](https://mko.re/waforth/), and in [a Logo-like Turtle graphics language](https://mko.re/thurtle/).
2018-05-23 19:47:23 +02:00
2022-05-26 21:05:31 +02:00
WAForth is [entirely written in (raw)
2020-01-04 19:57:07 +01:00
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
2022-10-01 19:37:49 +02:00
relies on external code is to dynamically load modules (since
2020-01-04 19:57:07 +01:00
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-06-01 21:33:01 +02:00
all built-in words comes down to 14k (7k gzipped), with an extra 15k (7k gzipped) for the JavaScript wrapper, web UI,
and encoding overhead.
2022-04-17 20:44:43 +02:00
2022-05-07 21:02:28 +02:00
WAForth implements all [ANS Core
2022-07-03 21:47:30 +02:00
Words](http://lars.nocrew.org/dpans/dpans6.htm#6.1) (and passes [Forth 200x
Test Suite](https://forth-standard.org/standard/testsuite) core word tests),
and many [ANS Core Extension
Words](http://lars.nocrew.org/dpans/dpans6.htm#6.2). You can get the complete
list of supported words [from the interactive
console](https://mko.re/waforth/?p=WORDS).
2019-03-15 13:09:02 +01:00
2022-05-07 21:02:28 +02:00
You can read more about the internals and the design of WAForth in the [Design
document](doc/Design.md).
2022-05-26 20:10:39 +02:00
<div align="center">
<div>
2022-05-26 21:05:31 +02:00
<a href="https://mko.re/waforth/"><img src="https://raw.githubusercontent.com/remko/waforth/master/doc/console.gif" alt="WAForth console"></a>
2022-05-26 20:10:39 +02:00
</div>
2022-05-26 21:05:31 +02:00
<figcaption><em><a href="https://mko.re/waforth/">WAForth console</a></em></figcaption>
2022-05-26 20:10:39 +02:00
</div>
2022-10-08 09:16:30 +02:00
<div align="center">
2022-05-26 20:10:39 +02:00
<div>
2022-10-08 09:16:30 +02:00
<a href="https://mko.re/thurtle/"><img style="width: 550px; margin-top: 1.5em;" src="https://raw.githubusercontent.com/remko/waforth/master/doc/thurtle.png" alt="Thurtle program"></a>
2022-05-26 20:10:39 +02:00
</div>
2022-05-26 21:05:31 +02:00
<figcaption><em>WAForth integrated in <a href="https://mko.re/thurtle/">Thurtle</a>, a <a href="https://en.wikipedia.org/wiki/Turtle_graphics">turtle graphics</a> programming environment using Forth</em></figcaption>
2022-05-26 20:10:39 +02:00
</div>
2022-05-26 14:25:19 +02:00
## Standalone shell
2022-05-26 19:33:06 +02:00
Although WebAssembly (and therefore WAForth) is typically used in a web environment
2022-06-01 21:33:01 +02:00
(web browsers, Node.js), WAForth also has a standalone native command-line shell.
2022-05-26 19:33:06 +02:00
You can download a pre-built binary of the standalone shell from
2022-05-26 14:25:19 +02:00
[the Releases page](https://github.com/remko/waforth/releases).
2022-05-26 21:23:09 +02:00
The standalone shell uses the [Wasmtime](https://wasmtime.dev) engine,
but its build configuration can easily be adapted to build using any
2022-05-26 19:33:06 +02:00
WebAssembly engine that supports the
[WebAssembly C API](https://github.com/WebAssembly/wasm-c-api) (although some
2022-05-26 21:23:09 +02:00
engines have [known issues](https://github.com/remko/waforth/issues/6#issue-326830993)).
2022-05-26 14:25:19 +02:00
2022-10-08 09:16:30 +02:00
<div align="center">
<div>
<a href="https://mko.re/thurtle/"><img style="width: 550px;" src="https://raw.githubusercontent.com/remko/waforth/master/doc/standalone.png" alt="Thurtle program"></a>
</div>
<figcaption><em>Standalone WAForth shell executable</em></figcaption>
</div>
2022-11-11 19:16:55 +01:00
## Native compiler
2022-11-25 19:07:40 +01:00
Besides just-in-time compilation (in a browser or native), WAForth can also be used to compile Forth ahead-of-time.
[`waforthc`](https://github.com/remko/waforth/tree/master/src/waforthc) is a tool that uses WAForth to compile a Forth program into a native executable.
2022-11-11 19:16:55 +01:00
WebAssembly is used as the host runtime platform and intermediate representation during compilation, and then compiled into an executable
that no longer contains any WebAssembly infrastructure.
2022-05-07 21:02:28 +02:00
## Using WAForth in a JavaScript application
You can embed WAForth in any JavaScript application.
A [simple example](https://github.com/remko/waforth/blob/master/src/web/examples/prompt/prompt.ts) ([CodePen](https://codepen.io/mko-re/pen/gOzzmXZ)) to illustrate starting WAForth, and binding JavaScript functions:
```typescript
import WAForth, { withLineBuffer } 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
// Initialize WAForth
const forth = new WAForth();
forth.onEmit = withLineBuffer((c) =>
log.appendChild(document.createTextNode(c)));
await forth.load();
2022-05-06 21:40:19 +02:00
// 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));
});
2022-05-06 21:40:19 +02:00
// 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");
});
})();
```
2022-05-14 08:56:27 +02:00
### Asynchronous bindings
For asynchronous bindings, use `bindAsync` instead of `bind`.
`bindAsync` expects an execution token on the stack, which is
to be called with a success flag after the bound function is called. This is illustrated in [the fetch example](https://github.com/remko/waforth/blob/master/src/web/examples/fetch/fetch.ts):
```typescript
forth.bindAsync("ip?", async () => {
const result = await (
await fetch("https://api.ipify.org?format=json")
).json();
forth.pushString(result.ip);
});
forth.interpret(`
( IP? callback. Called after IP address was received )
2022-05-14 20:05:01 +02:00
: IP?-CB ( true c-addr n | false -- )
2022-05-14 08:56:27 +02:00
IF
." Your IP address is " TYPE CR
ELSE
." Unable to fetch IP address" CR
THEN
;
( Fetch the IP address, and print it to console )
: IP? ( -- )
['] IP?-CB
S" ip?" SCALL
;
`);
```
2022-11-20 19:43:21 +01:00
## Notebooks
2022-11-19 22:29:27 +01:00
The [WAForth Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=remko.waforth-vscode-extension) adds support
2022-11-25 19:07:40 +01:00
for interactive Forth notebooks powered by WAForth. Thes lets you create documents that combine rich text with executable Forth code.
You can execute both text-based Forth code, as well as [Thurtle](https://mko.re/thurtle/) graphics.
2022-11-19 22:29:27 +01:00
Because it is powered by WebAssembly, this extension works both in the desktop version of Visual Studio Code and in [the browser version of Visual Studio Code](https://code.visualstudio.com/docs/editor/vscode-web) (e.g. https://github.dev, https://vscode.dev).
2022-11-20 19:43:21 +01:00
You can also convert the notebook into a lightweight self-contained page using [`wafnb2html`](https://github.com/remko/waforth/tree/master/src/web/notebook).
An example can be seen [here](https://mko.re/wafnb/drawing-with-forth).
2022-11-19 22:29:27 +01:00
<div align="center">
<div>
2022-11-20 09:20:35 +01:00
<a href="https://github.dev/remko/waforth/blob/master/src/web/notebook/examples/drawing-with-forth.wafnb"><img src="https://raw.githubusercontent.com/remko/waforth/master/src/web/vscode-extension/doc/notebook.gif" alt="WAForth notebook"></a>
2022-11-19 22:29:27 +01:00
</div>
2022-11-20 09:20:35 +01:00
<figcaption><em><a href="https://github.dev/remko/waforth/blob/master/src/web/notebook/examples/drawing-with-forth.wafnb">WAForth notebook</a></em></figcaption>
2022-11-19 22:29:27 +01:00
</div>
2022-05-06 21:40:19 +02:00
## Goals
Here are some of the goals (and non-goals) of WAForth:
-**WebAssembly-first**: Implement as much as possible in (raw) WebAssembly. Only call out to JavaScript for functionality that is not available in WebAssembly (I/O, loading compiled WebAssembly code).
2022-06-01 21:33:01 +02:00
-**Simplicity**: Keep the code as simple and clean as possible. Raw WebAssembly requires more effort to maintain than code in a high level language, so avoid complexity if you can.
2022-05-07 21:02:28 +02:00
-**Completeness**: Implement a complete (and correct) Forth system, following the [ANS Standard](http://lars.nocrew.org/dpans/dpans.htm), including all [ANS Core words](http://lars.nocrew.org/dpans/dpans6.htm#6.1).
-**Speed**: If some speed gains can be gotten without paying much in simplicity (e.g. better design of the system, more efficient implementation of words, simple compiler improvements, ...), then I do it. However, generating the most efficient code would require a smart compiler, and a smart compiler would introduce a lot of complexity if implemented in raw WebAssembly, so speed is not an ultimate goal. Although the low level of WebAssembly gives some speed advantages, the design of the system will cause execution to consist almost exclusively of indirect calls to small functions, so there will be languages targeting WebAssembly that run faster.
2022-06-10 20:29:02 +02:00
-**Binary size**: Since the entire system is written in raw WebAssembly, and since one of the main goals is simplicity, the resulting binary size is naturally quite small (±12k). However, I don't do any special efforts to save bytes here and there in the code (or the generated code) if it makes things more complex.
-**Ease of use**: Like most Forths, I currently don't do much effort to provide functionality to make Forth programming easy and safe (helpful errors, stacktraces, strict bounds checks, ...). However, the compiler emits debug information to help step through the WebAssembly code of words, and I hope to add more debugging aids to the compiler in the future (if it doesn't add too much complexity)
2022-05-06 21:40:19 +02:00
2022-06-10 20:29:02 +02:00
![Debugger view of a compiled word](doc/debugger.png "Debugger view of a compiled word")
2022-05-06 21:40:19 +02:00
## 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
2022-05-07 21:02:28 +02:00
text format into the binary format, and [Yarn](https://yarnpkg.com) (and therefore
[Node.JS](https://nodejs.org/en/)) for
2022-05-06 21:40:19 +02:00
managing the build process and 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
### Building & Running
2018-05-23 19:47:23 +02:00
To build everything:
2022-05-07 21:02:28 +02:00
yarn build
2018-05-23 19:47:23 +02:00
To run the development server:
2022-05-07 21:02:28 +02:00
yarn dev
2018-05-23 19:47:23 +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
2022-05-07 21:02:28 +02:00
yarn test