Browse Source

language: ISO procedure-type syntax (unnamed comma params, PROC result)

Procedure types accepted ';'-separated sections and the GNU type-only
(T1, T2) shorthand, but not the ISO/PIM forms in the corpus:

* comma-separated unnamed parameter types with a keyword or VAR start,
  e.g. 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 while the result position only accepted a TypeIdent.

The parameter list now treats a comma as either another name or a new
unnamed type entry; when a type entry starts, the collected names are
resolved as types (CHAR, a declared type, or the GNU shorthand).  Named
groups (a, b : T) are unchanged.  PROC in a result position maps to the
predeclared procedure-value type (dAddr).

Corpus +4: typeonly.def, testrecursive.def, procconv, procconv2.
Regression t_proctype2.  Suite 255/255, fixpoint OK (3,935,235 bytes),
corpus 503/612, 0 crashes.
Eric Streit 16 hours ago
parent
commit
f236600299
10 changed files with 4142 additions and 3999 deletions
  1. 2 2
      README.md
  2. 1 0
      compiler/run_tests.sh
  3. 46 12
      compiler/src/M2.atg
  4. 3999 3962
      compiler/src/M2.lst
  5. 39 0
      compiler/tests/t_proctype2.mod
  6. 2 2
      docs/INDEX.md
  7. 12 6
      docs/STATUS.md
  8. 5 1
      docs/features.md
  9. 7 0
      docs/gaps/README.md
  10. 29 14
      docs/summary_session-2026-10-22.md

+ 2 - 2
README.md

@@ -47,8 +47,8 @@ docs/              STATUS.md, plan.md, plan-*.md, analysis_*.md, summary_*.md
 
 ## Status (2026-10-22)
 
-- Suite **254/254**; self-hosting **fixpoint OK** (byte-identical
-  stage2/stage3, 3,930,173 bytes); corpus **499/612 compile-OK,
+- Suite **255/255**; self-hosting **fixpoint OK** (byte-identical
+  stage2/stage3, 3,935,235 bytes); corpus **503/612 compile-OK,
   0 crashes** (`tools/v3-corpus`; the corpus is gm2's testsuite, so it
   mixes conforming Modula-2 with gm2 leniencies — see `docs/gaps/`).
 - Active branch **`master`**.

+ 1 - 0
compiler/run_tests.sh

@@ -70,6 +70,7 @@ expect_run t_multidim.mod 42
 expect_run t_subrange.mod 42
 expect_run t_emptychar.mod 42
 expect_run t_system.mod 42
+expect_run t_proctype2.mod 42
 expect_run t_ctor1.mod 0
 expect_run t_ctor2.mod 0
 expect_run t_ctor3.mod 0

+ 46 - 12
compiler/src/M2.atg

@@ -275,6 +275,16 @@ PROCEDURE UnitIsGeneric () : BOOLEAN;
 PROCEDURE UnitIsDefModule () : BOOLEAN;
   BEGIN RETURN unitWasDef END UnitIsDefModule;
 
+PROCEDURE IsTypeName (n: SymTab.Name): BOOLEAN;
+(* TRUE when n denotes a type or a predeclared type name.  Used by the
+   procedure-type parameter list to tell a type-only entry from a
+   parameter name. *)
+  BEGIN
+    RETURN SymTab.Lookup(n)
+      AND ((SymTab.SymKind(n) = SymTab.KindType)
+        OR (SymTab.SymKind(n) = SymTab.KindPredef))
+  END IsTypeName;
+
 PROCEDURE RefineSeen () : BOOLEAN;
   BEGIN RETURN refSeen END RefineSeen;
 
@@ -1254,8 +1264,14 @@ PRODUCTIONS
     = ( "PROCEDURE" | "PROC" )         (. allowFwdType := FALSE;
                                            res := SymTab.InvalidType;
                                            t := SymTab.NewProcType(res); .)
