Compare commits
21 Commits
6ee4adfea5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
aa9c18346d
|
|||
|
a2f06c977e
|
|||
|
58e2cf3e1f
|
|||
|
efdae93d90
|
|||
|
804626ddad
|
|||
|
cbe5733fcd
|
|||
|
7961c68639
|
|||
|
d202157a58
|
|||
|
97fc43bf93
|
|||
|
2c13ad4e1f
|
|||
|
36429bf8bc
|
|||
|
c20e7e181b
|
|||
|
fe5c55cabf
|
|||
|
67fc1d8d7b
|
|||
|
4000522b3a
|
|||
|
19ef69958d
|
|||
|
0a52388030
|
|||
|
6e8439eeaf
|
|||
|
eaa3242cc0
|
|||
|
f77adffbef
|
|||
|
c91f46be88
|
84
README.md
84
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 non-text 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's a [Guile](https://www.gnu.org/software/guile/) script in the
|
||||
repo you can use for this:
|
||||
|
||||
```
|
||||
guile server.scm
|
||||
@@ -34,14 +27,20 @@ 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.
|
||||
However, since everything is bootstrapped on the client, basically any
|
||||
HTTP server will do as long as it sets the appropriate response
|
||||
headers for `SharedArrayBuffer` 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`
|
||||
|
||||
So, if you don't have Guile on your system you can use something else
|
||||
like Python's `http.server`.
|
||||
|
||||
**NOTE**: 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,36 +61,34 @@ 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
|
||||
|
||||
| Name | Address | Size / B | Access |
|
||||
|--------|---------|----------|--------------|
|
||||
| TXBUF | 00h | 32 | write |
|
||||
| RXBUF | 20h | 32 | read |
|
||||
| TXHEAD | 40h | 4 | atomic read |
|
||||
| TXTAIL | 44h | 4 | atomic write |
|
||||
| RXHEAD | 48h | 4 | atomic write |
|
||||
| RXTAIL | 4Ch | 4 | atomic read |
|
||||
| TXBUF | 000h | 32 | write |
|
||||
| RXBUF | 080h | 32 | read |
|
||||
| TXHEAD | 100h | 4 | atomic read |
|
||||
| TXTAIL | 104h | 4 | atomic write |
|
||||
| RXHEAD | 108h | 4 | atomic write |
|
||||
| RXTAIL | 10Ch | 4 | atomic read |
|
||||
|
||||
For both sending (`TX`) and receiving (`RX`), there are three
|
||||
registers: `xBUF`, `xHEAD` and `xTAIL`:
|
||||
|
||||
- `xBUF` registers are 32-byte FIFO ring buffers used for data
|
||||
- `xBUF` registers are 128-byte FIFO ring buffers used for data
|
||||
- The `xHEAD` and `xTAIL` registers specify the start and end of data
|
||||
in the ring buffer, `xHEAD` being the offset of the first byte of
|
||||
data, and `xTAIL` being the offset of the first byte *after* the data.
|
||||
data, and `xTAIL` being the offset of the first byte *after* the
|
||||
data.
|
||||
|
||||
In order to be distinguishable from the empty state, the ring buffers
|
||||
must never be completely full -- there must always be *at least one*
|
||||
@@ -101,7 +98,10 @@ unoccupied byte between the tail and the head.
|
||||
|
||||
| Name | Address | Size / B | Access |
|
||||
|----------|---------|----------|--------------|
|
||||
| SYSREADY | 50h | 1 | atomic write |
|
||||
| SYSREADY | 110h | 4 | atomic write |
|
||||
| SYSINTER | 114h | 4 | atomic read |
|
||||
|
||||
The `SYSREADY` register is used to indicate when the system has booted
|
||||
up and is ready for user input.
|
||||
up and is ready for user input. `SYSINTER` is set (and notified on)
|
||||
once the emulator has enabled user input and the system is
|
||||
interactive.
|
||||
|
||||
39
asm.js
39
asm.js
@@ -14,20 +14,29 @@ class Tokenizer {
|
||||
this.buffer = [];
|
||||
this.comment = false;
|
||||
this.string = false;
|
||||
this.cursor = 0;
|
||||
}
|
||||
|
||||
find(pred) {
|
||||
for (let i = this.cursor; i < this.buffer.length; ++i) {
|
||||
if (pred(this.buffer[i]))
|
||||
return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
skip() {
|
||||
const idx = this.buffer.findIndex((cp) => !this.skips.has(cp));
|
||||
this.buffer = idx == -1 ? [] : this.buffer.slice(idx);
|
||||
const idx = this.find((cp) => !this.skips.has(cp));
|
||||
this.cursor = idx == null ? this.buffer.length : idx;
|
||||
}
|
||||
|
||||
next_string() {
|
||||
const idx = this.buffer.findIndex((cp) => cp == this.string_quote);
|
||||
if (idx == -1) {
|
||||
const idx = this.find((cp) => cp == this.string_quote);
|
||||
if (idx == null) {
|
||||
this.string = true;
|
||||
} else {
|
||||
const string = this.buffer.slice(0, idx).join("");
|
||||
this.buffer = this.buffer.slice(idx + 1);
|
||||
const string = this.buffer.slice(this.cursor, idx).join("");
|
||||
this.cursor = idx + 1;
|
||||
this.string = false;
|
||||
return { string: string };
|
||||
}
|
||||
@@ -38,18 +47,20 @@ class Tokenizer {
|
||||
return this.next_string();
|
||||
|
||||
this.skip();
|
||||
if (this.buffer[0] == LINE_END)
|
||||
return this.buffer.shift();
|
||||
if (this.buffer[this.cursor] == LINE_END) {
|
||||
++this.cursor;
|
||||
return LINE_END;
|
||||
}
|
||||
|
||||
if (this.buffer[0] == this.string_quote) {
|
||||
this.buffer.shift();
|
||||
if (this.buffer[this.cursor] == this.string_quote) {
|
||||
++this.cursor;
|
||||
return this.next_string();
|
||||
}
|
||||
|
||||
const idx = this.buffer.findIndex((cp) => this.delims.has(cp));
|
||||
if (idx != -1) {
|
||||
const token = this.buffer.slice(0, idx).join("");
|
||||
this.buffer = this.buffer.slice(idx);
|
||||
const idx = this.find((cp) => this.delims.has(cp));
|
||||
if (idx != null) {
|
||||
const token = this.buffer.slice(this.cursor, idx).join("");
|
||||
this.cursor = idx;
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
29
boot.js
29
boot.js
@@ -1,7 +1,26 @@
|
||||
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)
|
||||
mod.instance.exports.reset();
|
||||
console.log('System halt');
|
||||
switch (e.data.type) {
|
||||
case "load":
|
||||
const exports = { emu: { mem: e.data.mem } };
|
||||
const wasm = await assemble;
|
||||
self.mod = await WebAssembly.instantiate(wasm, exports);
|
||||
await self.postMessage('ready');
|
||||
break;
|
||||
|
||||
case "boot":
|
||||
self.mod.instance.exports.reset();
|
||||
console.log('System halt');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
asm.js
|
||||
boot.js
|
||||
emu.js
|
||||
favicon.png
|
||||
index.html
|
||||
prelude.f
|
||||
styles.css
|
||||
wipforth.wasm
|
||||
wipforth.ws
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Assembler } from "./asm.js";
|
||||
import { writeAll } from "jsr:@std/io/write-all";
|
||||
|
||||
const asm = new Assembler();
|
||||
for await (const chunk of Deno.stdin.readable) {
|
||||
asm.push(chunk);
|
||||
}
|
||||
const wasm = asm.wasm();
|
||||
await writeAll(Deno.stdout, wasm);
|
||||
72
emu.js
72
emu.js
@@ -1,16 +1,17 @@
|
||||
const TXBUF = 0x00;
|
||||
const RXBUF = 0x20;
|
||||
const TXHEAD = 0x40;
|
||||
const TXTAIL = 0x44;
|
||||
const RXHEAD = 0x48;
|
||||
const RXTAIL = 0x4c;
|
||||
const SYSREADY = 0x50;
|
||||
const TXBUF = 0x000;
|
||||
const RXBUF = 0x080;
|
||||
const TXHEAD = 0x100;
|
||||
const TXTAIL = 0x104;
|
||||
const RXHEAD = 0x108;
|
||||
const RXTAIL = 0x10c;
|
||||
|
||||
const TXBUF_SIZE = 32;
|
||||
const RXBUF_SIZE = 32;
|
||||
const PERIPHS_SIZE = 81;
|
||||
const SYSREADY = 0x110;
|
||||
const SYSINTER = 0x114;
|
||||
|
||||
const POLL_INTERVAL_MS = 20;
|
||||
const PERIPHS_SIZE = 0x200;
|
||||
|
||||
const POLL_INTERVAL_MS = 5;
|
||||
const DOT_INTERVAL_MS = 25;
|
||||
|
||||
const COLS = 80;
|
||||
const TAB_WIDTH = 8;
|
||||
@@ -50,8 +51,19 @@ class Emulator {
|
||||
document.addEventListener('keydown', (e) => this.handle_keydown(e));
|
||||
window.addEventListener('resize', () => this.handle_resize());
|
||||
|
||||
this.worker = new Worker('boot.js');
|
||||
this.worker.postMessage(this.mem);
|
||||
this.forth = new Worker('boot.js', { type: 'module' });
|
||||
|
||||
this.print("Assembling kernel ");
|
||||
this.dots = setInterval(() => this.print("."), DOT_INTERVAL_MS);
|
||||
this.forth.postMessage({ type: "load", mem: this.mem });
|
||||
this.forth.onmessage = (e) => {
|
||||
clearInterval(this.dots);
|
||||
this.print(" done\n");
|
||||
|
||||
this.print("Loading prelude ");
|
||||
this.forth.postMessage({ type: "boot" });
|
||||
this.dots = setInterval(() => this.print("."), DOT_INTERVAL_MS);
|
||||
};
|
||||
|
||||
fetch('prelude.f')
|
||||
.then(res => res.text())
|
||||
@@ -77,15 +89,21 @@ class Emulator {
|
||||
if (!this.input_enable) {
|
||||
const sysready = Atomics.load(this.mem_u8, SYSREADY);
|
||||
if (sysready != 0) {
|
||||
clearInterval(this.dots);
|
||||
this.print(" done\n");
|
||||
|
||||
Atomics.store(this.mem_u8, SYSINTER, 1);
|
||||
Atomics.notify(this.mem_i32, SYSINTER / 4);
|
||||
|
||||
this.input_enable = true;
|
||||
this.blink = true;
|
||||
this.flush_output();
|
||||
document.getElementById('cursor').classList.add('blinking');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fifo_next(idx) {
|
||||
return (idx + 1) & 0x1f;
|
||||
return (idx + 1) & 0x7f;
|
||||
}
|
||||
|
||||
handle_tx_data(head, tail) {
|
||||
@@ -173,12 +191,14 @@ class Emulator {
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.blink = false;
|
||||
this.flush_output();
|
||||
|
||||
document.getElementById('cursor').classList.remove('blinking');
|
||||
if (this.idle_timer)
|
||||
clearTimeout(this.idle_timer);
|
||||
this.idle_timer = setTimeout(() => {
|
||||
this.blink = true;
|
||||
document.getElementById('cursor').classList.add('blinking');
|
||||
}, CURSOR_IDLE_TIME_MS);
|
||||
}
|
||||
@@ -265,10 +285,12 @@ class Emulator {
|
||||
return row.map((c, x) => {
|
||||
const ec = this.html_escape(c);
|
||||
if (this.input_enable
|
||||
&& x == this.cursor.x && y == this.cursor.y)
|
||||
return '<span id="cursor">' + ec + '</span>';
|
||||
else
|
||||
&& x == this.cursor.x && y == this.cursor.y) {
|
||||
const cl = this.blink ? 'class="blinking"' : '';
|
||||
return `<span id="cursor" ${cl}>` + ec + '</span>';
|
||||
} else {
|
||||
return ec;
|
||||
}
|
||||
}).join('').trimEnd();
|
||||
}).join('\n');
|
||||
this.output.innerHTML = html;
|
||||
@@ -325,6 +347,16 @@ class Emulator {
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('output').innerText = '';
|
||||
window.emu = new Emulator();
|
||||
const output = document.getElementById('output');
|
||||
output.innerText = '';
|
||||
|
||||
if (!self.crossOriginIsolated) {
|
||||
output.innerText = "Yeah so there's this thing where Chromium ends "
|
||||
+ "up ignoring COOP/COEP\nheaders after a hard reload "
|
||||
+ "sometimes, and I haven't been able to\nfigure out how to "
|
||||
+ "work around it yet. If you just wait a little while\nand "
|
||||
+ "then reload normally then hopefully it should work haha";
|
||||
} else {
|
||||
window.emu = new Emulator();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="emu.js"></script>
|
||||
<div id="output">I'm afraid you need javascript enabled for this to work :(
|
||||
<div id="output"><noscript>I'm afraid you need javascript enabled for this to work :(
|
||||
|
||||
cringe, I know... but I promise there are no frameworks™</div>
|
||||
</body>
|
||||
cringe, I know... but I promise there are no frameworks™</noscript></div></body>
|
||||
</html>
|
||||
|
||||
60
prelude.f
60
prelude.f
@@ -1,12 +1,5 @@
|
||||
76 EMIT 111 EMIT 97 EMIT 100 EMIT 105 EMIT 110 EMIT 103 EMIT 32 EMIT
|
||||
112 EMIT 114 EMIT 101 EMIT 108 EMIT 117 EMIT 100 EMIT 101 EMIT 32 EMIT
|
||||
|
||||
: \ KEY 10 = 0BRANCH [ -20 , ] ; IMMEDIATE \ Now we have line comments :)
|
||||
|
||||
\ We'll periodically sprinkle these in so that it's clear to the user
|
||||
\ that things are happening.
|
||||
46 EMIT
|
||||
|
||||
\ Conditionals
|
||||
|
||||
: IF
|
||||
@@ -27,8 +20,6 @@
|
||||
SWAP !
|
||||
; IMMEDIATE
|
||||
|
||||
46 EMIT
|
||||
|
||||
\ Loops
|
||||
|
||||
: BEGIN HERE @ ; IMMEDIATE
|
||||
@@ -43,8 +34,6 @@
|
||||
HERE @ - ,
|
||||
; IMMEDIATE
|
||||
|
||||
46 EMIT
|
||||
|
||||
\ Recursive calls
|
||||
|
||||
: RECURSE LATEST @ >CFA , ; IMMEDIATE
|
||||
@@ -61,8 +50,6 @@
|
||||
|
||||
( ( Take that, C ) )
|
||||
|
||||
46 EMIT
|
||||
|
||||
\ Printing utilities
|
||||
|
||||
: CR 10 EMIT ;
|
||||
@@ -80,8 +67,6 @@
|
||||
+ EMIT
|
||||
;
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
: .
|
||||
\ Handle negatives
|
||||
DUP 0< IF CHAR - EMIT NEGATE THEN
|
||||
@@ -110,8 +95,6 @@ CHAR . EMIT
|
||||
2DROP
|
||||
;
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
: TYPE ( addr len -- )
|
||||
BEGIN
|
||||
DUP 0= IF 2DROP EXIT THEN
|
||||
@@ -120,8 +103,6 @@ CHAR . EMIT
|
||||
AGAIN
|
||||
;
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
: C, HERE @ C! 1 HERE +! ;
|
||||
|
||||
: ."
|
||||
@@ -151,8 +132,6 @@ CHAR . EMIT
|
||||
THEN
|
||||
; IMMEDIATE
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
\ Misc utilities
|
||||
|
||||
: NIP SWAP DROP ;
|
||||
@@ -171,8 +150,6 @@ CHAR . EMIT
|
||||
|
||||
: [COMPILE] ' , ; IMMEDIATE
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
\ Constants, variables and values
|
||||
|
||||
: CONSTANT
|
||||
@@ -204,24 +181,21 @@ CHAR . EMIT
|
||||
THEN
|
||||
; IMMEDIATE
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
\ Peripheral register addresses
|
||||
|
||||
HEX
|
||||
|
||||
00 CONSTANT TXBUF
|
||||
20 CONSTANT RXBUF
|
||||
40 CONSTANT TXHEAD
|
||||
44 CONSTANT TXTAIL
|
||||
48 CONSTANT RXHEAD
|
||||
4C CONSTANT RXTAIL
|
||||
50 CONSTANT SYSREADY
|
||||
000 CONSTANT TXBUF
|
||||
080 CONSTANT RXBUF
|
||||
100 CONSTANT TXHEAD
|
||||
104 CONSTANT TXTAIL
|
||||
108 CONSTANT RXHEAD
|
||||
10C CONSTANT RXTAIL
|
||||
110 CONSTANT SYSREADY
|
||||
114 CONSTANT SYSINTER
|
||||
|
||||
DECIMAL
|
||||
|
||||
46 EMIT
|
||||
|
||||
\ A better word-not-found handler
|
||||
|
||||
: ANY-RX? RXHEAD AC@ RXTAIL AC@ <> ;
|
||||
@@ -244,13 +218,11 @@ DECIMAL
|
||||
|
||||
' WNF-HANDLER TO WNFHOOK
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
\ Version number
|
||||
|
||||
0 CONSTANT VERSION-MAJOR
|
||||
1 CONSTANT VERSION-MINOR
|
||||
0 CONSTANT VERSION-PATCH
|
||||
2 CONSTANT VERSION-MINOR
|
||||
2 CONSTANT VERSION-PATCH
|
||||
|
||||
: PRINT-VERSION
|
||||
CHAR v EMIT VERSION-MAJOR .
|
||||
@@ -258,8 +230,6 @@ CHAR . EMIT
|
||||
CHAR . EMIT VERSION-PATCH .
|
||||
;
|
||||
|
||||
CHAR . EMIT
|
||||
|
||||
\ Welcome banner
|
||||
|
||||
: BANNER
|
||||
@@ -269,7 +239,7 @@ CHAR . EMIT
|
||||
." |__,__/_/ .__(_)___/_//_/" CR
|
||||
." /_/ " CR
|
||||
CR
|
||||
." Wipforth " PRINT-VERSION CR
|
||||
." Welcome to Wipforth " PRINT-VERSION ." !" CR
|
||||
." Copyright (c) Camden Dixie O'Brien" CR
|
||||
CR
|
||||
." Wipforth is freely available to use, modify and distribute for personal use" CR
|
||||
@@ -277,8 +247,8 @@ CHAR . EMIT
|
||||
CR
|
||||
;
|
||||
|
||||
." done" CR
|
||||
BANNER
|
||||
|
||||
\ Set SYSREADY high to enable user input
|
||||
\ Set SYSREADY high and wait until interactive
|
||||
1 SYSREADY AC!
|
||||
SYSINTER WAIT DROP
|
||||
|
||||
BANNER
|
||||
|
||||
@@ -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)
|
||||
|
||||
14
tests.scm
14
tests.scm
@@ -19,13 +19,19 @@
|
||||
(define client (client-setup))
|
||||
|
||||
(navigate client "http://localhost:8080")
|
||||
(sleep 5)
|
||||
(sleep 1)
|
||||
|
||||
(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")
|
||||
|
||||
34
wipforth.ws
34
wipforth.ws
@@ -2,12 +2,12 @@
|
||||
.import main "emu" "mem"
|
||||
|
||||
;; Peripheral registers
|
||||
.def TXBUF 00h
|
||||
.def RXBUF 20h
|
||||
.def TXHEAD 40h
|
||||
.def TXTAIL 44h
|
||||
.def RXHEAD 48h
|
||||
.def RXTAIL 4Ch
|
||||
.def TXBUF 000h
|
||||
.def RXBUF 080h
|
||||
.def TXHEAD 100h
|
||||
.def TXTAIL 104h
|
||||
.def RXHEAD 108h
|
||||
.def RXTAIL 10Ch
|
||||
|
||||
.def DICT_START 0200h
|
||||
|
||||
@@ -635,6 +635,15 @@
|
||||
end
|
||||
call next
|
||||
|
||||
.func wait
|
||||
.elem codewords wait WAIT_CODEWORD
|
||||
call pop
|
||||
i32.const 0
|
||||
i64.const -1
|
||||
memory.atomic.wait32 2 0
|
||||
call push
|
||||
call next
|
||||
|
||||
;; Core utility words
|
||||
|
||||
.func exit
|
||||
@@ -752,7 +761,7 @@
|
||||
local.get head
|
||||
i32.const 1
|
||||
i32.add
|
||||
i32.const 1Fh
|
||||
i32.const 7Fh
|
||||
i32.and
|
||||
i32.atomic.store8 0 0
|
||||
|
||||
@@ -768,7 +777,7 @@
|
||||
local.tee tail
|
||||
i32.const 1
|
||||
i32.add
|
||||
i32.const 1Fh
|
||||
i32.const 7Fh
|
||||
i32.and
|
||||
local.tee n
|
||||
i32.const TXHEAD
|
||||
@@ -1226,6 +1235,15 @@ COPY:
|
||||
.word COPY_CODEWORD
|
||||
.def PREV _COPY
|
||||
|
||||
_WAIT:
|
||||
.word PREV
|
||||
.byte 4
|
||||
.utf8 "WAIT"
|
||||
.align
|
||||
WAIT:
|
||||
.word WAIT_CODEWORD
|
||||
.def PREV _WAIT
|
||||
|
||||
_EXIT:
|
||||
.word PREV
|
||||
.byte 4
|
||||
|
||||
Reference in New Issue
Block a user