Compare commits
6 Commits
6ee4adfea5
...
90bf0691a3
| Author | SHA1 | Date | |
|---|---|---|---|
|
90bf0691a3
|
|||
|
0a52388030
|
|||
|
6e8439eeaf
|
|||
|
eaa3242cc0
|
|||
|
f77adffbef
|
|||
|
c91f46be88
|
58
README.md
58
README.md
@@ -1,31 +1,24 @@
|
||||
# Wipforth
|
||||
|
||||
Wipforth is a simple Forth implementation that runs in the WebAssembly
|
||||
virtual machine. It does I/O via memory-mapped peripherals, which are
|
||||
emulated in JavaScript.
|
||||
Wipforth is a Forth implementation that runs in the WebAssembly
|
||||
virtual machine. The system is bootstrapped from source on page load:
|
||||
the only binary file is the favicon :)
|
||||
|
||||
- For the Forth kernel, see [wipforth.wat](./wipforth.wat)
|
||||
- For the JavaScript emulator, see [emu.js](./emu.js)
|
||||
- For the Forth prelude, which is loaded at start-up, see
|
||||
[prelude.f](./prelude.f)
|
||||
I/O is done via memory-mapped peripherals, which are emulated in
|
||||
JavaScript.
|
||||
|
||||
- For the Forth kernel, see [wipforth.ws](./wipforth.ws)
|
||||
- For the emulator, see [emu.js](./emu.js)
|
||||
- For the assembler, see [asm.js](./asm.js)
|
||||
- For the prelude (Forth code loaded right after the kernel boots),
|
||||
see [prelude.f](./prelude.f)
|
||||
- For a description of the peripherals, see the
|
||||
[Peripherals](#peripherals) section below.
|
||||
|
||||
## Building and Running Locally
|
||||
|
||||
You'll need:
|
||||
|
||||
- [WABT](https://github.com/WebAssembly/wabt) (not for long mwahaha)
|
||||
- [Guile](https://www.gnu.org/software/guile/) (or bring your own HTTP
|
||||
server -- see note below)
|
||||
|
||||
To run, first compile the WebAssembly module:
|
||||
|
||||
```
|
||||
wat2wasm --enable-threads wipforth.wat
|
||||
```
|
||||
|
||||
Then run the development server:
|
||||
There is simple [Guile](https://www.gnu.org/software/guile/) script
|
||||
you can use for this:
|
||||
|
||||
```
|
||||
guile server.scm
|
||||
@@ -34,14 +27,18 @@ guile server.scm
|
||||
You should then be able to open <http://localhost:8080> in a browser
|
||||
and use the system from there.
|
||||
|
||||
**NOTE**: The server is very simple and just serves the files with the
|
||||
cross-origin isolation headers required for `SharedMemoryBuffer` use.
|
||||
You could use any HTTP server that sets these headers.
|
||||
Since everything is bootstrapped on the client, you only need an HTTP
|
||||
server, so if you don't have Guile on your system you can bring your
|
||||
own. The only requirement is that it sets the cross-origin isolation
|
||||
headers required for `SharedMemoryBuffer` use:
|
||||
|
||||
You should **definitely not** use the development server to serve the
|
||||
application on the open internet; I just hacked it together for
|
||||
testing on localhost during development and it's probably hilariously
|
||||
insecure.
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
|
||||
You should **definitely not** use `server.scm` to serve the
|
||||
application on the open internet or anything like that; I just hacked
|
||||
it together for testing on localhost during development and it's
|
||||
probably hilariously insecure.
|
||||
|
||||
## End-to-End Tests
|
||||
|
||||
@@ -62,16 +59,13 @@ Given that's all sorted, you should be able to run:
|
||||
guile tests.scm
|
||||
```
|
||||
|
||||
It will print a JUnit XML report to standard out, you can pretty-print
|
||||
it with:
|
||||
It will print a JUnit XML report to standard out. You can
|
||||
pretty-print it with `xmllint` if you have it installed:
|
||||
|
||||
```
|
||||
guile tests.scm | xmllint --format -
|
||||
```
|
||||
|
||||
Though, of course, this will require that you have `xmllint` on your
|
||||
system.
|
||||
|
||||
## Peripherals
|
||||
|
||||
### Terminal
|
||||
|
||||
16
boot.js
16
boot.js
@@ -1,7 +1,19 @@
|
||||
import { Assembler } from './asm.js';
|
||||
|
||||
const assemble = (async () => {
|
||||
const asm = new Assembler();
|
||||
const resp = await fetch('wipforth.ws');
|
||||
for await (const chunk of resp.body) {
|
||||
asm.push(chunk);
|
||||
}
|
||||
return asm.wasm();
|
||||
})();
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
const exports = { emu: { mem: e.data } };
|
||||
const mod = await WebAssembly.instantiateStreaming(
|
||||
fetch('wipforth.wasm'), exports)
|
||||
const wasm = await assemble;
|
||||
const mod = await WebAssembly.instantiate(wasm, exports);
|
||||
await self.postMessage('booting');
|
||||
mod.instance.exports.reset();
|
||||
console.log('System halt');
|
||||
};
|
||||
|
||||
@@ -3,4 +3,4 @@ emu.js
|
||||
index.html
|
||||
prelude.f
|
||||
styles.css
|
||||
wipforth.wasm
|
||||
wipforth.ws
|
||||
|
||||
9
emu.js
9
emu.js
@@ -11,6 +11,7 @@ const RXBUF_SIZE = 32;
|
||||
const PERIPHS_SIZE = 81;
|
||||
|
||||
const POLL_INTERVAL_MS = 20;
|
||||
const DOT_INTERVAL_MS = 120;
|
||||
|
||||
const COLS = 80;
|
||||
const TAB_WIDTH = 8;
|
||||
@@ -50,8 +51,14 @@ class Emulator {
|
||||
document.addEventListener('keydown', (e) => this.handle_keydown(e));
|
||||
window.addEventListener('resize', () => this.handle_resize());
|
||||
|
||||
this.worker = new Worker('boot.js');
|
||||
this.print("Assembling kernel ");
|
||||
const dots = setInterval(() => this.print("."), DOT_INTERVAL_MS);
|
||||
this.worker = new Worker('boot.js', { type: 'module' });
|
||||
this.worker.postMessage(this.mem);
|
||||
this.worker.onmessage = (e) => {
|
||||
clearInterval(dots);
|
||||
this.print(" done\n");
|
||||
};
|
||||
|
||||
fetch('prelude.f')
|
||||
.then(res => res.text())
|
||||
|
||||
@@ -249,7 +249,7 @@ CHAR . EMIT
|
||||
\ Version number
|
||||
|
||||
0 CONSTANT VERSION-MAJOR
|
||||
1 CONSTANT VERSION-MINOR
|
||||
2 CONSTANT VERSION-MINOR
|
||||
0 CONSTANT VERSION-PATCH
|
||||
|
||||
: PRINT-VERSION
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
'(("html" . (text/html))
|
||||
("css" . (text/css))
|
||||
("js" . (application/javascript))
|
||||
("wasm" . (application/wasm))
|
||||
("f" . (text/plain))
|
||||
("wat" . (text/plain))
|
||||
("ws" . (text/plain))
|
||||
("png" . (image/png))))
|
||||
|
||||
(define (mime-type path)
|
||||
|
||||
12
tests.scm
12
tests.scm
@@ -21,11 +21,17 @@
|
||||
(navigate client "http://localhost:8080")
|
||||
(sleep 5)
|
||||
|
||||
(define-test kernel-assembles-successfully
|
||||
(let* ((display (get-display client))
|
||||
(line (first (lines display))))
|
||||
(assert (string-match "Assembling kernel \\.+ done" line)
|
||||
(format #f "Kernel assemble line: ~s" line))))
|
||||
|
||||
(define-test prelude-loads-successfully
|
||||
(let* ((display (get-display client))
|
||||
(first-line (first (lines display))))
|
||||
(assert (string-match "Loading prelude \\.+ done" first-line)
|
||||
(format #f "Prelude load line: ~s" first-line))))
|
||||
(line (second (lines display))))
|
||||
(assert (string-match "Loading prelude \\.+ done" line)
|
||||
(format #f "Prelude load line: ~s" line))))
|
||||
|
||||
(define-test six-seven-times-dot-cr-yields-42
|
||||
(input-line client "6 7 * . CR")
|
||||
|
||||
Reference in New Issue
Block a user