Metaxis

Examples

The examples

Every file here is run by make check against the output recorded beside it, and five of them are compiled and executed. The descriptions are the README's; the sources and outputs are the files in the tree, exactly.

first

the smallest file that shows the shape, and what REFERENCE.md § 1 opens with

examples/first.mx
; first.mx -- the smallest file that shows the whole shape, and the one
; REFERENCE.md § 1 opens with.
;
; A header of directives, then a body of foreign text. Nothing is built in:
; the comment, the separator, the two literal classes and every rule are
; declared here, and the body is read with what they say and with nothing else.
;
; It reads a small C-shaped language and writes C.

@token  name   "[A-Za-z_][A-Za-z0-9_]*"
@token  number "[0-9]+"
@comment "#" eol
@separator ";" => ";\n"

@syntax a "=" b   10 right  => "{a} = {b}"
@syntax a "+" b   60        => "add({a}, {b})"
@syntax "twice" e           => "({e} * 2)"
@end
x = 1;              # nothing here is built in
twice x + 2;
examples/first.out
x = 1;
(add(x, 2) * 2)

calc

the one file here with no target language: it runs its notation instead of translating it, and its header records where that stops

examples/calc.mx
; calc.mx -- a file that does not translate its language. It runs it.
;
; Every other example here rewrites one notation into another and the output is
; text in some target. This one has no target: each rule computes a number, the
; number goes back into the parse as the value of that subexpression, and what
; comes out is an answer.
;
; Nothing about the tool made that possible except arithmetic. A rule has always
; taken the values its children produced and combined them into its own; the
; only thing that changed is that the combining may now be `*` instead of
; concatenation. That is what "bottom-up attribute grammar" in docs/direction.md
; means when it is spelled out.
;
; `num(h)` is on every operand and there is no way to leave it off, because a
; hole holds text and reading a number out of text that merely looks like one is
; the sort of quiet coercion this tool refuses everywhere else. `+` adds when
; both sides are already numbers and joins when they are not, which is the rule
; comparison has always used.
;
; **What this file cannot do is the interesting half, and it is the reason
; docs/direction.md does not yet call this an interpreter generator.** The
; conditional below picks the right branch. It does not avoid the other one:
;
;     if 1 then 10 else (1 / 0)      ->      pt: '/' by zero
;
; A hole is filled by parsing and expanding its subexpression *before* the
; template runs, so both branches have already been computed by the time
; anything asks which was wanted. Evaluation here is eager and cannot be
; otherwise, which rules out anything that has to not-happen: a loop, a
; recursion, a short circuit, a definition used before it is run. What this is,
; exactly, is an evaluator for expressions.

@token number "[0-9]+"
@comment "#" eol
@separator "\n" => "\n"

@syntax "(" e ")"                    => { emit e }

@syntax a "*" b   70                 => { emit num(a) * num(b) }
@syntax a "/" b   70                 => { emit num(a) / num(b) }
@syntax a "%" b   70                 => { emit num(a) % num(b) }
@syntax a "+" b   60                 => { emit num(a) + num(b) }
@syntax a "-" b   60                 => { emit num(a) - num(b) }

@syntax a "<" b   40                 => { if num(a) < num(b) { emit 1 } else { emit 0 } }
@syntax a ">" b   40                 => { if num(a) > num(b) { emit 1 } else { emit 0 } }

; It selects. It does not defer -- see the note above.
@syntax "if" c "then" t "else" f  20 => { if num(c) { emit t } else { emit f } }
@end
2 + 3 * 4               # precedence, from the levels and nothing else
(2 + 3) * 4
100 / 7                 # integer division, because the values are integers
100 % 7
100 - 7 - 3             # left, so 90 and not 96
if 1 < 2 then 10 * 10 else 0
examples/calc.out
14
20
14
2
90
100

asm

C in, arm64 assembly out: a target that is a sequence rather than a tree, with labels and an order the input never mentions

examples/asm.mx
; asm.mx -- C in, arm64 assembly out. Stage 2, and the first target here that
; is not shaped like its input.
;
; A Pascal expression becomes a C expression one node at a time: the output
; nests the way the input nested. Assembly does not nest. It is a **sequence**,
; and an expression becomes a run of instructions that leave a value somewhere
; agreed. So the value a rule passes up is no longer a phrase -- it is the code
; that computes the phrase, and the rule's job is to put its children's code in
; front of its own.
;
; The discipline is a stack machine: **every rule's output is code that leaves
; exactly one value pushed.** A binary operator emits its left operand's code,
; then its right operand's, then pops two and pushes one. Nothing is allocated
; and no register survives a rule, which is what makes the rules composable at
; all -- the same reason the notation quotes everything.
;
; **Two things this file had to work around, and both are findings.**
;
; A **literal is not code**. `3` is a bare token, and a rule cannot match a bare
; token (the limitation examples/pascal.mx meets from the other side, where it
; cannot translate a string), so nothing turns `3` into `mov x0, #3`. What tells
; a literal from a subexpression is `level(h)`: every rule below declares a
; level, so `level(h) == 1000` means *nothing here produced this, it is a token
; standing for itself*.
;
; `@template load(x)` is that test, written once. The first version of this file
; wrote it out at every operand -- eight times, identically -- and that
; repetition is what asked for the directive: this tool could name a rule and
; nothing else. A template is called as a statement, emits into whatever called
; it, and sees its parameters and nothing else, so `load` can be read on its own
; without knowing which rule is using it.
;
; A **label is needed twice**, at the branch and at the place it jumps to, and
; `fresh("L")` used to return a different name each call. It now returns one name
; per label per application, exactly as `{~t}` always has in a string template.
; That was a defect this file found: the reference had said the two spellings
; were the same thing and they were not.
;
; tests/asm.sh assembles what comes out, links it against a four-line runtime
; and runs it, so the numbers below are checked by a CPU.

@token number "[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"
@comment "//" eol
@separator ";" => "\n"

; Load an operand's value onto the stack: a literal becomes an immediate, and
; anything a rule produced is already the code that pushes it.
@template load(x) {
    if level(x) == 1000 { emit "\tmov x0, #" + x + "\n\tstr x0, [sp, #-16]!\n" } else { emit x }
}

@syntax "(" e ")"  95
    => {
        load(e)
    }

@syntax a "*" b  70
    => {
        load(a)
        load(b)
        emit "\tldr x1, [sp], #16\n\tldr x0, [sp], #16\n\tmul x0, x0, x1\n\tstr x0, [sp, #-16]!\n"
    }

@syntax a "+" b  60
    => {
        load(a)
        load(b)
        emit "\tldr x1, [sp], #16\n\tldr x0, [sp], #16\n\tadd x0, x0, x1\n\tstr x0, [sp, #-16]!\n"
    }

@syntax a "-" b  60
    => {
        load(a)
        load(b)
        emit "\tldr x1, [sp], #16\n\tldr x0, [sp], #16\n\tsub x0, x0, x1\n\tstr x0, [sp, #-16]!\n"
    }

@syntax a "<" b  40
    => {
        load(a)
        load(b)
        emit "\tldr x1, [sp], #16\n\tldr x0, [sp], #16\n\tcmp x0, x1\n\tcset x0, lt\n\tstr x0, [sp, #-16]!\n"
    }

; The one rule whose output is not in the order its input was written. The
; condition's code comes first, then a branch to a label that does not exist
; yet, then the two arms with a label between them. Nothing about the input says
; "jump"; the target does.
@syntax c "?" t ":" f  30 right
    => {
        load(c)
        emit "\tldr x0, [sp], #16\n\tcbz x0, " + fresh("Lelse") + "\n"
        load(t)
        emit "\tb " + fresh("Lend") + "\n" + fresh("Lelse") + ":\n"
        load(f)
        emit fresh("Lend") + ":\n"
    }

@syntax "putn" "(" e ")"  95
    => {
        load(e)
        emit "\tldr x0, [sp], #16\n\tbl _putn"
    }

@syntax "int" "main" "(" "void" ")" "{" body:stmts "}"
    => ".text\n.globl _main\n.align 2\n_main:\n\tstp x29, x30, [sp, #-16]!\n\tmov x29, sp\n{body}\n\tmov w0, #0\n\tldp x29, x30, [sp], #16\n\tret" terminated
@end
// C in. Assembly out. Neither language the tool's.
int main(void) {
  putn(2 + 3 * 4);
  putn(100 - 7 - 3);
  putn(1 < 2 ? 10 : 20);
  putn(2 < 1 ? 10 : 20)
}
examples/asm.out
.text
.globl _main
.align 2
_main:
	stp x29, x30, [sp, #-16]!
	mov x29, sp
	mov x0, #2
	str x0, [sp, #-16]!
	mov x0, #3
	str x0, [sp, #-16]!
	mov x0, #4
	str x0, [sp, #-16]!
	ldr x1, [sp], #16
	ldr x0, [sp], #16
	mul x0, x0, x1
	str x0, [sp, #-16]!
	ldr x1, [sp], #16
	ldr x0, [sp], #16
	add x0, x0, x1
	str x0, [sp, #-16]!
	ldr x0, [sp], #16
	bl _putn
	mov x0, #100
	str x0, [sp, #-16]!
	mov x0, #7
	str x0, [sp, #-16]!
	ldr x1, [sp], #16
	ldr x0, [sp], #16
	sub x0, x0, x1
	str x0, [sp, #-16]!
	mov x0, #3
	str x0, [sp, #-16]!
	ldr x1, [sp], #16
	ldr x0, [sp], #16
	sub x0, x0, x1
	str x0, [sp, #-16]!
	ldr x0, [sp], #16
	bl _putn
	mov x0, #1
	str x0, [sp, #-16]!
	mov x0, #2
	str x0, [sp, #-16]!
	ldr x1, [sp], #16
	ldr x0, [sp], #16
	cmp x0, x1
	cset x0, lt
	str x0, [sp, #-16]!
	ldr x0, [sp], #16
	cbz x0, Lelse__1
	mov x0, #10
	str x0, [sp, #-16]!
	b Lend__2
Lelse__1:
	mov x0, #20
	str x0, [sp, #-16]!
Lend__2:
	ldr x0, [sp], #16
	bl _putn
	mov x0, #2
	str x0, [sp, #-16]!
	mov x0, #1
	str x0, [sp, #-16]!
	ldr x1, [sp], #16
	ldr x0, [sp], #16
	cmp x0, x1
	cset x0, lt
	str x0, [sp, #-16]!
	ldr x0, [sp], #16
	cbz x0, Lelse__3
	mov x0, #10
	str x0, [sp, #-16]!
	b Lend__4
Lelse__3:
	mov x0, #20
	str x0, [sp, #-16]!
Lend__4:
	ldr x0, [sp], #16
	bl _putn
	mov w0, #0
	ldp x29, x30, [sp], #16
	ret

tour

the idea in one file: infix, prefix, circumfix, mixfix, and then used as a word and as a name four lines apart

examples/tour.mx
; tour.mx -- the whole idea in one file. Emits Solveig, the way Proto does.
;
; The header mentions text. The body is text. Quotes are what tell the two
; apart, on both sides of the arrow, and they are the only thing that has to.
;
; Everything the body uses is declared here, including the send and the
; comment. Nothing is built in, which is the cost of being language agnostic
; and is paid once per file.

@comment ";" eol

@token  number "#-?[0-9]+"
@token  name   "[A-Za-z_][A-Za-z0-9_]*"
@token  string "\"[^\"]*\""

@separator "." => ".\n"

@syntax "(" e ")"                   => "({e})"
@syntax a ":" m:name      95        => "{a}:{m}"

@syntax a "+" b           60        => "{a}:add({b})"
@syntax a "-" b           60        => "{a}:sub({b})"
@syntax a "<" b           40        => "{a}:lessThan({b})"
@syntax a "=" b           10 right  => "{a} := {b}"

; A pattern beginning with a word is a prefix rule and needs no level. One
; beginning with a hole is infix or postfix and does. There is no second
; directive saying which -- the shape says it.
@syntax "unless" c "then" "{" t:stmts "}"
    => "{c}:not:ifTrue({{ {t} }})"
@syntax "if" c "then" "{" t:stmts "}" "else" "{" f:stmts "}"
    => "{c}:ifElse({{ {t} }}, {{ {f} }})"

; Words at both ends: a circumfix. Proto has no directive with this shape,
; because there a directive's name is what named the shape.
@syntax "|" a "|"                   => "{a}:abs"

; And a lone bar is a bar. It is one character inside a string, and it has
; nothing to do with the bar on the line above.
@syntax a "|" b           20        => "{a}:bitOr({b})"

n = #3.
unless n < #1 then { n:print }.                 ; #3
if n < #5 then { "small":print } else { "big":print }.
|#0 - #7|:print.                                ; #7
(#5 | #2):print.                                ; #7, the other way

; `if`, `then` and `unless` are not reserved -- they are matched by position,
; never by the lexer. Here `then` is an ordinary name, in the file that
; declared it as a word.
then = #1.
(then + n):print.                               ; #4
examples/tour.out
n := #3.
n:lessThan(#1):not:ifTrue({ n:print }).
n:lessThan(#5):ifElse({ "small":print }, { "big":print }).
#0:sub(#7):abs:print.
(#5:bitOr(#2)):print.
then := #1.
(then:add(n)):print

clike

the six things Proto's lib/clike.pro lists as impossible: ; between statements, x++, a[i], p->f, a lone |, for, and 42 without a sigil

examples/clike.mx
; clike.mx -- the six things Proto's lib/clike.pro lists as impossible.
;
; That file's header says what C has and a Proto dialect cannot take: `;`
; between statements, `x++` and `a[i]` and `p->f`, a lone `|`, `for`, and `42`
; without a `#`. Five of the six are impossible for one reason -- the spelling
; collides with something Proto's own grammar had already spent -- and the
; sixth is the lexer's rule about literals. All six are written below: the
; first five because a spelling inside a string collides with nothing, and the
; sixth because a literal is a declaration here too.
;
; Output is Solveig, so the comparison with Proto/examples/clike.pro is direct.

; `;` opens a comment in Proto, which is why `;` cannot end a statement there.
; The comment opener is declared here, so `;` is free the moment this line says
; something else is the opener. These two lines are themselves still written in
; `;` comments: `;` is Metaxis's own header comment and always works, and a
; declared one joins it rather than replacing it.
@comment "//" eol
@comment "/*" "*/"

// The sixth. A bare `42` is an integer because this line says what an integer
// looks like; in Proto the lexer had said first, and `#42` was the result.
@token number "0x[0-9a-fA-F]+|[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"

@separator ";" => ".\n"

// Parentheses as an ordinary rule rather than as core syntax.
@syntax "(" e ")"                              => "({e})"

// `.` is Proto's statement terminator and cannot be lent out. Here it is a
// field access, and the file that uses it terminates statements with `;`.
@syntax a "." m:name              95           => "{a}:{m}"

// Postfix and index. Proto has prefix and infix and nothing else, because the
// directive's name is what fixed the shape. Here the shape is the pattern's.
@syntax a "[" i "]"               95           => "{a}:at({i})"
@syntax a "++"                    95           => "{a} := {a}:add(1)"
@syntax a "->" m:name             95           => "{a}:deref:{m}"

// A lone `|`. Proto keeps it for a block's parameters and has nothing to lend.
@syntax a "|" b                   20           => "{a}:bitOr({b})"
@syntax a "||" b                  25           => "{a}:or({{ {b} }})"
@syntax a "&&" b                  30           => "{a}:and({{ {b} }})"
@syntax "!" a                     80           => "{a}:not"

@syntax a "=" b                   10 right     => "{a} := {b}"
@syntax a "==" b                  40           => "{a}:equals({b})"
@syntax a "!=" b                  40           => "{a}:notEquals({b})"
@syntax a "<" b                   40           => "{a}:lessThan({b})"
@syntax a "<=" b                  40           => "{a}:lessOrEqual({b})"
@syntax a ">" b                   40           => "{a}:greaterThan({b})"
@syntax a "+" b                   60           => "{a}:add({b})"
@syntax a "-" b                   60           => "{a}:sub({b})"
@syntax a "%" b                   70           => "{a}:mod({b})"

// A rule takes its own braces, so the pattern says where a body stops and
// nothing needs a kind that means *a block*.
@syntax "while" "(" c ")" "{" b:stmts "}"
    => "{{ {c} }}:whileTrue({{ {b} }})"
@syntax "if" "(" c ")" "{" t:stmts "}"
    => "{c}:ifTrue({{ {t} }})"
@syntax "if" "(" c ")" "{" t:stmts "}" "else" "{" f:stmts "}"
    => "{c}:ifElse({{ {t} }}, {{ {f} }})"

// `for`, the one loop C has and Proto does not. Two `;` inside one pattern,
// which is the entire demonstration: the character that ends a statement in
// this file's body appears twice in the declaration of a form, and there is
// nothing to disambiguate because there was never an ambiguity.
@syntax "for" "(" init ";" c ";" step ")" "{" b:stmts "}"
    => "{init}.\n{{ {c} }}:whileTrue({{ {b}. {step} }})"

@end

// Not one rule here is `terminated`, and that is a decision rather than an
// oversight. The output is Solveig, where a `.` *is* wanted between two
// statements however the one before ended -- so a `}` that needs no `;` in the
// C being read still needs a `.` in the Solveig being written. The input side
// and the output side are about two different languages, and this file is where
// they disagree. examples/groups.mx reads the same braces and declares the word.

total = 0;

for (i = 0; i < 20; i++) {
    if (i % 3 == 0 && i != 9) { total = total + i; }
    else { total = total - 1; }
}

total.print;

flags = 0x0c | 3;
if (!(flags == 0) && flags <= 0x0f) { flags.print; }

xs.first->next.print;
xs[2].print;
examples/clike.out
total := 0.
i := 0.
{ i:lessThan(20) }:whileTrue({ i:mod(3):equals(0):and({ i:notEquals(9) }):ifElse({ total := total:add(i) }, { total := total:sub(1) }). i := i:add(1) }).
total:print.
flags := 0x0c:bitOr(3).
(flags:equals(0)):not:and({ flags:lessOrEqual(0x0f) }):ifTrue({ flags:print }).
xs:first:deref:next:print.
xs:at(2):print

pascal

Pascal in, C out: stage 1. program, var, procedure, function, begin/end, if, while, for, repeat, case, calls, and the operator words. pascal.out keeps its parenthesis noise, which is the cost of agnosticism showing itself, and is the one output here that is expected not to compile

examples/pascal.mx
; pascal.mx -- Pascal in, C out. The point of this one is that neither
; language is the tool's, and the tool's own strings are unaffected by either.
;
; Pascal's comments are `{ }` and `(* *)`, its strings are `'…'` with a doubled
; quote for an apostrophe, its assignment is `:=` and its equality is `=`, and
; `and`, `or`, `not`, `div` and `mod` are words rather than punctuation. Every
; one of those is a spelling Proto's core had already spent. Here each is a
; string, and a string is the one thing whose end every reader can find.

@comment "{" "}"
@comment "(*" "*)"

@token number "[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"
@token string "'([^']|'')*'"

@separator ";" => ";\n"

@syntax "(" e ")"                              => "({e})"

; A call, now that there are things to call. `(` after an expression is this
; rule; `(` where an expression is expected is the circumfix one above. The
; parser never has to choose -- the two are in different positions.
@syntax a "(" [ x ]* sep "," join ", " ")"  95  => "{a}({x})"

@syntax a ":=" b                  10           => "{a} = {b}"
@syntax a "=" b                   40           => "({a} == {b})"
@syntax a "<>" b                  40           => "({a} != {b})"
@syntax a "<" b                   40           => "({a} < {b})"
@syntax a ">" b                   40           => "({a} > {b})"
@syntax a "+" b                   60           => "({a} + {b})"
@syntax a "-" b                   60           => "({a} - {b})"
@syntax a "*" b                   70           => "({a} * {b})"
@syntax a "div" b                 70           => "({a} / {b})"
@syntax a "mod" b                 70           => "({a} % {b})"
@syntax "not" a                   80           => "(!{a})"
@syntax a "and" b                 30           => "({a} && {b})"
@syntax a "or" b                  25           => "({a} || {b})"

; A declared word is still not reserved. `mod` above is a word between two
; operands and a name everywhere else, which is why the body can have a
; variable called `mod` without this line and that one arguing. Nothing in the
; lexer knows about `mod`: it is a name token whose text happens to match.

; Declarations. Pascal names its variables and C insists on it, so without
; these the output assigns to names C never heard of and does not compile --
; which is the whole reason the target is C: `diff` cannot notice that and a
; compiler cannot miss it.
;
; A type is a quoted *word* rather than a `name` hole, because a hole splices
; the token it matched and `integer` has to come out as `int`. One rule per
; type is also the only place a translation can happen at all: a rule cannot
; match a bare token, so nothing can rewrite `integer` where it stands.
@syntax "program" n:name                       => "/* {n} */\n#include <stdio.h>" terminated
@syntax a "," b                   20           => "{a}, {b}"
@syntax a ":" "integer"           15           => "int {a}"
@syntax a ":" "real"              15           => "double {a}"

; A type is also a rule of its own, a word alone, so the parameter list below
; can hold a hole where the type goes. This file cannot *use* what that hole
; reads -- see the note there -- but without it a `real` parameter would not
; parse at all, and the two files must read the same program.
@syntax "integer"                              => "int"
@syntax "boolean"                              => "int"
@syntax "real"                                 => "double"

; A case arm, and the price of a string template paid in the pattern rather
; than in the output. `case n of 1: a; 2: b end` wants its arms to be a repeated
; group of `[ v ":" s ]`, which is two holes and therefore two parallel lists;
; a string template splices each of them joined and has no way to interleave
; them. So the arm is a *rule* instead, folding the pair into one value before
; the group ever sees it. examples/code.mx keeps the two holes and walks them
; with `for i, x in v` and `at(s, i)`, which is what that pair of additions was
; built for.
;
; This is safe only because the type rules above are declared first: both
; patterns are three elements long, so declaration order is what tries
; `: integer` before `: anything`. That warning used to end "add a second type
; and it goes above this line too -- and nothing enforces that". A second type
; arrived, `real` went above this line, and nothing enforced it: the file said
; where the trap was and the trap still had to be stepped around by hand.
@syntax a ":" s                   15           => "case {a}: {s}; break;"
@syntax a ":" "boolean"           15           => "int {a}"

; `var` opens the section and does nothing to the declaration after it; the
; declarations that follow are statements like any other, which is what lets
; one `var` cover a whole block the way Pascal writes it.
@syntax "var" d                                => "{d}"

; The `;` is unconditional for the same reason: @separator puts one between two
; statements and never after the last, and this template cannot ask whether the
; last one already ended.
@syntax "begin" body:stmts "end"               => "{{\n{body};\n}}" terminated

; Pascal's post-tested loop is C's, inverted: `until` says when to stop and
; `while` says when to go on.
@syntax "repeat" b:stmts "until" c             => "do {{\n{b};\n}} while (!({c}))"

; Pascal's arms do not fall through and C's do, so every one ends in a `break`
; the source never wrote. That is a translation and not a rename, which is the
; same thing `**` would be if this file read Python.
@syntax "case" e "of" [ arm ]* sep ";" join "\n" "end"
    => "switch ({e}) {{\n{arm}\n}}" terminated
@syntax "case" e "of" [ arm ]* sep ";" join "\n" "else" d "end"
    => "switch ({e}) {{\n{arm}\ndefault: {d}; break;\n}}" terminated

; The outer block, which ends in a full stop. It is a longer pattern than the
; one above, so it is tried first and the inner blocks are unaffected.
@syntax "begin" body:stmts "end" "."
    => "int main(void) {{\n{body};\nreturn 0;\n}}" terminated

; A branch is braced whether or not it needs to be. C's `if (c) x = 1; else`
; wants a semicolon that C's `if (c) {{ … }} else` must not have, and which of
; the two a branch is depends on the rule that filled the hole. A code template
; asks -- `terminated(t)`, which examples/code.mx uses to write neither brace
; nor stray semicolon. A string template cannot ask anything, so it braces
; every branch and is right either way. This is the same trade as the
; parentheses one line of rules up, in the other punctuation.
@syntax "if" c "then" t                        => "if ({c}) {{ {t}; }}"
@syntax "if" c "then" t "else" f               => "if ({c}) {{ {t}; }} else {{ {f}; }}"
@syntax "while" c "do" b                       => "while ({c}) {{ {b}; }}"
; Two `writeln`s of the same pattern length, so declaration order is what
; tries the literal first. A class-kind hole is how that rule says *only a
; string*: given a number it does not match, and the second is tried.
@syntax "writeln" "(" x:string ")"             => "puts({x})"
@syntax "writeln" "(" x ")"                    => "printf(\"%d\\n\", {x})"

; The hole before a word must be able to stop at it. `i:name` is why this
; pattern's `:=` is the `for`'s and not the infix rule's -- a kind is how a
; hole says how far it reaches, and it is the one thing quoting does not do
; by itself.
@syntax "for" i:name ":=" a "to" b "do" s
    => "for (int {i} = {a}; {i} <= {b}; {i}++) {{ {s}; }}"

; A parameter list. Pascal writes `(a: integer; b: integer)` and C wants
; `(int a, int b)`, so the type has to appear once per parameter, and the group
; is where that happens: `join ", int "` puts the word back between the turns
; and the template writes the first one.
;
; That used to come out identical to the loop examples/code.mx uses, and this
; note used to say so and add that `join` "stops being enough the moment two
; parameters have different types". It does. `Scale(n: integer; k: real)` is in
; the program below, and this file writes
;
;     void Scale(int n, int k)
;
; where `double k` was declared. **That is wrong output and it is recorded on
; purpose**, the same way this file's Pascal string literal is: `join` writes
; one word in front of every turn and cannot write a different one per turn, so
; the type hole `t` in the fragment above is read and then thrown away, because
; a string template splices each list joined and has no way to interleave two.
; examples/code.mx walks the two lists in step and gets `double k` right.
;
; So the parameter list, which was the one place in these two files where the
; code template bought nothing, is now the clearest place it buys something.
;
; `procedure` and `function` want the identical list, and before `@fragment`
; there was no way to say it once -- the two lines below were written out twice,
; and this file and examples/code.mx each did it. The fragment is spliced with
; `@params` where the list goes, and what the rules see afterwards is what they
; would have seen written by hand: the holes come with it, so the templates
; still splice `{p}`.
@fragment params = "(" [ p:name ":" t ]* sep ";" join ", int " ")"

@syntax "procedure" f:name @params ";" b
    => "void {f}(int {p}) {b}" terminated
@syntax "function" f:name @params ":" rt ";" b
    => "int {f}(int {p}) {b}" terminated

; Standard Pascal returns by assigning to the function's own name, and that is
; not reachable here: the `:=` inside a body is an ordinary rule and nothing
; tells it which function it is inside. A rule sees its own pattern and no
; context at all. So this reads Free Pascal's `Result`, which is a local answer
; to a local question and is the only kind this tool gives.
@syntax "Result" ":=" e                        => "return {e}"

@end

{ Everything below is Pascal. Nothing below is Metaxis's. }

program Fizz;

var
  total, mod: integer;
  i, n: integer;

{ A procedure and a function. Pascal separates parameter groups with `;` and
  C gives every parameter its own type, so one Pascal group becomes several
  C ones. }

procedure Show(n: integer);
begin
  writeln(n)
end;

procedure Scale(n: integer; k: real);
begin
  writeln(n)
end;

procedure Pair(a: integer; b: integer);
begin
  writeln(a + b)
end;

function Double(n: integer): integer;
begin
  Result := n * 2
end;

begin
  total := 0;
  mod := 3;
  for i := 1 to 20 do
    if (i mod mod = 0) and (i <> 9) then
      total := total + i
    else
      total := total - 1;
  if not (total > 100) then writeln('it''s middling') else writeln('big');
  if total > 30 then
    begin
      total := total + 1;
      writeln(total)
    end
  else
    writeln(total);

  { `until` says when to stop; C's `while` says when to go on. }
  n := 0;
  repeat
    n := n + 1
  until n > 3;
  writeln(n);

  { Pascal's arms do not fall through. C's do, so each one gains a `break`. }
  case n of
    1: writeln(11);
    4: writeln(44)
  else
    writeln(0)
  end;

  Show(Double(total));
  Scale(7, 2);
  Pair(total, 2)
end.

(* Two things this file does not do. The first still stops the output being a
   program that runs; the second compiles and is simply wrong, which is worse
   and is why it is recorded rather than left to be discovered.

   The apostrophe string reaches the output as Pascal spelled it --
   puts('it''s middling') -- because a `string` hole splices the source text it
   matched and nothing translates a literal unless a rule says so. C wants
   double quotes. Everything else here compiles: the declarations are declared,
   main is main, and tests/pascal.sh builds and runs it. That one literal is
   what fails, and it fails at the compiler rather than in a diff, which is the
   difference the whole target language was chosen for.

   It cannot be fixed where it stands, either. A rule cannot match a bare token,
   so there is no @syntax that rewrites a string wherever one appears; the
   translation has to happen in a rule that has a word in it, which is what
   examples/code.mx does inside `writeln`. This is a string template being
   honest: the tool moved the token, it did not understand it.

   The second is the type in a parameter list. `Scale(n: integer; k: real)`
   comes out as void Scale(int n, int k), because `join ", int "` writes one
   word in front of every turn and a string template has no way to write a
   different one per turn. It compiles. It links. It runs. And `k` is the wrong
   type, which no compiler here will say and no diff would have questioned --
   so examples/pascal.out carries it deliberately, and the diff against
   examples/code.out, which gets `double k`, is the argument.

   The two failures are worth telling apart. The literal is a thing this
   notation *cannot express*. The type is a thing it expresses *wrongly and
   quietly*, and only the second kind needs to be written down, because the
   first announces itself.

   This note is in a Pascal comment because it is below @end, and below @end
   the only comments are the ones this file declared. *)
examples/pascal.out
/* Fizz */
#include <stdio.h>
int total, mod;
int i, n;
void Show(int n) {
printf("%d\n", n);
}
void Scale(int n, int k) {
printf("%d\n", n);
}
void Pair(int a, int b) {
printf("%d\n", (a + b));
}
int Double(int n) {
return (n * 2);
}
int main(void) {
total = 0;
mod = 3;
for (int i = 1; i <= 20; i++) { if (((((i % mod) == 0)) && ((i != 9)))) { total = (total + i); } else { total = (total - 1); }; };
if ((!((total > 100)))) { puts('it''s middling'); } else { puts('big'); };
if ((total > 30)) { {
total = (total + 1);
printf("%d\n", total);
}; } else { printf("%d\n", total); };
n = 0;
do {
n = (n + 1);
} while (!((n > 3)));
printf("%d\n", n);
switch (n) {
case 1: printf("%d\n", 11); break;
case 4: printf("%d\n", 44); break;
default: printf("%d\n", 0); break;
}
Show(Double(total));
Scale(7, 2);
Pair(total, 2);
return 0;
}

python

Python in, C out: stage 3, and a block that is an indentation. @separator "\n" indent gives the lexer a stack of columns and a block hole reads what it emits: the one delimiter in the notation that is not a string, because an indent is not text anybody wrote. Its body is real Python, and tests/python.sh runs it under python3 as well as compiling the C, so the two answers can be compared

examples/python.mx
; python.mx -- stage 3. A language whose blocks are an indentation, into C.
;
; Everything below the header is Python. It runs under python3 and prints the
; same three numbers this file's C prints, which is what tests/python.sh checks
; both halves of.
;
; **The one new thing is `@separator "\n" indent` and the `block` kind.** A
; block has no closing word to stop a `stmts` hole at -- no `}`, no `end` -- so
; the lexer keeps an indent stack and emits two tokens no file spells, and a
; `block` hole owns them both. They carry no text, so no quoted word can name
; one, which is why this is a kind and not a pair of strings: a string here
; would be quoting text the source does not contain, and the one rule this
; notation rests on is that a quoted thing is text you can find in the file.
;
; What that bought, and did not have to be taught: `b:block "else"` works,
; because a block ends itself and so is not greedy the way a `stmts` hole is.
; Nesting works, because an inner block consumes its own dedent before the
; outer hole sees it. And a blank or comment-only line closes nothing, because
; indentation is only ever measured on the line that carries the next token.

@comment "#" eol

@token number "[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"
@token string "\"[^\"]*\""

; `indent` is a word on the directive that already owns what separates
; statements, and it needs a separator with a newline in it -- indentation is
; what a line break leads to. Out: C's `;` between two statements.
@separator "\n" => ";\n" indent

; A call wrapped onto a second line: between `(` and its `)` the lexer treats
; a newline as whitespace and measures no indentation, so the last `print`
; below reads. The lexer can count brackets only because this line named them.
@bracket "(" ")"
@syntax "(" e ")"                              => { emit "(" + e + ")" }
; A code template is handed the group's turns as a *list* and joins them
; itself, which is what `for … sep` is for; `join` is the string template's
; half of the same job and is left off here because nothing would read it.
; examples/code.mx writes `emit a + "(" + x + ")"` for the same pattern and
; gets its commas from an infix `,` rule that Pascal's declarations needed
; anyway -- Python has no such rule, so this one does the joining in the open.
@syntax f "(" [ x ]* sep "," ")"               95
    => {
        emit f + "("
        for a in x sep ", " { emit a }
        emit ")"
    }

@syntax a "==" b                  40           => { emit group(a, 40) + " == " + group(b, 41) }
@syntax a "!=" b                  40           => { emit group(a, 40) + " != " + group(b, 41) }
@syntax a "<"  b                  40           => { emit group(a, 40) + " < "  + group(b, 41) }
@syntax a ">"  b                  40           => { emit group(a, 40) + " > "  + group(b, 41) }
@syntax a "+"  b                  60           => { emit group(a, 60) + " + "  + group(b, 61) }
@syntax a "-"  b                  60           => { emit group(a, 60) + " - "  + group(b, 61) }
@syntax a "*"  b                  70           => { emit group(a, 70) + " * "  + group(b, 71) }
@syntax a "//" b                  70           => { emit group(a, 70) + " / "  + group(b, 71) }
@syntax a "%"  b                  70           => { emit group(a, 70) + " % "  + group(b, 71) }
@syntax "not" a                   80           => { emit "!" + group(a, 80) }
@syntax a "and" b                 30           => { emit group(a, 30) + " && " + group(b, 31) }
@syntax a "or"  b                 25           => { emit group(a, 25) + " || " + group(b, 26) }

; A module maps to a header, one quoted rule per module, for the reason
; examples/code.mx gives about a Pascal type: a hole would splice the module's
; own name and C's header is not called that. Python's `print` writes to
; `sys.stdout`, so this mapping is the honest one rather than a stand-in.
@syntax "import" "sys"                         => { emit "#include <stdio.h>" } terminated

; Assignment and declaration are two rules, and Python is what tells them
; apart. C needs `int x = 0` the first time and `x = 0` after, and nothing here
; knows which time it is -- that wants a symbol table, the same wall
; `writeln` and a parameterless call sit against in stage 1. Python's own
; annotated assignment says it, so the example writes it and nothing is faked.
@syntax n:name "=" v              5 right      => { emit n + " = " + v }
@syntax n:name ":" "int" "=" v    5 right      => { emit "int " + n + " = " + v }

@syntax "return" e                             => { emit "return " + e }
@syntax "print" "(" x ")"                      => { emit "printf(\"%d\\n\", " + x + ")" }

; The three block rules. `terminated(b)` is the same question examples/code.mx
; asks of `begin … end`: @separator puts a `;` *between* two statements and
; never after the last, so the `}` would otherwise close over an unterminated
; one. A block answers for its last statement, exactly as a `stmts` hole does.
@syntax "while" c ":" b:block
    => {
        emit "while (" + c + ") {\n" + b
        if not terminated(b) { emit ";" }
        emit "\n}"
    } terminated

@syntax "if" c ":" b:block
    => {
        emit "if (" + c + ") {\n" + b
        if not terminated(b) { emit ";" }
        emit "\n}"
    } terminated

; A block followed by a word. This is the shape a `stmts` hole could never have
; had without a closing word to stop at, and it needs no new machinery: the
; block stops itself, so `"else"` is an ordinary next element.
@syntax "if" c ":" b:block "else" ":" e:block
    => {
        emit "if (" + c + ") {\n" + b
        if not terminated(b) { emit ";" }
        emit "\n} else {\n" + e
        if not terminated(e) { emit ";" }
        emit "\n}"
    } terminated

; Python's annotations are what give C its types, which is the same trade the
; declaration above makes: the type is in the source or it is nowhere.
@syntax "def" f:name "(" [ p:name ":" "int" ]* sep "," ")" "->" "int" ":" b:block
    => {
        emit "int " + f + "("
        for x in p sep ", " { emit "int " + x }
        emit ") {\n" + b
        if not terminated(b) { emit ";" }
        emit "\n}"
    } terminated

; Python's own way of saying where the program starts, so C's `main` is read
; off the source rather than assumed. Tried before the plain `if` because it is
; the longer pattern, which is the same rule that settles the dangling else.
@syntax "if" "__name__" "==" m:string ":" b:block
    => {
        emit "int main(void) {\n" + b
        if not terminated(b) { emit ";" }
        emit "\nreturn 0;\n}"
    } terminated
@end
import sys


def twice(n: int) -> int:
    return n * 2


def clamp(n: int, hi: int) -> int:
    if n > hi:
        return hi
    return n


if __name__ == "__main__":
    total: int = 0
    i: int = 1
    while i < 20:
        # a comment-only line inside a block closes nothing, and neither does
        # the blank one below it

        if i % 3 == 0 and i != 9:
            total = total + i
        else:
            total = total - 1
        i = i + 1
    print(total)
    print(twice(total))
    print(clamp(twice(total),
                50))

# ------------------------------------------------------------------ the note
#
# What this file gets right, and what it does not. Four things, and the first
# two are the point of the exercise.
#
#   The blocks.  Nesting, `else`, blank and comment-only lines inside a block,
#                and a dedent that closes two blocks at once. None of it needed
#                a rule to be told about it: an inner block consumes its own
#                dedent, so the outer hole never sees it.
#
#   The types.   C's are read off Python's annotations -- `n: int`, `-> int`,
#                `total: int = 0` -- so a declaration and a reassignment are two
#                rules that the *source* tells apart. Nothing is guessed. Take
#                the annotations away and this file cannot be written, which is
#                the honest form of the wall stage 1 met from three directions.
#
#   `elif`.      Not read. Python spells it as its own word, so each arm count
#                is a rule of its own, and that is the shape docs/ROADMAP.md 6
#                describes and declines to build for.
#
#   A wrapped call.  `f(a,` newline `b)` was not read until 2026-09-07: Python's
#                lexer suppresses the newline inside brackets and this one did
#                not. It does now, under `@bracket "(" ")"`, and the last
#                `print` above is wrapped to show it.
#
# And one the compiler found. This file's Python says `twice` because a
# function called `double` translates to a C function called `double`, and a
# rewriter that moves tokens has no idea that the word it just copied is a
# keyword in the language it is writing. Nothing here can see that, and the
# only honest response is that the example does not do it.
examples/python.out
#include <stdio.h>
int twice(int n) {
return n * 2;
}
int clamp(int n, int hi) {
if (n > hi) {
return hi;
}
return n;
}
int main(void) {
int total = 0;
int i = 1;
while (i < 20) {
if (i % 3 == 0 && i != 9) {
total = total + i;
} else {
total = total - 1;
}
i = i + 1;
}
printf("%d\n", total);
printf("%d\n", twice(total));
printf("%d\n", clamp(twice(total), 50));
return 0;
}

basic

BASIC in, C out: stage 4, and a source that declares nothing. A line number is the left operand of its statement, FOR and NEXT are two statements the way BASIC means them, and the type of a variable is the sigil on its name. The declarations C wants first are the aggregate of every line below, and the LET and FOR that meet a name contribute its declaration to a collection that leads the output; tests/basic.sh compiles the result with nothing supplied but main

examples/basic.mx
; basic.mx -- BASIC in, C out. Stage 4, and the first source here that
; declares nothing.
;
; Pascal says `var total: integer` before it uses `total`, and stage 1 turned
; that line into C's. BASIC has no such line: a variable exists because a
; statement somewhere mentions it, and its type is the sigil on its name --
; `T` is a number and `A$` is a string. C wants every one of them declared at
; the top, before the first statement, and *which* ones is the aggregate of
; every LET, FOR and PRINT below. That is the customer this stage was picked
; to be: **the head of the output is determined by its body**, and no rule
; here can see past itself.
;
; **It can contribute, though.** `contribute("vars", "int T;")` in a rule adds
; a line to a collection, once however many times it is said, and a
; collection nobody splices goes at the start of the output -- which is where
; a program with no head wants it. So the declarations below come out of the
; LET and FOR that mention the variables, and tests/basic.sh compiles the
; result with nothing supplied but `main` and the include, which are not
; rules. Until 2026-09-06 that test wrote the declarations by hand and was
; pinned to fail the day this file started writing them; it did, and the pin
; flipped in the same commit. docs/COMPLETED.md has the mechanism and the
; rehearsal that settled its shape.
;
; **What the sigil buys.** Stage 1 stopped at `writeln` because printing a
; value means knowing its type, and nothing here has types. BASIC's answer is
; older than the question: the type is spelled on the name. `print` below
; reads it off -- a `$` means a string, a `"` means a literal, and anything
; else is a number -- so the wall stage 1 hit is not here, by choice of
; source and not by any new mechanic.
;
; **Line numbers** are the left operand of every statement. `10 LET T = 0`
; reads as the number 10 followed by an infix `LET` -- a led rule whose left
; hole is the line and whose word is the keyword -- which is what a Pratt
; parser makes of it without being told anything. Every line becomes a C
; label, so `GOTO 80` is `goto L80`. Which lines are jumped *to* is the same
; aggregate question as the declarations, met from the other side, and this
; file answers it by labelling every line; a C compiler does not mind.
;
; **FOR and NEXT are two statements**, not one with a body, because that is
; what they are in BASIC -- `GOTO 70` below jumps to a NEXT from inside its
; loop and BASIC allows it. So FOR opens a brace and NEXT closes one, the way
; examples/asm.mx emits a sequence rather than a tree, and the C nests because
; the BASIC did.
;
; **What is not here, and says so.** REM is not read: the comment would win
; over the word and leave the line number standing alone as a statement, so
; the body comments with `'`, which several BASICs accept. GOSUB wants a
; return stack C does not have. Strings are assigned and printed, never
; joined. And `main` and the include, which the test wraps around the output
; the way tests/hygiene.sh does, because neither is a rule.

@comment "'" eol

@token number "[0-9]+"
@token name   "[A-Z][A-Z0-9]*[$]?"
@token string "\"[^\"]*\""

@separator "\n" => ";\n"

; Expressions. `=` is comparison here and assignment only after LET, where
; it is a word in the pattern and never reaches these rules. NOT binds looser
; than a comparison, which is BASIC's rule and not C's: `NOT T > 30` means
; `NOT (T > 30)`, and C's `!` binds tighter than anything on this ladder, so
; its operand is bracketed unless it is an atom -- `group(a, 80)`, one above
; the top. `group(a, 35)` was the first draft, and it wrote `!T > 30`.
@syntax "(" e ")"           => { emit "(" + e + ")" }
@syntax a "OR" b        25  => { emit group(a, 25) + " || " + group(b, 26) }
@syntax a "AND" b       30  => { emit group(a, 30) + " && " + group(b, 31) }
@syntax "NOT" a         35  => { emit "!" + group(a, 80) }
@syntax a "=" b         40  => { emit group(a, 40) + " == " + group(b, 41) }
@syntax a "<>" b        40  => { emit group(a, 40) + " != " + group(b, 41) }
@syntax a "<" b         40  => { emit group(a, 40) + " < "  + group(b, 41) }
@syntax a ">" b         40  => { emit group(a, 40) + " > "  + group(b, 41) }
@syntax a "+" b         60  => { emit group(a, 60) + " + "  + group(b, 61) }
@syntax a "-" b         60  => { emit group(a, 60) + " - "  + group(b, 61) }
@syntax a "*" b         70  => { emit group(a, 70) + " * "  + group(b, 71) }
@syntax a "/" b         70  => { emit group(a, 70) + " / "  + group(b, 71) }
@syntax a "MOD" b       70  => { emit group(a, 70) + " % "  + group(b, 71) }

; A name in C: `A$` is not one, so the sigil becomes a suffix.
@template var(v) { emit replace(v, "$", "_s") }

; The declaration C wants, contributed by whichever rule mentions the name.
; The type is the sigil again. A collection keeps one copy of each line, so
; `T` said nine times is declared once, in the order names were first met.
@template decl(v) {
    if replace(v, "$", "") != v { contribute("vars", "const char *" + replace(v, "$", "_s") + ";") }
    else                        { contribute("vars", "int " + v + ";") }
}

; The type is on the name. A `$` is a string variable, a `"` is a string
; literal, and everything else is a number -- which is a rule about the
; *spelling* of the operand, the only thing a template can see, and it is
; exactly what BASIC's own designers made it.
@template print(x) {
    if replace(x, "$", "") != x or replace(x, "\"", "") != x {
        emit "puts("; var(x); emit ")"
    } else {
        emit "printf(\"%d\\n\", " + x + ")"
    }
}

; Each statement is written once, as a template, and read twice: with a line
; number in front of it, as a line, and bare, after THEN. The line form is a
; led rule -- the number is its left operand -- and the bare form is a nud.
@template let(v, e) { decl(v); var(v); emit " = " + e }
@template line(l)   { emit "L" + l + ": " }

@syntax l "LET" v:name "=" e   5  => { line(l); let(v, e) }
@syntax   "LET" v:name "=" e      => { let(v, e) }
@syntax l "PRINT" x            5  => { line(l); print(x) }
@syntax   "PRINT" x               => { print(x) }
@syntax l "GOTO" t:number      5  => { line(l); emit "goto L" + t }
@syntax   "GOTO" t:number         => { emit "goto L" + t }

; `THEN 60` is a jump and `THEN LET …` is a statement. The two patterns are
; the same length, so they are tried in this order, and a `number` hole
; refuses a keyword.
@syntax l "IF" c "THEN" t:number  5  => { line(l); emit "if (" + c + ") goto L" + t }
@syntax l "IF" c "THEN" s         5  => { line(l); emit "if (" + c + ") " + s }

; A label needs a statement after it and `}` is not one, hence the `;`.
@syntax l "FOR" i:name "=" a "TO" b  5
    => { line(l); decl(i); emit "for (" + i + " = " + a + "; " + i + " <= " + b + "; " + i + "++) {" } terminated
@syntax l "NEXT" i:name  5  => { line(l); emit "; }" } terminated

; END is the last line, so no separator follows it and the `;` is its own.
; The first draft left it off and the compiler said so on the first run.
@syntax l "END"          5  => { line(l); emit "return 0;" } terminated

@end

' Everything below is BASIC. Nothing below is Metaxis's.
' It computes what examples/pascal.mx computes, as far as the two overlap:
' the loop, then a count, then a string, then a branch.

10 LET T = 0
20 FOR I = 1 TO 20
30 IF I MOD 3 = 0 AND I <> 9 THEN 60
40 LET T = T - 1
50 GOTO 70
60 LET T = T + I
70 NEXT I
80 PRINT T

' A loop written with a jump, which is what BASIC has instead of `repeat`.
90 LET N = 0
100 LET N = N + 1
110 IF N < 4 THEN 100
120 PRINT N

' The type of A$ is its name.
130 LET A$ = "it's middling"
140 IF T > 100 THEN LET A$ = "big"
150 PRINT A$

' NOT binds looser than >, so this reads NOT (T > 30).
160 IF NOT T > 30 THEN 190
170 LET T = T + 1
180 PRINT T
190 END
examples/basic.out
int T;
int I;
int N;
const char *A_s;
L10: T = 0;
L20: for (I = 1; I <= 20; I++) {
L30: if (I % 3 == 0 && I != 9) goto L60;
L40: T = T - 1;
L50: goto L70;
L60: T = T + I;
L70: ; }
L80: printf("%d\n", T);
L90: N = 0;
L100: N = N + 1;
L110: if (N < 4) goto L100;
L120: printf("%d\n", N);
L130: A_s = "it's middling";
L140: if (T > 100) A_s = "big";
L150: puts(A_s);
L160: if (!(T > 30)) goto L190;
L170: T = T + 1;
L180: printf("%d\n", T);
L190: return 0;

island

stage 5: C in, C out, over a file not written for it. The rules in lib/island.mx turn one fprintf shape into a call and insert its definition, and leave everything else alone; tests/island.sh points them at metaxis/cmd/mx.c itself, compiles what comes out against the tool's own objects, and runs it. A third rule renames a variable and leaves outpath, a string and a comment alone, because the file declares C's tokens as classes and the scan moves by them; a fourth puts text after a hole over a nested call, which is right because the file declares C's brackets. Text mode was an island grammar all along

examples/island.mx
; island.mx -- the rules in lib/island.mx, over a body that is C and not a
; program written for a grammar. The body below is four lines shaped like
; the ones in metaxis/cmd/mx.c; tests/island.sh points the same rules at
; that file itself, compiles what comes out against the tool's own objects,
; and runs the result. Stage 5.
;
; Three of the four `fprintf` lines change and one does not: the last is a
; different format string, and the rule's pattern is the whole call up to the
; argument, so it passes through. The third is the nested case: the hole is
; all of `f(x, g(y))`, because lib/island.mx declares C's brackets. Before
; it did, the hole stopped at the first `)` and the result was right only
; because the template kept the hole last, with the leftover `))` copied
; through behind it. The `!(` line is where that shows: its template puts
; text after the hole, and the hole crosses `fopen(outpath, "wb")` whole.
;
; The rename is the other thing to watch. `out` becomes `res` once, in
; `fputs(out, f)`, and is left alone three times: inside `outpath`, inside
; the usage string's `output`, and inside the comment on the first line.
; The four @token classes in lib/island.mx are what make that so -- without
; them the word fired at every one -- and the `[` rule shows a class hole in
; text mode taking exactly one name, and refusing `12`. The `out` inside it
; is not renamed: a class hole is spliced as its source text, as it is in
; expression mode, and only a text hole is expanded in its turn.

@use "../lib/island.mx"
@syntax "[" x:name "]" => "<{x}>"
@end
/* mx.c -- mx [-o out] file.mx, and [err] is what it says when it cannot */
static void usage(void)
{
    if (!src) { fprintf(stderr, "mx: %s\n", err); return 1; }
    fprintf(stderr, "mx: %s\n", f(x, g(y)));
    fprintf(stderr, "mx: cannot write %s\n", outpath);
    if (outpath && !(f = fopen(outpath, "wb"))) return 1;
    fputs("usage: mx [-o output]\n", stderr); fputs(out, f); [out] = [12];
}
examples/island.out
/* mx.c -- mx [-o out] file.mx, and [err] is what it says when it cannot */
static void complain(const char *e) { fprintf(stderr, "mx: %s\n", e); }

static void usage(void)
{
    if (!src) { complain(err); return 1; }
    complain(f(x, g(y)));
    fprintf(stderr, "mx: cannot write %s\n", outpath);
    if (outpath && (f = fopen(outpath, "wb")) == 0) return 1;
    fputs("usage: mx [-o output]\n", stderr); fputs(res, f); <out> = [12];
}

cpp

stage 6: a C preprocessor in text mode, over a small program and the header cpp.h it includes. #define remembers a body under a name, #undef forgets it, a rule on every call binds a macro's arguments to its parameters through the store, a rule led by a class hole fires on every identifier and emits an argument, a body rescanned, or the identifier itself, and #ifdef takes one arm as a raw hole and drops the other unread. It is the first customer of the store, REFERENCE §8.5, the one mechanism by which a rule's output depends on a rule that ran before it; tests/cpp.sh compiles what comes out and compares it with the C compiler's own preprocessor on the same body. The file's note says what it declares rather than reads

examples/cpp.mx
; cpp.mx -- a C preprocessor, in text mode. Stage 6, the first cut.
;
; C in, C out, and the mechanic it drives is the store (REFERENCE §8.5).
; A preprocessor is the smallest real program whose entire job is a symbol
; table: `#define` puts a name in it and every later line is read against
; it, in order, so a macro defined below its use is not expanded. That is
; exactly what a rule could not do before 2026-09-07 -- a rule saw its own
; pattern and nothing else -- and what `remember` and `recall` are for.
;
; The rules. `#define NAME body` remembers the body under the name and
; emits nothing, so the line goes; `#define NAME(a, b) body` remembers a
; body and its parameters; `#undef NAME` forgets; a rule on every call binds
; a macro's arguments to its parameters through the store and expands the
; body; a rule led by a class hole, `x:name`, fires on every identifier
; in the file and emits an argument, a macro's body, or the identifier
; itself, in that order of preference; and `#ifdef`, `#ifndef`, `#else`
; and `#endif` take one arm and drop the other unread. The four
; classes are C's, as in lib/island.mx, so that a macro name inside a string,
; a character literal or a comment is a token the scan passes over and never
; a name the third rule sees; the three brackets keep a hole over `(a, b)`
; whole.
;
; What this file does not do, said plainly. A directive wants
; exactly one space after its word and before its body, because text mode
; matches a word byte for byte; `#  define` and a tab do not read, and a
; function-like macro's parameters are written `(a, b)` with that one space.
; An argument that forms a call with the text after it, `CALL(F)` for
; `#define CALL(f) f(1)`, is not rescanned as a call, where cpp would expand
; `F(1)`. A conditional's word ends its line, so `#ifdef X` reads and
; `#ifdef X /* why */` does not; there is no `#if` over an expression and
; no `#elif`. An included file is read once per include and is not searched
; for on any path but beside this file. `#x` quotes the argument after it
; has been expanded, where cpp quotes it as written, so `STR(LIMIT)` is
; `"10"` here and `"LIMIT"` there; and an argument keeps the whitespace it
; was written with, so `#x` of ` hello` has the leading space. Everything
; the item listed is read; what the tool had to grow for it is in
; docs/COMPLETED.md, and each thing above is a rule, not a change to it.
; `#include <stdio.h>` is not a directive this file knows, so it goes
; through whole, which is right: the output is C for a compiler that has a
; preprocessor of its own.
;
; tests/cpp.sh runs the body below through this file and compiles what comes
; out, and runs the same body through the C compiler directly, whose own
; preprocessor is the oracle: the two programs must print the same three
; lines, and the first must contain no `#define`, no `#undef` and no macro
; name outside the one string that mentions one.

@mode text

@token name    "[A-Za-z_][A-Za-z0-9_]*"
@token string  "\"([^\"\\\\]|\\\\.)*\""
@token char    "'([^'\\\\]|\\\\.)*'"
@token comment "/\\*([^*]|\\*+[^*/])*\\*+/"

@bracket "(" ")"
@bracket "[" "]"
@bracket "{" "}"

; The conditionals nest, so an arm has to run to its own `#endif` and not
; the first one it meets, which is what a declared bracket gives a hole.
; Two opens share the close, since a close balances whichever open stands
; behind it.
@bracket "#ifdef"  "#endif"
@bracket "#ifndef" "#endif"

; A function-like macro remembers its body under `fn:` and each parameter
; under its position. Declared before the object-like rule, which shares
; its first word: `#define F(x)` has `(` where the other wants a space.
; The body is a `raw` hole, kept as written and expanded at each use by the
; rules below, which is cpp's order: `#define TOTAL (LIMIT * STEP)` names
; whatever `STEP` means where `TOTAL` is used, not where it was defined.
@syntax "#define " n:name "(" [ p:name ]* sep ", " ")" " " b:raw "\n" => {
    remember("fn:" + n, b)
    for i, q in p { remember("param:" + n + ":" + i, q) }
}
@syntax "#define " n:name " " b:raw "\n" => { remember("def:" + n, b) }
@syntax "#define " n:name "\n"           => { remember("def:" + n, "") }
@syntax "#undef " n:name "\n"        => { forget("def:" + n); forget("fn:" + n) }

; `#include "file"` is the file's text, found beside this one, run through
; these rules in its turn: a `#define` in it is known to the lines after the
; include, and a macro used in it is expanded before its text is emitted.
; The string class takes the quoted name whole; `drop` takes the quotes off.
; `#include <stdio.h>` has no rule and goes through for the C compiler.
@syntax "#include " f:string "\n" => { emit expand(read(drop(f, 1, 1))) }

; Conditional inclusion. Each arm is a `raw` hole, so a `#define` inside the
; arm not taken never fires; the arm taken is handed to `expand`, and a
; conditional nested inside it fires then, in its turn. The variant with an
; `#else` is declared first: for a conditional without one, its `t` reaches
; the rule's own `#endif`, which a hole may not span, so that variant fails
; and the next is tried. `defined` in the C sense is `known` under either
; key, since a function-like macro is defined too.
@syntax "#ifdef " n:name "\n" t:raw "#else\n" f:raw "#endif\n"
    => { if known("def:" + n) or known("fn:" + n) { emit expand(t) } else { emit expand(f) } }
@syntax "#ifdef " n:name "\n" t:raw "#endif\n"
    => { if known("def:" + n) or known("fn:" + n) { emit expand(t) } }
@syntax "#ifndef " n:name "\n" t:raw "#else\n" f:raw "#endif\n"
    => { if known("def:" + n) or known("fn:" + n) { emit expand(f) } else { emit expand(t) } }
@syntax "#ifndef " n:name "\n" t:raw "#endif\n"
    => { if not (known("def:" + n) or known("fn:" + n)) { emit expand(t) } }

; A call. Every call in the file fires this, and one that is not a macro is
; written back as it was. One that is binds each argument to its parameter's
; name and expands the body, and the binding *is* the store: the identifier
; rule below looks a name up as an argument before it looks it up as a
; macro. The bindings are keyed by a depth the file counts, because a body
; may call another macro whose parameter has the same name, `TWICE(x)`
; calling `DOUBLE(x)` below: the inner call's arguments are expanded before
; its template runs, at the outer depth, and its own bindings live one
; deeper and are gone before the outer body's next `x`. That is cpp's
; order, arguments first and the body after. The `busy:` key is the same
; guard the object-like rule has.
; The two operators, meaningful inside a function-like body. `#x` is the
; argument bound to `x`, quoted; a `#` before anything else, `#include
; <stdio.h>` say, goes through as it was, since every directive's own word
; is longer and was tried first. `a ## b` pastes the two sides and rescans
; the result, so `GLUE(LIM, IT)` is `LIMIT` and then 10; each side is run
; through the rules to resolve it as an argument, and its spaces dropped,
; since what pasting makes is one token. Declared before the call and the
; identifier rules, which would otherwise take the left side first.
@syntax "#" x:name => {
    if known("depth") and known("arg:" + recall("depth") + ":" + x) {
        emit "\"" + recall("arg:" + recall("depth") + ":" + x) + "\""
    } else { emit "#" + x }
}
@syntax x:name " ## " y:name => { emit expand(replace(expand(x), " ", "") + replace(expand(y), " ", "")) }
@syntax x:name "##" y:name   => { emit expand(replace(expand(x), " ", "") + replace(expand(y), " ", "")) }

@syntax n:name "(" [ a ]* sep "," ")" => {
    if known("fn:" + n) and not known("busy:" + n) {
        if known("depth") { remember("depth", num(recall("depth")) + 1) } else { remember("depth", 1) }
        for i, v in a { remember("arg:" + recall("depth") + ":" + recall("param:" + n + ":" + i), v) }
        remember("busy:" + n, "1")
        emit expand(recall("fn:" + n))
        forget("busy:" + n)
        for i, v in a { forget("arg:" + recall("depth") + ":" + recall("param:" + n + ":" + i)) }
        remember("depth", num(recall("depth")) - 1)
    } else {
        emit expand(n) + "("
        for v in a sep "," { emit v }
        emit ")"
    }
}

; A name that is a macro is replaced by its body run through these rules
; again, which is cpp's rescan: `#define A B` then `#define B 5` gives `A`
; the value 5, because `B` is looked up when `A` is used and not when it was
; defined. cpp's rule for a body that names its own macro is that the name
; is not expanded again while it is being expanded, which is what stops
; `#define SELF SELF`; here that is a `busy:` key remembered for the
; duration and forgotten after. Without it the depth cap would stop it
; instead, at 64, with an error.
@syntax x:name => {
    if known("depth") and known("arg:" + recall("depth") + ":" + x) {
        emit recall("arg:" + recall("depth") + ":" + x)
    } else {
        if known("def:" + x) and not known("busy:" + x) {
            remember("busy:" + x, "1")
            emit expand(recall("def:" + x))
            forget("busy:" + x)
        } else { emit x }
    }
}

@end
#include <stdio.h>
#include "cpp.h"

#define LIMIT 10
#define STEP 3
#define TOTAL (LIMIT * STEP)
#define GREETING "limit=%d step=%d total=%d\n"
#define A B
#define B 5
#define SELF SELF
#define DOUBLE(x) ((x) * 2)
#define ADD(a, b) ((a) + (b))
#define SEVEN() 7
#define TWICE(x) (DOUBLE(x) + x)
#define STR(x) #x
#define GLUE(a, b) a ## b
#define DEBUG
#ifdef DEBUG
#define LEVEL 2
#else
#define LEVEL 0
#endif
#ifndef DEBUG
#define LEVEL 9
#endif
#ifdef NOTDEFINED
#define LEVEL 8
#else
#ifdef DEBUG
#define MODE "nested"
#endif
#endif

int main(void)
{
    int limit = LIMIT;
    printf(GREETING, limit, STEP, TOTAL);
    printf("LIMIT is not a macro inside a string\n");
#undef STEP
    int STEP = 7;                 /* an ordinary variable now, STEP being undefined */
    printf("%d\n", STEP + LIMIT);
    printf("%d\n", TOTAL);        /* 70: its body is read here, where the name it multiplies by is the variable */
    int SELF = 4;                 /* a macro that names itself expands once and stops, as cpp stops */
    printf("%d %d\n", A, SELF);   /* the first is 5: its body names a macro defined after it, looked up at use */
    printf("%d %d %d %d\n", DOUBLE(LIMIT), ADD(1, DOUBLE(2)), SEVEN(), TWICE(3));
    printf("%d %s\n", LEVEL, MODE);   /* 2 nested: the arms taken, the ones not taken never defining anything */
    printf("%d\n", helper(TWICE(1)));  /* 103: the function and the constant it adds come from the included file */
    int value = 5;
    printf("%d %s %d\n", GLUE(val, ue), STR(hello world), GLUE(LIM, IT));  /* pasted, quoted, and pasted then expanded */
    return 0;
}
examples/cpp.out
#include <stdio.h>
/* cpp.h -- included by the body of examples/cpp.mx with #include "cpp.h".
   Its text goes through the same rules it was included into, so the macro
   it defines is known to the lines after the include, and the macro's use
   inside the function below is expanded before the function is emitted. */
static int helper(int x) { return x + 100; }


int main(void)
{
    int limit = 10;
    printf("limit=%d step=%d total=%d\n", limit, 3, (10 * 3));
    printf("LIMIT is not a macro inside a string\n");
    int STEP = 7;                 /* an ordinary variable now, STEP being undefined */
    printf("%d\n", STEP + 10);
    printf("%d\n", (10 * STEP));        /* 70: its body is read here, where the name it multiplies by is the variable */
    int SELF = 4;                 /* a macro that names itself expands once and stops, as cpp stops */
    printf("%d %d\n", 5, SELF);   /* the first is 5: its body names a macro defined after it, looked up at use */
    printf("%d %d %d %d\n", ((10) * 2), ((1) + ( ((2) * 2))), 7, (((3) * 2) + 3));
    printf("%d %s\n", 2, "nested");   /* 2 nested: the arms taken, the ones not taken never defining anything */
    printf("%d\n", helper((((1) * 2) + 1)));  /* 103: the function and the constant it adds come from the included file */
    int value = 5;
    printf("%d %s %d\n", value, "hello world", 10);  /* pasted, quoted, and pasted then expanded */
    return 0;
}

poem

@mode text: prose in, HTML out

examples/poem.mx
; poem.mx -- @mode text. The body is prose, not a program: a rule fires where
; it matches and everything else is copied through untouched.
;
; Same directives, same rule about quoting. The only difference is what happens
; to text no rule claimed -- an error in expression mode, output in this one.

@mode text
@comment "%%" eol

@syntax "//" t:text "//"          => "<em>{t}</em>"
@syntax "**" t:text "**"          => "<strong>{t}</strong>"
@syntax "[[" t:text "|" u:text "]]" => "<a href=\"{u}\">{t}</a>"

; A link with no label, declared second so the labelled form is tried first.
; This pair is the regression test for the rule two lines down: on `[[here]]`
; the labelled form has to *fail*, and before 2026-09-04 it did not -- `t` went
; looking for a `|` and found one three lines away, swallowing the `]]` and
; everything between. A hole stops at the earliest of every word still to come
; in its pattern, and a `]]` reached before the `|` means this construct has
; already ended.
@syntax "[[" t:text "]]"            => "<a href=\"{t}\">{t}</a>"
@syntax "~"                       => "&nbsp;"

; Three dashes, declared shortest first on purpose. Text mode munches the way
; the lexer does -- the longest leading word that matches wins, and the order
; they were declared in breaks a tie between two of the same length and decides
; nothing else. Declared in this order and picked in the other one.
; A part that need not be there, and a part that repeats -- in prose. Until
; groups reached text mode the first of these was two rules and the second was
; not writable at all. Both use the other kind of template, because both need to
; ask something: whether the optional part was there, and what each turn was.
@syntax "![" alt:text "](" src:text [ " " title:text ] ")"
    => {
        emit "<img src=\"" + src + "\" alt=\"" + alt + "\""
        if matched(title) { emit " title=" + title }
        emit ">"
    }

@syntax "{{" n:text [ "," a:text ]* "}}"
    => {
        emit "<call name=\"" + n + "\">"
        for x in a { emit "<arg>" + x + "</arg>" }
        emit "</call>"
    }

@syntax "-"                       => "&#8209;"
@syntax "--"                      => "&ndash;"
@syntax "---"                     => "&mdash;"

; Three of these are worth a second look.
;
; `"|"` inside the link pattern is the character Proto keeps for a block's
; parameters and can never hand to a file. Here it is a separator inside one
; rule's pattern and means nothing anywhere else.
;
; `"~"` is a pattern with a word and no hole at all -- neither prefix, infix
; nor anything Proto has a directive for.
;
; `"<a href=\"{u}\">{t}</a>"` is the whole argument in one line. The template
; contains quotes; it is a string; a string escapes a quote as `\"`; and there
; is no second reader, no second rule and no place where knowing what kind of
; thing you are looking at comes before finding where it ends.

@end
%% This line is a comment and does not reach the output.

The //quick// brown fox jumps over the **lazy** dog.
He was late --- twelve~o'clock, and the gate still shut.
See [[the manual|https://example.com/manual]] for the rest.
Or just [[https://example.com]], and a stray | pipe well after it.
Pages 3-4, an aside -- and a longer one --- than that.
![a cat](cat.png) beside ![a dog](dog.png "Good dog").
Shortcodes {{plain}} and {{sum,one,two,three}} in one rule each.

Nested, because a hole's text is expanded in its turn:
**a //loud and slanted// claim**.
examples/poem.out
The <em>quick</em> brown fox jumps over the <strong>lazy</strong> dog.
He was late &mdash; twelve&nbsp;o'clock, and the gate still shut.
See <a href="https://example.com/manual">the manual</a> for the rest.
Or just <a href="https://example.com">https://example.com</a>, and a stray | pipe well after it.
Pages 3&#8209;4, an aside &ndash; and a longer one &mdash; than that.
<img src="cat.png" alt="a cat"> beside <img src="dog.png" alt="a dog" title="Good dog">.
Shortcodes <call name="plain"></call> and <call name="sum"><arg>one</arg><arg>two</arg><arg>three</arg></call> in one rule each.

Nested, because a hole's text is expanded in its turn:
<strong>a <em>loud and slanted</em> claim</strong>.

reserved

every character Metaxis writes a directive with, declared as an operator by a directive: @, =>, ., :, <, >, ", {, }

examples/reserved.mx
; reserved.mx -- nothing is reserved, including Metaxis's own spellings.
;
; The point of the quoting rule is that a directive cannot be read as the thing
; it declares. The test of it is to declare, one at a time, every character
; Metaxis uses to write a directive with.

@comment ";" eol
@token name "[A-Za-z_][A-Za-z0-9_]*"
@separator "\n"

@syntax a "@" b        50        => "{a}:at({b})"          ; the directive sigil
@syntax a "=>" b       15        => "{a}:then({{ {b} }})"  ; the template arrow
@syntax a "." m:name   95        => "{a}:{m}"              ; Proto's terminator
@syntax a ":" b        90        => "{a}:pair({b})"        ; the send colon
@syntax "<" a ">"                => "{a}:tagged"           ; Proto's hole brackets
@syntax a "\"" b       50        => "{a}:quote({b})"       ; the quote itself
@syntax a "{" b "}"    50        => "{a}:brace({b})"       ; and the splice braces

; `terminated` says a rule's output ends a statement on its own, and it sits
; after the template because that is the one place in a rule where a bare word
; cannot be a hole. So it reserves nothing, and this line is the proof: the hole
; is called `terminated` and the rule is `terminated`, four words apart.
@syntax "hold" terminated        => "{terminated}:held"    terminated

@end
env@home.print
ready => go.print
<x>.print
a " b
k{v}.print
hold x

; The last two are the only interesting cases.
;
; `"\""` is a string containing a quote, spelled the way every string in every
; language spells one. `"{"` and `"}"` are strings containing a brace; where a
; template needs a literal brace it writes `{{` and `}}`, which is the one
; extra escaping rule this notation adds and it is confined to templates.
;
; There is no rule beyond those two, no escape hatch, and no place where the
; reader has to know what kind of thing it is looking at before it can find the
; end of it. That is the argument: the boundary between what a directive says
; and what it says it about is a boundary the reader already had.
;
; The separator here is a newline, which is a declaration like any other and is
; why none of the five lines above ends in anything.
examples/reserved.out
env:at(home:print)
ready:then({ go:print })
x:tagged:print
a:quote(b)
k:brace(v):print
x:held

use

@use, taking its arithmetic from lib/arith.mx and keeping its own comment and separator, a diamond through lib/vector.mx, and an override of one of arith's rules

examples/use.mx
; use.mx -- a file whose arithmetic came from somewhere else.
;
; `@use` reads another file's directives into this one's header. The used file
; is looked for beside this one, holds directives and nothing else, is read
; once however many times it is reached, and stops at 64 deep. What it does not
; bring is a comment or a separator: this file declares those, because they are
; its own and not its arithmetic's.

@use "../lib/arith.mx"
@use "../lib/vector.mx"     ; which uses arith.mx as well -- a diamond, and
                            ; a file is read once, so that costs nothing

@comment "#" eol
@separator ";" => ".\n"

; Two files declaring one thing is refused, and this is how a file says it
; meant it: `/` already came in from arith.mx, and the word after the template
; says so. Without `override` this line is an error naming both declarations;
; with it, this one wins and nothing is said, because it was said here.
@syntax a "/" b 70 => "{a}:idiv({b})" override

@end
x = 2 ^ 3 ^ 2;          # right, so 2 ^ (3 ^ 2) and not (2 ^ 3) ^ 2
y = -x + 4 * 5;         # the prefix `-` binds tighter than the infix one
(x - y):print;
<x, y>:show;            # from vector.mx, whose own arithmetic is this one
(9 / 2):print;          # `/` is this file's now, not the one arith.mx declared
examples/use.out
x := 2:pow(3:pow(2)).
y := x:negated:add(4:mul(5)).
(x:sub(y)):print.
vec(x, y):show.
(9:idiv(2)):print

code

=> { … }: examples/pascal.mx rule for rule, with the parenthesis noise gone, the literal translated, and the C indented. diff examples/pascal.out examples/code.out is the point, and tests/pascal.sh compiles this one and runs it

examples/code.mx
; code.mx -- the same Pascal as examples/pascal.mx, through the other kind of
; template.
;
;     diff examples/pascal.out examples/code.out
;
; is the whole argument for this file existing. The body below is character for
; character the body of pascal.mx. Every rule is the same rule. The only
; difference is that a template here is `=> { … }` rather than `=> "…"`, and
; two things pascal.out is recorded as getting wrong come out right:
;
;   the parentheses  pascal.out writes `(((((i % mod) == 0)) && ((i != 9))))`,
;                    because a string template can only bracket every operand
;                    unconditionally. Here a rule asks an operand what level it
;                    was parsed at and brackets it only when it must.
;
;   the literal      pascal.out writes `puts('it''s middling')` into C, because
;                    a `string` hole splices the source text it matched. Here
;                    the rule translates it.
;
; The language is Metaxis's own, so it lives outside the strings; the foreign
; text it emits lives inside them. That is the same rule the pattern side
; follows, which is why `{` after the `=>` was enough to tell the two forms
; apart and nothing had to be reserved.

@comment "{" "}"
@comment "(*" "*)"

@token number "[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"
@token string "'([^']|'')*'"

@separator ";" => ";\n"

; A group is an atom whatever is inside it, so nothing below ever needs to
; bracket it again.
@syntax "(" e ")"                              => { emit "(" + e + ")" }
@syntax a "(" [ x ]* sep "," join ", " ")"  95  => { emit a + "(" + x + ")" }

; `group(x, n)` is the operand `x`, bracketed when the rule that produced it
; binds looser than `n`. The right operand asks for one more than the left, so
; `a - b - c` keeps its grouping and `a - (b - c)` keeps its brackets.
@syntax a ":=" b                  10           => { emit a + " = " + b }
@syntax a "=" b                   40           => { emit group(a, 40) + " == " + group(b, 41) }
@syntax a "<>" b                  40           => { emit group(a, 40) + " != " + group(b, 41) }
@syntax a "<" b                   40           => { emit group(a, 40) + " < "  + group(b, 41) }
@syntax a ">" b                   40           => { emit group(a, 40) + " > "  + group(b, 41) }
@syntax a "+" b                   60           => { emit group(a, 60) + " + "  + group(b, 61) }
@syntax a "-" b                   60           => { emit group(a, 60) + " - "  + group(b, 61) }
@syntax a "*" b                   70           => { emit group(a, 70) + " * "  + group(b, 71) }
@syntax a "div" b                 70           => { emit group(a, 70) + " / "  + group(b, 71) }
@syntax a "mod" b                 70           => { emit group(a, 70) + " % "  + group(b, 71) }
@syntax "not" a                   80           => { emit "!" + group(a, 80) }
@syntax a "and" b                 30           => { emit group(a, 30) + " && " + group(b, 31) }
@syntax a "or" b                  25           => { emit group(a, 25) + " || " + group(b, 26) }

; The include is not written here. `writeln` contributes it to a collection
; called `head` -- once, however many times it fires -- and `program` says
; where the aggregate goes with `splice("head")`. A program that never prints
; gets no include, which is the difference between a head that is the body's
; aggregate and one that was guessed. This was the first customer for the
; mechanism, named in docs/prior-art.md the day before it was built.
;
; Declarations. Nothing here is better in this form than in a string, and both
; files say it the same way -- a type is a quoted word because `integer` has to
; come out as `int`, and a hole would splice the token it matched.
@syntax "program" n:name                       => { emit "/* " + n + " */\n" + splice("head") } terminated
@syntax a "," b                   20           => { emit a + ", " + b }
; A type is a rule of its own, a word alone, so that it can be *read* as well
; as matched. That is what lets a parameter list hold a hole where the type goes
; and translate each one on its own -- see the fragment below.
@syntax "integer"                              => { emit "int" }
@syntax "boolean"                              => { emit "int" }
@syntax "real"                                 => { emit "double" }

; The declaration still names the type as a quoted word, one rule per type, and
; cannot use the hole. `a ":" t` would read every `case` arm as a declaration:
; `1: writeln(11)` and `mod: integer` are both `expr ":" expr` and nothing here
; tells them apart. That is the context wall again -- the same one `writeln` and
; a parameterless call sit against -- reached this time from the type side.
@syntax a ":" "integer"           15           => { emit "int " + a }
@syntax a ":" "boolean"           15           => { emit "int " + a }
@syntax a ":" "real"              15           => { emit "double " + a }
@syntax "var" d                                => { emit d }

; A block's last statement needs its own semicolon: @separator puts one
; *between* two statements and never after the last, so the `}` would otherwise
; close over an unterminated one. `terminated(body)` is the same question asked
; of a run of statements, where it means *the last one*.
@syntax "begin" body:stmts "end"
    => {
        emit "{\n" + indent(body, 4)
        if not terminated(body) { emit ";" }
        emit "\n}"
    } terminated

; `group(c, 80)` again: C's `!` binds tighter than any comparison, so the
; condition needs brackets unless it is an atom. examples/pascal.mx writes them
; unconditionally, and `while (!(1))` is what that costs.
@syntax "repeat" b:stmts "until" c
    => {
        emit "do {\n" + indent(b, 4)
        if not terminated(b) { emit ";" }
        emit "\n} while (!" + group(c, 80) + ")"
    }

; Pascal's arms do not fall through and C's do, so every one ends in a `break`
; the source never wrote.
;
; The arm is `[ v ":" s ]` -- **two** holes in one repeated group, which is two
; parallel lists. `for i, x in v` walks the labels with their position and
; `at(s, i)` takes the matching body. examples/pascal.mx cannot do this: a
; string template splices each list joined and has no way to interleave them,
; so it declares an infix `a ":" s` rule to fold the pair into one value before
; the group sees it, and that rule then means *case arm* everywhere a colon is
; not already claimed. This is the difference the two files exist to show, and
; it is the sharpest one on the page.
@syntax "case" e "of" [ v ":" s ]* sep ";" "end"
    => {
        emit "switch (" + e + ") {\n"
        for i, x in v sep "\n" { emit indent("case " + x + ": " + at(s, i) + "; break;", 4) }
        emit "\n}"
    } terminated
@syntax "case" e "of" [ v ":" s ]* sep ";" "else" d "end"
    => {
        emit "switch (" + e + ") {\n"
        for i, x in v sep "\n" { emit indent("case " + x + ": " + at(s, i) + "; break;", 4) }
        emit "\n" + indent("default: " + d + "; break;", 4) + "\n}"
    } terminated
@syntax "begin" body:stmts "end" "."
    => { emit "int main(void) {\n" + indent(body + ";\nreturn 0;", 4) + "\n}" } terminated

; `terminated(h)` is `level(h)`'s other half. C's `if (c) x = 1; else` wants a
; semicolon that C's `if (c) { … } else` must not have, and which of the two a
; branch is depends on the rule that filled the hole -- exactly what a rule
; says when it declares itself `terminated`, and exactly what a hole now
; remembers. examples/pascal.mx cannot ask, so it braces every branch
; unconditionally and the recorded diff shows what that costs.
; A rule punctuates *inside* itself and never at its end: the semicolon before
; an `else` is C's and has to be written here, and the one that ends the whole
; statement is @separator's, the way it is for every other statement. Emitting
; both is how the first draft of this got `x = 1;;`.
@syntax "if" c "then" t
    => { emit "if (" + c + ") " + t }
@syntax "if" c "then" t "else" f
    => {
        emit "if (" + c + ") " + t
        if not terminated(t) { emit ";" }
        emit " else " + f
    }
@syntax "while" c "do" b
    => { emit "while (" + c + ") " + b }

; Pascal's `'it''s'` becomes C's "it's": drop the quote off each end, undouble
; what is left, and put C's quotes back. `drop` and `replace` are the two
; smallest things a template needs to translate a literal rather than move it.
@syntax "writeln" "(" x:string ")"
    => {
        contribute("head", "#include <stdio.h>")
        emit "puts(\"" + replace(drop(x, 1, 1), "''", "'") + "\")"
    }
@syntax "writeln" "(" x ")"
    => {
        contribute("head", "#include <stdio.h>")
        emit "printf(\"%d\\n\", " + x + ")"
    }

@syntax "for" i:name ":=" a "to" b "do" s
    => {
        emit "for (int " + i + " = " + a + "; "
        emit i + " <= " + b + "; " + i + "++) "
        emit s
    }

; A parameter list, and the place the two forms finally part company.
; `p` is a hole inside a repeated group, so it is a **list**; `t` is a second
; one holding the type of each turn, and the loop below walks them in step with
; `for i, x in p` and `at(t, i)`. That is what lets `Scale(n: integer; k: real)`
; come out as `void Scale(int n, double k)`.
;
; examples/pascal.mx cannot. `join ", int "` writes one word in front of every
; turn and cannot vary it, and a string template splices each list joined with
; no way to interleave two -- so that file writes `int k` for a `real` and its
; recorded output carries the wrong type on purpose. This used to be the one
; place in these two files where the code template bought nothing. It is now
; the clearest place it buys something, and the argument is the diff between
; the two recorded outputs rather than this paragraph.
;
; The list itself is written once. `@fragment` names a piece of *pattern* and
; `@params` splices it where the list goes; the holes come with it, which is why
; the bodies below still say `p` without declaring it. It is not a template and
; takes no arguments -- a template is called at expansion and this is spliced at
; declaration, so by the time either rule is matched there is nothing left to
; say a fragment was ever involved.
@fragment params = "(" [ p:name ":" t ]* sep ";" ")"

; And the body once, which is where the two mechanics meet: the pattern is
; shared by `@params` and the template by `subprogram`, and the only thing left
; that differs between a procedure and a function is C's return type. A list
; goes through a template parameter unchanged -- `p` arrives as a list and the
; loop below walks it -- which nothing had asked for until this call site.
@template subprogram(ret, f, p, t, b) {
    emit ret + " " + f + "("
    if count(p) == 0 { emit "void" }
    for i, x in p sep ", " { emit at(t, i) + " " + x }
    emit ") " + b
}

@syntax "procedure" f:name @params ";" b
    => { subprogram("void", f, p, t, b) } terminated
@syntax "function" f:name @params ":" rt ";" b
    => { subprogram(rt, f, p, t, b) } terminated

; Free Pascal's `Result`, for the reason examples/pascal.mx gives.
@syntax "Result" ":=" e                        => { emit "return " + e }

@end

{ Everything below is Pascal. Nothing below is Metaxis's.
  It is the body of examples/pascal.mx, unchanged. }

program Fizz;

var
  total, mod: integer;
  i, n: integer;

{ A procedure and a function. Pascal separates parameter groups with `;` and
  C gives every parameter its own type, so one Pascal group becomes several
  C ones. }

procedure Show(n: integer);
begin
  writeln(n)
end;

procedure Scale(n: integer; k: real);
begin
  writeln(n)
end;

procedure Pair(a: integer; b: integer);
begin
  writeln(a + b)
end;

function Double(n: integer): integer;
begin
  Result := n * 2
end;

begin
  total := 0;
  mod := 3;
  for i := 1 to 20 do
    if (i mod mod = 0) and (i <> 9) then
      total := total + i
    else
      total := total - 1;
  if not (total > 100) then writeln('it''s middling') else writeln('big');
  if total > 30 then
    begin
      total := total + 1;
      writeln(total)
    end
  else
    writeln(total);

  { `until` says when to stop; C's `while` says when to go on. }
  n := 0;
  repeat
    n := n + 1
  until n > 3;
  writeln(n);

  { Pascal's arms do not fall through. C's do, so each one gains a `break`. }
  case n of
    1: writeln(11);
    4: writeln(44)
  else
    writeln(0)
  end;

  Show(Double(total));
  Scale(7, 2);
  Pair(total, 2)
end.
examples/code.out
/* Fizz */
#include <stdio.h>
int total, mod;
int i, n;
void Show(int n) {
    printf("%d\n", n);
}
void Scale(int n, double k) {
    printf("%d\n", n);
}
void Pair(int a, int b) {
    printf("%d\n", a + b);
}
int Double(int n) {
    return n * 2;
}
int main(void) {
    total = 0;
    mod = 3;
    for (int i = 1; i <= 20; i++) if ((i % mod == 0) && (i != 9)) total = total + i; else total = total - 1;
    if (!(total > 100)) puts("it's middling"); else puts("big");
    if (total > 30) {
        total = total + 1;
        printf("%d\n", total);
    } else printf("%d\n", total);
    n = 0;
    do {
        n = n + 1;
    } while (!(n > 3));
    printf("%d\n", n);
    switch (n) {
        case 1: printf("%d\n", 11); break;
        case 4: printf("%d\n", 44); break;
        default: printf("%d\n", 0); break;
    }
    Show(Double(total));
    Scale(7, 2);
    Pair(total, 2);
    return 0;
}

backends

one grammar, two targets. Every rule is written once; where the two agree there is one template and no tag, and where they differ a second => … as tight sits under the first. mx and mx -b tight emit different C from the same file, both compile, and both print 7 2: the difference is what it reads like, not what it means

examples/backends.mx
; backends.mx -- one grammar, two targets.
;
; Every rule below is written once. Where the two targets agree, there is one
; template and no tag, and that is most of the file. Where they differ, a
; second `=> … as tight` sits under the first:
;
;     mx examples/backends.mx            the default, every operand bracketed
;     mx -b tight examples/backends.mx   brackets only where precedence needs
;
; Both emit C. The difference is not what the program means but what it reads
; like, which is the whole reason a second target is worth having: the grammar
; is the expensive half and it is not written twice.
;
; `terminated` is declared per template rather than per rule, and the `if`
; below is why -- the default braces its branch and so ends a statement on its
; own; the tight one does not brace a single statement and so does not.

@comment "#" eol

@token number "[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"

@separator ";" => ";\n"

; Agreed. A group is an atom whatever is in it, so neither target ever needs to
; bracket one again -- one template, no tag, and nothing to choose between.
@syntax "(" e ")"                  => "({e})"

; Agreed. An assignment is an assignment.
@syntax a "=" b        10 right    => "{a} = {b}"

; Differ, and only in the brackets. A string template can put them on or leave
; them off and cannot ask; a code template asks the operand what level it was
; parsed at and brackets it only when it must.
@syntax a "+" b        60          => "({a} + {b})"
                                   => { emit group(a, 60) + " + " + group(b, 61) } as tight
@syntax a "-" b        60          => "({a} - {b})"
                                   => { emit group(a, 60) + " - " + group(b, 61) } as tight
@syntax a "*" b        70          => "({a} * {b})"
                                   => { emit group(a, 70) + " * " + group(b, 71) } as tight
@syntax a "<" b        40          => "({a} < {b})"
                                   => { emit group(a, 40) + " < " + group(b, 41) } as tight

; Differ in `terminated`, which is the reason it belongs to the template. The
; default braces the branch, so what it emits already ends a statement. The
; tight one writes a single statement and its semicolon, so it does not.
@syntax "if" c "then" "{" t:stmts "}"
    => "if ({c}) {{\n    {t};\n}}" terminated
    => { emit "if (" + c + ") " + t + ";" } as tight

; Agreed again, and this is the economy the feature is for: a file that adds a
; target does not rewrite the rules that did not change.
@syntax "print" x                  => "printf(\"%d\\n\", {x})"
@syntax "let" n:name "=" v         => "int {n} = {v}"

@end

let a = 2;
let b = 3;
let c = a * b + 1;
print c;
if a < b then { print a }
examples/backends-tight.out (mx -b tight)
int a = 2;
int b = 3;
int c = a * b + 1;
printf("%d\n", c);
if (a < b) printf("%d\n", a);
examples/backends.out
int a = 2;
int b = 3;
int c = ((a * b) + 1);
printf("%d\n", c);
if ((a < b)) {
    printf("%d\n", a);
}

groups

[ … ], [ … ]* and [ … ]+: an argument list of any arity in one rule, and an optional part

examples/groups.mx
; groups.mx -- a part that repeats, and a part that need not be there.
;
; `[ … ]` is Metaxis's own bracket and lives outside the strings, so it can
; never be mistaken for a bracket the body writes -- one of those would be
; quoted, and this one cannot be. Three forms:
;
;     [ … ]     once or not at all
;     [ … ]*    zero or more
;     [ … ]+    one or more
;
; A repeated group may say what separates its turns on the way in and what
; joins them on the way out:
;
;     [ x ]* sep "," join ", "
;
; `join` defaults to `sep`, and `sep` to nothing at all. A hole inside a
; repeated group collects every turn; a hole inside an optional group that did
; not match is empty. Nothing is ever unbound, so a template never has to ask
; whether a part was there.
;
; Output is JavaScript.

@comment "//" eol

@token number "[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"
@token string "\"[^\"]*\""

@separator ";" => ";\n"

@syntax "(" e ")"                              => "({e})"
@syntax a "+" b                   60           => "({a} + {b})"
@syntax a "=" b                   10 right     => "{a} = {b}"

// Zero or more, and the one rule covers every arity there is. Before groups
// this was a rule per arity, or it was not written.
@syntax a "(" [ x ]* sep "," join ", " ")"   95
    => "{a}({x})"

// One or more, with an output joiner that is not the input separator.
@syntax "let" [ n:name ]+ sep "," join ", "
    => "let {n}"

// A group may hold words as well as holes, and may nest inside another.
@syntax "fn" f:name "(" [ p:name ]* sep "," join ", " ")" "{" b:stmts "}"
    => "function {f}({p}) {{\n{b};\n}}"
    terminated

// Once or not at all. `{~i}` still means a name nobody else has, and the
// counter does not care that it is inside a group.
//
// Both of these are `terminated`: what they emit is a JavaScript block, and
// JavaScript wants no `;` after one. That is a statement about the output and
// not about the input -- see the note at the foot of examples/clike.mx, which
// reads the same braces and declines the word for the opposite reason.
@syntax "loop" n "times" "{" b:stmts "}" [ "or" "{" e:stmts "}" ]
    => "for (let {~i} = 0; {~i} < {n}; {~i}++) {{\n{b};\n}}\nif (!{n}) {{\n{e};\n}}"
    terminated

@end

let a, b, c;

fn nothing() { a = 1 }
fn one(x) { a = x }
fn three(x, y, z) { a = x + y + z }

nothing();
one(1);
three(1, 2 + 3, a);

loop 3 times { one(a) }
loop 0 times { one(a) } or { nothing() }

// The optional group above is where a template that is a string runs out. It
// can leave `{e}` empty and it cannot make the *output* differ: with no `or`
// part the expansion still writes `if (!0) { ; }`, because a splice is the only
// thing a string template can vary. Making the shape of the output depend on
// whether a part matched is the first customer for docs/ROADMAP.md's second
// kind of template, and is why that entry exists.
examples/groups.out
let a, b, c;
function nothing() {
a = 1;
}
function one(x) {
a = x;
}
function three(x, y, z) {
a = ((x + y) + z);
}
nothing();
one(1);
three(1, (2 + 3), a);
for (let i__1 = 0; i__1 < 3; i__1++) {
one(a);
}
if (!3) {
;
}
for (let i__2 = 0; i__2 < 0; i__2++) {
one(a);
}
if (!0) {
nothing();
}

hygiene

{~t}, and the half of hygiene it cannot close. tests/hygiene.sh compiles the C it emits and runs it, so the remaining wrong answer is a number

examples/hygiene.mx
; hygiene.mx -- one half fixed, one half charged.
;
; A Proto template is a tree in a language Proto knows, so a name the template
; introduces can be renamed before anybody sees it. A Metaxis template is a
; string. `{~t}` gives it the one thing a string can be given: a name nobody
; else has. That closes the half of the problem where a template *introduces* a
; name, and does not touch the half where a template *reaches out* for one.
;
; Both halves are below and `tests/hygiene.sh` compiles the output and runs it,
; so which one works is a number rather than an argument.
;
; The output is C, minus one line: `tests/hygiene.sh` puts `#include <stdio.h>`
; in front of it. Everything else here comes out of the tool.
;
; `fn` below is declared `terminated`: what it emits is a C function definition,
; and C wants no semicolon after one. Until that word existed this file's output
; carried a `};` at file scope -- `cc` accepted it and C11 did not oblige it to --
; because the input side knew a statement ending in a word needs no separator
; and the output side joined unconditionally anyway. The two sides are about two
; different languages and now say so separately.

@comment "//" eol

@token number "[0-9]+"
@token name   "[A-Za-z_][A-Za-z0-9_]*"
@token string "\"[^\"]*\""

@separator ";" => ";\n"

@syntax "(" e ")"                        => "({e})"
@syntax a "(" ")"          95            => "{a}()"
@syntax a "=" b            10 right      => "{a} = {b}"
@syntax a "+" b            60            => "({a} + {b})"

@syntax "var" n:name                     => "int {n} = 0"
@syntax "local" n:name "=" v             => "int {n} = {v}"
@syntax "give" e                         => "return {e}"
@syntax "show" "(" s "," a "," b ")"
    => "printf(\"%s: %d %d\\n\", {s}, {a}, {b})"
@syntax "fn" f:name "(" ")" "{" b:stmts "}"
    => "int {f}(void) {{\n{b};\n}}"
    terminated

; ---------------------------------------------------------------------------
; The half a string can close.
;
; `swap` needs somewhere to put a value. `{~t}` is a name nobody else has: the
; two occurrences below are one name within one expansion, and the next use of
; the rule gets another. The caller's `t` is not it, and neither is the `t` the
; other call site got.

@syntax "swap" "(" a "," b ")"
    => "{{ int {~t} = {a}; {a} = {b}; {b} = {~t}; }}"

; ---------------------------------------------------------------------------
; The half it cannot.
;
; `bump` does not introduce a name -- it *means* one, the file-scope `total`
; that was in scope where this line was written. A fresh name is no use here:
; there is nothing to invent. What is wanted is a way to say *the outer one*,
; and a template that is a string has no way to see a scope, let alone reach
; past a caller's. Proto can, because its expander works on trees in a language
; whose scopes it knows. That is what agnosticism costs, and it is charged
; below, in the two numbers `run` returns.

@syntax "bump" "(" n ")"                 => "total = total + {n}"

@end

var t;
var u;
var p;
var q;
var total;
var r;

// Two callers, one form, one name in its template. The caller happens to have
// a `t`; and the second call site must not get the first one's temporary.
fn caller() {
    t = 1;
    u = 2;
    swap(t, u);
    p = 3;
    q = 4;
    swap(p, q);
    give 0;
}

// The caller happens to have a `total`. So does the form, and it meant the
// other one -- the file-scope `total` six lines above `caller`.
fn run() {
    local total = 100;
    bump(5);
    give total;
}

fn main() {
    r = caller();
    show("swap", t, u);
    show("again", p, q);
    r = run();
    show("bump", r, total);
    give 0;
}
examples/hygiene.out
int t = 0;
int u = 0;
int p = 0;
int q = 0;
int total = 0;
int r = 0;
int caller(void) {
t = 1;
u = 2;
{ int t__1 = t; t = u; u = t__1; };
p = 3;
q = 4;
{ int t__2 = p; p = q; q = t__2; };
return 0;
}
int run(void) {
int total = 100;
total = total + 5;
return total;
}
int main(void) {
r = caller();
printf("%s: %d %d\n", "swap", t, u);
printf("%s: %d %d\n", "again", p, q);
r = run();
printf("%s: %d %d\n", "bump", r, total);
return 0;
}