Study reviewed: ../Generics/ (the three m2-generics-coco-r-*.md
analyses, the m2generic/ prototype: M2Generic.atg,
gm2-gcc-0.2.atg → gm2-gcc-generics-0.3.atg, src/GenericGen.mod,
templates/{Stack,List,Map}.g*).
Verdict in one line: the study's recommendation (integrate into the compiler, not a preprocessor) is right — but its plan is written for an AST-based compiler, and V3 has no AST, so the practical design is re-entrant template parsing with a type-substitution scope, not AST specialization.
Two options, with a clear lean to the second:
IMPORT Stack<INTEGER>; a tool instantiates templates, rewrites
the user's source (IMPORT Stack<INTEGER> → IMPORT StackInteger),
then gm2 compiles the result.X<T> natively,
specializes internally, type-checks, and generates code; no gm2,
no source rewriting.m2generic is a competent option-1 prototype:
M2Generic.atg (48 lines) parses a .gin file of
INSTANTIATE Name<Type> FROM "x.gdef", "x.gmod";.GenericGen (Generate/Generate2) does lexical substitution of
the identifier T (or K,V) outside strings/chars/comments, plus
module renaming via MakeName/MakeName2
(Stack<INTEGER> → StackInteger), writing real .def/.mod.gm2-gcc-generics-0.3.atg adds the generic syntax to the full gm2
grammar's Import:
GenericTypeActual = Qualident.
GenericTypeActualList = GenericTypeActual { "," GenericTypeActual }.
GenericModuleDesignator = Ident "<" GenericTypeActualList ">".
ImportModule = Ident | GenericModuleDesignator.
ImportModuleList = ImportModule { "," ImportModule }.
Import = "FROM" ImportModule "IMPORT" IdentList ";" |
"IMPORT" ImportModuleList ";".
Its README already flags the gap: parsing ≠ generation — the import-rewriting step (source→source) is still missing.
The supporting docs (analysis-0, analysis-1) survey Modula-2
generic techniques (ADDRESS + SYSTEM, code generation, generic
modules, procedure parameters, opaque types) and conclude that for a
custom compiler with OOP the in-compiler route is preferable, with a
3-level plan: type generics → multiple parameters → generics + OOP.
Strengths. Right conclusion for a custom compiler; good survey of
the design space; a working prototype that pins the naming convention
and the X<T> syntax; the 3-level staging is sensible.
Weaknesses (for the in-compiler path).
T replacement is fragile — it can't tell the type
parameter from a coincidentally-named user identifier, and it must
special-case strings/comments/chars by hand. No nesting, no
constraints, no shadowing discipline.gm2 stays the type-checker — the whole point of having V3 is not
to need it.MakeName/MakeName2 bake in 1–2 parameters;
Map<K,V> is a special case, not a general map.The study's recommended architecture is:
Generic AST → instantiation → specialized AST → type check → codegen
V3's frontend is a Coco/R grammar with inline semantic actions
(M2.atg) that call SymTab/QbeGen while parsing — single-pass,
declaration-before-use, no intermediate tree. So "specialize the
AST" is not directly implementable. Two ways to read
"integrate into the compiler":
TypeIdent/Lookup path simply
finds T as a type. This is V3's native analogue of the study's
"specialized AST", and strictly better than GenericGen.T abstractly and
specialize at instantiation. Much harder without an AST; a redesign.(A) is the target.
Syntax (minimal, grammatical subset first):
GENERIC DEFINITION MODULE Stack<T>;
TYPE Stack; PROCEDURE Push(VAR s : Stack; x : T); ...
END Stack.
GENERIC IMPLEMENTATION MODULE Stack<T>; ... END Stack.
Use:
IMPORT Stack<INTEGER>;
FROM Stack<INTEGER> IMPORT Stack, Push;
Mechanism, as a session-expansion pre-pass over the driver's unit list (reusing the fact that V3 already compiles several units — def + impl + program — per session):
IMPORT Name<Args> / FROM Name<Args> IMPORT … in the
inputs. (Cheap token scan, or fold it into the parser's import
action.)Name<Args…> once
(dedup cache), topologically ordered before its importer..gdef then .gmod) with a substitution
scope live: push a scope binding T (or K,V) to the resolved
actual type indices; the normal TypeIdent/Lookup path resolves
T. Module/type names are mangled per instance
(Stack<INTEGER> → StackInteger).StackInteger as an ordinary module:
full type checking and real codegen, no source rewriting, no
external tool, no gm2.Naming: generalise the mangle to N parameters
(Map<INTEGER,REAL> → MapIntegerReal) and to nested actuals
(Stack<Stack<INTEGER>> → e.g. StackStackInteger, hashed if long).
IMPORT X<T> / FROM X<T> IMPORT …, dedup, substitution-scope
parse. (Stack<INTEGER>, Stack<REAL>.)Map<K,V>) — a map, not
special cases.T must be ordinal), then
generics + OOP (GENERIC CLASS).Parse callable from an action if the
pre-pass proves insufficient..gdef/.gmod files or is
recognised inside ordinary .def/.mod by the GENERIC keyword.T replacement for the in-compiler path — use a
substitution scope; keep GenericGen only as a test oracle.gm2 from the final pipeline (V3 type-checks).GENERIC … MODULE X<T>; + IMPORT X<T> first;
FROM X<T> IMPORT … next.The ../Generics study is a solid survey and its recommendation
(in-compiler) is right for V3. Its concrete plan assumes an AST, which
V3 doesn't have, so the realizable design is re-entrant template
parsing with a type-substitution scope plus a session-expansion/dedup
pre-pass — no AST rewriting, no textual substitution, no gm2. That
is a genuine language feature of roughly CLASS-lowering scale, and it
fits V3's architecture and self-hosting guard.