Compare commits
8 Commits
8929dc868f
...
277c06b999
| Author | SHA1 | Date | |
|---|---|---|---|
| 277c06b999 | |||
| 33047a49e0 | |||
| f9447d22d1 | |||
| 8ed8bbad3e | |||
| 7f4d900688 | |||
| 54be0a8c0e | |||
| 832ce55108 | |||
| 259aa730f7 |
11
README.md
11
README.md
@@ -1,5 +1,16 @@
|
|||||||
# Wipforth
|
# 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.
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
- For a description of the peripherals, see the
|
||||||
|
[Peripherals](#peripherals) section below.
|
||||||
|
|
||||||
## Building and Running
|
## Building and Running
|
||||||
|
|
||||||
To run, first compile the WebAssembly module:
|
To run, first compile the WebAssembly module:
|
||||||
|
|||||||
45
emu.js
45
emu.js
@@ -13,7 +13,6 @@ const PERIPHS_SIZE = 69; // Nice
|
|||||||
const POLL_INTERVAL_MS = 20;
|
const POLL_INTERVAL_MS = 20;
|
||||||
|
|
||||||
const COLS = 80;
|
const COLS = 80;
|
||||||
const ROWS = 36;
|
|
||||||
const TAB_WIDTH = 8;
|
const TAB_WIDTH = 8;
|
||||||
|
|
||||||
const CURSOR_IDLE_TIME_MS = 1000;
|
const CURSOR_IDLE_TIME_MS = 1000;
|
||||||
@@ -36,8 +35,9 @@ class Emulator {
|
|||||||
this.rx_queue = [];
|
this.rx_queue = [];
|
||||||
this.timer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
this.timer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
||||||
|
|
||||||
|
this.rows = this.max_rows();
|
||||||
this.grid = Array.from(
|
this.grid = Array.from(
|
||||||
{ length: ROWS },
|
{ length: this.rows },
|
||||||
() => new Array(COLS).fill(' '));
|
() => new Array(COLS).fill(' '));
|
||||||
this.cursor = { x: 0, y: 0 };
|
this.cursor = { x: 0, y: 0 };
|
||||||
this.range = {
|
this.range = {
|
||||||
@@ -47,6 +47,7 @@ class Emulator {
|
|||||||
this.idle_timer = null;
|
this.idle_timer = null;
|
||||||
this.input_enable = false;
|
this.input_enable = false;
|
||||||
document.addEventListener('keydown', (e) => this.handle_keydown(e));
|
document.addEventListener('keydown', (e) => this.handle_keydown(e));
|
||||||
|
window.addEventListener('resize', () => this.handle_resize());
|
||||||
|
|
||||||
this.worker = new Worker('boot.js');
|
this.worker = new Worker('boot.js');
|
||||||
this.worker.postMessage(this.mem);
|
this.worker.postMessage(this.mem);
|
||||||
@@ -142,6 +143,8 @@ class Emulator {
|
|||||||
this.cursor_move(1, 0);
|
this.cursor_move(1, 0);
|
||||||
} else if (e.key == 'Enter') {
|
} else if (e.key == 'Enter') {
|
||||||
this.cursor.y = this.range.end.y + 1;
|
this.cursor.y = this.range.end.y + 1;
|
||||||
|
while (this.cursor.y >= this.rows)
|
||||||
|
this.scroll();
|
||||||
this.cursor.x = 0;
|
this.cursor.x = 0;
|
||||||
this.submit_line();
|
this.submit_line();
|
||||||
Object.assign(this.range.start, this.cursor);
|
Object.assign(this.range.start, this.cursor);
|
||||||
@@ -226,7 +229,7 @@ class Emulator {
|
|||||||
next_cell(cell) {
|
next_cell(cell) {
|
||||||
if (cell.x < COLS - 1)
|
if (cell.x < COLS - 1)
|
||||||
return { x: cell.x + 1, y: cell.y };
|
return { x: cell.x + 1, y: cell.y };
|
||||||
else if (cell.y < ROWS - 1)
|
else if (cell.y < this.rows - 1)
|
||||||
return { x: 0, y: cell.y + 1 };
|
return { x: 0, y: cell.y + 1 };
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
@@ -239,6 +242,8 @@ class Emulator {
|
|||||||
this.grid[cell.y][cell.x] = this.grid[prev.y][prev.x];
|
this.grid[cell.y][cell.x] = this.grid[prev.y][prev.x];
|
||||||
cell = prev;
|
cell = prev;
|
||||||
}
|
}
|
||||||
|
if (this.next_cell(this.range.end) == null)
|
||||||
|
this.scroll();
|
||||||
this.range.end = this.next_cell(this.range.end);
|
this.range.end = this.next_cell(this.range.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +267,7 @@ class Emulator {
|
|||||||
return '<span id="cursor">' + ec + '</span>';
|
return '<span id="cursor">' + ec + '</span>';
|
||||||
else
|
else
|
||||||
return ec;
|
return ec;
|
||||||
}).join('');
|
}).join('').trimEnd();
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
this.output.innerHTML = html;
|
this.output.innerHTML = html;
|
||||||
}
|
}
|
||||||
@@ -283,6 +288,38 @@ class Emulator {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scroll() {
|
||||||
|
this.grid.shift()
|
||||||
|
this.grid.push(new Array(COLS).fill(' '));
|
||||||
|
this.cursor.y -= 1;
|
||||||
|
this.range.start.y -= 1;
|
||||||
|
this.range.end.y -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
max_rows() {
|
||||||
|
const style = getComputedStyle(this.output);
|
||||||
|
const line_height = parseFloat(style.lineHeight);
|
||||||
|
const margin_top = parseFloat(style.marginTop);
|
||||||
|
const margin_bottom = parseFloat(style.marginBottom);
|
||||||
|
|
||||||
|
const viewport_height = window.innerHeight;
|
||||||
|
const output_height = viewport_height - margin_top - margin_bottom;
|
||||||
|
return Math.floor(output_height / line_height) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_resize() {
|
||||||
|
this.rows = this.max_rows();
|
||||||
|
while (this.grid.length < this.rows)
|
||||||
|
this.grid.push(new Array(COLS).fill(' '));
|
||||||
|
while (this.grid.length > this.rows) {
|
||||||
|
this.grid.shift()
|
||||||
|
this.cursor.y -= 1;
|
||||||
|
this.range.start.y -= 1;
|
||||||
|
this.range.end.y -= 1;
|
||||||
|
}
|
||||||
|
this.flush_output();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|||||||
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 499 B |
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Wipforth</title>
|
<title>Wipforth</title>
|
||||||
<link rel="stylesheet" type="text/css" href="styles.css"/>
|
<link rel="stylesheet" type="text/css" href="styles.css"/>
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="text/javascript" src="emu.js"></script>
|
<script type="text/javascript" src="emu.js"></script>
|
||||||
|
|||||||
10
prelude.f
10
prelude.f
@@ -88,7 +88,7 @@
|
|||||||
; IMMEDIATE
|
; IMMEDIATE
|
||||||
|
|
||||||
: EMIT-DIGIT
|
: EMIT-DIGIT
|
||||||
DUP 10 < IF CHAR 0 ELSE CHAR A THEN
|
DUP 10 < IF CHAR 0 ELSE 10 - CHAR A THEN
|
||||||
+ EMIT
|
+ EMIT
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -165,6 +165,10 @@ CHAR . EMIT
|
|||||||
|
|
||||||
: NIP SWAP DROP ;
|
: NIP SWAP DROP ;
|
||||||
|
|
||||||
|
: HEX 16 BASE ! ;
|
||||||
|
|
||||||
|
: DECIMAL 10 BASE ! ;
|
||||||
|
|
||||||
: '
|
: '
|
||||||
WORD FIND DUP 0<> IF
|
WORD FIND DUP 0<> IF
|
||||||
>CFA
|
>CFA
|
||||||
@@ -258,6 +262,10 @@ CHAR . EMIT
|
|||||||
." /_/ " CR
|
." /_/ " CR
|
||||||
CR
|
CR
|
||||||
." Wipforth " VERSION-PRINT CR
|
." Wipforth " VERSION-PRINT CR
|
||||||
|
." Copyright (c) Camden Dixie O'Brien" CR
|
||||||
|
CR
|
||||||
|
." Wipforth is freely available to use, modify and distribute for personal use" CR
|
||||||
|
." under the Komorebi license, version 2.0.0." CR
|
||||||
CR
|
CR
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#output {
|
#output {
|
||||||
|
width: 80ch;
|
||||||
margin: 1.5em auto;
|
margin: 1.5em auto;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user