Explorar o código

v3 step 1.7 — declarations: scalar TYPEs, headings, bodies, FORWARD (15/15 tests green)

Eric Streit hai 3 semanas
pai
achega
42749c6426

+ 5 - 0
compiler/run_tests.sh

@@ -50,9 +50,14 @@ expect_run t_minimal.mod 0
 expect_run t_exit.mod 7
 expect_run t_arith.mod 25
 expect_run showcase1.mod 50
+expect_run t_types.mod 8
+expect_fail t_enumdecl.mod "not supported yet"
+expect_fail t_proc.mod "not supported yet"
+expect_fail t_forward.mod "not supported yet"
 expect_fail t_bad_undecl.mod "undeclared identifier"
 expect_fail t_bad_dup.mod "duplicate identifier"
 expect_fail t_bad_mismatch.mod "module name mismatch"
+expect_fail t_bad_opaque.mod "opaque type outside definition"
 expect_fail t_defunit.mod "not supported yet"
 expect_fail t_implunit.mod "not supported yet"
 expect_fail t_nested.mod "not supported yet"

+ 138 - 41
compiler/src/M2.atg

@@ -10,10 +10,12 @@ COMPILER M2
 
    Semantic errors reuse the V1/V2/Test2 family: 200 duplicate,
    201 undeclared, 202 module name mismatch, 210 bad assignment,
-   211 bad arithmetic, 221 not a type, 230 construct not supported
-   in step 1 (REAL/STRING literals, non-INTEGER VAR types,
-   non-literal CONST expressions, imported names as values).
-   Full language arrives in later steps; the 230s mark its edge. *)
+   211 bad arithmetic, 221 not a type, 230 not supported yet,
+   231 opaque type outside definition.
+   Scalar-phase TYPEs (named, integer subrange, enum) check fully;
+   composite forms wait for step 3. Procedure headings (formals,
+   result, FORWARD) enter scopes now; bodies parse + check with one
+   230 at END (lowering = step 4). *)
 
 IMPORT SymTab, QbeGen;
 
@@ -34,6 +36,7 @@ COMMENTS FROM "//" TO lf
 TOKENS
   ident   = letter { letter | digit } .
   integer = digit { digit }
+          | digit { digit } CONTEXT("..")
           | "0x" hexDigit { hexDigit }
           | "0X" hexDigit { hexDigit } .
   real    = digit { digit } "." { digit }
@@ -51,7 +54,7 @@ PRODUCTIONS
     = DefUnit
     | ImplUnit
     | ProgModule .
-  DefUnit                               (. VAR m1, m2: SymTab.Name; .)
+  DefUnit                               (. VAR m1, m2, pn: SymTab.Name; .)
     = "DEFINITION" "MODULE"
       GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
                                            IF ~SymTab.Enter(m1,
@@ -59,7 +62,8 @@ PRODUCTIONS
                                              SemError(200) END; .)
       ";"
       { Import }
-      { ConstBlock | OpaqueBlock | VarBlock }
+      { ConstBlock | TypeBlock<TRUE> | VarBlock
+      | ProcHeading<pn> ";"             (. SymTab.PopScope; .) }
       "END"
       GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
                                              SemError(202) END; .)
@@ -101,7 +105,8 @@ PRODUCTIONS
                                         (. QbeGen.EndModule;
                                            SymTab.PrintTable; .) .
   DeclSeq
-    = { ConstBlock | VarBlock | NestedModule ";" } .
+    = { ConstBlock | TypeBlock<FALSE> | VarBlock | ProcDecl ";"
+      | NestedModule ";" } .
   (* Local module, Wirth form. Parses + checks fully (own scope);
      lowering waits for step 4, marked by one 230 at END. *)
   NestedModule                          (. VAR m1, m2: SymTab.Name; .)
@@ -149,14 +154,72 @@ PRODUCTIONS
                                              SemError(200) END; .) } .
   (* Opaque TYPE declarations (definition modules). The targetless
      alias resolves to InvalidType until step 4 completes it. *)
