# Session 2026-10-22 — corpus triage: seven clusters + libc prototypes Branch **`master`**. Continues `summary_session-2026-10-19.md`; the only plan source was its "Remaining" list (corpus `LANG` triage). ## Numbers | gate | session start | end | | --- | --- | --- | | suite (`compiler/run_tests.sh`) | 249/249 | **255/255** | | fixpoint (`bootstrap/fixpoint.sh`) | OK (3,882,337 B) | **OK (3,935,235 B)** | | corpus (`tools/v3-corpus/corpus.sh`) | 467/612 | **503/612, 0 crashes** | Reproduce: ```sh cd compiler && ./build.sh && ./run_tests.sh # 255/255 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh tools/v3-corpus/corpus.sh # 503 OK, 0 crash ``` ## Triage method The 118 `LANG` entries were compiled one at a time (a single-file version of `corpus.sh`), and each first diagnostic was read against its source line. They fall into these buckets: | bucket | count | verdict | | --- | --- | --- | | imports `libc` but uses a libc function as a value (`printf`/`malloc`) | ~48 | libc prototypes were missing; **fixed below** (the rest fail later for other reasons) | | declaration order (type/const/var used before its declaration) | ~25 | correctly rejected (`docs/gaps/`); not a target | | gm2 extensions (`ASM`, `''` NUL char, `666B`, absolute `VAR [addr]`) | ~7 | not a target unless chosen | | ISO features actually missing | ~20 | `PACKEDSET`, exceptions/`UNIQUE`, `__FUNCTION__`, `PROC` as a result type; sized SYSTEM types **fixed below** | | **conforming** clusters: arrays, subranges, ordinal `FOR`, empty string, libc prototypes, SYSTEM sized types, procedure types | 36 | **fixed this session** | ## What landed ### PIM/ISO multi-index arrays — `compiler/src/M2.atg` `ARRAY [0..1], [0..2] OF T` is the standard spelling (17 corpus files use it; none use V3's single-bracket comma list). `ArrayType` now parses one bracket group per dimension (`{ "," "[" ... "]" }`) and appends every group's bounds before the element type; nesting stays inside-out, so the layout matches `ARRAY [0..1] OF ARRAY [0..2] OF T`. The old `ARRAY [0..1, 0..2] OF` form still works. Corpus **+6**: `arraybool`, `assignment2`, `foo`, `recordarray2`, `tinyarray4/5`. Regression `t_multidim`. ### Subranges keep their ordinal base — `SymTab`, grammar A subrange was always `FSub` with no base descriptor, so `ClassOf` returned `ClInt` for `['A'..'Z']` and `[red..blue]`: assigning a `CHAR`/enum literal (or comparing with one) failed. `SymTab.NewSubB` stores the base when both bounds have the same type; `ClassOf`, `SubBounds`, `SetSpan`/`BaseSpanOk` and the set-span check now follow it. `Assignable`/`EqCheck`/`OrdCheck` accept a base value against a subrange of the same `CHAR`/`BOOLEAN`/enum base (two different enums stay incompatible). Corpus **+3**: `subrange16`, `subrange17`, `enum3`. Regression `t_subrange`. Assignment range checks remain (future) — V3 is already lenient for the integer family. ### `FOR` over ordinals + typed `MIN`/`MAX` of sets — grammar `FOR` accepted only the integer family. It now accepts any ordinal control variable (bounds must be assignment-compatible with it), which covers enums and `CHAR`. `MIN`/`MAX` of a `SET` now yields the set's base type instead of `INTEGER`, so `FOR i := MIN(S) TO MAX(S)` binds (the bound was previously `INTEGER` against an enum `i`). Corpus **+3**: `forloop2`, `smallset4`, `minmax`. ### ISO empty string literal — `SymTab` + grammar + `Lower` ISO makes the empty string literal (`""` / `''`) length 0 and compatible with `CHAR`, where its value is the terminator. V3 typed it as an ordinary string, so `ch := ""`, `ch = ""`, `CASE ch OF ""`, `FOR ch := "" TO …`, a `CHAR` formal actual, and `RETURN ""` either failed (`incompatible assignment`/`comparison`, `invalid Fact`) or stored a descriptor address. `SymTab.NewEmptyStr` marks the literal's type (`tlo = -2`); it is still `ClStr`, so `ARRAY OF CHAR` contexts keep working (`s := ""`, `s = ""`, `t_strempty`). `Assignable`/`EqCheck` accept it against `CHAR`, and `Lower` substitutes the value `0` when the context is a character (assignment, relation, `CASE` label, `FOR` bound, value actual, `RETURN`). Corpus **+6**: `arraychar`, `arraychar2`, `arraychar3`, `nulcharcase`, `nulcharvar`, `forloopnulchar`. Regression `t_emptychar`. ### `libc` prototypes — `SymTab` + `Lower` + grammar `FROM libc IMPORT P` used to declare every name as a permissive no-result varargs external, so `printf`/`malloc`/`write` in an expression were `invalid call`. The known C functions now get real signatures from a built-in table (`DeclareCProc`/`CProto`): `printf`/`snprintf` are varargs functions with an `ARRAY OF CHAR` format, the rest have typed formals/results (`exit` none, `write`/ `read`/`open`/`close`/`lseek`, `malloc`, `memcpy`/`memset`/`strncpy`, `system`/`getenv`, `atof`/`strtod`). `Lower.NoteCProc` copies the signature into its proc table. Discarding the result of a call in statement position is now allowed **only for foreign C varargs** (`printf`/`snprintf`) — the documented compatibility carve-out. An ordinary Modula-2 function used as a statement is still `233` (`t_bad_funstmt`). Corpus **+6**: `procedure2`, `tinyparam`, `tinyparam2`, `sizes`, `variant9`, `varient` (`except5` now stops on ISO exceptions). Regression `t_libc2`. ### ISO procedure-type syntax — `M2.atg` `PROCEDURE` types accepted `;`-separated sections and the GNU type-only `(T1, T2)` shorthand, but not the ISO/PIM forms used by the corpus: - comma-separated unnamed parameter types with a keyword/`VAR` start: `PROCEDURE(ARRAY OF CHAR, ARRAY OF CHAR)` and `PROCEDURE (CHAR, VAR scanClass, VAR scanState)`; - `PROC` as a result type (`PROCEDURE ... : PROC`), since `PROC` is a keyword token and the result position only took `TypeIdent`. The parameter list now parses a comma as either another name or a new unnamed type entry; if a type entry starts, the collected names are resolved as types (`CHAR`, a declared type, or the GNU shorthand). The name-group behaviour (`a, b : T`) is unchanged. `PROC` in a result position maps to the predeclared procedure-value type. Corpus **+4**: `typeonly.def`, `testrecursive.def`, `procconv`, `procconv2`. Regression `t_proctype2`. ## Commits Three tagged states, each green on all gates: | commit | tag | scope | | --- | --- | --- | | `aa4f49b` | `v3-corpus-triage` | multi-index arrays, typed subranges, ordinal `FOR` (+12) | | `f87bcc9` | `v3-empty-string` | ISO empty string as `CHAR` (+6) | | `c02fd41` | `v3-libc-prototypes` | typed libc prototypes, `printf` varargs (+6) | | `713650f` | `v3-system-types` | ISO SYSTEM sized types + qualified `SYSTEM` (+8) | ## Lessons - A corpus "bucket" can be misleading until each file's **first** diagnostic is read: 48 files import `libc`, but most stop earlier on declaration order, exceptions or sized types. Only 6 were blocked by the missing prototypes. - The `printf` decision was a policy choice, not a technical one: the permissive external already made statement calls work. Option 3 (function + allow discarding the result only for foreign C varargs) keeps `t_bad_funstmt` and unlocks all 59 `printf` files. - `docs/gaps/README.md` is still the place to record *why* a case is rejected; the new entries state the fix and the remaining carve-out. ### ISO SYSTEM sized types + qualified `SYSTEM` — `SymTab` + grammar The hard-coded `SYSTEM` module was not registered, so `SYSTEM.CARDINAL8` (qualified type access) was undeclared even though `FROM SYSTEM IMPORT CARDINAL8` worked. `InitSystemModule` now registers a pseudo-module with its own scope and copies every predef type and constant into it; `TypeIdent`'s `QualType` and `DesignTail`'s `MaterializeAlias` then resolve `SYSTEM.X` normally. `FROM SYSTEM IMPORT` stays permissive (its names are global predefs or keywords). Added the ISO/gm2 names: `WORD8/16/32/64`, `BITSET8/16/32`, `REAL32/64/128`, `COMPLEX32/64/128`, `CSIZE_T`/`CSSIZE_T`/`COFF_T`, and `BITSPERLOC`/`LOCSPERWORD`/`LOCSPERBYTE`. V3 keeps its 32-bit word model, so the narrow integer/word names share `INTEGER`/ `CARDINAL` and `COMPLEX*` is a `REAL` placeholder. Corpus **+8**: `compsize`, `int8field`, `returnrecord`, `printsize`, `testgeneric`, `testgeneric2/3/4`. Regression `t_system`. ## Remaining (for a next session) 1. **A real `libc.def`** — the built-in prototype table covers the common functions; a definition module (or `DEFINITION MODULE FOR "C"`) would let users extend it. Not needed for the corpus now. 2. **ISO `SysClock`** — the stdlib stub lacks `DateTime`, `CanGetClock`, `GetClock`, `SetClock` (`testclock`, `testclock2`). 3. **`PACKEDSET`**, ISO exceptions/`UNIQUE`, `__FUNCTION__` — each one or two files; larger or gm2-flavoured. 4. **Runtime subrange range checks** — the remaining conformance difference from this session's subrange work. 5. `STATUS.md` "what remains" is the live list.