ソースを参照

generics inc3: procedure parameters

A generic formal may be a procedure:
GENERIC DEFINITION MODULE Sort (Element : TYPE;
  Compare : PROCEDURE(a,b : Element) : INTEGER); ...
instantiated by MODULE IntSort = Sort(INTEGER, Cmp.IntCompare);

- Formal type is an inline ProcType; the actual is a *qualified*
  procedure name (the refinement parses actuals before any import).
- Template: the formal is a proc-typed callable so the body checks
  (indirect call there); instantiation aliases it to the actual:
  SymTab.AliasProc clones the actual's SymNode under the formal name;
  Lower.AliasProc adds a scope-neutral proc-table entry so call
  emission targets the actual; ProcBaseByUid + a uid->base map let
  QbeGen.Mangled recover the base after the instance scope is popped.
- Also: refinement forms accept { Import }.

Verified end-to-end (Smaller(7,3) through IntSort with IntCompare in a
separate module -> 3). Suite 211/211 (SortProg); fixpoint OK; corpus
unchanged (372 OK, 0 crash).
Eric Streit 1 日 前
親
コミット
cb97d17ade

+ 2 - 0
compiler/run_tests.sh

@@ -285,6 +285,8 @@ expect_run_files PairProg 73 \
   g_pair.def g_pair.mod g_intchar.def g_intchar.mod g_pair_prog.mod
 expect_run_files BufProg 42 \
   g_buf.def g_buf.mod g_buf10.def g_buf10.mod g_bufprog.mod
+expect_run_files SortProg 3 \
+  g_cmp.def g_cmp.mod g_sort.def g_sort.mod g_isort.def g_isort.mod g_sortprog.mod
 expect_run_files WrapProg 42 \
   ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod \
   ../stdlib/iochan.def ../stdlib/iochan.mod \

+ 3 - 0
compiler/src/Lower.def

@@ -41,6 +41,9 @@ PROCEDURE CurScopeProc (): INTEGER;
 (* The proc entry NoteProc most recently made current. *)
 PROCEDURE NoteProc (name: ARRAY OF CHAR; uid: CARDINAL;
                     res: SymTab.TypeIndex; depth: CARDINAL; ext: BOOLEAN);
+PROCEDURE AliasProc (name: ARRAY OF CHAR; uid: CARDINAL;
+                     res: SymTab.TypeIndex; depth: CARDINAL; ext: BOOLEAN);
+(* Bind a generic procedure formal to an actual (proc-table alias). *)
 PROCEDURE MarkProcExternal;
 PROCEDURE NoteCProc (name: ARRAY OF CHAR);
 PROCEDURE NoteImportedProc (name: ARRAY OF CHAR);

+ 17 - 0
compiler/src/Lower.mod

