Spec M22 — examples/lang: a higher-level language taught to mc by a prelude
Goal: prove mc is a language generator. Everything in this milestone is developer code — it
lives in examples/lang/, uses only the public hooks (M12, M21) and the object primitives, and
could be replaced by a different prelude with different semantics without touching src/. A module examples/lang/lang.mc, compiled into a taught
compiler (mc build with [compiler].modules), gives programs in examples/lang/*.lx:
class Shape {
virtual i64 area(self) { return 0; }
virtual str name(self) { return "shape"; }
}
class Circle : Shape {
i64 r;
override i64 area(self) { return 3 * self.r * self.r; }
override str name(self) { return "circle"; }
}
interface Printable { str show(self); }
class Box<T, const N: i64> : Printable where T : Shape {
T items[N]; i64 count;
fn push(self, T s) { self.items[self.count] = s; self.count += 1; }
fn total(self) -> i64 { i64 t = 0; for (i64 i = 0; i < self.count; i += 1) { t += self.items[i].area(); } return t; }
str show(self) { return "box"; }
}
fn bump(ref i64 x) { x += 1; }
fn main() -> i64 {
Box<Circle, 4> b = new Box<Circle, 4>();
Circle c = new Circle(); c.r = 2; b.push(c);
i64 n = 0; bump(ref n);
print(b.total() + n); // 13
return 0;
}
Features and their lowering (all inside lang.mc, using M21 hooks and the M12 API) #
fn name(params) -> T { }: the module owns function parsing: it parses statements one by one so it knows every local of class type and can inject memory management (below). Missing-> T= void.- Classes with single inheritance: fields of the base first (prefix layout), header word 0 =
vtable pointer, word 1 = reference count.
virtualdeclares a slot,overridefills the inherited slot; non-virtual methods are direct callsClass_method(self, ...).base.method(...)calls the parent's implementation directly. Interfaces as inexamples/api/oop.mc(vtable with the class's slots; an interface pointer is the object pointer, dispatch through the class vtable using a per-class interface table). - Member access and calls via
syntax_infix("."): static type of the left expression comes from the module's own symbol table (locals, params, fields, method return types); fields lower told*/st*atbase + OFFSETwith the field width;obj.m(args)lowers tocallp(vtable_slot, obj, args)for virtual methods and to a direct call otherwise; arrays ofTinside a class areuptrarrays ofNelements (items[i]viasyntax_infix("[")on class-typed arrays). - Generics (
class Box<T, const N: i64> where T : Shape): the class body is recorded withp_skip_balancedand instantiated on first use ofBox<Circle, 4>byp_replaywithT -> Circle,N -> 4, producing a concrete classBox__Circle__4(mangled, deterministic). Constraints (where T : Shapeorwhere T : Printable, C# style, several separated by commas) are checked at instantiation: the argument must be the class, a subclass, or an implementer; violation is an error naming the constraint. Generic functionsfn max<T>(T a, T b) where T : Comparable -> Tare instantiated per call site by the same mechanism. refparameters:ref i64 xbecomesuptr x_ref; uses ofxin the body lower told64/st64by the declared width; call sitesf(ref y)pass&y(y must be a local or param; error otherwise).- Automatic memory (owner's decision, 2026-09-03: reference counting): reference counting with
size-class free lists in
examples/lang/lib/rt.mc.new C(...)allocates (rt_alloc(C_SIZE)), sets the vtable, rc = 1, and calls the constructor if declared (init(self, ...)). Thefnhandler injectsrc_dec(x)for every class-typed local at each scope exit and before everyreturnexcept for the returned value;x = ewherexis a class-typed local or field doesrc_inc(new)/rc_dec(old); parameters are borrowed (no traffic);return xtransfers ownership.rc_decreaching zero callsvirtual dispose(self)if defined, decrements class-typed fields, then frees. Cycles leak (documented).printand other builtins come from the module's prelude. - Namespaces (sugar over includes):
namespace geo { class Circle ... fn area(...) ... }at top level (syntax("namespace")) mangles every declaration inside asgeo__Circle,geo__areaand records them in a namespace table;import geo;resolvesgeo.lxthrough the include search order (includer dir,[include].paths, then the bundle) — it is exactly#includeplususing geo;;using geo;alone brings the names into unqualified scope for the rest of the file; qualified usegeo.Circle/geo.area(x)is handled by the.infix hook when the left identifier is a known namespace (namespaces win over locals only when no local of that name exists — error on ambiguity). Nested namespacesa.ballowed; same name reopened in two files merges. Types in generics accept qualified names (Box<geo.Circle, 4>). - Diagnostics: every error names
file:lineof the.lxsource, plusinstantiated fromfor generics.
Acceptance #
examples/lang/:lang.mc(+lib/rt.mc,lib/prelude.lx),mc.toml([compiler] modules = ["lang.mc"],entry = "main.lx"),tests/*.lxwithexpect-stdout/expect-exitheaders covering: inheritance + virtual dispatch (3 levels), interface through generic constraint, namespaces across two files (import,using, qualified access, merge of a reopened namespace, ambiguity error), const generic array sizes,ref,whereviolation error text, automatic release (a test allocates 100 000 objects in a loop inside a 4 MiB arena — only possible if memory is actually freed), dispose order, mangled names in--dump-asm.examples/lang/test.shcompiles each test with the taught compiler (--exe) and checks outputs;make checkgainscheck-lang. README explaining the language and how it is built from the surface.