Commit graph

19 commits

Author SHA1 Message Date
e547b54750
playground: swap vault and bend URI — vault left, bend right 2026-06-14 13:32:26 -04:00
367071ec51
repl: single page scroller — header pinned, everything else flows
Header (logo + tagline) is the only fixed region. Footer removed.
Everything else — tab bar, transcript, prompt — now lives inside one
.repl-stream scroller. A fresh session shows the prompt right under
the tabs near the top; as entries arrive the prompt drifts down with
them. Tabs use a dashed bottom rule instead of a heavy bar so they
read as the start of the stream rather than a separate chrome strip.
2026-06-14 13:21:02 -04:00
eb58cdc33c
repl: terminal-style transcript — drop card chrome
Reads as one continuous stream now. Each entry is just:
  λ> <input>
  <output>   ; tier · NNms

No left border, no boxed cards, no side-column tier label. Output
indents under the prompt (3ch) using monospace ch units. Tier+time
render as a Lisp-comment-style suffix in muted color.

Prompt bar: borderless textarea on the code-bg surface so the input
visually joins the transcript above. Placeholder cut to "(+ 1 2)" —
the surrounding text already explains the semantics.

Multi-line inputs keep prompt continuation marks ("..").
2026-06-14 13:17:17 -04:00
b3bcff5f08
repl: explain PBKDF2 + AES-GCM on the lock-screen modal 2026-06-14 13:12:05 -04:00
656f206c40
playground+repl: bend+vault on one line, grid-only layout, hidden fix
- bend URL and vault password now share a single config-bar row, split
  via a 2-column grid (1fr 1fr). Vault still hidden when free-form
  isn't selected; just collapses its column.
- Every flexbox removed. Every multi-child container uses CSS grid:
  .controls, .config-bar, .bend-bar, .vault-bar, .panes, .pane,
  .brand, .tabbar, .tabs, .tab, .transcript, .entry, .tier-output,
  .prompt-bar, .lock-screen, .lock-card, .lock-row.
- Added `[hidden] { display: none !important; }` so the ephemeral
  button on the REPL lock screen actually hides the modal. Without
  this, .lock-screen's `display: grid` overrode the hidden attribute's
  UA-default display: none.
