The language engine is browser-independent and imports no UI code. Node tests run the identical modules imported by the browser. The app has no third-party dependencies.
flowchart LR
A[Editable source] --> B[Lexer: source spans and tokens]
B --> C[Parser: precedence and AST]
C --> D[Compiler: lexical checks and bytecode]
D --> E[Bounded stack VM]
E --> F[Immutable drawing log]
F --> G[Canvas renderer]
E <--> H[Exact bounded snapshots]
H --> I[Time travel and trace]
B --> J[Tokens inspector]
C --> K[AST inspector]
D --> L[Bytecode inspector]
E --> M[Stack, locals, and call frames]
The architecture dialog renders this flow as navigable source cards. Links: lexer, parser, compiler, VM, builtins, renderer, controller.
lex(source) emits token objects with type, raw lexeme, decoded value, and start/end offsets, lines, and columns. Comments remain available for editor highlighting but are filtered from parsing. Character and token limits bound work. Invalid characters, strings, and non-finite literals raise GlassError with a recovery hint.
parse(tokens) performs recursive descent. Every AST node owns an ID and source span. Expression tiers establish precedence, and power recurses on the right. Blocks, statements, and expression nesting have an explicit guard. flattenTree provides UI traversal without a separate representation of the language.
compile(ast) resolves lexical variable names and validates callable arity, parameter names, top-level function placement, and returns. It emits real bytecode and patches absolute jump targets after compiling branches and loops. Functions are registered first, then compiled after the main HALT with an entry-point table. A function’s compile-time name environment includes top-level globals and its own parameters/local scopes.
Each instruction has an opcode, argument, source location, and originating AST node ID. LINE marks statement boundaries for breakpoints. Logical operators compile to conditional jumps and preserve short-circuit semantics. No host-language expression evaluator or generated JavaScript is involved.
The VM is the sole owner of execution state:
Calls pop arguments in source order, create parameter bindings, and preserve the caller’s operand stack base. Returns restore that base and push one result. Global bindings are shared across calls; a callee never searches caller-local scopes. The engine uses own-key checks and null-prototype binding maps, so language identifiers cannot mutate host prototypes.
step() executes one instruction and captures its resulting state. run() executes a bounded batch, optionally stops at a frame() boundary, or stops before a breakpoint. stepLine() advances to the next LINE marker. The VM independently stops after its instruction budget even if source has no drawing calls or frame yields.
A snapshot clones stacks, bindings, and call frames. Drawing commands and output arrays are persistent: appends create a new array, and drawing command objects are immutable. Older snapshots can safely share those arrays without copying every primitive on every instruction.
At capacity, the regular checkpoint interval doubles. The origin, evenly spaced exact states across the whole run, and the newest state survive. An additional off-grid latest snapshot is replaced as execution advances. This keeps the entire execution scrubbable without biasing almost all snapshots toward the end, while memory stays bounded. The UI exposes the exact saved instruction count and never implies that every old step survives.
seek(index) restores all VM fields from that snapshot. Running or stepping after a seek truncates the future before computation resumes. Replay tests compare the entire final VM state, including frames, drawing and output, against uninterrupted execution. Reset restores the original state without recompiling or modifying source.
The controller compiles edits after 280 ms of inactivity. An edit invalidates the previous compiled artifact and canvas immediately, preventing stale output from being presented as current. Auto-run executes the new artifact in batches; disabling it leaves the new VM ready to step. Breakpoints are source-line markers and remain active across edits, but a new example clears them.
Animation uses requestAnimationFrame. Every frame executes at most a small number of 700-instruction batches. Animated mode yields at a language frame() call; instant mode advances more batches per browser frame. Canvas repaint happens each browser frame, while heavier inspector refreshes are throttled. Pause cancels the next scheduled frame. The engine’s 60,000-instruction cap remains independent of this UI scheduler.
renderCanvas projects the current drawing log into a fixed 640 × 640 logical surface at double pixel resolution. A faint reference grid is overlaid independently. The renderer owns no program state, clock, randomness, or animation counter. A snapshot change therefore changes rendered geometry directly.
Lexer, parser, compiler, and runtime share a structured GlassError: phase, message, source position, and hint. The UI clears invalid artifacts, highlights the line, and provides a clickable diagnostic location. Runtime errors preserve the partial drawing and history for inspection. An instruction-budget error remains rewindable and resettable.
scripts/build.mjs copies native ES modules and documentation into an inspectable static distribution, then records language-source hashes. scripts/serve.mjs serves that distribution only on 127.0.0.1:43103, with no fallback port. The content security policy excludes host-language evaluation and remote dependencies. No publication or external API is required.
This is a teaching language, not a production sandbox or a JavaScript implementation. It intentionally omits collections, closures, imports, arbitrary I/O, and persisted editor sessions. The VM cooperates on the browser’s main thread instead of a worker; small batches and independent limits keep the editor usable. Current-frame variables are shown by default; the frame selector allows inspection of a suspended caller. Every semantic module has direct behavioral tests.
# GLASSBOX architecture The language engine is browser-independent and imports no UI code. Node tests run the identical modules imported by the browser. The app has no third-party dependencies. ```mermaid flowchart LR A[Editable source] --> B[Lexer: source spans and tokens] B --> C[Parser: precedence and AST] C --> D[Compiler: lexical checks and bytecode] D --> E[Bounded stack VM] E --> F[Immutable drawing log] F --> G[Canvas renderer] E <--> H[Exact bounded snapshots] H --> I[Time travel and trace] B --> J[Tokens inspector] C --> K[AST inspector] D --> L[Bytecode inspector] E --> M[Stack, locals, and call frames] ``` The architecture dialog renders this flow as navigable source cards. Links: [lexer](../src/language/lexer.js), [parser](../src/language/parser.js), [compiler](../src/language/compiler.js), [VM](../src/language/vm.js), [builtins](../src/language/builtins.js), [renderer](../src/renderer.js), [controller](../src/app.js). ## Lexer `lex(source)` emits token objects with type, raw lexeme, decoded value, and start/end offsets, lines, and columns. Comments remain available for editor highlighting but are filtered from parsing. Character and token limits bound work. Invalid characters, strings, and non-finite literals raise `GlassError` with a recovery hint. ## Parser `parse(tokens)` performs recursive descent. Every AST node owns an ID and source span. Expression tiers establish precedence, and power recurses on the right. Blocks, statements, and expression nesting have an explicit guard. `flattenTree` provides UI traversal without a separate representation of the language. ## Compiler `compile(ast)` resolves lexical variable names and validates callable arity, parameter names, top-level function placement, and returns. It emits real bytecode and patches absolute jump targets after compiling branches and loops. Functions are registered first, then compiled after the main `HALT` with an entry-point table. A function’s compile-time name environment includes top-level globals and its own parameters/local scopes. Each instruction has an opcode, argument, source location, and originating AST node ID. `LINE` marks statement boundaries for breakpoints. Logical operators compile to conditional jumps and preserve short-circuit semantics. No host-language expression evaluator or generated JavaScript is involved. ## Virtual machine The VM is the sole owner of execution state: - instruction pointer, last executed pointer, and cumulative instruction count; - operand stack; - global binding map; - call frames containing return pointer, operand stack base, function name, and lexical local scopes; - drawing log, paper color, ink color, opacity, output log, and visual frame counter; - completion/error state; - retained snapshot history and cursor. Calls pop arguments in source order, create parameter bindings, and preserve the caller’s operand stack base. Returns restore that base and push one result. Global bindings are shared across calls; a callee never searches caller-local scopes. The engine uses own-key checks and null-prototype binding maps, so language identifiers cannot mutate host prototypes. `step()` executes one instruction and captures its resulting state. `run()` executes a bounded batch, optionally stops at a `frame()` boundary, or stops before a breakpoint. `stepLine()` advances to the next `LINE` marker. The VM independently stops after its instruction budget even if source has no drawing calls or frame yields. ## Snapshots and replay A snapshot clones stacks, bindings, and call frames. Drawing commands and output arrays are persistent: appends create a new array, and drawing command objects are immutable. Older snapshots can safely share those arrays without copying every primitive on every instruction. At capacity, the regular checkpoint interval doubles. The origin, evenly spaced exact states across the whole run, and the newest state survive. An additional off-grid latest snapshot is replaced as execution advances. This keeps the entire execution scrubbable without biasing almost all snapshots toward the end, while memory stays bounded. The UI exposes the exact saved instruction count and never implies that every old step survives. `seek(index)` restores all VM fields from that snapshot. Running or stepping after a seek truncates the future before computation resumes. Replay tests compare the entire final VM state, including frames, drawing and output, against uninterrupted execution. Reset restores the original state without recompiling or modifying source. ## Controller and rendering The controller compiles edits after 280 ms of inactivity. An edit invalidates the previous compiled artifact and canvas immediately, preventing stale output from being presented as current. Auto-run executes the new artifact in batches; disabling it leaves the new VM ready to step. Breakpoints are source-line markers and remain active across edits, but a new example clears them. Animation uses `requestAnimationFrame`. Every frame executes at most a small number of 700-instruction batches. Animated mode yields at a language `frame()` call; instant mode advances more batches per browser frame. Canvas repaint happens each browser frame, while heavier inspector refreshes are throttled. Pause cancels the next scheduled frame. The engine’s 60,000-instruction cap remains independent of this UI scheduler. `renderCanvas` projects the current drawing log into a fixed 640 × 640 logical surface at double pixel resolution. A faint reference grid is overlaid independently. The renderer owns no program state, clock, randomness, or animation counter. A snapshot change therefore changes rendered geometry directly. ## Diagnostics Lexer, parser, compiler, and runtime share a structured `GlassError`: phase, message, source position, and hint. The UI clears invalid artifacts, highlights the line, and provides a clickable diagnostic location. Runtime errors preserve the partial drawing and history for inspection. An instruction-budget error remains rewindable and resettable. ## Local delivery `scripts/build.mjs` copies native ES modules and documentation into an inspectable static distribution, then records language-source hashes. `scripts/serve.mjs` serves that distribution only on `127.0.0.1:43103`, with no fallback port. The content security policy excludes host-language evaluation and remote dependencies. No publication or external API is required. ## Tradeoffs This is a teaching language, not a production sandbox or a JavaScript implementation. It intentionally omits collections, closures, imports, arbitrary I/O, and persisted editor sessions. The VM cooperates on the browser’s main thread instead of a worker; small batches and independent limits keep the editor usable. Current-frame variables are shown by default; the frame selector allows inspection of a suspended caller. Every semantic module has direct behavioral tests.