-  OpaqueBlock
-    = "TYPE" { OpaqueDecl ";" } .
-  OpaqueDecl                            (. VAR n: SymTab.Name; .)
+  (* Scalar-phase TYPEs: named types, integer subranges, enumerations.
+     Opaque "TYPE T;" needs isDef (definition units); elsewhere 231.
+     Composite forms (ARRAY/RECORD/SET/POINTER) arrive with step 3. *)
+  TypeBlock<isDef: BOOLEAN>
+    = "TYPE" { TypeItem<isDef> ";" } .
+  TypeItem<isDef: BOOLEAN>              (. VAR n: SymTab.Name;
+                                             t: SymTab.TypeIndex; .)
     = GetIdent<n>                       (. IF ~SymTab.Enter(n,
                                              SymTab.KindType) THEN
-                                             SemError(200) END;
+                                             SemError(200) END; .)
+      ( "=" Type<t>                     (. SymTab.SetSymType(n, t); .)
+      |                                 (. IF ~isDef THEN
+                                             SemError(231) END;
                                            SymTab.SetSymType(n,
-                                             SymTab.NewAlias()); .) .
+                                             SymTab.NewAlias()); .) ) .
+  Type<VAR t: SymTab.TypeIndex>
+    = TypeIdent<t>
+    | Subrange<t>
+    | Enum<t> .
+  TypeIdent<VAR t: SymTab.TypeIndex>    (. VAR n: SymTab.Name;
+                                             k: INTEGER; .)
+    = GetIdent<n>                       (. IF ~SymTab.Lookup(n) THEN
+                                             SemError(201);
+                                             t := SymTab.InvalidType
+                                           ELSE k := SymTab.SymKind(n);
+                                             IF (k = SymTab.KindType)
+                                                OR (k = SymTab.KindPredef) THEN
+                                               t := SymTab.SymType(n)
+                                             ELSIF k = SymTab.KindImport THEN
+                                               t := SymTab.InvalidType
+                                             ELSE SemError(221);
+                                               t := SymTab.InvalidType
+                                             END
+                                           END; .) .
+  Subrange<VAR t: SymTab.TypeIndex>     (. VAR tlo, thi: SymTab.TypeIndex;
+                                             qlo, qhi: QbeGen.QVal;
+                                             lo, hi: INTEGER; .)
+    = "[" Expr<tlo, qlo> ".." Expr<thi, qhi>
+                                        (. IF (tlo = SymTab.InvalidType)
+                                            OR (thi = SymTab.InvalidType) THEN
+                                           t := SymTab.InvalidType
+                                         ELSIF (SymTab.ClassOf(tlo) #
+                                                SymTab.ClInt)
+                                            OR (SymTab.ClassOf(thi) #
+                                                SymTab.ClInt) THEN
+                                           SemError(230);
+                                           t := SymTab.InvalidType
+                                         ELSIF ~SymTab.ConstInt(qlo, lo)
+                                            OR ~SymTab.ConstInt(qhi, hi)
+                                            OR (lo > hi) THEN
+                                           SemError(230);
+                                           t := SymTab.InvalidType
+                                         ELSE t := SymTab.NewSubR(lo, hi)
+                                         END; .)
+      "]" .
+  Enum<VAR t: SymTab.TypeIndex>         (. VAR n: SymTab.Name; .)
+    = "("                               (. t := SymTab.NewEnum(); .)
+      GetIdent<n>                       (. IF ~SymTab.Enter(n,
+                                             SymTab.KindConst) THEN
+                                             SemError(200) END;
+                                           SymTab.SetSymType(n, t); .)
+      { "," GetIdent<n>                 (. IF ~SymTab.Enter(n,
+                                             SymTab.KindConst) THEN
+                                             SemError(200) END;
+                                           SymTab.SetSymType(n, t); .) }
+      ")" .
   ConstBlock
     = "CONST" { ConstDecl ";" } .
   ConstDecl                             (. VAR n: SymTab.Name;
@@ -176,23 +239,12 @@ PRODUCTIONS
                                            QbeGen.DeclConst(n, qv, t); .) .
   VarBlock
     = "VAR" { VarDecl ";" } .
