Internals - vroby65/MiniForthConsole GitHub Wiki

๐Ÿงฌ Internals of Mini Forth Console

This document explains how the Mini Forth Console works under the hood โ€” its architecture, interpreter design, and core components.


๐Ÿง  Architecture Overview

The console is entirely client-side and written in vanilla HTML, CSS, and JavaScript.

  • HTML: Provides the structure (a <textarea> for input/output)
  • CSS: Dark-theme terminal aesthetic, responsive
  • JavaScript: Implements the full Forth-like interpreter

No frameworks or external dependencies are used (except for the Fira Code font).


๐Ÿ“ฆ Core Components

1. Stack

A single JavaScript array called stack simulates the Forth data stack.

let stack = [];

All operations manipulate this array.


2. Words Dictionary

There are two dictionaries:

  • words: built-in words (native JavaScript functions)
  • userWords: user-defined words (stored as token arrays)
const words = { '+': () => { ... }, 'dup': () => { ... } }
const userWords = { 'square': ['dup', '*'] }

Words are case-insensitive, except for quoted strings.


3. Variables & Arrays

Variables are stored in a dictionary:

const variables = {
  x: 42,
  myarr: [1, 2, 3]
}

Array access uses @ / ! with index:

0 myarr @ .
123 1 myarr !

4. Interpreter Loop

The function interpretLine(line) parses the input and executes it token by token.

  • Tokens are split using a regex that preserves quoted strings
  • Each token is:
    • a number โ†’ pushed on the stack
    • a string literal โ†’ pushed as-is
    • a built-in word โ†’ executed
    • a user word โ†’ expanded and re-interpreted
    • unknown โ†’ pushed as-is (assumed variable name or JS string)

5. Word Definition

Words are defined using : and ;. Tokens between them are collected:

: square dup * ;

This is stored as:

userWords['square'] = ['dup', '*']

6. Control Flow

  • if ... else ... then is interpreted inline, collecting branches
  • do ... loop and for ... next are implemented with actual JavaScript loops
  • leave throws an exception to exit loops early

Nested control flow is handled by re-invoking interpretLine with the selected branch tokens.


7. Persistence

Words and variables are saved as JSON in:

  • localStorage (auto-loaded on page refresh)
  • downloadable file (save)
  • restorable via file upload (load)

8. Special Features

  • .* โ€” loop: executes the following tokens until the stack is empty
  • $ โ€” capture output instead of printing it (useful for pushing back to stack)
  • js โ€” executes raw JavaScript in browser context via eval()

9. Input System

  • A <textarea> is both the display and input
  • Input is processed on keydown (Enter/ArrowUp/ArrowDown)
  • Command history is managed manually in a commandHistory[] array

๐Ÿ“ค Execution Flow Example

3 4 + .
  1. 3 โ†’ number โ†’ pushed โ†’ [3]
  2. 4 โ†’ number โ†’ pushed โ†’ [3, 4]
  3. + โ†’ word โ†’ popped 4,3 โ†’ pushed 7 โ†’ [7]
  4. . โ†’ word โ†’ popped 7 โ†’ printed โ†’ 7 OK

๐Ÿ” Security Notes

The js command uses eval() and runs in the page context. It has full access to the DOM and JS environment. Use it carefully.


๐Ÿ“š Extending the Interpreter

You can add new words by extending the words dictionary:

words['square'] = () => {
  const x = stack.pop();
  stack.push(x * x);
};

Or add user words with:

: square dup * ;

๐Ÿงช Testing Tips

  • Use .$ and .v to inspect state
  • Use clr to clear the screen
  • Use reset before testing new definitions

โœ… Summary

  • Fully client-side, no build step
  • Self-contained interpreter
  • Persistent and extensible
  • Ideal for experimentation and education

Feel free to modify the source and customize commands or UI to suit your needs!

โš ๏ธ **GitHub.com Fallback** โš ๏ธ