git » fjorth.git » commit 2775062

add readme

author Alan Dipert
2025-10-06 19:23:38 UTC
committer Alan Dipert
2025-10-06 19:23:38 UTC
parent 90a3d9625312363aa8b2b99f0cc824c79e2096b3

add readme

README.md +354 -0

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d2e0625
--- /dev/null
+++ b/README.md
@@ -0,0 +1,354 @@
+# Fjorth
+
+A hyper-minimal, embeddable Forth for the browser, written in JavaScript.
+Design goals: tiny core, clear semantics, strong introspection and debugging hooks for pedagogical IDEs.
+
+This README is plain-text ASCII. Copy freely.
+
+---
+
+## Features
+
+- Minimal stack-based VM with numeric bytecodes.
+- Colon definitions, immediate words, basic control flow:
+  - IF ELSE THEN, BEGIN UNTIL, BEGIN AGAIN
+- Data stack (D) and return stack (R) primitives:
+  - DUP DROP SWAP OVER, >R R> R@
+- Memory words and basic arithmetic:
+  - @ ! + - * 1+ 1-
+- Output words: . EMIT CR .S
+- Structured error reporting via callback (no throws on normal faults).
+- Preemptible run loop (time-sliced) with pause/resume.
+- Introspection and debugging layer:
+  - Source maps: ip -> token (line, col)
+  - Readable word call stack
+  - Tracing events (step, wordEnter, wordExit, break)
+  - Breakpoints by ip, by word+token, or by predicate
+  - Disassembler and memory map
+- QUnit test suite (browser-based).
+
+---
+
+## Quick Start
+
+Include Fjorth in a page:
+
+```html
+<script src="fjorth.js"></script>
+<script>
+  const vm = new Fjorth({
+    print:   s => { /* write to your console/output */ },
+    println: s => { /* optional line printer */ },
+    onError: err => console.warn("Fjorth error:", err)
+  });
+
+  vm.reset();
+  vm.load(": SQUARE DUP * ; 7 SQUARE . CR");
+  vm.run(err => {
+    if (err) {
+      console.log("Finished with error:", err);
+    } else {
+      console.log("Program completed.");
+    }
+  });
+</script>
+```
+
+Console should show: `49` followed by newline.
+
+---
+
+## Public API (runtime)
+
+```js
+const vm = new Fjorth(options)
+```
+
+Options:
+- print(string): called by . and EMIT (required for visible output).
+- println(string): optional line printer.
+- onError(error): structured error callback (see Errors below).
+- sliceMs: number, time between scheduler slices (default 0).
+- stepBudget: number, max VM steps per slice (default 2000).
+
+Methods:
+- vm.reset(): reset VM, stacks, dictionary, memory.
+- vm.load(source: string): tokenize and stage Forth source.
+- vm.run(onIdle?: (err?: Error) => void): start/resume execution; calls onIdle when idle or error.
+- vm.pause(): pause time-sliced execution at next boundary.
+- vm.resume(): resume if paused.
+- vm.defineJS(name, fn, immediate=false): define a word implemented in JS.
+  - Inside fn, `this` is the VM. You can this.pop(), this.push(), etc.
+- vm.find(name) -> { name, xt, immediate } | null: lookup dictionary word.
+
+Stacks and memory helpers (JS-level, if you extend VM):
+- vm.push(n), vm.pop(), vm.peek()
+- vm.H (memory cells), vm.here (next free cell)
+
+---
+
+## Words provided by the core
+
+Stack (data):
+- DUP DROP SWAP OVER
+
+Return stack:
+- >R R> R@
+
+Memory:
+- @ ! , HERE
+
+Arithmetic / logic:
+- + - * 1+ 1- 0=
+
+Output:
+- . EMIT CR .S
+
+Control flow:
+- IF ELSE THEN
+- BEGIN UNTIL
+- BEGIN AGAIN
+
+Compiler metawords:
+- : ; IMMEDIATE '
+  - `' NAME` (tick) pushes NAME's execution token (xt) onto the stack.
+
+Comments:
+- Backslash to EOL: `\ comment to end of line`
+- Paren comments: `( comment until )` (single-line or across token stream, not nested)
+
+Numbers:
+- Only base-10 integers are recognized by default (e.g., -42, 0, 123).
+
+---
+
+## Structured Error Handling
+
+Fjorth does not throw for normal program faults. Instead, it:
+1) Calls `options.onError(err)` if provided.
+2) Halts the scheduler and invokes the `run(onIdle)` callback with the same `err` object.
+
+Error shape (Error with extra fields):
+```js
+{
+  message: "undefined word: FOO",
+  code: "undefined_word",      // stack_underflow, rstack_underflow, bad_jump, bad_call, bad_opcode, colon_no_name, ...
+  phase: "interpret" | "compile" | "exec" | "schedule",
+  token: "FOO",                // when available
+  line: 12, col: 5,            // when available
+  ip: 123,                     // instruction pointer for exec faults
+  stackDepth: 0,
+  rstackDepth: 0
+}
+```
+
+Example:
+```js
+const vm = new Fjorth({ onError: e => console.log(e.code, e.message) });
+vm.reset();
+vm.load("NOPE");
+vm.run(err => { /* err === same Error object */ });
+```
+
+---
+
+## Preemption and Scheduling
+
+- Time-sliced loop with configurable `stepBudget` and `sliceMs`.
+- Call `vm.pause()` to stop at a safe boundary. Call `vm.resume()` or `vm.run()` to continue.
+- Errors halt the scheduler and call your callbacks.
+
+---
+
+## Debugging and Introspection
+
+Enable tracing and breakpoints when needed. This layer is opt-in and incurs minimal overhead when disabled.
+
+Enable:
+```js
+vm.debug.enable({ step:true, word:true, stack:true });
+vm.debug.onTrace = e => console.log(e);
+```
+
+Common operations:
+- Break by word+token index:
+  ```js
+  vm.debug.breakAtWordToken("MYWORD", 2); // pause before 3rd token
+  ```
+- Break by ip:
+  ```js
+  vm.debug.break({ ip: 42 });
+  ```
+- Conditional break:
+  ```js
+  vm.debug.break({ cond: e => e.op === '!' && e.word === 'STOREIT' });
+  ```
+- Step and continue:
+  ```js
+  vm.debug.stepInto(); // single-instruction step
+  vm.debug.continue(); // resume
+  ```
+- View current state for IDE:
+  ```js
+  const v = vm.debug.view(); // { D, R, ip, word, tokenIndex, line, col }
+  ```
+- Call stack:
+  ```js
+  const frames = vm.debug.getCallStack(); // [{name, xt, ret, baseSp}, ...]
+  ```
+- Disassembly:
+  ```js
+  console.log(vm.debug.disassemble("MYWORD"));
+  ```
+- Memory map:
+  ```js
+  console.table(vm.debug.memoryMap());
+  ```
+
+Trace event example (when enabled):
+```js
+{
+  type: 'step' | 'wordEnter' | 'wordExit' | 'break',
+  when: 'before' | 'after',  // for step
+  ip: 123,
+  op: 'LIT' | 'CALL' | ...,
+  word: 'CURRENTWORD',
+  tokenIndex: 5,
+  line: 10, col: 3,
+  D: [...], R: [...]
+}
+```
+
+Notes:
+- Source maps: each emitted cell records the token index that produced it; symbolication finds line/col.
+- Frames: calls to colon words push a readable frame; returning pops it.
+
+---
+
+## Extending with JS
+
+Define a JS-backed word:
+
+```js
+vm.defineJS("TWICE", function () {
+  const a = this.pop();
+  if (this.lastError) return;  // guard after pop
+  this.push(a * 2);
+});
+
+vm.reset();
+vm.load("21 TWICE .");
+vm.run();
+```
+
+Immediate word example:
+
+```js
+vm.defineJS("IWORD", function() {
+  // runs during compile if used while STATE=1
+  this.push(9);
+}, true);
+```
+
+---
+
+## Testing with QUnit
+
+The project includes a browser QUnit suite. You can run tests by loading:
+- fjorth.js
+- qunit.js and qunit.css
+- the test harness script
+
+If you cannot or do not want to rely on a network CDN, keep local copies of QUnit assets and reference them directly.
+
+---
+
+## Design Notes
+
+Why numeric bytecodes?
+- Compact memory representation, easy disassembly, consistent stepping.
+- Great for building source maps and teaching the fetch-decode-execute cycle.
+
+Error model:
+- Fail fast, report richly, avoid throwing across the run loop.
+- Both onError callback and the run(onIdle) callback receive the same Error object.
+
+Debug-first:
+- All features (breaks, stepping, disasm, call stack) are additive and do not alter core semantics.
+- Tracing and symbolication are opt-in.
+
+Future work:
+- Optional function-threaded tier or superinstruction compiler that fuses straight-line code into a single JS function while keeping source maps for stepping and breakpoints.
+- Watchpoints and time-travel snapshots (ring buffer) for reverse stepping.
+- A REPL UI with SEE/WORDS and live stack graphs.
+
+---
+
+## Minimal Cheat Sheet
+
+:
+  Define with colon and semicolon:
+    : NAME ... ;
+
+IMMEDIATE:
+  Mark last defined word immediate.
+
+Tick:
+  ' NAME   (push NAME execution token)
+
+Memory:
+  @ ! , HERE
+
+Stack ops:
+  DUP DROP SWAP OVER
+
+Return stack:
+  >R R> R@
+
+Arithmetic / flags:
+  + - * 1+ 1- 0=
+
+Output:
+  . EMIT CR .S
+
+Control flow:
+  IF ... ELSE ... THEN
+  BEGIN ... UNTIL
+  BEGIN ... AGAIN
+
+Comments:
+  \ comment to end of line
+  ( comment until )    (not nested)
+
+Numbers:
+  Base-10 integers only by default.
+
+---
+
+## Embedding Tips
+
+- Provide `print` and/or `println` so users see output.
+- Use `onError` to surface structured errors in your IDE (decorate gutter, highlight token, etc.).
+- Enable `vm.debug` only when you need it (breakpoints, tracing).
+- For long-running student code, keep `stepBudget` moderate (e.g., 1000 - 5000) to preserve UI responsiveness.
+
+---
+
+## Contributing
+
+- Keep the interpreter tiny and readable.
+- Add tests for every public API and new word.
+- Do not change existing tests unless they are clearly wrong.
+- Prefer structured errors over thrown exceptions.
+
+---
+
+## License
+
+MIT (or your preferred permissive license). Replace this line if you adopt something else.
+
+---
+
+## Acknowledgments
+
+Inspired by classic Forth systems and educational interpreters. Fjorth focuses on clarity, debuggability, and embeddability for teaching environments.