-  VarDecl                               (. VAR n, nm: SymTab.Name;
+  VarDecl                               (. VAR nm: SymTab.Name;
                                              t: SymTab.TypeIndex;
                                              i: CARDINAL;
                                              cls: INTEGER; .)
     = VarIdents ":"
-      GetIdent<n>                       (. IF ~SymTab.Lookup(n) THEN
-                                             SemError(201);
-                                             t := SymTab.InvalidType
-                                           ELSIF (SymTab.SymKind(n) #
-                                                  SymTab.KindType)
-                                              & (SymTab.SymKind(n) #
-                                                 SymTab.KindPredef) THEN
-                                             SemError(221);
-                                             t := SymTab.InvalidType
-                                           ELSE t := SymTab.SymType(n)
-                                           END;
-                                           cls := SymTab.ClassOf(t);
+      TypeIdent<t>                      (. cls := SymTab.ClassOf(t);
                                            IF (t # SymTab.InvalidType)
                                               & (cls # SymTab.ClInt) THEN
                                              SemError(230) END;
@@ -211,6 +263,43 @@ PRODUCTIONS
         GetIdent<n>                     (. IF ~SymTab.EnterPending(n,
                                              SymTab.KindVar) THEN
                                              SemError(200) END; .) } .
+  ParIdents<isV: BOOLEAN>               (. VAR n: SymTab.Name; .)
+    = GetIdent<n>                       (. IF ~SymTab.EnterParam(n, isV) THEN
+                                             SemError(200) END; .)
+      { "," GetIdent<n>                 (. IF ~SymTab.EnterParam(n, isV) THEN
+                                             SemError(200) END; .) } .
+  (* Procedure headings enter now (scopes, params, result); bodies
+     parse + check with one 230 at END (lowering = step 4).
+     FORWARD marks; the body heading re-enters (signature compare
+     tightens in step 4). *)
+  ProcHeading<VAR pn: SymTab.Name>      (. VAR t: SymTab.TypeIndex; .)
+    = "PROCEDURE"
+      GetIdent<pn>                      (. IF ~SymTab.EnterProc(pn) THEN
+                                           IF ~SymTab.ReenterProc(pn) THEN
+                                             SemError(200) END
+                                           END; .)
+      [ FormalParams ]
+      [ ":" TypeIdent<t>                (. SymTab.SetProcRes(t); .) ] .
+  FormalParams
+    = "(" [ ParamSection { ";" ParamSection } ] ")" .
+  ParamSection                          (. VAR t: SymTab.TypeIndex;
+                                             isV: BOOLEAN; .)
+    =                                   (. isV := FALSE; .)
+      [ "VAR"                           (. isV := TRUE; .) ]
+      ParIdents<isV> ":" TypeIdent<t>   (. SymTab.FixPending(t); .) .
+  ProcDecl                              (. VAR pn, m2: SymTab.Name; .)
+    = ProcHeading<pn> ";"
+      ( "FORWARD"                       (. SymTab.MarkFwd;
+                                           SymTab.PopScope; .)
+      | Block<pn>                       (. SymTab.PopScope;
+                                           SemError(230); .) ) .
+  Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
+    = DeclSeq
+      [ "BEGIN"
+        [ StatSeq ] ]
+      "END"
+      GetIdent<m2>                      (. IF ~SymTab.Equal(pn, m2) THEN
+                                             SemError(202) END; .) .
   StatSeq
     = Statement { ";" Statement } .
   Statement
@@ -221,12 +310,14 @@ PRODUCTIONS
                                              qn: SymTab.Name; .)
     = Design<dt, dk, qd, qn> ":="
       Expr<et, qe>                      (. IF (dt # SymTab.InvalidType)
-                                           & (dk # SymTab.KindVar) THEN
+                                           & (dk # SymTab.KindVar)
+                                           & (dk # SymTab.KindParam) THEN
                                            SemError(210)
                                          ELSIF ~SymTab.Assignable(et,
                                                   dt) THEN
                                            SemError(210) END;
-                                         IF (dk = SymTab.KindVar)
+                                         IF ((dk = SymTab.KindVar)
+                                             OR (dk = SymTab.KindParam))
                                             & (dt # SymTab.InvalidType)
                                             & (et # SymTab.InvalidType) THEN
                                            QbeGen.StoreVar(qn, qe, FALSE)
@@ -264,23 +355,29 @@ PRODUCTIONS
                                                    QbeGen.LoadVar(n,
                                                      FALSE, q)
                                                  ELSE
+                                                   IF t #
+                                                      SymTab.InvalidType THEN
+                                                     SemError(230)
+                                                   END;
                                                    QbeGen.CopyOp("0", q)
                                                  END
                                                END
-                                             ELSIF k = SymTab.KindVar THEN
-                                               cls :=
-                                                 SymTab.ClassOf(t);
-                                               IF cls = SymTab.ClInt THEN
-                                                 QbeGen.LoadVar(n,
-                                                   FALSE, q)
-                                               ELSE SemError(230);
-                                                 QbeGen.CopyOp("0", q)
-                                               END
-                                             ELSE QbeGen.CopyOp("0", q);
-                                               IF k = SymTab.KindImport THEN
-                                                 SemError(230)
-                                               END
-                                             END
+                                              ELSIF (k = SymTab.KindVar)
+                                                 OR (k = SymTab.KindParam) THEN
+                                                cls :=
+                                                  SymTab.ClassOf(t);
+                                                IF cls = SymTab.ClInt THEN
+                                                  QbeGen.LoadVar(n,
+                                                    FALSE, q)
+                                                ELSE SemError(230);
+                                                  QbeGen.CopyOp("0", q)
+                                                END
+                                              ELSE QbeGen.CopyOp("0", q);
+                                                IF (k = SymTab.KindImport)
+                                                   OR (k = SymTab.KindProc) THEN
+                                                  SemError(230)
+                                                END
+                                              END
                                            END; .) .
   Expr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
     = SimExpr<t, q> .

+ 6 - 6
compiler/src/M2.lst

@@ -14,12 +14,12 @@ LL(1) conditions:         --  ok  --
 
 Statistics:
 
-  nr of terminals:        33 (limit   400)
-  nr of non-terminals:    29 (limit   210)
-  nr of pragmas:           0 (limit   467)
-  nr of symbolnodes:      62 (limit   500)
-  nr of graphnodes:      211 (limit  1500)
-  nr of conditionsets:     1 (limit   100)
+  nr of terminals:        36 (limit   400)
+  nr of non-terminals:    39 (limit   210)
+  nr of pragmas:           0 (limit   464)
+  nr of symbolnodes:      75 (limit   500)
+  nr of graphnodes:      295 (limit  1500)
+  nr of conditionsets:     2 (limit   100)
   nr of charactersets:    11 (limit   250)
 
 

+ 34 - 0
compiler/src/SymTab.def

@@ -26,6 +26,8 @@ CONST
   KindModule = 4;
   KindPredef = 5;
   KindField  = 6;
+  KindProc   = 7;
+  KindParam  = 8;
 
   (* type classes returned by ClassOf *)
   ClInvalid = 0;
@@ -110,10 +112,38 @@ PROCEDURE PushRecord (t: TypeIndex): BOOLEAN;
 
 PROCEDURE PrintTable;
 
+(* ---------------- procedures (headings now, bodies step 4) ---------------- *)
+
+PROCEDURE EnterProc (name: ARRAY OF CHAR): BOOLEAN;
+(* Enters a KindProc heading and pushes its parameter scope.
+   FALSE on duplicate (no scope pushed). Resets result to
+   InvalidType (proper procedure), clears fwd, empties param chain. *)
+
+PROCEDURE ReenterProc (name: ARRAY OF CHAR): BOOLEAN;
+(* Resumes a FORWARD-marked procedure of this scope for its body
+   heading: clears fwd, pushes a fresh parameter scope. FALSE
+   when no such forward heading exists (then 200 applies). *)
+
+PROCEDURE EnterParam (name: ARRAY OF CHAR; isVar: BOOLEAN): BOOLEAN;
+(* Enters a KindParam in the heading scope and chains it into the
+   current procedure's positional param list. FALSE on duplicate. *)
+
+PROCEDURE SetProcRes (t: TypeIndex);
+(* Sets the current procedure's result type (InvalidType = none). *)
+
+PROCEDURE ProcRes (name: ARRAY OF CHAR): TypeIndex;
+(* Result type of the named procedure, InvalidType if absent/none. *)
+
+PROCEDURE MarkFwd;
+(* Marks the current procedure FORWARD (body must follow). *)
+
 (* ---------------- type descriptors ---------------- *)
 
 PROCEDURE NewAlias (): TypeIndex;
 PROCEDURE NewSub (base: TypeIndex): TypeIndex;
+PROCEDURE NewSubR (lo, hi: INTEGER): TypeIndex;
+(* Integer subrange [lo..hi] (no base descriptor needed). Bounds
+   accessors arrive with step 3; the backend cannot lower them yet. *)
 PROCEDURE NewEnum (): TypeIndex;
 PROCEDURE NewArray (elem: TypeIndex): TypeIndex;
 PROCEDURE NewRecord (): TypeIndex;
@@ -174,4 +204,8 @@ PROCEDURE SetFor (elem: TypeIndex): TypeIndex;
 
 PROCEDURE StrLen (s: ARRAY OF CHAR): CARDINAL;
 
+PROCEDURE ConstInt (q: ARRAY OF CHAR; VAR v: INTEGER): BOOLEAN;
+(* Parses a folded integer immediate ("42", "-5", "16") as produced
+   by NormInt/NegFold. FALSE for temporaries and non-numerics. *)
+
 END SymTab.

+ 108 - 1
compiler/src/SymTab.mod

@@ -25,6 +25,10 @@ TYPE
     scope : ScopePtr;   (* owning scope *)
     left  : SymPtr;     (* BST links within the owning scope *)
     right : SymPtr;
+    rslt  : TypeIndex;  (* KindProc result type, InvalidType = none *)
+    plink : SymPtr;     (* next formal, positional chain *)
+    isVar : BOOLEAN;    (* KindParam: VAR formal *)
+    fwd   : BOOLEAN;    (* KindProc: FORWARD body pending *)
   END;
   ScopeNode = RECORD
     parent : ScopePtr;  (* scope tree link *)
@@ -51,7 +55,11 @@ VAR
   nPendF : CARDINAL;
   tform : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
   tref : ARRAY [0 .. MaxTypes - 1] OF TypeIndex;
+  tlo : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
+  thi : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
   nTypes : CARDINAL;
+  curProc : SymPtr;   (* heading being declared *)
+  curPTail : SymPtr;  (* positional param chain tail *)
   dInt, dCard, dReal, dChar, dBool : TypeIndex;
 
 (* ---------------- strings ---------------- *)
@@ -97,6 +105,22 @@ PROCEDURE StrLen (s: ARRAY OF CHAR): CARDINAL;
     RETURN i
   END StrLen;
 
+PROCEDURE ConstInt (q: ARRAY OF CHAR; VAR v: INTEGER): BOOLEAN;
+  VAR i, d: CARDINAL;
+    neg: BOOLEAN;
+  BEGIN
+    v := 0; i := 0; neg := FALSE;
+    IF q[0] = "-" THEN neg := TRUE; i := 1 END;
+    IF (i >= HIGH(q)) OR (q[i] = 0C) THEN RETURN FALSE END;
+    WHILE (i < HIGH(q)) & (q[i] # 0C) DO
+      d := ORD(q[i]);
+      IF (d < ORD("0")) OR (d > ORD("9")) THEN RETURN FALSE END;
+      v := v * 10 + VAL(INTEGER, d - ORD("0")); INC(i)
+    END;
+    IF neg THEN v := -v END;
+    RETURN TRUE
+  END ConstInt;
+
 (* ---------------- scope tree + per-scope BST ---------------- *)
 
 PROCEDURE NewScope (parent: ScopePtr; level: CARDINAL): ScopePtr;
@@ -324,6 +348,14 @@ PROCEDURE NewSub (base: TypeIndex): TypeIndex;
     RETURN NewDesc(FSub, base)
   END NewSub;
 
+PROCEDURE NewSubR (lo, hi: INTEGER): TypeIndex;
+  VAR t: TypeIndex;
+  BEGIN
+    t := NewDesc(FSub, InvalidType);
+    IF t # InvalidType THEN tlo[t] := lo; thi[t] := hi END;
+    RETURN t
+  END NewSubR;
+
 PROCEDURE NewEnum (): TypeIndex;
   BEGIN
     RETURN NewDesc(FEnum, InvalidType)
@@ -400,7 +432,8 @@ PROCEDURE ClassOf (t: TypeIndex): INTEGER;
     | FSet : RETURN ClSet
     | FPtr : RETURN ClPtr
     | FStr : RETURN ClStr
-    | FSub : RETURN ClassOf(tref[r])
+    | FSub : IF tref[r] = InvalidType THEN RETURN ClInt
+             ELSE RETURN ClassOf(tref[r]) END
     ELSE RETURN ClInvalid
     END
   END ClassOf;
@@ -469,6 +502,77 @@ PROCEDURE PushRecord (t: TypeIndex): BOOLEAN;
     RETURN TRUE
   END PushRecord;
 
+(* ---------------- procedures ---------------- *)
+
+PROCEDURE EnterProc (name: ARRAY OF CHAR): BOOLEAN;
+  VAR node: SymPtr;
+  BEGIN
+    node := RawEnter(name, KindProc);
+    IF node = NIL THEN curProc := NIL; RETURN FALSE END;
+    node^.rslt := InvalidType;
+    node^.plink := NIL;
+    node^.isVar := FALSE;
+    node^.fwd := FALSE;
+    curProc := node;
+    curPTail := NIL;
+    PushScope;
+    RETURN TRUE
+  END EnterProc;
+
+PROCEDURE ReenterProc (name: ARRAY OF CHAR): BOOLEAN;
+  VAR node: SymPtr;
+  BEGIN
+    node := TreeFind(curScope^.root, name);
+    IF (node = NIL) OR (node^.kind # KindProc) OR ~node^.fwd THEN
+      RETURN FALSE
+    END;
+    node^.fwd := FALSE;
+    node^.plink := NIL;  (* fresh signature; step 4 compares old vs new *)
+    curProc := node;
+    curPTail := NIL;
+    PushScope;
+    RETURN TRUE
+  END ReenterProc;
+
+PROCEDURE EnterParam (name: ARRAY OF CHAR; isVar: BOOLEAN): BOOLEAN;
+  VAR node: SymPtr;
+  BEGIN
+    node := RawEnter(name, KindParam);
+    IF node = NIL THEN RETURN FALSE END;
+    node^.isVar := isVar;
+    node^.plink := NIL;
+    IF (nPend < MaxPend) THEN
+      pend[nPend] := node; INC(nPend)
+    END;
+    IF curProc # NIL THEN
+      IF curProc^.plink = NIL THEN curProc^.plink := node
+      ELSE curPTail^.plink := node
+      END;
+      curPTail := node
+    END;
+    RETURN TRUE
+  END EnterParam;
+
+PROCEDURE SetProcRes (t: TypeIndex);
+  BEGIN
+    IF curProc # NIL THEN curProc^.rslt := t END
+  END SetProcRes;
+
+PROCEDURE ProcRes (name: ARRAY OF CHAR): TypeIndex;
+  VAR node: SymPtr;
+  BEGIN
+    node := Find(name);
+    IF (node = NIL) OR (node^.kind # KindProc) THEN
+      RETURN InvalidType
+    END;
+    RETURN node^.rslt
+  END ProcRes;
+
+PROCEDURE MarkFwd;
+  BEGIN
+    IF curProc # NIL THEN curProc^.fwd := TRUE END
+  END MarkFwd;
+
 (* ---------------- predicates (unchanged) ---------------- *)
 
 PROCEDURE SetBasesOk (a, b: TypeIndex): BOOLEAN;
@@ -629,6 +733,7 @@ PROCEDURE Init;
     fields := NIL; nFields := 0;
     nPend := 0; nPendF := 0;
     nTypes := 0;
+    curProc := NIL; curPTail := NIL;
     curScope := NewScope(NIL, 0);
     dInt := NewDesc(FInt, InvalidType);
     dCard := NewDesc(FInt, InvalidType);
@@ -660,6 +765,8 @@ PROCEDURE WriteKind (kind: INTEGER);
     | KindModule : FileIO.WriteString(FileIO.StdOut, "MODULE")
     | KindPredef : FileIO.WriteString(FileIO.StdOut, "PREDEF")
     | KindField  : FileIO.WriteString(FileIO.StdOut, "FIELD")
+    | KindProc   : FileIO.WriteString(FileIO.StdOut, "PROC")
+    | KindParam  : FileIO.WriteString(FileIO.StdOut, "PARAM")
     ELSE FileIO.WriteString(FileIO.StdOut, "???")
     END
   END WriteKind;

+ 1 - 0
compiler/src/compiler.frm

@@ -103,6 +103,7 @@ MODULE -->Grammar;
         | 223: Msg("cyclical type definition")
         | 224: Msg("ordinal type required")
         | 230: Msg("not supported yet")
+        | 231: Msg("opaque type outside definition")
         ELSE         Msg("Error: "); WriteInt(f, nr, 0);
         END
       END ErrText;

+ 4 - 0
compiler/tests/t_bad_opaque.mod

@@ -0,0 +1,4 @@
+MODULE TBadOpaque;
+TYPE T;
+BEGIN
+END TBadOpaque.

+ 8 - 0
compiler/tests/t_enumdecl.mod

@@ -0,0 +1,8 @@
+MODULE TEnumDecl;
+TYPE Color = (Red, Green, Blue);
+VAR e : Color;
+VAR ExitCode : INTEGER;
+BEGIN
+  e := Red;
+  ExitCode := 0
+END TEnumDecl.

+ 10 - 0
compiler/tests/t_forward.mod

@@ -0,0 +1,10 @@
+MODULE TFwd;
+VAR ExitCode : INTEGER;
+PROCEDURE P(x : INTEGER); FORWARD;
+PROCEDURE P(x : INTEGER);
+BEGIN
+  ExitCode := x
+END P;
+BEGIN
+  ExitCode := 0
+END TFwd.

+ 10 - 0
compiler/tests/t_proc.mod

@@ -0,0 +1,10 @@
+MODULE TProc;
+VAR ExitCode : INTEGER;
+PROCEDURE Add(a, b : INTEGER; VAR sum : INTEGER) : INTEGER;
+BEGIN
+  sum := a + b;
+  ExitCode := sum
+END Add;
+BEGIN
+  ExitCode := 0
+END TProc.

+ 15 - 0
compiler/tests/t_types.mod

@@ -0,0 +1,15 @@
+MODULE TTypes;
+// Scalar TYPEs: subrange (decimal + hex bounds), alias, use.
+TYPE R = [0..9];
+TYPE Alias = INTEGER;
+TYPE Hex = [0x0..0xF];
+VAR x : R;
+VAR y : Alias;
+VAR h : Hex;
+VAR ExitCode : INTEGER;
+BEGIN
+  x := 7;
+  y := x + 1;
+  h := 0xA;
+  ExitCode := y + h - h
+END TTypes.

+ 57 - 0
docs/summary_step1.7.md

@@ -0,0 +1,57 @@
+# V3 step 1.7 — declarations: scalar TYPEs, headings, bodies, FORWARD (done 2026-09-19)
+
+Interactive grammar §3, landed syntax-now/backend-later like §2.
+Suite 15/15 (5 run + 10 reject), LL(1)-clean.
+
+## Grammar (`compiler/src/M2.atg`)
+
+- `TypeBlock<isDef>` replaces `OpaqueBlock` in both DefUnit (TRUE)
+  and DeclSeq (FALSE). `TypeItem = ident ("=" Type | /*opaque*/)` —
+  the branch happens *after* the ident so `=` vs `;` keeps it LL(1).
+  Opaque outside DEFINITION → new 231 (frame text added).
+- Scalar-phase `Type = TypeIdent | Subrange | Enum`. Composites
+  (ARRAY/RECORD/SET/POINTER) are still syntax errors → step 3.
+- `TypeIdent` factored (named/predef/import-tolerant) and reused by
+  VarDecl (which now also accepts imported type names as Invalid).
+- `Subrange = "[" Expr ".." Expr "]"`: integer-family bounds folded
+  via new `SymTab.ConstInt` (literals incl. `0x`, folded unary minus);
+  non-literal/non-int/inverted bounds → 230. Stored via new
+  `NewSubR(lo,hi)`; baseless `FSub` resolves to `ClInt` (ClassOf fix).
+- `Enum = "(" idents ")"`: literals entered as KindConst of the new
+  type. *Use* of non-INTEGER consts (enum/char/bool/real) is now 230
+  (Design KindConst branch tightened; NIL stays silent).
+- `ProcHeading<pn>` (OUT name): `EnterProc`/`ReenterProc` + optional
+  formals + optional `: TypeIdent` result. DefUnit uses it + `PopScope`.
+- `FormalParams`: `"(" [groups] ")"`, `VAR` per group, `EnterParam`
+  + `FixPending` (params behave as typed variables in bodies).
+- `ProcDecl = heading ";" ("FORWARD" | Block)`; Block parses decls +
+  optional body with END-name check, one 230 (lowering = step 4).
+  Wirth trailing-`;` convention (V1 parity): DeclSeq uses
+  `ProcDecl ";"`.
+
+## SymTab growth (additive, no interface breakage)
+
+KindProc/KindParam; node fields rslt/plink/isVar/fwd; EnterProc
+(pushes scope), ReenterProc (fwd→body, fresh signature), EnterParam
+(pend-records + positional chain), SetProcRes/ProcRes/MarkFwd,
+NewSubR + tlo/thi, ConstInt. PrintTable shows PROC/PARAM.
+
+## Bugs found by the new tests (all fixed, suite proved each)
+
+1. `ProcDecl ";"` missing → FORWARD's trailing `;` misparsed as
+   'END' expected (V1 had the same separator rule).
+2. `[0..9]` lexed wrong without Test2's `CONTEXT("..")` guard on the
+   integer token (spaced `[0 .. 9]` worked, unspaced didn't).
+3. `ReenterProc` didn't reset `plink` → second param list chained
+   onto a NIL tail: SIGABRT heap crash, bisected to fwd+params on
+   both headings.
+4. `EnterParam` didn't pend-record → params stayed InvalidType and
+   every use falsely 230'd (5 errors in t_proc instead of 1).
+
+## Tests
+
+`t_types` run → 8 (subrange/alias/hex use); `t_enumdecl`,
+`t_proc` (value + VAR formals, result), `t_forward` (fwd/body pair)
+→ single 230s; `t_bad_opaque` → 231. Deferred on record: unmatched
+FORWARD (no body) is silently accepted — step 4 makes it 231;
+fwd/body signature compare also step 4.