Compare commits

...

6 Commits

7 changed files with 60 additions and 42 deletions

View File

@@ -1,31 +1,24 @@
# Wipforth # Wipforth
Wipforth is a simple Forth implementation that runs in the WebAssembly Wipforth is a Forth implementation that runs in the WebAssembly
virtual machine. It does I/O via memory-mapped peripherals, which are virtual machine. The system is bootstrapped from source on page load:
emulated in JavaScript. the only binary file is the favicon :)
- For the Forth kernel, see [wipforth.wat](./wipforth.wat) I/O is done via memory-mapped peripherals, which are emulated in
- For the JavaScript emulator, see [emu.js](./emu.js) JavaScript.
- For the Forth prelude, which is loaded at start-up, see
[prelude.f](./prelude.f) - 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 - For a description of the peripherals, see the
[Peripherals](#peripherals) section below. [Peripherals](#peripherals) section below.
## Building and Running Locally ## Building and Running Locally
You'll need: There is simple [Guile](https://www.gnu.org/software/guile/) script
you can use for this:
- [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:
``` ```
guile server.scm guile server.scm
@@ -34,14 +27,18 @@ guile server.scm
You should then be able to open <http://localhost:8080> in a browser You should then be able to open <http://localhost:8080> in a browser
and use the system from there. and use the system from there.
**NOTE**: The server is very simple and just serves the files with the Since everything is bootstrapped on the client, you only need an HTTP
cross-origin isolation headers required for `SharedMemoryBuffer` use. server, so if you don't have Guile on your system you can bring your
You could use any HTTP server that sets these headers. 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 - `Cross-Origin-Opener-Policy: same-origin`
application on the open internet; I just hacked it together for - `Cross-Origin-Embedder-Policy: require-corp`
testing on localhost during development and it's probably hilariously
insecure. 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 ## End-to-End Tests
@@ -62,16 +59,13 @@ Given that's all sorted, you should be able to run:
guile tests.scm guile tests.scm
``` ```
It will print a JUnit XML report to standard out, you can pretty-print It will print a JUnit XML report to standard out. You can
it with: pretty-print it with `xmllint` if you have it installed:
``` ```
guile tests.scm | xmllint --format - guile tests.scm | xmllint --format -
``` ```
Though, of course, this will require that you have `xmllint` on your
system.
## Peripherals ## Peripherals
### Terminal ### Terminal

16
boot.js
View File

@@ -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) => { self.onmessage = async (e) => {
const exports = { emu: { mem: e.data } }; const exports = { emu: { mem: e.data } };
const mod = await WebAssembly.instantiateStreaming( const wasm = await assemble;
fetch('wipforth.wasm'), exports) const mod = await WebAssembly.instantiate(wasm, exports);
await self.postMessage('booting');
mod.instance.exports.reset(); mod.instance.exports.reset();
console.log('System halt'); console.log('System halt');
}; };

View File

@@ -3,4 +3,4 @@ emu.js
index.html index.html
prelude.f prelude.f
styles.css styles.css
wipforth.wasm wipforth.ws

9
emu.js
View File

@@ -11,6 +11,7 @@ const RXBUF_SIZE = 32;
const PERIPHS_SIZE = 81; const PERIPHS_SIZE = 81;
const POLL_INTERVAL_MS = 20; const POLL_INTERVAL_MS = 20;
const DOT_INTERVAL_MS = 120;
const COLS = 80; const COLS = 80;
const TAB_WIDTH = 8; const TAB_WIDTH = 8;
@@ -50,8 +51,14 @@ class Emulator {
document.addEventListener('keydown', (e) => this.handle_keydown(e)); document.addEventListener('keydown', (e) => this.handle_keydown(e));
window.addEventListener('resize', () => this.handle_resize()); 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.postMessage(this.mem);
this.worker.onmessage = (e) => {
clearInterval(dots);
this.print(" done\n");
};
fetch('prelude.f') fetch('prelude.f')
.then(res => res.text()) .then(res => res.text())

View File

@@ -249,7 +249,7 @@ CHAR . EMIT
\ Version number \ Version number
0 CONSTANT VERSION-MAJOR 0 CONSTANT VERSION-MAJOR
1 CONSTANT VERSION-MINOR 2 CONSTANT VERSION-MINOR
0 CONSTANT VERSION-PATCH 0 CONSTANT VERSION-PATCH
: PRINT-VERSION : PRINT-VERSION

View File

@@ -16,9 +16,8 @@
'(("html" . (text/html)) '(("html" . (text/html))
("css" . (text/css)) ("css" . (text/css))
("js" . (application/javascript)) ("js" . (application/javascript))
("wasm" . (application/wasm))
("f" . (text/plain)) ("f" . (text/plain))
("wat" . (text/plain)) ("ws" . (text/plain))
("png" . (image/png)))) ("png" . (image/png))))
(define (mime-type path) (define (mime-type path)

View File

@@ -21,11 +21,17 @@
(navigate client "http://localhost:8080") (navigate client "http://localhost:8080")
(sleep 5) (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 (define-test prelude-loads-successfully
(let* ((display (get-display client)) (let* ((display (get-display client))
(first-line (first (lines display)))) (line (second (lines display))))
(assert (string-match "Loading prelude \\.+ done" first-line) (assert (string-match "Loading prelude \\.+ done" line)
(format #f "Prelude load line: ~s" first-line)))) (format #f "Prelude load line: ~s" line))))
(define-test six-seven-times-dot-cr-yields-42 (define-test six-seven-times-dot-cr-yields-42
(input-line client "6 7 * . CR") (input-line client "6 7 * . CR")