Saw #8: Tuplet, Smalltalk-on-BASIC, Forth-from-Forth, sw-MLPL Split, and I2C on COR24
2481 words • 13 min read • Abstract

Why Sharpen the Saw? — The name comes from Covey’s Habit 7: stop cutting long enough to sharpen the blade. This series tracks weekly investment in the tools themselves—agent orchestration, testing infrastructure, compiler toolchains, language platforms—so the feature work on top goes faster.
| Resource | Link |
|---|---|
| Tuplet | github.com/sw-vibe-coding/tuplet |
| Smalltalk on COR24 BASIC | github.com/sw-embed/sw-cor24-smalltalk |
| Smalltalk video (YouTube Short) | youtube.com/shorts/fL5NLSKkLoU |
| Forth-from-Forth (proto-forth-wasm) | github.com/sw-vibe-coding/sw-fth-wasm — live demo |
| COR24 Demo Hub (Status tab) | sw-embed.github.io/web-sw-cor24-demos |
| Repos & Live Demos | Table below |
| Prior Post | Saw #7: Prolog, Many-Agent Isolation, Self-Hosting Assembler, and MLPL |
| Comments | Discord |
Tuplet: A Glyph-and-Whitespace Language Playground

Tuplet is a new experimental infix language with first-class named tuple bundles, multi-output verbs, call-site argument splicing, and a glyph-heavy surface syntax. The kernel is small (~10 forms); everything else—if/then/else, while, every operator, every helper—is defined in a prelude using a single mechanism called mint (▪, BLACK SMALL SQUARE, U+25AA). User code can read, replace, or extend the same definitions the prelude uses. Tuplet compiles to Forth and runs on the COR24 runtime.
A taste of the surface syntax—a Power verb that uses Tuplet glyphs for type signature, assignment, and a piecewise definition:
⎧ 1 →
▪ Power (n : ℤ e : ℤ) → (p : ℤ) ← ⎨ loop e times iff e is positive
⎩ n (×) →
The glyphs in that fragment are: ▪ (BLACK SMALL SQUARE, U+25AA, mint), ℤ (DOUBLE-STRUCK Z, U+2124, integer type), → (RIGHTWARDS ARROW, U+2192, map / signature), ← (LEFTWARDS ARROW, U+2190, assign), ⎧⎨⎩ (LEFT CURLY BRACKET UPPER HOOK + MIDDLE PIECE + LOWER HOOK, U+23A7/U+23A8/U+23A9, multi-line piecewise group), and × (MULTIPLICATION SIGN, U+00D7, multiplication). Every glyph has an ASCII fallback the lexer accepts, and the lexer folds Unicode forms to ASCII canonicals at lex time so AST output and error messages stay portable. See docs/glyphs.md for the full alphabet.
Two things make Tuplet immediately practical:
- Implementation language: Tuplet’s host implementation is an OCaml subset that runs on sw-cor24-ocaml. Using OCaml as a load-bearing part of the toolchain forced a round of new features in the OCaml interpreter—details in the Smalltalk-and-Tuplet section below.
- Compilation target: Tuplet lowers to Forth, so it inherits whatever the COR24 Forth and Forth-from-Forth efforts deliver. New Forth features cascade into Tuplet for free.
Glyph Input Infrastructure: Espanso and Emacs
A glyph language is unusable if you can’t type it. The glyph-entry layer is its own piece of the toolchain, and it now has two parts:
- Espanso is a cross-platform text expander. Installing and configuring Espanso gives every editor and terminal a shared way to type Tuplet glyphs by short trigger sequences. The same configuration also covers GNU APL glyph entry—APL has the canonical “language with non-ASCII glyphs” problem, and Espanso solves both at once. See
docs/cli-inputs.mdfor the Tuplet trigger conventions. - Emacs has its own glyph-entry configuration so the same triggers work natively in Emacs buffers without going through Espanso. TeX-style input methods, Agda-style mappings, and a custom Quail input method are all options—see
docs/emacs-inputs.md. This keeps the editing experience consistent whether you’re in a terminal REPL, a browser-based demo, or an Emacs buffer.
Glyph entry counts as Sharpen the Saw work because it is infrastructure for future experiments: every new language that wants non-ASCII syntax—Tuplet, GNU APL, the next experiment—rides on the same input layer.
Smalltalk on COR24 BASIC: Dogfooding as a Forcing Function
A small integer/toy Smalltalk is now running, implemented in COR24 BASIC v1—a 26-variable, no-array, integer-only BASIC interpreter. The Smalltalk is in the spirit of the ~1000-line BASIC-hosted Smalltalk evaluator that Alan Kay’s group built in 1972. It has a tagged-pointer object encoding (low-bit-1 SmallIntegers, low-bit-0 heap pointers), 14 bytecodes, 6 primitives in v0, and a real CLASSOF -> LOOKUP -> ACTIVATE -> primitive-or-frame-push dispatch loop. The whole heap, method table, and frames live in 1024 24-bit words of PEEK/POKE scratch RAM. Three nested fetch/decode/execute loops (p-code VM, BASIC interpreter, Tinytalk VM) run at all times.
Video walkthrough (YouTube Short): https://www.youtube.com/shorts/fL5NLSKkLoU
The point is not speed. The point is to make the OO mental model visible—every dispatch step is a numbered BASIC line you can single-step through—and to dogfood COR24 BASIC by writing a real, recognizable system in it. The forcing function worked: writing Smalltalk in BASIC immediately revealed gaps. Six BASIC feature requests landed and closed this week:
| BASIC issue | Feature |
|---|---|
| FR-1 | DIM integer arrays |
| FR-2 | DATA / READ / RESTORE statements |
| FR-3 | ON expr GOTO/GOSUB statements |
| FR-4 | MOD operator |
| FR-5 | Bitwise operators (BAND/BOR/BXOR/SHL/SHR) |
| FR-6 | CONT after STOP |
Tagged-pointer dispatch needs the bitwise ops; method tables and bytecode arrays want DIM; DATA/READ is the natural way to ship the boot image; ON expr GOTO is the dispatch-jump that powers the bytecode interpreter. Each was a real shortcoming Smalltalk hit, not a speculative wishlist item. The BASIC live demo ships them all.
The same forcing-function pattern played out for Tuplet on the OCaml side. Eleven OCaml interpreter issues closed in the last 24 hours, almost all driven by Tuplet’s host code:
| OCaml issue | Feature |
|---|---|
| #1 | TRAP 2 in demo_adventure (round-trip bug) |
| #2 | User-defined variant types (algebraic data types) |
| #3 | Top-level let bindings (without in EXPR) |
| #4 | String escapes (\n, \t, …) and arbitrary tuples (3+) |
| #5 | Multi-line match expressions |
| #6 | Mutable references (ref, !, :=) |
| #7 | Record types and field access |
| #8 | List combinators (List.map, List.fold_left, List.filter) |
| #9 | Block comments (* ... *) |
| #10 | Char literals 'a' + Char.code / Char.chr |
| #11 | Exceptions (raise / try / with) or a Result type |
A lexer, AST, and IR lowering pipeline needs records, variants, top-level lets, multi-line match, mutable refs, and exceptions just to be writable. The OCaml live demo now ships every one of those. The Forth side picked up the matching upgrades—:NONAME for anonymous colon definitions and a SEE-CFA truncation fix—in sw-cor24-forth over the same window.
Forth-from-Forth: A Browser-Native Forth via WASM
Forth-from-Forth (proto-forth-wasm) is a browser-native Forth running on Rust/WASM. The Rust Machine owns the data stack, return stack, memory, dictionary, tokenizer, compiler, and VM loop; colon definitions compile to an opcode IR; user-word execution runs on an iterative VM loop with no host-stack recursion for nested calls. The vocabulary already covers arithmetic, stack shuffling, comparisons, structured control flow (IF/ELSE/THEN, BEGIN/UNTIL, BEGIN/WHILE/REPEAT, DO/LOOP with I), memory (VARIABLE, CONSTANT, @, !, +!, ALLOT), the return stack, I/O, and introspection (SEE, WORDS).
The live demo puts the REPL, source pane, stack, dictionary, output, history, and trace all on one page. WASM matters here for the same reason a self-hosting toolchain matters elsewhere: zero-install distribution. A reader can land on the page and have a working Forth in seconds.
Two Forth-in-Forth issues closed this week pulled the project closer to self-hosting at the language level: #1 added a hashed dictionary to speed up FIND, and #2 added DO/LOOP, ?DO, WHILE/REPEAT, AGAIN, CONSTANT, and VARIABLE to the Forth-in-Forth implementation. Open issue #3 tracks the remaining standard words: +LOOP/J/LEAVE, DOES>, RECURSE, PICK/ROLL/?DUP/MIN/MAX/<=/>=/<>.
A Forth-builder is in planning—a small kit (inner interpreter, dictionary, I/O backend, target word set) that composes Forth systems by configuration instead of by fork. Once the builder exists, spinning up a new Forth flavor for an experiment becomes a build-time decision.
sw-MLPL: 35 GB Forces a Split into Parallelizable Pieces
sw-MLPL (live demo) hit a different kind of forcing function this week: its build directory blew past 35 GB on a MacBook, and that is without the CUDA backend wired in yet. A single repo trying to hold a CPU runtime, the MLX backend, a future CUDA backend, an interpreter, a compiler, a REPL, and a Web UI is not a structure that fits on one laptop.
The fix is to split:
- Linux/CUDA only — one fork that targets NVIDIA CUDA and lives on a host with the disk space and toolchain to support it.
- Mac/MLX only — a sibling fork that stays on Apple Silicon and the MLX unified-memory path, where the build stays small enough to iterate on a laptop.
- Web UI separated — the in-browser demo carved out into its own repo, decoupled from the runtime build cycle.
- Compiler and REPL separated — the two have different dependency profiles and different test harnesses; separating them lets each grow without dragging the other’s build along.
The win is not just disk space. Once the project is split, different hosts can develop different pieces in parallel: a Linux/CUDA box pushes the GPU backend while a MacBook iterates on MLX or the compiler, and neither waits on the other’s artifacts. The Web UI repo’s CI doesn’t care which backend any given commit targets. This is the same pattern the COR24 stack uses by default—small, single-purpose, parallelizable repos—applied retroactively to a project that grew past the point of fitting in one place.
I2C on the COR24 Emulator
The COR24 emulator is gaining I2C support, with example programs to drive it. I2C unlocks the obvious next layer of peripheral experiments—temperature/pressure sensors (the BMP280 repo is already in the tree), small EEPROMs, OLED displays, and any of the usual hobbyist breakout boards. I2C is the right first bus for the emulator: low pin count, well-documented protocol, plenty of devices to talk to, and the bring-up code is small enough that the Smalltalk and Tuplet languages above could plausibly drive a real sensor in a future demo.
The pattern is the same as everywhere else in this post: build the platform piece (the bus, the examples) so that future feature work has somewhere to land.
Demo Site: Status Tab Tracks the New Languages
The COR24 demo hub now has a Status tab that tracks the languages, their recent commits, and their open issues in one place. Adding Tuplet and Smalltalk to the lineup made the per-language pages harder to skim, so the Status tab consolidates:
- The current language list (now including Tuplet and Smalltalk, alongside PL/SW, SNOBOL4, MLPL, BASIC, Forth, OCaml, Pascal, APL, MacroLisp, P-code, TinyC, and the assemblers).
- Recent commits per language—a single cross-repo activity feed.
- Open issues per language, so the in-flight work is visible without bouncing between GitHub repos.
Demo Hub (and Status tab): sw-embed.github.io/web-sw-cor24-demos/#/
The Status tab is the same kind of investment as the glyph-input layer and the I2C bus: infrastructure for moving faster, not a feature in any one language. New languages plug into it; the cost of adding the next one is now bounded.
Repos and Live Demos
| Project | GitHub | Live Demo |
|---|---|---|
| Tuplet | sw-vibe-coding/tuplet | in development |
| Smalltalk on COR24 BASIC | sw-embed/sw-cor24-smalltalk | sw-embed.github.io/web-sw-cor24-smalltalk |
| Forth-from-Forth (WASM) | sw-vibe-coding/sw-fth-wasm | sw-vibe-coding.github.io/sw-fth-wasm |
| COR24 BASIC | sw-embed/sw-cor24-basic | sw-embed.github.io/web-sw-cor24-basic |
| COR24 Forth | sw-embed/sw-cor24-forth | sw-embed.github.io/web-sw-cor24-forth |
| COR24 OCaml | sw-embed/sw-cor24-ocaml | sw-embed.github.io/web-sw-cor24-ocaml |
| COR24 Emulator (I2C) | sw-embed/sw-cor24-emulator | sw-embed.github.io/cor24-rs |
| sw-MLPL | sw-ml-study/sw-mlpl | sw-ml-study.github.io/sw-mlpl |
| COR24 Demo Hub | sw-embed/web-sw-cor24-demos | Demo Hub |
What’s Next
Tuplet: Get the Tuplet live demo working alongside the other COR24 language demos, and add a way to enter glyphs directly in the Tuplet web UI (so visitors don’t need Espanso or Emacs configured locally to try the demo).
Smalltalk on COR24 BASIC: Get the Smalltalk live demo working—a browser-hosted version of the BASIC interpreter running the Tinytalk image, so the YouTube walkthrough has a hands-on counterpart.
Glyph Input + Emacs: Publish concrete Emacs configuration examples (init snippets, Quail rules, TeX-style mappings) drawn from docs/emacs-inputs.md, and add org-mode support—which means babel blocks for Tuplet and every other COR24 language (BASIC, Forth, OCaml, PL/SW, SNOBOL4, etc.). Babel-blocks across the language set turns org-mode into a real notebook for the COR24 stack.
Forth-from-Forth: Close the remaining standard Forth words (sw-cor24-forth #3) and start the Forth-builder kit so new Forth flavors compose by configuration.
sw-MLPL Split: Land the four-way split (Linux/CUDA, Mac/MLX, Web UI, separated Compiler vs. REPL) and confirm each piece builds cleanly on a host that doesn’t have the others’ dependencies. Republish the live demo from the new Web UI repo.
COR24 Emulator I2C: Land the first round of I2C example programs end to end (sensor reads, EEPROM round-trip), then expose I2C from the higher-level languages so a Tuplet or Smalltalk program can drive a peripheral.
Forcing functions sharpen languages faster than feature lists do. Follow for more Sharpen the Saw updates.
Part 8 of the Sharpen the Saw Sundays series. View all parts | Next: Part 9 →
Comments or questions? SW Lab Discord or YouTube @SoftwareWrighter.