mirror of
https://github.com/remko/waforth
synced 2024-11-17 07:48:06 +01:00
Update README
This commit is contained in:
parent
a11c7a10ea
commit
b5a1663bc1
2 changed files with 96 additions and 75 deletions
99
README.md
99
README.md
|
@ -49,14 +49,16 @@ import WAForth from "waforth";
|
|||
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
|
||||
// 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
|
||||
// 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 )
|
||||
|
@ -76,14 +78,33 @@ import WAForth from "waforth";
|
|||
})();
|
||||
```
|
||||
|
||||
## 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).
|
||||
- ✅ **Simplicity**: Keep the code as simple as possible. Raw WebAssembly code requires more effort to maintain than code in a high level language, so avoid complexity if you can.
|
||||
- ✅ **Completeness**: Implement a complete (and correct) ANS Forth system, including all the ANS Core words.
|
||||
- ❓ **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 high speed isn't to be expected.
|
||||
- ❌ **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**: I currently don't make any effort to provide functionality to make Forth programming easy (helpful errors, ...). However, the compiler emits debug information to help step through the WebAssembly code of words.
|
||||
|
||||
![Debugger view of a compiled
|
||||
word](doc/debugger.png "Debugger view of a
|
||||
compiled word")
|
||||
|
||||
## Development
|
||||
|
||||
You can read more about the internals and the design of WAForth in the [Design document](doc/Design.md).
|
||||
|
||||
Below you can find instructions on setting up a development environment.
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
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.
|
||||
managing the build process and the dependencies of the shell.
|
||||
|
||||
brew install wabt yarn
|
||||
yarn
|
||||
|
@ -106,75 +127,3 @@ The tests are served from `/waforth/tests` by the development server.
|
|||
You can also run the tests in Node.JS by running
|
||||
|
||||
make check
|
||||
|
||||
## Design
|
||||
|
||||
The WAForth core is written as [a single
|
||||
module](https://github.com/remko/waforth/blob/master/src/waforth.wat) in
|
||||
WebAssembly's [text
|
||||
format](https://webassembly.github.io/spec/core/text/index.html).
|
||||
|
||||
### The Interpreter
|
||||
|
||||
The interpreter runs a loop that processes commands, and switches to and from
|
||||
compiler mode.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### The Compiler
|
||||
|
||||
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.
|
||||
This of course introduces some overhead, although it appears limited.
|
||||
|
||||
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.
|
||||
|
||||
Finally, the compiler adds minimal debug information about the compiled word in
|
||||
the [name
|
||||
section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#name-section),
|
||||
making it easier for doing some debugging in the browser.
|
||||
|
||||
![Debugger view of a compiled
|
||||
word](doc/debugger.png "Debugger view of a
|
||||
compiled word")
|
||||
|
||||
|
||||
### The Loader
|
||||
|
||||
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.
|
||||
|
||||
### The Shell
|
||||
|
||||
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
|
||||
terminal, and externally provides a `interpret()` function to execute a fragment of
|
||||
Forth code.
|
||||
|
||||
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
|
||||
in action [here](https://mko.re/waforth/).
|
||||
|
||||
|
|
72
doc/Design.md
Normal file
72
doc/Design.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
# WAForth: Design
|
||||
|
||||
The WAForth core is written as [a single
|
||||
module](https://github.com/remko/waforth/blob/master/src/waforth.wat) in
|
||||
WebAssembly's [text
|
||||
format](https://webassembly.github.io/spec/core/text/index.html).
|
||||
|
||||
## The Interpreter
|
||||
|
||||
The interpreter runs a loop that processes commands, and switches to and from
|
||||
compiler mode.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## The Compiler
|
||||
|
||||
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.
|
||||
This of course introduces some overhead, although it appears limited.
|
||||
|
||||
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.
|
||||
|
||||
Finally, the compiler adds minimal debug information about the compiled word in
|
||||
the [name
|
||||
section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#name-section),
|
||||
making it easier for doing some debugging in the browser.
|
||||
|
||||
![Debugger view of a compiled
|
||||
word](debugger.png "Debugger view of a
|
||||
compiled word")
|
||||
|
||||
|
||||
## The Loader
|
||||
|
||||
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.
|
||||
|
||||
## The Shell
|
||||
|
||||
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
|
||||
terminal, and externally provides a `interpret()` function to execute a fragment of
|
||||
Forth code.
|
||||
|
||||
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
|
||||
in action [here](https://mko.re/waforth/).
|
||||
|
||||
![WAForth Console](console.gif "WAForth Console")
|
Loading…
Reference in a new issue