· 4 min read
The catalog dreams in i32
Guarded While reveals how a hundred distinct grammars are constrained by one small expression language and an i32 WebAssembly result.
There is a language in this catalog called Guarded While, and its while never goes around. The canonical program while 1 8 3 evaluates the guard once, performs one addition, and returns 11; hand it a zero guard and you get your 8 back untouched. However hard you stare at the lowering, no iteration is hiding in it.
{ id: "guarded-while", name: "Guarded While", model: { storage: "counter", control: "guarded", values: "natural", }, grammar: { program: "WHILE NUMBER NUMBER NUMBER" }, inputs: numberInputs(3), expression: choose( operand(0), binary("add", operand(1), operand(2)), operand(1), ), example: { source: "while 1 8 3", expected: 11 }, probe: { source: "while 0 8 3", expected: 8 },}That mismatch is the honest door into the catalog at commit d7e9220: a hundred micro-languages, each with its own grammar and witness programs, and every one of them squeezed through the same bounded expression representation on its way to a single i32. I find the gap between the names and the machine more charming than damning. But you need to see it, or you will read the catalog as something it is not.
You can trace Guarded While end to end without running the generator. The grammar accepts WHILE and three numbers, numberInputs(3) turns the tokens into operands, and choose uses the first operand to pick between one addition and the untouched second operand. Nothing feeds a result back into the guard.
Grammar
Recognize a language-specific token arrangement.
Selectors
Turn matched text into integer operands.
Expression
Bind operands into constant, unary, binary, or select nodes.
WebAssembly
Emit one no-import function returning i32.
The taxonomy is larger than the machine#
Each LanguageSpec declares a family plus storage, control, and value labels — lovely for browsing, and purely descriptive. The part that executes lives in two other fields: the token selectors and the ExpressionTemplate they feed.
| Boundary | Declared range | Status |
|---|---|---|
| Catalog shape | Ten named families with ten entries each | Enforced by catalog validation |
| Storage taxonomy | Accumulator, stack, tape, counter, term, grid, signal, scalar, source, collection | Descriptive metadata |
| Control taxonomy | Ten labels including guarded, normalize, vector, and reflect | Descriptive metadata |
| Executable algebra | Input, constant, unary, binary, select | The only expression node kinds |
| Result boundary | run(): i32 | One exported integer result |
Look at what the algebra offers: constants, inputs, a fixed set of integer operators, and conditional selection. No loop, no recursive call, no heap, no function values. So when an entry advertises "reflection" or "rewriting", the word describes the source metaphor and its decoder — the labels are doing literature while the nodes do arithmetic.
To read the catalog right, follow the selectors, since a storage label of "stack" allocates no stack and a control label of "reflect" reflects nothing at runtime. The five node kinds are the machine all hundred languages actually share.
Validation keeps the hundred apart#
What keeps this from collapsing into one language wearing a hundred costumes is the catalog validator, and it is strict: ten entries per family, unique IDs and names, example and probe sources that differ, valid closest-relative links, no unused grammar tokens, in-range expression inputs, an i32 expected result. It even rejects normalized grammar duplicates and repeated semantic fingerprints.
That cage is why the artifact feels orderly — every generated entry has to occupy its own checked point in a fixed design space. Real variety survives at the decoding layer, where Roman numerals, balanced ternary, and token-length tricks all appear, but the compiler behind them always receives integer operands and the same five expression forms.
The example-and-probe pair is the validator's nicest idea: each language must ship one witness and one counterexample against a dead input. For Guarded While the pair covers both branches of the choose. What it cannot establish is loop behavior, because no expected result anywhere contains a second application of the body.
A disclosure before the last word: I inspected the source at the pinned commit and did not regenerate the private catalog, so take my judgments about the names as taste. The boundary claims you can check yourself — the validator will run for you.
The guarded-while README calls the construct a bounded loop body. Its probe returns 8, its canonical witness returns 11, and between those two numbers the supposed loop has exactly enough room for one addition.