Spec M5.5 — fixes from the M3/M5 reviews + core pieces M6 needs

A. Fixes (M3 review) #

  1. Lexer: escape \0 inside a string literal is an error ("\0 not allowed in a string"); in a char literal it stays valid. Reason: S_CSTRING_LITERALS makes ld merge C-string literals, which would corrupt "a\0b" vs "a". Document in docs/core-language.md (one line).
  2. #include: path_join normalizes . and .. lexically (no filesystem access) before the once-only check. Test: a/b.mc including ../c.mc plus the root including c.mc → only one inclusion.
  3. Token carries a file: Token.file; node_new/err_at use the file of the token that gave the line, not the lexer's current file. Test: frag.mc with i64 helper(i64 x) { left unclosed → the error cites frag.mc.
  4. #define vs name: declaring a local, parameter, global, or function with the name of an existing #define is the error name already defined by #define.
  5. Safe reductions in gen_arm64.c (dump via rrr tables, an ld/st table, a unified mem_op, a name-resolution helper) — target ≈ −25 lines, without changing the generated code (verify with cmp on the .o of every test, before/after).

B. Fixes (M5) #

  1. Section order in the load commands: __text, __cstring, __data, __bss (whichever exist) and only then the custom ones, in order of first appearance. #section registers into a table; actual creation happens in gen_sections. Recheck otool -l for 030.

C. New core pieces (requested by M4) #

  1. Global array initializer: type name[N] = { e1, e2, ... }; and type name[] = { ... } (N inferred). Elements: folded constants (written with the type's width) or, for uptr, a string literal → 8 zero bytes + R_UNSIGNED (len 3, pcrel 0, extern 1) for l_strN. Goes into __data (or a custom non-zerofill section), aligned to 16; N > count pads with zeros; count > N is an error. Does it support #opcode? No. Test: uptr names[] = {"zero","one","two"}; with puts(ld64(names + 8*i)) → stdout one; u32 t[4] = {1,2,3};ld32(t+8) == 3.
  2. Unsigned division: / and % with a u8/u16/u32/u64/uptr left operand → udiv/msub; fold mirrors this. Test: (u64) 0xFFFFFFFFFFFFFFFF / 2 = 0x7FFFFFFFFFFFFFFF.
  3. Prototype: type name(params); at top level registers a signature; a later definition must match it (return type and arity); a prototype with neither a definition nor an extern is an error at the end.
  4. Makefile: target check-lex (scripts/check-lex.sh build/mc0); make test unchanged.
  5. CLAUDE.md § State: M3, M4, M5 and M5.5 closed; next is M6.

Acceptance #

make stage0 with no warnings, stage0-san, budget (report it), test (mc0 and mc0-san), check-lex; before/after reproductions for A1–A4; cmp of the .o files before/after A5 for every test (except the ones affected by B6, whose only difference is section order); new tests 040-arrinit.mc, 041-udiv.mc, 042-proto.mc, 043-include-norm.mc (+ helper files under tests/inc/).

Edit this page