-      [ "(" [ ProcTypeSection<t> { ";" ProcTypeSection<t> } ] ")" ]
-      [ ":" TypeIdent<res>              (. SymTab.SetProcTypeRes(t, res); .) ] .
+      [ "(" [ ProcTypeSection<t>
+               { (";" | ",") ProcTypeSection<t> } ] ")" ]
+      [ ":" ( TypeIdent<res>            (. SymTab.SetProcTypeRes(t, res); .)
+            | "PROC"                    (. (* PROC as a result type: the
+                                              pervasive procedure value
+                                              type (ISO/PIM) *)
+                                           SymTab.SetProcTypeRes(t,
+                                             SymTab.AddrType()); .) ) ] .
   ProcTypeSection<t: SymTab.TypeIndex>  (. VAR pt: SymTab.TypeIndex;
                                              isV: BOOLEAN;
                                              cnt, k: CARDINAL;
@@ -1263,21 +1279,32 @@ PRODUCTIONS
     =                                   (. isV := FALSE; cnt := 0; .)
       [ "VAR"                           (. isV := TRUE; .) ]
       ( GetIdent<names[cnt]>            (. INC(cnt); .)
-        { "," GetIdent<names[cnt]>      (. INC(cnt); .) }
-        ( ":" Type<pt, TRUE>           (. k := 0;
+        { "," ( GetIdent<names[cnt]>    (. INC(cnt); .)
+              |                         (. (* a comma followed by a type
+                                              (possibly VAR) ends the name
+                                              list: the collected names
+                                              were types, not names *)
+                                           k := 0;
+                                           WHILE k < cnt DO
+                                             SymTab.ProcTypeAdd(t, isV,
+                                               SymTab.SymType(names[k]));
+                                             INC(k)
+                                           END;
+                                           cnt := 0; .)
+                [ "VAR"                 (. isV := TRUE; .) ]
+                Type<pt, TRUE>          (. SymTab.ProcTypeAdd(t, isV, pt); .) ) }
+        ( ":" Type<pt, TRUE>            (. k := 0;
                                            WHILE k < cnt DO
                                              SymTab.ProcTypeAdd(t, isV, pt);
                                              INC(k)
                                            END; .)
         |                               (. (* type-only parameter list:
-                                              each name is a type (GNU
-                                              shorthand used by the
-                                              Coco/R scanner frame) *)
+                                              each collected name is a
+                                              type (CHAR, a declared type,
+                                              ... or the GNU shorthand) *)
                                            k := 0;
                                            WHILE k < cnt DO
-                                             IF SymTab.Lookup(names[k])
-                                                AND ((SymTab.SymKind(names[k]) = SymTab.KindType)
-  OR (SymTab.SymKind(names[k]) = SymTab.KindPredef)) THEN
+                                             IF IsTypeName(names[k]) THEN
                                                pt := SymTab.SymType(names[k])
                                              ELSE SemError(201);
                                                pt := SymTab.InvalidType
@@ -1985,9 +2012,16 @@ PRODUCTIONS
                                                SemError(233) END
                                            END; .)
       [ FormalParams ]
-      [ ":" TypeIdent<t>                (. IF NOT SymTab.SetProcRes(t) THEN
+      [ ":" ( TypeIdent<t>              (. IF NOT SymTab.SetProcRes(t) THEN
+                                             SemError(235) END;
+                                           Lower.SetProcRes(t); .)
+            | "PROC"                    (. (* PROC result: the procedure
+                                              value type *)
+                                           IF NOT SymTab.SetProcRes(
+                                              SymTab.AddrType()) THEN
                                              SemError(235) END;
-                                           Lower.SetProcRes(t); .) ] .
+                                           Lower.SetProcRes(
+                                             SymTab.AddrType()); .) ) ] .
   FormalParams
     = "(" [ ParamSection { ";" ParamSection } ] ")" .
   ParamSection                          (. VAR t: SymTab.TypeIndex;

File diff suppressed because it is too large
+ 3999 - 3962
compiler/src/M2.lst


+ 39 - 0
compiler/tests/t_proctype2.mod

@@ -0,0 +1,39 @@
+MODULE TProcType2;
+// ISO/PIM procedure-type syntax: comma-separated unnamed parameter
+// types (ARRAY OF CHAR, ...; CHAR, VAR T, ...), named groups, and PROC
+// as a result type. Exit 42.
+TYPE
+  Error_Proc = PROCEDURE(ARRAY OF CHAR, ARRAY OF CHAR);
+  ScanClass  = (padding, valid, invalid, terminator);
+  ScanState  = PROCEDURE (CHAR, VAR ScanClass, VAR ScanState);
+  GetProc    = PROCEDURE() : PROC;
+  Named      = PROCEDURE (a, b : INTEGER) : INTEGER;
+VAR ExitCode : INTEGER;
+VAR e : Error_Proc;
+VAR s : ScanState;
+VAR g : GetProc;
+VAR n : Named;
+PROCEDURE Err (a, b: ARRAY OF CHAR);
+BEGIN
+END Err;
+PROCEDURE Scan (c: CHAR; VAR cls: ScanClass; VAR st: ScanState);
+BEGIN
+END Scan;
+PROCEDURE Idle;
+BEGIN
+END Idle;
+PROCEDURE Get () : PROC;
+BEGIN
+  RETURN Idle
+END Get;
+PROCEDURE Add (a, b: INTEGER) : INTEGER;
+BEGIN
+  RETURN a + b
+END Add;
+BEGIN
+  e := Err;
+  s := Scan;
+  g := Get;
+  n := Add;
+  IF n(20, 22) = 42 THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TProcType2.

+ 2 - 2
docs/INDEX.md

@@ -34,8 +34,8 @@ bisecting *why* a decision was made, not for reading end-to-end).
 
 - **`summary_session-2026-10-22.md`** — corpus triage: PIM/ISO
   multi-index arrays, typed CHAR/enum subranges, ordinal `FOR`, ISO
-  empty string, typed libc prototypes, ISO SYSTEM sized types
-  (corpus 467 → 499).
+  empty string, typed libc prototypes, ISO SYSTEM sized types,
+  procedure-type syntax (corpus 467 → 503).
 - **`summary_session-2026-10-19.md`** — the whole 2026-10-19 session
   (P0–P5, gap14, robustness, library; corpus 405 → 467).
 - **2026-10-19 (P0–P4):** generics double-instantiation fixed (lower each

+ 12 - 6
docs/STATUS.md

@@ -4,10 +4,10 @@ This answers "where are we now?".
 
 ## Numbers
 
-- Suite **254/254** (`compiler/run_tests.sh`).
+- Suite **255/255** (`compiler/run_tests.sh`).
 - Self-hosting **FIXPOINT OK** — stage2 and stage3 images are
-  byte-identical (3,930,173 bytes).
-- Corpus **499 / 612** sources compile with 0 errors, **0 crashes**
+  byte-identical (3,935,235 bytes).
+- Corpus **503 / 612** sources compile with 0 errors, **0 crashes**
   (`tools/v3-corpus/corpus.sh` over the gm2 testsuite; the harness now
   resolves V3's own shipped libraries); the rest are genuine language
   gaps, **gm2 leniencies V3 deliberately rejects**, or unresolved
@@ -59,6 +59,11 @@ comparison, `CASE` labels, `FOR` bounds, actuals and `RETURN`, while
 remaining a string for `ARRAY OF CHAR` (whole-array assignment and
 content comparison).
 
+Procedure types accept comma-separated unnamed parameter types
+(`PROCEDURE(ARRAY OF CHAR, ARRAY OF CHAR)`, `PROCEDURE (CHAR, VAR T, ...)`)
+as well as named groups and `;` sections, and `PROC` is accepted as a
+result type (`PROCEDURE (): PROC`).
+
 The hard-coded `SYSTEM` module is registered as a real module, so
 qualified access resolves (`SYSTEM.CARDINAL8`, `SYSTEM.BITSET32`) as
 well as `FROM SYSTEM IMPORT`.  ISO/gm2 sized names are predefined:
@@ -143,8 +148,9 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
    subranges, ordinal `FOR`, ISO empty string `""`/`''` as CHAR), added
    real **libc prototypes** (+6: `printf`/`snprintf` are varargs
    functions, `malloc`/`memset`/`write`/… typed; the remaining libc
-   importers fail later for other reasons), and **ISO SYSTEM sized
-   types + qualified `SYSTEM`** (+8).  The remaining `LANG` entries are
+   importers fail later for other reasons), **ISO SYSTEM sized
+   types + qualified `SYSTEM`** (+8), and **ISO procedure-type syntax**
+   (+4: comma-separated unnamed params, `PROC` result).  The remaining `LANG` entries are
    mostly: **declaration order** cases V3 rejects by policy
    (`docs/gaps/`); ISO exceptions/`PACKEDSET`; multi-dimensional open
    arrays (representation, see below); and gm2-only modules
@@ -185,7 +191,7 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
 ```sh
 cd compiler
 ./build.sh          # M2.atg -> scanner/parser/driver -> ./M2
-./run_tests.sh      # 254/254
+./run_tests.sh      # 255/255
 cd ..
 ./bootstrap/fixpoint.sh   # FIXPOINT OK (byte-identical stage2/stage3)
 ```

+ 5 - 1
docs/features.md

@@ -1,4 +1,4 @@
-# m2compiler-V3 — feature status (2026-10-22; suite 254/254 green)
+# m2compiler-V3 — feature status (2026-10-22; suite 255/255 green)
 
 Pipeline: Coco/R `M2.atg` (1748 lines, 73 productions) → `gm2`-built
 `M2` → QBE `.ssa` → `qbe` → `cc` → run. `SymTab.mod` 1623 lines,
@@ -77,6 +77,10 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
   whole-string assignment to a 1-D `ARRAY OF UCHAR`, `+`
   concatenation of `U"…"` (and single-codepoint operands), and
   `LEN`/`LENGTH`/`HIGH` of a UString.
+- ✅ Procedure types (`PROCEDURE`/`PROC`): named parameter sections
+  separated by `;`, comma-separated unnamed types
+  (`PROCEDURE(ARRAY OF CHAR, ARRAY OF CHAR)`, `PROCEDURE (CHAR, VAR T)`),
+  the GNU type-only `(T1, T2)` shorthand, and `PROC` as a result type.
 - ✅ Opaque `TYPE T;` + completion in the implementation.
 - ✅ Variant records (`RECORD CASE … OF … END`); whole-record
   assignment is a flat byte blit (correct for overlapping fields) and

+ 7 - 0
docs/gaps/README.md

@@ -118,6 +118,13 @@ gap22_file_line.mod             invalid Fact            (__LINE__)
   `testgeneric`, `testgeneric2/3/4`.  Regression `t_system`.  V3's
   word model is 32-bit, so the narrow names share `INTEGER`/`CARDINAL`
   and `COMPLEX*` is a `REAL` placeholder.
+- **ISO procedure-type syntax — fixed** (2026-10-22): comma-separated
+  unnamed parameter types with a keyword or `VAR` start
+  (`PROCEDURE(ARRAY OF CHAR, ARRAY OF CHAR)`,
+  `PROCEDURE (CHAR, VAR scanClass, VAR scanState)`), and `PROC` as a
+  result type.  Named groups (`a, b : T`) and `;` sections still work.
+  Corpus **+4**: `typeonly.def`, `testrecursive.def`, `procconv`,
+  `procconv2`.  Regression `t_proctype2`.
 
 - **Multi-index arrays — fixed** (2026-10-22): the PIM/ISO form is
   `ARRAY [lo..hi], [lo..hi] OF T` (one bracket group per dimension).

+ 29 - 14
docs/summary_session-2026-10-22.md

@@ -1,4 +1,4 @@
-# Session 2026-10-22 — corpus triage: six clusters + libc prototypes
+# 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).
@@ -7,16 +7,16 @@ only plan source was its "Remaining" list (corpus `LANG` triage).
 
 | 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** |
+| 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          # 254/254
+cd compiler && ./build.sh && ./run_tests.sh          # 255/255
 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
-tools/v3-corpus/corpus.sh                            # 499 OK, 0 crash
+tools/v3-corpus/corpus.sh                            # 503 OK, 0 crash
 ```
 
 ## Triage method
@@ -31,7 +31,7 @@ source line.  They fall into these buckets:
 | 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** |
+| **conforming** clusters: arrays, subranges, ordinal `FOR`, empty string, libc prototypes, SYSTEM sized types, procedure types | 36 | **fixed this session** |
 
 ## What landed
 
@@ -101,6 +101,24 @@ statement is still `233` (`t_bad_funstmt`).  Corpus **+6**:
 `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:
@@ -147,13 +165,10 @@ word model, so the narrow integer/word names share `INTEGER`/
 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`,
+2. **ISO `SysClock`** — the stdlib stub lacks `DateTime`,
    `CanGetClock`, `GetClock`, `SetClock` (`testclock`, `testclock2`).
-4. **`PACKEDSET`**, ISO exceptions/`UNIQUE`, `__FUNCTION__` — each
+3. **`PACKEDSET`**, ISO exceptions/`UNIQUE`, `__FUNCTION__` — each
    one or two files; larger or gm2-flavoured.
-5. **Runtime subrange range checks** — the remaining conformance
+4. **Runtime subrange range checks** — the remaining conformance
    difference from this session's subrange work.
-6. `STATUS.md` "what remains" is the live list.
+5. `STATUS.md` "what remains" is the live list.

Some files were not shown because too many files changed in this diff