2026-06-14 13:11:09 -04:00
e24176a447
playground/repl: stack radio buttons vertically in fieldsets 2026-06-14 13:07:14 -04:00
4df6dcfa67
playground: simpler free-form default — (print "67") (/ 42 6) 2026-06-14 13:06:10 -04:00
7e32eefd6b
playground+repl: bend dispatch from WASM + 1.33 zoom + free-form runner
bend!-call from the WAT tier
  - New WAT import: (import "env" "bend_call"). The host loader supplies
    a sync XMLHttpRequest that POSTs the payload to a configured URL
    (workers only — sync XHR isn't allowed on main thread).
  - New primitive (bend!-call "<payload>") returns the response as a
    lumbda string. Works in playground and REPL once the bend URL is
    saved in the new top bar.
  - bendUrl persists in plain localStorage (not encrypted — it's a
    server address, not a secret).
  - Tests stub the import with a no-op so unit / integration / functional
    suites keep instantiating cleanly.

Playground + REPL zoom 133% by default
  - html { zoom: 1.33 } so the styleguide sizes read comfortably without
    requiring browser-level zoom.

Free-form default = cross-tier assertion runner in Lisp
  - The default editor content for "free form" is now a small assertion
    framework matching tests/functional.lsp's PASS/FAIL convention. A
    starter the user can extend, runs identically on the three tiers.

C tier + Python tier (bend) are wired through the runner stub; full
bend integration in those tiers comes next once their loaders learn
about setBendUrl.
2026-06-14 13:05:06 -04:00
51b449a83d
wat: write primitive + cross-tier functional.lsp runner
Adds (write x) and (write-string s) primitives. write quotes strings
and #\-prefixes chars — what tests/functional.lsp's assert-equal
uses to print failures.

New target: make wasm-test-functional-cross (also rolled into wasm-test)
runs the 205-assertion tests/functional.lsp against each WASM tier and
reports pass counts:

  c-wasm:   205 / 205   (full parity with native c)
  asm-wasm:  93 / 111   reaches mid-suite before stack overflow on
                        a deeply recursive test; the 84% it reaches
                        passes. Documented progress toward full parity
                        with asm/lumbda.s.

The runner exit-soft on the asm tier — it's a measurement, not a gate.
2026-06-14 13:00:06 -04:00
6773c50ff7
tests: update functional radio count to 5 (mandelbrot/fib-ack/sieve/self-interp/free-form) 2026-06-14 12:57:37 -04:00
3d6d5d1010
playground: free-form code option + encrypted vault, single-scroll layout
Free-form radio adds a 5th demo slot. When selected, a vault bar appears
under the controls: enter a password, "unlock" derives a per-device
vault and decrypts (or creates fresh). Edits in the editor auto-save
350ms after typing stops. Reload + same password restores the code.

Same Web Crypto stack as /repl/ (PBKDF2 + AES-GCM, vault id =
SHA-256(password || device-salt)).

Layout: one shared vertical scroller — code pane and output pane both
grow with content, the body scrolls. No more independent in-pane
scrollers fighting the page.

Home page split into "Demo" and "REPL" sections with their own CTAs.
2026-06-14 12:56:25 -04:00
d8ffab5ea6
repl: /repl/ page with encrypted multi-tab sessions
Interactive REPL at lumbda.com/repl with:
  - multi-tab sessions (click + to add, × to close, double-click to rename)
  - per-tab tier selector (python/c/asm/all-three race)
  - persistent transcripts encrypted in localStorage via Web Crypto
    (PBKDF2 + AES-GCM, vault id = SHA-256(password || device-salt) —
    same pattern as unsandbox's vault-encryption-design.md, native
    crypto.subtle API instead of CryptoJS)
  - ephemeral mode (skip vault, transcripts vanish on reload)
  - one worker per (tab × tier) — state persists across evals in a tab
  - reboot tier button (terminate this tab's worker, fresh state next eval)
  - cancel button (kills the running worker in active tab)

Home page now links to both /playground/ and /repl/.

Tier state itself does NOT persist across reloads — the transcript does,
but defines/set!/hash-tables vanish with the worker. Portal save/resume
in WAT (deferred) will let a tier session survive close+reopen.
2026-06-14 12:50:35 -04:00
1c9d74b407
wat iter-4: embedded Lisp prelude + append-only race output
WAT prelude (evaluated after primitive binding at init) adds:
  map, filter, fold-left, fold-right, for-each, any, every,
  count, find, sort (quicksort), vector-map, vector-for-each,
  vector-fill!, string-split, string-trim, string->list,
  random-state, assert-equal/true/false.

Higher-order ops are now Lisp-defined, not primitive bloat. Eval-time
parse + bind happens once per WASM instance startup.

Playground output: per fox, single append-only column instead of
3-up grid. Tiers still race in parallel workers; whichever finishes
first appears first in the output. Live ms counters move to the status
bar (python 312ms · c 47ms · asm 89ms).
2026-06-14 12:46:06 -04:00
3800271b1b
wat iter-3: vectors + hash tables (19 new primitives)
Two new heap tags:
  7 = vector  [tag, len, elem_0, elem_1, ...]   8 + 4*len bytes
  8 = hashtable  [tag, count, alist_ptr]        12 bytes

Vector primitives (88-95):
  vector, vector?, make-vector, vector-length,
  vector-ref, vector-set!, vector->list, list->vector

Hash table primitives (96-106):
  make-hash-table, hash-table?, hash-table-set!,
  hash-table-ref, hash-table-ref/default,
  hash-table-delete!, hash-table-exists?, hash-table-size,
  hash-table-keys, hash-table-values, hash-table->alist

Hash table lookup is linear (equal? on each key) — fine for browser-scale
demos. Same linear-scan caveat as the symbol intern; would warrant a real
hash function at scale.

print_value now renders vectors as #(a b c) and hashtables as #<hashtable>.
2026-06-14 12:41:36 -04:00
0c0bec7784
playground: race 3 tiers in parallel workers
One Worker per tier (python/c/asm). "All three" mode dispatches
Promise.all so the tiers race on independent threads — a slow Pyodide
no longer blocks C and asm. Each tier-block ticks its own ms counter
until its worker resolves.

Output grid: 3 columns when 3 tier-blocks render, else stacked.
Status bar announces the winner: "ok — c won in 47 ms".

Cancel terminates every active worker.
2026-06-14 12:38:22 -04:00
3054fccc33
wat iter-2: chars + 27 string/char primitives
Char type added (tag=6, 8 bytes). Reader handles #\char and named chars
(space, newline, tab, return, null). Chars self-evaluate in eval, render
via print_value, compare via equal_p.

Primitives added (IDs 61-87):
  string-length, string-ref (returns char), substring, string-append,
  string=?, string<?, string-upcase, string-downcase, string->list,
  list->string, string->symbol, symbol->string, make-string,
  char?, char->integer, integer->char,
  char-alphabetic?, char-numeric?, char-whitespace?,
  char-upcase, char-downcase, char=?, char<?,
  number->string, string->number,
  string-contains, string-join

Plus helpers: substring_op, string_append_op (variadic),
string_lt, string_case_op, string_to_list, list_to_string,
symbol_to_string, make_string_filled, number_to_string,
string_to_number, string_contains_p, string_join_op,
bytes_eq_s, read_char_literal.

39/39 wasm tests pass (20 unit + 8 integration + 11 functional).
2026-06-14 12:36:41 -04:00
73dc042ea8
wat iter-1: special forms + 35 primitives toward asm/lumbda.s parity
Special forms added:
  let*, letrec, when, unless, case, named-let

Primitives added (IDs 25-60):
  quotient, remainder, min, max, expt
  even?, odd?, positive?, negative?
  set-car!, set-cdr!, equal?, eqv?
  number?, integer?, symbol?, string?, procedure?, boolean?
  caar, cadr, cdar, cddr, caddr, cadddr
  reverse, append, apply, error
  member, memq, assoc, assq
  list-ref, list-tail, void

Plus helpers: equal_p (deep structural), append2, member_eq, assoc_eq.

39/39 wasm tests pass (20 unit + 8 integration + 11 functional).
Footer language softened — no more "minimal subset" disclaimer.
2026-06-14 12:30:01 -04:00
1b7de2c9c6
wasm/playground: cancel button, asm state-leak fix, restyle to match homepage
User-visible changes
  - Cancel button — terminates the running worker. Pyodide's slow mandelbrot
    no longer freezes the UI; click cancel and the elapsed counter freezes
    at "(cancelled @ NNNN ms)".
  - Live ms counter ticks per animation frame while a tier is busy, so the
    Pyodide tier's ~5-15 s wait is visible instead of looking hung.
  - Restyled to match lumbda.com: chunkfive wordmark, --green: #227842
    (light) / #5ec07a (dark), lumbda-logo-green.png, lowercase "lumbda"
    everywhere. Pulls fonts/chunkfive locally so the playground stays
    self-contained.

Architecture
  - All tier evaluations now run inside a Web Worker (wasm/app/worker.mjs)
    so the main thread stays responsive. Cancel = worker.terminate(); next
    eval respawns a fresh worker.
  - Loaders use new URL("./...", import.meta.url) so paths resolve against
    the loader file's own location — works identically in window and
    worker contexts, no baseURL argument needed.
  - C tier Emscripten build flipped to EXPORT_ES6=1; loader uses dynamic
    `import()` of the factory module. Integration test updated accordingly.
  - Python loader uses `import("pyodide.mjs")` (ES module) instead of
    document.createElement, which doesn't exist in workers.

Bug fixes
  - Asm tier state leak: running the same demo twice on a cached WASM
    instance produced corrupted output (every other cell on row 2+ rendered
    as " " instead of the expected shade char). Root cause: top-level eval
    passed `global_env` as the env, so closures captured stale globals;
    fixed by passing NIL — env_lookup falls back to the CURRENT global_env
    via its existing two-pass walk. Multi-run regression added to the
    functional test suite.
  - fib-ack demo: (ack 3 4) was too heavy for Pyodide (minutes). Cut to
    (ack 3 3) + (fib 20) max so every tier finishes in seconds.

Test discipline
  - Root `make test-all` now includes `wasm-test`. Adding a language
    feature without exercising it on all six implementations is no longer
    possible by accident.
  - Functional suite: 11 assertions (was 10) — adds asm multi-run stability.
  - Integration + unit: still 20 + 8.
2026-06-14 12:13:20 -04:00
346b873247
wasm: three-tier Lumbda to WebAssembly + browser playground
Adds a parallel build of all three Lumbda implementations to WASM, a
single-page playground at www/playground/, and a verified test suite.

Tiers
  - Python: Pyodide (CPython-in-WASM) hosting lumbda.py
  - C:      Emscripten build of c/ (tree-walker + bytecode VM; jit.c
            stubbed, gc.c uses its existing no-Boehm fallback)
  - Asm:    hand-written asm/lumbda.wat — parallel impl to asm/lumbda.s.
            Reader, eval (lambda/define/if/cond/let/and/or/quote/set!),
            recursion across mutated top-level env, bump allocator with
            memory.grow, 24 primitives. ~1200 lines of raw WAT.

SPA (wasm/app/, deployed to www/playground/)
  - CodeMirror 6 editor (Scheme highlighting) on left, output on right
  - Radios: 4 demos (Mandelbrot, Fib+Ack, Sieve, self-interp meta-eval)
            x 4 tiers (Python | C | Asm | All three)
  - All-three mode renders the three tier outputs side by side with
    per-tier elapsed timing

Tests (38 verified assertions)
  - 20 unit (Node): per-tier module loads, eval smoke
  - 8 integration (Node): each demo on c+asm WASM byte-matches the
                          canonical native Python run
  - 10 functional (Playwright headless Chromium): page mounts, every
                          demo runs on every tier, all-three renders

Makefile
  - Root targets: wasm-build, wasm-test, wasm-test-fn, wasm-serve,
                  wasm-deploy, wasm-clean
  - wasm/Makefile orchestrates the three tier builds; deploy copies
    dist/ into www/playground/

Asm tier notes
  - WAT linear symbol intern + linear env lookup is MOAD-0001 at scale;
    documented in the asm/lumbda.wat header and in the SPA footer. The
    demos hit ~30 globals so the linear walks are cheap enough.
  - Bump allocator never frees (matches asm/lumbda.s heap discipline);
    memory.grow expands by 1 MB chunks. Browser tab tears down at unload.

Toolchain (developer prerequisites)
  - Emscripten 6.0.0 via emsdk at ~/git/emsdk
  - wabt 1.0.36 at ~/git/wabt
  - Playwright for functional tests (symlinked from ~/git/agnt)
2026-06-14 11:40:34 -04:00