summary_session-2026-10-22.md 8.0 KB

Session 2026-10-22 — corpus triage: six 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 254/254
fixpoint (bootstrap/fixpoint.sh) OK (3,882,337 B) OK (3,930,173 B)
corpus (tools/v3-corpus/corpus.sh) 467/612 499/612, 0 crashes

Reproduce:

cd compiler && ./build.sh && ./run_tests.sh          # 254/254
cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
tools/v3-corpus/corpus.sh                            # 499 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 32 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.

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)

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 procedure-type syntax — comma-separated unnamed parameter types and PROC as a result type (testrecursive.def, typeonly.def, procconv, procconv2); parser-level.
  3. ISO SysClock — the stdlib stub lacks DateTime, CanGetClock, GetClock, SetClock (testclock, testclock2).
  4. PACKEDSET, ISO exceptions/UNIQUE, __FUNCTION__ — each one or two files; larger or gm2-flavoured.
  5. Runtime subrange range checks — the remaining conformance difference from this session's subrange work.
  6. STATUS.md "what remains" is the live list.