@@ -234,6 +234,23 @@ PROCEDURE NoteProc (name: ARRAY OF CHAR; uid: CARDINAL;
     INC(nProcs)
   END NoteProc;
 
+PROCEDURE AliasProc (name: ARRAY OF CHAR; uid: CARDINAL;
+                     res: SymTab.TypeIndex; depth: CARDINAL; ext: BOOLEAN);
+(* A generic procedure formal bound to an actual procedure: add a
+   proc-table entry so calls through the formal resolve to the actual,
+   without disturbing the scope stack (unlike NoteProc). *)
+  BEGIN
+    IF nProcs >= MaxProc THEN RETURN END;
+    CopySpan(name, procName, 0, nProcs * 64);
+    procUid[nProcs] := uid; procRes[nProcs] := res;
+    procDep[nProcs] := depth; procExt[nProcs] := ext;
+    procNpar[nProcs] := 0;
+    procParent[nProcs] := -1;
+    procSymBase[nProcs] := nSyms;
+    HashPut(procHash, procName, name, nProcs);
+    INC(nProcs)
+  END AliasProc;
+
 PROCEDURE MarkProcExternal;
 (* The grammar calls this from the EXTERNAL branch, after NoteProc, so
    the walk records that the callee takes no static link. *)

+ 93 - 12
compiler/src/M2.atg

@@ -151,8 +151,9 @@ VAR
   refInst, refGen: SymTab.Name;
   refN: CARDINAL;
   refActual: ARRAY [0 .. 15] OF SymTab.TypeIndex;
-  refKind: ARRAY [0 .. 15] OF INTEGER;   (* 0 = type actual, 1 = value *)
+  refKind: ARRAY [0 .. 15] OF INTEGER;   (* 0 = type, 1 = value, 2 = proc *)
   refVal:  ARRAY [0 .. 15] OF SymTab.Name;
+  refMod:  ARRAY [0 .. 15] OF SymTab.Name;
   isGen: BOOLEAN;
 
   (* Generics (Increment 2): instantiation re-parse context.  When the
@@ -164,6 +165,7 @@ VAR
   instActual: ARRAY [0 .. 15] OF SymTab.TypeIndex;
   instKind: ARRAY [0 .. 15] OF INTEGER;
   instVal:  ARRAY [0 .. 15] OF SymTab.Name;
+  instMod:  ARRAY [0 .. 15] OF SymTab.Name;
   instN: CARDINAL;
   instFk: CARDINAL;   (* formal cursor while binding during re-parse *)
 
@@ -220,8 +222,20 @@ PROCEDURE InstActualVal (i: CARDINAL; VAR v: SymTab.Name);
     v[k] := CHR(0)
   END InstActualVal;
 
+PROCEDURE InstActualMod (i: CARDINAL; VAR m: SymTab.Name);
+  VAR k: CARDINAL;
+  BEGIN
+    m[0] := CHR(0);
+    IF i >= instN THEN RETURN END;
+    k := 0;
+    WHILE (k <= HIGH(m)) AND (instMod[i][k] # CHR(0)) DO
+      m[k] := instMod[i][k]; INC(k)
+    END;
+    m[k] := CHR(0)
+  END InstActualMod;
+
 PROCEDURE InstAddActual (kind: INTEGER; t: SymTab.TypeIndex;
-                         val: ARRAY OF CHAR);
+                         val, m: ARRAY OF CHAR);
   VAR k: CARDINAL;
   BEGIN
     IF instN > HIGH(instActual) THEN RETURN END;
@@ -231,6 +245,11 @@ PROCEDURE InstAddActual (kind: INTEGER; t: SymTab.TypeIndex;
       instVal[instN][k] := val[k]; INC(k)
     END;
     instVal[instN][k] := CHR(0);
+    k := 0;
+    WHILE (k <= HIGH(instMod[instN])) AND (m[k] # CHR(0)) DO
+      instMod[instN][k] := m[k]; INC(k)
+    END;
+    instMod[instN][k] := CHR(0);
     INC(instN)
   END InstAddActual;
 
@@ -273,6 +292,18 @@ PROCEDURE RefineActualKind (i: CARDINAL) : INTEGER;
     RETURN refKind[i]
   END RefineActualKind;
 
+PROCEDURE RefineActualMod (i: CARDINAL; VAR m: SymTab.Name);
+  VAR k: CARDINAL;
+  BEGIN
+    m[0] := CHR(0);
+    IF i >= refN THEN RETURN END;
+    k := 0;
+    WHILE (k <= HIGH(m)) AND (refMod[i][k] # CHR(0)) DO
+      m[k] := refMod[i][k]; INC(k)
+    END;
+    m[k] := CHR(0)
+  END RefineActualMod;
+
 PROCEDURE RefineActualVal (i: CARDINAL; VAR v: SymTab.Name);
   VAR k: CARDINAL;
   BEGIN
@@ -546,6 +577,10 @@ PRODUCTIONS
   GenericFormal                         (. VAR nm: SymTab.Name; vt: SymTab.TypeIndex;
                                              names: ARRAY [0 .. 15] OF SymTab.Name;
                                              nn, k: CARDINAL;
+                                             mmod: SymTab.Name;
+                                             quid, qdep: CARDINAL;
+                                             qres: SymTab.TypeIndex;
+                                             qext: BOOLEAN;
                                              isTypeFormal: BOOLEAN; .)
     =                                   (. nn := 0; .)
       GetIdent<nm>                      (. k := 0;
@@ -558,6 +593,7 @@ PRODUCTIONS
                                            names[nn][k] := CHR(0); INC(nn); .) }
       ":"                               (. vt := SymTab.InvalidType; isTypeFormal := FALSE; .)
       ( "TYPE"                          (. isTypeFormal := TRUE; .)
+      | ProcType<vt>                    (. isTypeFormal := FALSE; .)
       | TypeIdent<vt> )                 (. k := 0;
                                            WHILE k < nn DO
                                              IF isTypeFormal THEN
@@ -571,6 +607,26 @@ PRODUCTIONS
                                                    SemError(200) END
                                                ELSIF NOT SymTab.EnterTypeParam(names[k]) THEN
                                                  SemError(200) END
+                                             ELSIF SymTab.ClassOf(vt) = SymTab.ClProc THEN
+                                               (* procedure formal *)
+                                               IF instActive THEN
+                                                 IF InstActualKind(instFk) # 2 THEN
+                                                   SemError(233) END;
+                                                 InstActualMod(instFk, mmod);
+                                                 InstActualVal(instFk, nm);
+                                                 IF NOT SymTab.AliasProc(names[k], mmod, nm) THEN
+                                                   SemError(201)
+                                                 ELSIF SymTab.QualProcInfo(mmod, nm, quid, qdep, qres, qext) THEN
+                                                   Lower.AliasProc(names[k], quid, qres, qdep, qext)
+                                                 END
+                                               ELSE
+                                                 (* template: a callable
+                                                    placeholder of the
+                                                    formal's proc type *)
+                                                 IF NOT SymTab.Enter(names[k], SymTab.KindVar) THEN
+                                                   SemError(200) END;
+                                                 SymTab.SetSymType(names[k], vt)
+                                               END
                                              ELSE
                                                (* constraint: a value
                                                   formal needs a value
@@ -595,34 +651,58 @@ PRODUCTIONS
                                              s: ARRAY [0 .. 255] OF CHAR;
                                              qv: QbeGen.QVal;
                                              isCh, isV: BOOLEAN;
+                                             aKind: INTEGER;
+                                             amod: SymTab.Name;
+                                             nm2: SymTab.Name;
                                              k: CARDINAL; .)
-    =                                   (. isV := FALSE; at := SymTab.InvalidType; nm[0] := CHR(0); .)
+    =                                   (. isV := FALSE; at := SymTab.InvalidType;
+                                           nm[0] := CHR(0); nm2[0] := CHR(0);
+                                           aKind := 0; amod[0] := CHR(0); .)
       ( integer                         (. LexString(s); QbeGen.NormInt(s, qv);
-                                           isV := TRUE; at := SymTab.IntType();
+                                           isV := TRUE; aKind := 1; at := SymTab.IntType();
                                            CopyNm(qv, nm); .)
       | charConst                       (. LexString(s); QbeGen.NormLit(s, qv, isCh);
-                                           isV := TRUE; at := SymTab.CharType();
+                                           isV := TRUE; aKind := 1; at := SymTab.CharType();
                                            CopyNm(qv, nm); .)
-      | GetIdent<nm>                    (. IF SymTab.Lookup(nm)
+      | GetIdent<nm> [ "." GetIdent<nm2> ]
+                                        (. isV := FALSE; at := SymTab.InvalidType;
+                                           aKind := 0; amod[0] := CHR(0);
+                                           IF nm2[0] # CHR(0) THEN
+                                             (* qualified procedure actual:
+                                                Mod.Proc, no import needed *)
+                                             aKind := 2;
+                                             CopyNm(nm, amod);
+                                             CopyNm(nm2, nm)
+                                           ELSIF SymTab.Lookup(nm)
                                               AND ((SymTab.SymKind(nm) = SymTab.KindType)
                                                 OR (SymTab.SymKind(nm) = SymTab.KindPredef)) THEN
-                                              at := SymTab.SymType(nm); isV := FALSE
+                                              at := SymTab.SymType(nm)
+                                            ELSIF SymTab.Lookup(nm)
+                                              AND (SymTab.SymKind(nm) = SymTab.KindProc) THEN
+                                              aKind := 2; at := SymTab.SymType(nm);
+                                              IF NOT SymTab.SymMod(nm, amod) THEN
+                                                amod[0] := CHR(0) END
                                             ELSIF SymTab.Lookup(nm)
                                               AND (SymTab.SymKind(nm) = SymTab.KindConst) THEN
-                                              isV := TRUE; at := SymTab.SymType(nm);
+                                              isV := TRUE; aKind := 1; at := SymTab.SymType(nm);
                                               IF NOT SymTab.GetSymVal(nm, s) THEN s[0] := CHR(0) END;
                                               CopyNm(s, nm)
-                                            ELSE SemError(201);
-                                              at := SymTab.InvalidType; isV := FALSE
+                                            ELSE SemError(201)
                                             END; .) )
                                         (. IF refN <= HIGH(refActual) THEN
-                                             IF isV THEN refKind[refN] := 1
-                                             ELSE refKind[refN] := 0 END;
+                                             IF aKind = 0 THEN
+                                               IF isV THEN refKind[refN] := 1
+                                               ELSE refKind[refN] := 0 END
+                                             ELSE refKind[refN] := aKind END;
                                              refActual[refN] := at;
                                              k := 0;
                                              WHILE (k <= HIGH(refVal[refN])) AND (nm[k] # CHR(0)) DO
                                                refVal[refN][k] := nm[k]; INC(k) END;
                                              refVal[refN][k] := CHR(0);
+                                             k := 0;
+                                             WHILE (k <= HIGH(refMod[refN])) AND (amod[k] # CHR(0)) DO
+                                               refMod[refN][k] := amod[k]; INC(k) END;
+                                             refMod[refN][k] := CHR(0);
                                              INC(refN)
                                            END; .) .
   Unit
@@ -822,6 +902,7 @@ PRODUCTIONS
                                              AST.MakeLeaf(AST.NkIdent, m1));
                                            astDecl := AST.NoNode;
                                            astStmt := AST.NoNode; .)
+        { Import                        (. (* refinement unit import *) .) }
         "END"
         GetIdent<m2>                    (. IF NOT SymTab.Equal(m1, m2) THEN
                                              SemError(202) END;

ファイルの差分が大きいため隠しています
+ 3769 - 3688
compiler/src/M2.lst


+ 3 - 1
compiler/src/QbeGen.mod

@@ -929,7 +929,9 @@ PROCEDURE Mangled (pname: ARRAY OF CHAR; uid: CARDINAL; VAR q: QVal);
       Cpy(q, link);
       RETURN
     END;
-    IF NOT SymTab.SymBase(pname, base) THEN Cpy(base, pname) END;
+    IF NOT SymTab.SymBase(pname, base) THEN
+      IF NOT SymTab.ProcBaseByUid(uid, base) THEN Cpy(base, pname) END
+    END;
     Cpy(q, base);
     App(q, "_");
     AppNum(q, uid)

+ 11 - 0
compiler/src/SymTab.def

@@ -288,7 +288,18 @@ PROCEDURE MaterializeAlias (mod, name: ARRAY OF CHAR;
    params); the node remembers its base name and module for QBE
    symbol emission. *)
 
+PROCEDURE ProcBaseByUid (uid: CARDINAL; VAR base: Name): BOOLEAN;
+(* Base (mangling) name of a procedure by uid. *)
 PROCEDURE SymBase (name: ARRAY OF CHAR; VAR base: Name): BOOLEAN;
+
+PROCEDURE QualProcInfo (mod, name: ARRAY OF CHAR; VAR uid, depth: CARDINAL;
+                        VAR res: TypeIndex; VAR ext: BOOLEAN): BOOLEAN;
+(* Proc facts of a module-qualified procedure name. *)
+
+PROCEDURE SymMod (name: ARRAY OF CHAR; VAR m: Name): BOOLEAN;
+(* Defining module of the visible entry ("" for a local). *)
+PROCEDURE AliasProc (formalName: ARRAY OF CHAR; mod, actualName: ARRAY OF CHAR): BOOLEAN;
+(* Bind a generic procedure formal to the actual procedure. *)
 (* Base symbol name of the visible entry (drops any alias prefix). *)
 
 PROCEDURE GlobalRef (name: ARRAY OF CHAR; VAR g: Name): BOOLEAN;

+ 57 - 0
compiler/src/SymTab.mod

@@ -93,6 +93,7 @@ VAR
   pfwdT : ARRAY [0 .. 255] OF TypeIndex;
   (* deferred procedure bodies (two-phase parse): a skipped body's own
      scope, saved while still current, restored for the re-parse pass *)
+  uidBase : ARRAY [0 .. 1023] OF Name;  (* uid -> base name (mangling) *)
   nDefer : CARDINAL;
   deferProc : ARRAY [0 .. 255] OF SymPtr;
   deferScope : ARRAY [0 .. 255] OF ScopePtr;
@@ -1928,6 +1929,7 @@ PROCEDURE EnterProc (name: ARRAY OF CHAR): BOOLEAN;
     node^.fsnap := FALSE;
     node^.link[0] := CHR(0);
     node^.uid := nextUid; INC(nextUid);
+    IF node^.uid <= HIGH(uidBase) THEN Assign(uidBase[node^.uid], name) END;
     node^.fdep := nProc;
     node^.vslot := -1;
     node^.hasBody := FALSE;
@@ -1956,6 +1958,7 @@ PROCEDURE DeclareCProc (name: ARRAY OF CHAR): BOOLEAN;
     node^.fsnap := FALSE;
     Assign(node^.link, name);
     node^.uid := nextUid; INC(nextUid);
+    IF node^.uid <= HIGH(uidBase) THEN Assign(uidBase[node^.uid], name) END;
     node^.fdep := nProc;
     node^.vslot := -1;
     node^.hasBody := TRUE;
@@ -2532,6 +2535,60 @@ PROCEDURE MaterializeAlias (mod, name: ARRAY OF CHAR;
     RETURN TRUE
   END MaterializeAlias;
 
+PROCEDURE SymMod (name: ARRAY OF CHAR; VAR m: Name): BOOLEAN;
+(* The defining module of the visible entry ("" for a local). *)
+  VAR node: SymPtr;
+  BEGIN
+    m[0] := CHR(0);
+    node := Find(name);
+    IF node = NIL THEN RETURN FALSE END;
+    Assign(m, node^.mod);
+    RETURN TRUE
+  END SymMod;
+
+PROCEDURE AliasProc (formalName: ARRAY OF CHAR; mod, actualName: ARRAY OF CHAR): BOOLEAN;
+(* Generic procedure formal: clone the actual procedure's symbol
+   (uid/mod/params/result) under formalName in the current scope, so a
+   call through the formal resolves to the actual. *)
+  VAR src, node: SymPtr;
+  BEGIN
+    src := QualNode(mod, actualName);
+    IF src = NIL THEN
+      src := Find(actualName)   (* same-module actual *)
+    END;
+    IF src = NIL THEN RETURN FALSE END;
+    IF TreeFind(curScope^.root, formalName) # NIL THEN RETURN TRUE END;
+    NEW(node);
+    node^ := src^;
+    Assign(node^.name, formalName);
+    node^.left := NIL;
+    node^.right := NIL;
+    node^.scope := curScope;
+    IF NOT TreeInsert(curScope, node) THEN RETURN FALSE END;
+    RETURN TRUE
+  END AliasProc;
+
+PROCEDURE QualProcInfo (mod, name: ARRAY OF CHAR; VAR uid, depth: CARDINAL;
+                        VAR res: TypeIndex; VAR ext: BOOLEAN): BOOLEAN;
+(* Proc facts of a qualified (module-qualified) procedure name. *)
+  VAR n: SymPtr;
+  BEGIN
+    n := QualNode(mod, name);
+    IF n = NIL THEN RETURN FALSE END;
+    uid := n^.uid; depth := n^.fdep; res := n^.rslt; ext := n^.ext;
+    RETURN n^.kind = KindProc
+  END QualProcInfo;
+
+PROCEDURE ProcBaseByUid (uid: CARDINAL; VAR base: Name): BOOLEAN;
+(* Base (mangling) name of a procedure by its uid.  Lets a proc alias or
+   a call target whose scope is gone still mangle correctly. *)
+  BEGIN
+    base[0] := CHR(0);
+    IF (uid > HIGH(uidBase)) OR (uidBase[uid][0] = CHR(0)) THEN RETURN FALSE END;
+    Assign(base, uidBase[uid]);
+    RETURN TRUE
+  END ProcBaseByUid;
+
 PROCEDURE SymBase (name: ARRAY OF CHAR; VAR base: Name): BOOLEAN;
 (* Base symbol name of the visible entry (differs from `name` for
    qualified aliases). *)

+ 4 - 2
compiler/src/compiler.frm

@@ -8,10 +8,11 @@ MODULE -->Grammar;
   FROM -->Scanner IMPORT lst, src, errors, Error, CharAt;
   FROM -->Parser IMPORT Parse, Successful, GetUnit,
     InInst, InstBegin, InstAddActual, InstEnd, InstNameGet, InstActualGet,
-    InstActualKind, InstActualVal,
+    InstActualKind, InstActualVal, InstActualMod,
     UnitNameGet, UnitIsGeneric, UnitIsDefModule,
     RefineSeen, RefineIsDef, RefineInst, RefineGen,
     RefineActualCount, RefineActualGet, RefineActualKind, RefineActualVal,
+    RefineActualMod,
     SetTwoPhase;
   IMPORT
     Strings, Storage, SYSTEM, FileIO, SymTab, QbeGen, AST, Lower;
@@ -404,7 +405,8 @@ MODULE -->Grammar;
     j := 0;
     WHILE j < RefineActualCount() DO
       RefineActualVal(j, iname);
-      InstAddActual(RefineActualKind(j), RefineActualGet(j), iname); INC(j)
+      RefineActualMod(j, gname);
+      InstAddActual(RefineActualKind(j), RefineActualGet(j), iname, gname); INC(j)
     END;
     AST.Init;
     Lower.BeginUnit;

+ 3 - 1
compiler/src/parser.frm

@@ -146,12 +146,13 @@ PROCEDURE GetUnit (): AST.Node;
 PROCEDURE InInst (): BOOLEAN;
 PROCEDURE InstBegin (name: ARRAY OF CHAR);
 PROCEDURE InstAddActual (kind: INTEGER; t: SymTab.TypeIndex;
-                         val: ARRAY OF CHAR);
+                         val, m: ARRAY OF CHAR);
 PROCEDURE InstEnd;
 PROCEDURE InstNameGet (VAR nm: SymTab.Name);
 PROCEDURE InstActualGet (i: CARDINAL): SymTab.TypeIndex;
 PROCEDURE InstActualKind (i: CARDINAL): INTEGER;
 PROCEDURE InstActualVal (i: CARDINAL; VAR v: SymTab.Name);
+PROCEDURE InstActualMod (i: CARDINAL; VAR m: SymTab.Name);
 
 PROCEDURE UnitNameGet (VAR nm: SymTab.Name);
 PROCEDURE UnitIsGeneric (): BOOLEAN;
@@ -165,6 +166,7 @@ PROCEDURE RefineActualCount (): CARDINAL;
 PROCEDURE RefineActualGet (i: CARDINAL): SymTab.TypeIndex;
 PROCEDURE RefineActualKind (i: CARDINAL): INTEGER;
 PROCEDURE RefineActualVal (i: CARDINAL; VAR v: SymTab.Name);
+PROCEDURE RefineActualMod (i: CARDINAL; VAR m: SymTab.Name);
 
 (* Returns TRUE if no errors have been recorded while parsing *)
 

+ 3 - 0
compiler/tests/g_cmp.def

@@ -0,0 +1,3 @@
+DEFINITION MODULE Cmp;
+PROCEDURE IntCompare(a, b : INTEGER) : INTEGER;
+END Cmp.

+ 4 - 0
compiler/tests/g_cmp.mod

@@ -0,0 +1,4 @@
+IMPLEMENTATION MODULE Cmp;
+PROCEDURE IntCompare(a, b : INTEGER) : INTEGER;
+BEGIN RETURN a - b END IntCompare;
+END Cmp.

+ 2 - 0
compiler/tests/g_isort.def

@@ -0,0 +1,2 @@
+DEFINITION MODULE IntSort = Sort(INTEGER, Cmp.IntCompare);
+END IntSort.

+ 2 - 0
compiler/tests/g_isort.mod

@@ -0,0 +1,2 @@
+IMPLEMENTATION MODULE IntSort = Sort(INTEGER, Cmp.IntCompare);
+END IntSort.

+ 4 - 0
compiler/tests/g_sort.def

@@ -0,0 +1,4 @@
+GENERIC DEFINITION MODULE Sort (Element : TYPE;
+                                Compare : PROCEDURE(a, b : Element) : INTEGER);
+PROCEDURE Smaller(x, y : Element) : Element;
+END Sort.

+ 6 - 0
compiler/tests/g_sort.mod

@@ -0,0 +1,6 @@
+GENERIC IMPLEMENTATION MODULE Sort;
+PROCEDURE Smaller(x, y : Element) : Element;
+BEGIN
+  IF Compare(x, y) <= 0 THEN RETURN x ELSE RETURN y END
+END Smaller;
+END Sort.

+ 4 - 0
compiler/tests/g_sortprog.mod

@@ -0,0 +1,4 @@
+MODULE SortProg;
+FROM IntSort IMPORT Smaller;
+VAR ExitCode : INTEGER;
+BEGIN ExitCode := Smaller(7, 3) END SortProg.

+ 31 - 10
docs/analysis_generics.md

@@ -43,16 +43,37 @@
 > formal a value one) — otherwise `233`; tested by `g_badbuf`.
 > Suite **210/210**, fixpoint OK, corpus unchanged.
 >
-> **Still open for inc3+:** *procedure parameters*
-> (`Compare : CompareProc`, actual = a procedure).  These need two
-> mechanisms not yet present: (1) the refinement form
-> (`MODULE X = G(...);`) must accept `{ Import }` so the actual
-> procedure is visible; (2) a cross-parse **procedure alias** — clone
-> the actual proc's `SymNode` under the formal's name in the
-> instantiation scope (the `Materialize`/`MaterializeAlias` pattern,
-> which keeps `uid`/`mod` so mangling resolves).  Also open: a
-> **constraint model** beyond kind-matching (e.g. "`T` must be
-> ordinal"), and `GENERIC CLASS`.
+> **Still open for inc3+:** a **constraint model** beyond kind-matching
+> (e.g. "`T` must be ordinal"), and `GENERIC CLASS`.
+>
+> **Done (Increment 3 — 2026-10-16, continued): procedure parameters.**
+> A formal may be a procedure:
+> ```modula2
+> GENERIC DEFINITION MODULE Sort
+>   (Element : TYPE; Compare : PROCEDURE(a, b : Element) : INTEGER);
+> PROCEDURE Smaller(x, y : Element) : Element;
+> END Sort.
+> DEFINITION/IMPLEMENTATION MODULE IntSort = Sort(INTEGER, Cmp.IntCompare);
+> ```
+> The formal's type is an inline `ProcType`.  The **actual is a
+> *qualified* procedure name** (`Cmp.IntCompare`) — the refinement form
+> parses its actuals before any import, so a qualified name is the only
+> way to reach a procedure without one.  In the **abstract template** the
+> formal is entered as a proc-typed callable (KindVar + proc type), so
+> the body type-checks (calls are indirect there); at **instantiation**
+> it is aliased to the actual procedure:
+> - `SymTab.AliasProc(formal, mod, actual)` clones the actual's
+>   `SymNode` under the formal's name (keeps `uid`/`mod`/`sym`), so
+>   name resolution and argument checks see the actual;
+> - `Lower.AliasProc(formal, uid, res, depth, ext)` adds a **proc-table
+>   entry** (without disturbing the scope stack) so `Lower`'s call
+>   emission targets the actual;
+> - `SymTab.ProcBaseByUid` + a uid→base map let `QbeGen.Mangled` recover
+>   the actual's base name after the instance scope has been popped.
+>
+> Verified end-to-end: `Smaller(7, 3)` through `IntSort` (with
+> `IntCompare` in a separate module) returns 3.  Suite **211/211**
+> (`SortProg`), fixpoint OK, corpus unchanged.
 
 Study reviewed: `../Generics/` (the three `m2-generics-coco-r-*.md`
 analyses, the `m2generic/` prototype: `M2Generic.atg`,

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません