Просмотр исходного кода

v3 step 3.1 — arrays: bounded/open, strings, whole assign (42/42 tests green)

Eric Streit 2 недель назад
Родитель
Сommit
cf6d9b32f7

+ 29 - 0
compiler/run_tests.sh

@@ -60,6 +60,7 @@ expect_run t_for.mod 85
 expect_run t_case.mod 103
 expect_run t_bool.mod 42
 expect_run t_real.mod 31
+expect_run t_array.mod 108
 expect_fail t_classdef.mod "not supported yet"
 expect_fail t_class.mod "not supported yet"
 expect_fail t_bad_parent.mod "undeclared identifier"
@@ -78,6 +79,34 @@ expect_fail t_bad_exit.mod "not supported yet"
 expect_fail t_bad_return.mod "bad RETURN"
 expect_fail t_bad_for.mod "FOR needs integer variable and bounds"
 expect_fail t_bad_mixed.mod "incompatible assignment"
+expect_fail t_bad_index.mod "array index must be integer"
+expect_fail t_bad_bounds.mod "not supported yet"
+expect_fail t_openformal.mod "not supported yet"
+expect_fail t_bad_open.mod "not supported yet"
+
+expect_trap() {
+  # $1 = test file — must parse, assemble, link, then abort (rc=134)
+  name="$1"; mod=$(modof "$name")
+  rm -f "gen_ssa/$mod.ssa" "gen_ssa/$mod.s" "gen_ssa/$mod"
+  if ./M2 "tests/$name" 2>&1 | grep -q "Parsed correctly"; then :; else
+    fail=$((fail+1)); echo "FAIL(trap): $name rejected"; return
+  fi
+  if ! qbe -o "gen_ssa/$mod.s" "gen_ssa/$mod.ssa" 2> "gen_ssa/$mod.qbeerr"; then
+    fail=$((fail+1)); echo "FAIL(trap): $name qbe"; return
+  fi
+  if ! cc "gen_ssa/$mod.s" -o "gen_ssa/$mod" 2> "gen_ssa/$mod.ccerr"; then
+    fail=$((fail+1)); echo "FAIL(trap): $name cc"; return
+  fi
+  timeout 10 "gen_ssa/$mod" > /dev/null 2>&1; got=$?
+  if [ "$got" = "134" ]; then
+    pass=$((pass+1)); echo "PASS(trap): $name -> abort"
+  else
+    fail=$((fail+1)); echo "FAIL(trap): $name rc=$got want 134"
+  fi
+}
+
+expect_trap t_trap_index.mod
+expect_trap t_trap_count.mod
 expect_fail t_defunit.mod "not supported yet"
 expect_fail t_implunit.mod "not supported yet"
 expect_fail t_nested.mod "not supported yet"

+ 206 - 42
compiler/src/M2.atg

@@ -27,7 +27,13 @@ COMPILER M2
    (per the Table example, not the sketch's ","), VIRTUAL flagged.
    Scopes and member checks now; lowering later (one 230 per
    class/impl block). Classic identifiers: no underscores, so the
-   Table example's _names stay lexically out of reach. *)
+   Table example's _names stay lexically out of reach.
+   Step 3.1 arrays: "ARRAY [lo..hi, ...] OF T" (folded literal
+   bounds, int/char) and open "ARRAY OF T" formals; index suffixes
+   with per-level checks (217/218, trap on breach via $abort);
+   whole-array ":=" with runtime count check + blit; string
+   literals lower as descriptors (1-char stays CHAR, empty works);
+   array/string "=" is 213 (no built-in whole comparison). *)
 
 IMPORT SymTab, QbeGen;
 
@@ -176,15 +182,57 @@ PRODUCTIONS
     = GetIdent<n>                       (. IF ~SymTab.Enter(n,
                                              SymTab.KindType) THEN
                                              SemError(200) END; .)
-      ( "=" Type<t>                     (. SymTab.SetSymType(n, t); .)
+      ( "=" Type<t, FALSE>              (. SymTab.SetSymType(n, t); .)
       |                                 (. IF ~isDef THEN
                                              SemError(231) END;
                                            SymTab.SetSymType(n,
                                              SymTab.NewAlias()); .) ) .
-  Type<VAR t: SymTab.TypeIndex>
+  Type<VAR t: SymTab.TypeIndex; allowOpen: BOOLEAN>
     = TypeIdent<t>
     | Subrange<t>
-    | Enum<t> .
+    | Enum<t>
+    | ArrayType<t, allowOpen> .
+  (* Arrays: "OF" without bounds is an open formal (allowed only
+     where allowOpen); "[lo..hi, ...]" nests bounded levels inside
+     out. Bounds are folded literals (int/char); anything else 230.
+     Bare-type indices ("ARRAY Color OF") wait for enum ordinals. *)
+  ArrayType<VAR t: SymTab.TypeIndex; allowOpen: BOOLEAN>
+                                        (. VAR elem: SymTab.TypeIndex;
+                                             ok: BOOLEAN; .)
+    = "ARRAY"
+      ( "OF" Type<elem, FALSE>          (. IF ~allowOpen THEN
+                                             SemError(230) END;
+                                           t := SymTab.NewOpenArray(elem); .)
+      | "["                             (. SymTab.BoundBegin; ok := TRUE; .)
+        BoundPair<ok>
+        { "," BoundPair<ok> }
+        "]" "OF" Type<elem, FALSE>
+                                        (. IF ok THEN
+                                             t := SymTab.NestArray(elem)
+                                           ELSE t := SymTab.InvalidType
+                                           END; .) ) .
+  BoundPair<VAR ok: BOOLEAN>            (. VAR tlo, thi: SymTab.TypeIndex;
+                                             qlo, qhi: QbeGen.QVal;
+                                             lo, hi: INTEGER;
+                                             cl, cl2: INTEGER; .)
+    = Expr<tlo, qlo> ".." Expr<thi, qhi>
+                                        (. IF (tlo = SymTab.InvalidType)
+                                            OR (thi = SymTab.InvalidType) THEN
+                                           ok := FALSE
+                                         ELSE cl := SymTab.ClassOf(tlo);
+                                           cl2 := SymTab.ClassOf(thi);
+                                           IF ((cl # SymTab.ClInt)
+                                              & (cl # SymTab.ClChar))
+                                              OR ((cl2 # SymTab.ClInt)
+                                              & (cl2 # SymTab.ClChar)) THEN
+                                             SemError(230); ok := FALSE
+                                           ELSIF ~SymTab.ConstInt(qlo, lo)
+                                              OR ~SymTab.ConstInt(qhi, hi)
+                                              OR (lo > hi) THEN
+                                             SemError(230); ok := FALSE
+                                           ELSIF ~SymTab.BoundAdd(lo, hi) THEN
+                                             SemError(230); ok := FALSE
+                                           END; .) .
   TypeIdent<VAR t: SymTab.TypeIndex>    (. VAR n: SymTab.Name;
                                              k: INTEGER; .)
     = GetIdent<n>                       (. IF ~SymTab.Lookup(n) THEN
@@ -360,12 +408,13 @@ PRODUCTIONS
                                              i: CARDINAL;
                                              cls: INTEGER; .)
     = VarIdents ":"
-      TypeIdent<t>                      (. cls := SymTab.ClassOf(t);
+      Type<t, FALSE>                    (. cls := SymTab.ClassOf(t);
                                            IF (t # SymTab.InvalidType)
                                               & (cls # SymTab.ClInt)
                                               & (cls # SymTab.ClBool)
                                               & (cls # SymTab.ClChar)
-                                              & (cls # SymTab.ClReal) THEN
+                                              & (cls # SymTab.ClReal)
+                                              & (cls # SymTab.ClArray) THEN
                                              SemError(230) END;
                                            i := 0;
                                            WHILE i < SymTab.PendCount() DO
@@ -398,14 +447,19 @@ PRODUCTIONS
                                              SemError(200) END
                                            END; .)
       [ FormalParams ]
-      [ ":" TypeIdent<t>                (. SymTab.SetProcRes(t); .) ] .
+      [ ":" TypeIdent<t>                (. SymTab.SetProcRes(t);
+                                           IF (t #
+                                              SymTab.InvalidType)
+                                              & (SymTab.ClassOf(t)
+                                                 = SymTab.ClArray) THEN
+                                             SemError(230) END; .) ] .
   FormalParams
     = "(" [ ParamSection { ";" ParamSection } ] ")" .
   ParamSection                          (. VAR t: SymTab.TypeIndex;
                                              isV: BOOLEAN; .)
     =                                   (. isV := FALSE; .)
       [ "VAR"                           (. isV := TRUE; .) ]
-      ParIdents<isV> ":" TypeIdent<t>   (. SymTab.FixPending(t); .) .
+      ParIdents<isV> ":" Type<t, TRUE>  (. SymTab.FixPending(t); .) .
   ProcDecl                              (. VAR pn, m2: SymTab.Name; .)
     = ProcHeading<pn> ";"
       ( "FORWARD"                       (. SymTab.MarkFwd;
@@ -437,8 +491,9 @@ PRODUCTIONS
                                              dk: INTEGER;
                                              qd, qe, qt: QbeGen.QVal;
                                              qn: SymTab.Name;
-                                             isR, conv: BOOLEAN; .)
-    = Design<dt, dk, qd, qn> ":="
+                                             isR, conv: BOOLEAN;
+                                             sfx: BOOLEAN; .)
+    = Design<dt, dk, qd, qn, sfx> ":="
       Expr<et, qe>                      (. IF (dt # SymTab.InvalidType)
                                            & (dk # SymTab.KindVar)
                                            & (dk # SymTab.KindParam) THEN
@@ -456,7 +511,22 @@ PRODUCTIONS
                                              OR (dk = SymTab.KindParam))
                                             & (dt # SymTab.InvalidType)
                                             & (et # SymTab.InvalidType) THEN
-                                           IF conv THEN
+                                           IF sfx THEN
+                                             IF SymTab.ClassOf(dt) =
+                                                SymTab.ClArray THEN
+                                               QbeGen.CopyArray(qd, qe,
+                                                 dt)
+                                             ELSIF conv THEN
+                                               QbeGen.ConvIR(qe, qt);
+                                               QbeGen.ElemStore(qd, qt,
+                                                 dt)
+                                             ELSE QbeGen.ElemStore(qd, qe,
+                                               dt)
+                                             END
+                                           ELSIF SymTab.ClassOf(dt) =
+                                                 SymTab.ClArray THEN
+                                             QbeGen.CopyArray(qd, qe, dt)
+                                           ELSIF conv THEN
                                              QbeGen.ConvIR(qe, qt);
                                              QbeGen.StoreVar(qn, qt, TRUE)
                                            ELSE
@@ -679,11 +749,22 @@ PRODUCTIONS
                                                SemError(232)
                                              END
                                            END; .) .
+  (* Designator: scalar loads, array addresses, and index suffixes.
+     Each index descends one level (bounds-checked, trap on breach);
+     nested levels reload the inner descriptor address. q ends as the
+     value (scalars), the descriptor address (plain arrays), or the
+     element address (indexed); sfx marks the indexed form. *)
   Design<VAR t: SymTab.TypeIndex; VAR k: INTEGER;
-         VAR q: QbeGen.QVal; VAR qn: SymTab.Name>
+         VAR q: QbeGen.QVal; VAR qn: SymTab.Name; VAR sfx: BOOLEAN>
                                         (. VAR n: SymTab.Name;
-                                             cls: INTEGER; .)
+                                             cls: INTEGER;
+                                             it, eT: SymTab.TypeIndex;
+                                             iq, ql, qlo, qhi, qe:
+                                               QbeGen.QVal;
+                                             lo, hi: INTEGER;
+                                             isOpen: BOOLEAN; .)
     = GetIdent<n>                       (. QbeGen.CopyOp(n, qn);
+                                           sfx := FALSE;
                                            IF ~SymTab.Lookup(n) THEN
                                              SemError(201);
                                              t := SymTab.InvalidType;
@@ -721,29 +802,105 @@ PRODUCTIONS
                                                    QbeGen.CopyOp("0", q)
                                                  END
                                                END
-                                              ELSIF (k = SymTab.KindVar)
-                                                 OR (k = SymTab.KindParam) THEN
-                                                cls :=
-                                                  SymTab.ClassOf(t);
-                                                IF (cls = SymTab.ClInt)
-                                                   OR (cls = SymTab.ClBool)
-                                                   OR (cls = SymTab.ClChar)
-                                                   OR (cls
-                                                       = SymTab.ClReal) THEN
-                                                  QbeGen.LoadVar(n,
-                                                    cls = SymTab.ClReal, q)
-                                                ELSE SemError(230);
-                                                  QbeGen.CopyOp("0", q)
-                                                END
-                                              ELSE QbeGen.CopyOp("0", q);
-                                                IF (k = SymTab.KindImport)
-                                                   OR (k = SymTab.KindProc)
-                                                   OR (k
-                                                       = SymTab.KindField) THEN
-                                                  SemError(230)
-                                                END
-                                              END
-                                           END; .) .
+                                             ELSIF (k = SymTab.KindVar)
+                                                OR (k = SymTab.KindParam) THEN
+                                               cls :=
+                                                 SymTab.ClassOf(t);
+                                               IF (cls = SymTab.ClInt)
+                                                  OR (cls = SymTab.ClBool)
+                                                  OR (cls = SymTab.ClChar)
+                                                  OR (cls
+                                                      = SymTab.ClReal) THEN
+                                                 QbeGen.LoadVar(n,
+                                                   cls = SymTab.ClReal, q)
+                                               ELSIF cls
+                                                     = SymTab.ClArray THEN
+                                                 QbeGen.AddrOf(n, q)
+                                               ELSE SemError(230);
+                                                 QbeGen.CopyOp("0", q)
+                                               END
+                                             ELSE QbeGen.CopyOp("0", q);
+                                               IF (k = SymTab.KindImport)
+                                                  OR (k = SymTab.KindProc)
+                                                  OR (k
+                                                      = SymTab.KindField) THEN
+                                                 SemError(230)
+                                               END
+                                             END
+                                           END; .)
+      { "[" Expr<it, iq>
+                                        (. IF t = SymTab.InvalidType THEN
+                                           ELSIF SymTab.ClassOf(t) #
+                                                 SymTab.ClArray THEN
+                                             SemError(217);
+                                             t := SymTab.InvalidType
+                                           ELSIF ~SymTab.IsIntFamily(it)
+                                              & (SymTab.ClassOf(it) #
+                                                 SymTab.ClChar) THEN
+                                             SemError(218);
+                                             t := SymTab.InvalidType
+                                           ELSE
+                                             QbeGen.WidenIndex(iq, ql);
+                                             isOpen :=
+                                               SymTab.IsOpenArray(t);
+                                             IF isOpen THEN
+                                               QbeGen.CopyOp("0", qlo);
+                                               QbeGen.OpenHi(q, qhi)
+                                             ELSE
+                                               lo := SymTab.ArrayLo(t);
+                                               hi := SymTab.ArrayHi(t);
+                                               QbeGen.IntStr(lo, qlo);
+                                               QbeGen.IntStr(hi, qhi)
+                                             END;
+                                             QbeGen.CheckRange(ql, qlo,
+                                               qhi);
+                                             eT := SymTab.ArrayElem(t);
+                                             QbeGen.ElemAddr(q, ql, qlo,
+                                               t, qe);
+                                             IF SymTab.ClassOf(eT) =
+                                                SymTab.ClArray THEN
+                                               QbeGen.ElemLoad(qe, eT, q)
+                                             ELSE QbeGen.CopyOp(qe, q)
+                                             END;
+                                             t := eT; sfx := TRUE
+                                           END; .)
+        { "," Expr<it, iq>
+                                        (. IF t = SymTab.InvalidType THEN
+                                           ELSIF SymTab.ClassOf(t) #
+                                                 SymTab.ClArray THEN
+                                             SemError(217);
+                                             t := SymTab.InvalidType
+                                           ELSIF ~SymTab.IsIntFamily(it)
+                                              & (SymTab.ClassOf(it) #
+                                                 SymTab.ClChar) THEN
+                                             SemError(218);
+                                             t := SymTab.InvalidType
+                                           ELSE
+                                             QbeGen.WidenIndex(iq, ql);
+                                             isOpen :=
+                                               SymTab.IsOpenArray(t);
+                                             IF isOpen THEN
+                                               QbeGen.CopyOp("0", qlo);
+                                               QbeGen.OpenHi(q, qhi)
+                                             ELSE
+                                               lo := SymTab.ArrayLo(t);
+                                               hi := SymTab.ArrayHi(t);
+                                               QbeGen.IntStr(lo, qlo);
+                                               QbeGen.IntStr(hi, qhi)
+                                             END;
+                                             QbeGen.CheckRange(ql, qlo,
+                                               qhi);
+                                             eT := SymTab.ArrayElem(t);
+                                             QbeGen.ElemAddr(q, ql, qlo,
+                                               t, qe);
+                                             IF SymTab.ClassOf(eT) =
+                                                SymTab.ClArray THEN
+                                               QbeGen.ElemLoad(qe, eT, q)
+                                             ELSE QbeGen.CopyOp(qe, q)
+                                             END;
+                                             t := eT; sfx := TRUE
+                                           END; .) }
+        "]" } .
   Expr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR t2: SymTab.TypeIndex;
                                              op: INTEGER;
@@ -863,7 +1020,8 @@ PRODUCTIONS
                                              et, dt, t2: SymTab.TypeIndex;
                                              dk: INTEGER;
                                              qd, q2: QbeGen.QVal;
-                                             qn: SymTab.Name; .)
+                                             qn: SymTab.Name;
+                                             sfx: BOOLEAN; .)
     = integer                           (. LexString(s);
                                            QbeGen.NormInt(s, q);
                                            t := SymTab.IntType(); .)
@@ -871,16 +1029,22 @@ PRODUCTIONS
                                            QbeGen.NormReal(s, q);
                                            t := SymTab.RealType(); .)
     | string                            (. LexString(s);
-                                           IF SymTab.StrLen(s) <= 3 THEN
+                                           IF SymTab.StrLen(s) = 3 THEN
                                              t := SymTab.CharType();
                                              QbeGen.IntStr(
                                                QbeGen.CharVal(s), q)
                                            ELSE t := SymTab.NewStr();
-                                             SemError(230);
-                                             QbeGen.CopyOp("0", q)
+                                             QbeGen.DeclStr(s, q)
+                                           END; .)
+    | Design<dt, dk, qd, qn, sfx>       (. t := dt;
+                                           IF sfx THEN
+                                             IF dt #
+                                                SymTab.InvalidType THEN
+                                               QbeGen.ElemLoad(qd, dt, q)
+                                             ELSE QbeGen.CopyOp("0", q)
+                                             END
+                                           ELSE QbeGen.CopyOp(qd, q)
                                            END; .)
-    | Design<dt, dk, qd, qn>            (. t := dt;
-                                           QbeGen.CopyOp(qd, q); .)
     | "(" Expr<et, q> ")"               (. t := et; .)
     | "NOT" Fact<t2, q2>                (. IF SymTab.BoolCheck(t2) THEN
                                              t := SymTab.BoolType()

+ 1035 - 871
compiler/src/M2.lst

@@ -44,884 +44,1048 @@ Listing:
    27     (per the Table example, not the sketch's ","), VIRTUAL flagged.
    28     Scopes and member checks now; lowering later (one 230 per
    29     class/impl block). Classic identifiers: no underscores, so the
-   30     Table example's _names stay lexically out of reach. *)
-   31  
-   32  IMPORT SymTab, QbeGen;
-   33  
-   34  CHARACTERS
-   35    eol      = CHR(13) .
-   36    lf       = CHR(10) .
-   37    letter   = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" .
-   38    digit    = "0123456789" .
-   39    hexDigit = digit + "ABCDEFabcdef" .
-   40    noQuote1 = ANY - "'" - eol .
-   41    noQuote2 = ANY - '"' - eol .
-   42  
-   43  IGNORE CHR(9) .. CHR(13)
-   44  
-   45  COMMENTS FROM "(*" TO "*)" NESTED
-   46  COMMENTS FROM "//" TO lf
-   47  
-   48  TOKENS
-   49    ident   = letter { letter | digit } .
-   50    integer = digit { digit }
-   51            | digit { digit } CONTEXT("..")
-   52            | "0x" hexDigit { hexDigit }
-   53            | "0X" hexDigit { hexDigit } .
-   54    real    = digit { digit } "." { digit }
-   55              [ ( "E" | "e" ) [ "+" | "-" ] digit { digit } ] .
-   56    string  = "'" { noQuote1 } "'"
-   57            | '"' { noQuote2 } '"' .
-   58  
-   59  PRODUCTIONS
-   60    M2
-   61      = Unit "." .
-   62    (* Units: program modules compile fully; DEFINITION and
-   63       IMPLEMENTATION modules parse + check now but lower in step 4
-   64       (each ends with one 230); same for nested local modules. *)
-   65    Unit
-   66      = DefUnit
-   67      | ImplUnit
-   68      | ProgModule .
-   69    DefUnit                               (. VAR m1, m2, pn: SymTab.Name; .)
-   70      = "DEFINITION" "MODULE"
-   71        GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
-   72                                             IF ~SymTab.Enter(m1,
-   73                                                SymTab.KindModule) THEN
-   74                                               SemError(200) END; .)
-   75        ";"
-   76        { Import }
-   77        { ConstBlock | TypeBlock<TRUE> | VarBlock
-   78        | ProcHeading<pn> ";"             (. SymTab.CloseProc; .) }
-   79        "END"
-   80        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
-   81                                               SemError(202) END; .)
-   82                                          (. QbeGen.CloseModule;
-   83                                             SemError(230);
-   84                                             SymTab.PrintTable; .) .
-   85    ImplUnit                              (. VAR m1, m2: SymTab.Name; .)
-   86      = "IMPLEMENTATION" "MODULE"
-   87        GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
-   88                                             IF ~SymTab.Enter(m1,
-   89                                                SymTab.KindModule) THEN
-   90                                               SemError(200) END; .)
-   91        ";"
-   92        { Import }
-   93        DeclSeq
-   94        [ "BEGIN"                         (. QbeGen.BeginBody; .)
-   95          [ StatSeq ] ]
-   96        "END"
-   97        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
-   98                                               SemError(202) END; .)
-   99                                          (. QbeGen.CloseModule;
-  100                                             SemError(230);
-  101                                             SymTab.PrintTable; .) .
-  102    ProgModule                            (. VAR m1, m2: SymTab.Name; .)
-  103      = "MODULE"
-  104        GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
-  105                                             IF ~SymTab.Enter(m1,
-  106                                                SymTab.KindModule) THEN
-  107                                               SemError(200) END; .)
-  108        [ Priority ]
-  109        ";"
-  110        { Import }
-  111        DeclSeq
-  112        [ "BEGIN"                         (. QbeGen.BeginBody; .)
-  113          [ StatSeq ] ]
-  114        "END"
-  115        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
-  116                                               SemError(202) END; .)
-  117                                          (. QbeGen.EndModule;
-  118                                             SymTab.PrintTable; .) .
-  119    DeclSeq
-  120      = { ConstBlock | TypeBlock<FALSE> | VarBlock | ProcDecl ";"
-  121        | NestedModule ";" | ClassItem ";" } .
-  122    (* Local module, Wirth form. Parses + checks fully (own scope);
-  123       lowering waits for step 4, marked by one 230 at END. *)
-  124    NestedModule                          (. VAR m1, m2: SymTab.Name; .)
-  125      = "MODULE"
-  126        GetIdent<m1>                      (. IF ~SymTab.Enter(m1,
-  127                                               SymTab.KindModule) THEN
-  128                                               SemError(200) END;
-  129                                             SymTab.PushScope; .)
-  130        [ Priority ]
-  131        ";"
-  132        { Import }
-  133        [ Export ]
-  134        DeclSeq
-  135        [ "BEGIN"
-  136          [ StatSeq ] ]
-  137        "END"
-  138        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
-  139                                               SemError(202) END;
-  140                                             SymTab.PopScope;
-  141                                             SemError(230); .) .
-  142    Priority
-  143      = "[" integer "]"                   (. SemError(230); .) .
-  144    Export                                (. VAR nm: SymTab.Name; .)
-  145      = "EXPORT"                          (* names wire up in step 4 *)
-  146        [ "QUALIFIED" ]
-  147        GetIdent<nm>
-  148        { "," GetIdent<nm> }
-  149        ";" .
-  150    Import                                (. VAR n: SymTab.Name; .)
-  151      = "FROM"
-  152        GetIdent<n>                       (. IF ~SymTab.Enter(n,
-  153                                               SymTab.KindImport) THEN
-  154                                               SemError(200) END; .)
-  155        "IMPORT"
-  156        ImpList ";"
-  157      | "IMPORT"
-  158        ImpList ";" .
-  159    ImpList                               (. VAR n: SymTab.Name; .)
-  160      = GetIdent<n>                       (. IF ~SymTab.Enter(n,
-  161                                               SymTab.KindImport) THEN
-  162                                               SemError(200) END; .)
-  163        { ","
-  164          GetIdent<n>                     (. IF ~SymTab.Enter(n,
-  165                                               SymTab.KindImport) THEN
-  166                                               SemError(200) END; .) } .
-  167    (* Opaque TYPE declarations (definition modules). The targetless
-  168       alias resolves to InvalidType until step 4 completes it. *)
-  169    (* Scalar-phase TYPEs: named types, integer subranges, enumerations.
-  170       Opaque "TYPE T;" needs isDef (definition units); elsewhere 231.
-  171       Composite forms (ARRAY/RECORD/SET/POINTER) arrive with step 3. *)
-  172    TypeBlock<isDef: BOOLEAN>
-  173      = "TYPE" { TypeItem<isDef> ";" | ClassItem ";" } .
-  174    TypeItem<isDef: BOOLEAN>              (. VAR n: SymTab.Name;
-  175                                               t: SymTab.TypeIndex; .)
-  176      = GetIdent<n>                       (. IF ~SymTab.Enter(n,
-  177                                               SymTab.KindType) THEN
-  178                                               SemError(200) END; .)
-  179        ( "=" Type<t>                     (. SymTab.SetSymType(n, t); .)
-  180        |                                 (. IF ~isDef THEN
-  181                                               SemError(231) END;
-  182                                             SymTab.SetSymType(n,
-  183                                               SymTab.NewAlias()); .) ) .
-  184    Type<VAR t: SymTab.TypeIndex>
-  185      = TypeIdent<t>
-  186      | Subrange<t>
-  187      | Enum<t> .
-  188    TypeIdent<VAR t: SymTab.TypeIndex>    (. VAR n: SymTab.Name;
-  189                                               k: INTEGER; .)
-  190      = GetIdent<n>                       (. IF ~SymTab.Lookup(n) THEN
-  191                                               SemError(201);
-  192                                               t := SymTab.InvalidType
-  193                                             ELSE k := SymTab.SymKind(n);
-  194                                               IF (k = SymTab.KindType)
-  195                                                  OR (k = SymTab.KindPredef) THEN
-  196                                                 t := SymTab.SymType(n)
-  197                                               ELSIF k = SymTab.KindImport THEN
-  198                                                 t := SymTab.InvalidType
-  199                                               ELSE SemError(221);
-  200                                                 t := SymTab.InvalidType
-  201                                               END
-  202                                             END; .) .
-  203    Subrange<VAR t: SymTab.TypeIndex>     (. VAR tlo, thi: SymTab.TypeIndex;
-  204                                               qlo, qhi: QbeGen.QVal;
-  205                                               lo, hi: INTEGER; .)
-  206      = "[" Expr<tlo, qlo> ".." Expr<thi, qhi>
-  207                                          (. IF (tlo = SymTab.InvalidType)
-  208                                              OR (thi = SymTab.InvalidType) THEN
-  209                                             t := SymTab.InvalidType
-  210                                           ELSIF (SymTab.ClassOf(tlo) #
-  211                                                  SymTab.ClInt)
-  212                                              OR (SymTab.ClassOf(thi) #
-  213                                                  SymTab.ClInt) THEN
-  214                                             SemError(230);
-  215                                             t := SymTab.InvalidType
-  216                                           ELSIF ~SymTab.ConstInt(qlo, lo)
-  217                                              OR ~SymTab.ConstInt(qhi, hi)
-  218                                              OR (lo > hi) THEN
-  219                                             SemError(230);
-  220                                             t := SymTab.InvalidType
-  221                                           ELSE t := SymTab.NewSubR(lo, hi)
-  222                                           END; .)
-  223        "]" .
-  224    Enum<VAR t: SymTab.TypeIndex>         (. VAR n: SymTab.Name; .)
-  225      = "("                               (. t := SymTab.NewEnum(); .)
-  226        GetIdent<n>                       (. IF ~SymTab.Enter(n,
-  227                                               SymTab.KindConst) THEN
-  228                                               SemError(200) END;
-  229                                             SymTab.SetSymType(n, t); .)
-  230        { "," GetIdent<n>                 (. IF ~SymTab.Enter(n,
-  231                                               SymTab.KindConst) THEN
-  232                                               SemError(200) END;
-  233                                             SymTab.SetSymType(n, t); .) }
-  234        ")" .
-  235    (* Clarion-form classes (docs/OOP.txt): declaration + single
-  236       inheritance + IMPLEMENTATION blocks. Scopes and member checks
-  237       now; lowering (vtable, dispatch, THIS) later — one 230 per
-  238       class/impl block. Methods end with ";" per the Table example
-  239       (not "," as in the sketch). No underscores in identifiers. *)
-  240    (* Single CLASS item in both loops: separating declaration from
-  241       IMPLEMENTATION at the loop level needs 2-token lookahead
-  242       (CLASS ident vs CLASS IMPLEMENTATION), which LL(1) cannot do.
-  243       The second token decides after CLASS is consumed. A misplaced
-  244       CLASS IMPLEMENTATION inside TYPE still parses (harmless: the
-  245       whole unit ends 230 until lowering). *)
-  246    ClassItem
-  247      = "CLASS" ( "IMPLEMENTATION" ClassImplRest | ClassRest ) .
-  248    ClassRest                             (. VAR cn, m2, pn: SymTab.Name;
-  249                                               ct: SymTab.TypeIndex; .)
-  250      = GetIdent<cn>                      (. IF ~SymTab.Enter(cn,
-  251                                               SymTab.KindType) THEN
-  252                                               SemError(200) END;
-  253                                             ct := SymTab.NewClass();
-  254                                             SymTab.SetSymType(cn, ct);
-  255                                             SymTab.PushClassScope(ct); .)
-  256        [ Parents<ct> ]
-  257        ";"
-  258        { ClassField<ct> ";" }
-  259        { MethodHeading<pn> ";"           (. SymTab.CloseProc; .) }
-  260        "END"
-  261        GetIdent<m2>                      (. IF ~SymTab.Equal(cn, m2) THEN
-  262                                               SemError(202) END;
-  263                                             SymTab.PopScope;
-  264                                             SemError(230); .) .
-  265    Parents<ct: SymTab.TypeIndex>         (. VAR p: SymTab.Name; .)
-  266      = "(" Parent1<ct>
-  267        { "," GetIdent<p>                 (. SemError(230); .) }
-  268        ")" .
-  269    Parent1<ct: SymTab.TypeIndex>         (. VAR p: SymTab.Name;
-  270                                               pt: SymTab.TypeIndex; .)
-  271      = GetIdent<p>                       (. IF ~SymTab.Lookup(p) THEN
-  272                                               SemError(201)
-  273                                             ELSE pt := SymTab.SymType(p);
-  274                                               IF SymTab.ClassOf(pt) #
-  275                                                  SymTab.ClClass THEN
-  276                                                 SemError(230)
-  277                                               ELSE SymTab.SetParent(ct, pt)
-  278                                               END
-  279                                             END; .) .
-  280    ClassField<ct: SymTab.TypeIndex>      (. VAR n, rhs: SymTab.Name;
-  281                                               t: SymTab.TypeIndex; .)
-  282      = GetIdent<n>
-  283        ( "=" GetIdent<rhs>               (. IF ~SymTab.Enter(n,
-  284                                               SymTab.KindConst) THEN
-  285                                               SemError(200) END;
-  286                                             IF SymTab.Lookup(rhs) THEN
-  287                                               SymTab.SetSymType(n,
-  288                                                 SymTab.SymType(rhs))
-  289                                             END; .)
-  290        |                                 (. IF ~SymTab.FieldPending(ct,
-  291                                               n) THEN
-  292                                               SemError(200) END; .)
-  293          { "," GetIdent<n>               (. IF ~SymTab.FieldPending(ct,
-  294                                               n) THEN
-  295                                               SemError(200) END; .) }
-  296          ":" TypeIdent<t>                (. SymTab.FixPendingF(ct, t); .) ) .
-  297    MethodHeading<VAR pn: SymTab.Name>    (. VAR wantVirt: BOOLEAN; .)
-  298      =                                   (. wantVirt := FALSE; .)
-  299        [ "VIRTUAL"                       (. wantVirt := TRUE; .) ]
-  300        ProcHeading<pn>                   (. IF wantVirt THEN
-  301                                               SymTab.MarkVirtual END; .) .
-  302    ClassImplRest                         (. VAR cn, m2: SymTab.Name;
-  303                                               ct: SymTab.TypeIndex; .)
-  304      = GetIdent<cn>                      (. IF ~SymTab.Lookup(cn) THEN
-  305                                               SemError(201);
-  306                                               ct := SymTab.InvalidType
-  307                                             ELSE ct := SymTab.SymType(cn);
-  308                                               IF SymTab.ClassOf(ct) #
-  309                                                  SymTab.ClClass THEN
-  310                                                 SemError(230);
-  311                                                 ct := SymTab.InvalidType
-  312                                               END
-  313                                             END;
-  314                                             IF ct #
-  315                                                SymTab.InvalidType THEN
-  316                                               IF ~SymTab.PushClassMembers(
-  317                                                      ct) THEN
-  318                                                 SemError(230) END
-  319                                             END; .)
-  320        ";" { MethodImpl<ct> ";" }
-  321        [ "BEGIN"
-  322          [ StatSeq ] ]
-  323        "END"
-  324        GetIdent<m2>                      (. IF ~SymTab.Equal(cn, m2) THEN
-  325                                               SemError(202) END;
-  326                                             SymTab.PopScope;
-  327                                             SemError(230); .) .
-  328    MethodImpl<ct: SymTab.TypeIndex>      (. VAR pn: SymTab.Name; .)
-  329      = MethodHeading<pn> ";"
-  330                                          (. IF (ct #
-  331                                                SymTab.InvalidType)
-  332                                               & ~SymTab.MethodExists(ct,
-  333                                                    pn) THEN
-  334                                               SemError(201) END; .)
-  335        ( "FORWARD"                       (. SymTab.MarkFwd;
-  336                                             SymTab.CloseProc; .)
-  337        | Block<pn>                       (. SymTab.CloseProc;
-  338                                             SemError(230); .) ) .
-  339    ConstBlock
-  340      = "CONST" { ConstDecl ";" } .
-  341    ConstDecl                             (. VAR n: SymTab.Name;
-  342                                               t: SymTab.TypeIndex;
-  343                                               qv: QbeGen.QVal;
-  344                                               cls: INTEGER; .)
-  345      = GetIdent<n>                       (. IF ~SymTab.Enter(n,
-  346                                               SymTab.KindConst) THEN
-  347                                               SemError(200) END; .)
-  348        "="
-  349        Expr<t, qv>                       (. SymTab.SetSymType(n, t);
-  350                                             cls := SymTab.ClassOf(t);
-  351                                             IF cls = SymTab.ClStr THEN
-  352                                               SemError(230)
-  353                                             ELSIF ~QbeGen.IsImm(qv) THEN
-  354                                               SemError(230) END;
-  355                                             QbeGen.DeclConst(n, qv, t); .) .
-  356    VarBlock
-  357      = "VAR" { VarDecl ";" } .
-  358    VarDecl                               (. VAR nm: SymTab.Name;
-  359                                               t: SymTab.TypeIndex;
-  360                                               i: CARDINAL;
-  361                                               cls: INTEGER; .)
-  362      = VarIdents ":"
-  363        TypeIdent<t>                      (. cls := SymTab.ClassOf(t);
-  364                                             IF (t # SymTab.InvalidType)
-  365                                                & (cls # SymTab.ClInt)
-  366                                                & (cls # SymTab.ClBool)
-  367                                                & (cls # SymTab.ClChar)
-  368                                                & (cls # SymTab.ClReal) THEN
-  369                                               SemError(230) END;
-  370                                             i := 0;
-  371                                             WHILE i < SymTab.PendCount() DO
-  372                                               SymTab.PendName(i, nm);
-  373                                               QbeGen.DeclVar(nm, t);
-  374                                               INC(i)
-  375                                             END;
-  376                                             SymTab.FixPending(t); .) .
-  377    VarIdents                             (. VAR n: SymTab.Name; .)
-  378      = GetIdent<n>                       (. IF ~SymTab.EnterPending(n,
-  379                                               SymTab.KindVar) THEN
-  380                                               SemError(200) END; .)
-  381        { ","
-  382          GetIdent<n>                     (. IF ~SymTab.EnterPending(n,
-  383                                               SymTab.KindVar) THEN
-  384                                               SemError(200) END; .) } .
-  385    ParIdents<isV: BOOLEAN>               (. VAR n: SymTab.Name; .)
-  386      = GetIdent<n>                       (. IF ~SymTab.EnterParam(n, isV) THEN
-  387                                               SemError(200) END; .)
-  388        { "," GetIdent<n>                 (. IF ~SymTab.EnterParam(n, isV) THEN
-  389                                               SemError(200) END; .) } .
-  390    (* Procedure headings enter now (scopes, params, result); bodies
-  391       parse + check with one 230 at END (lowering = step 4).
-  392       FORWARD marks; the body heading re-enters (signature compare
-  393       tightens in step 4). *)
-  394    ProcHeading<VAR pn: SymTab.Name>      (. VAR t: SymTab.TypeIndex; .)
-  395      = "PROCEDURE"
-  396        GetIdent<pn>                      (. IF ~SymTab.EnterProc(pn) THEN
-  397                                             IF ~SymTab.ReenterProc(pn) THEN
-  398                                               SemError(200) END
-  399                                             END; .)
-  400        [ FormalParams ]
-  401        [ ":" TypeIdent<t>                (. SymTab.SetProcRes(t); .) ] .
-  402    FormalParams
-  403      = "(" [ ParamSection { ";" ParamSection } ] ")" .
-  404    ParamSection                          (. VAR t: SymTab.TypeIndex;
-  405                                               isV: BOOLEAN; .)
-  406      =                                   (. isV := FALSE; .)
-  407        [ "VAR"                           (. isV := TRUE; .) ]
-  408        ParIdents<isV> ":" TypeIdent<t>   (. SymTab.FixPending(t); .) .
-  409    ProcDecl                              (. VAR pn, m2: SymTab.Name; .)
-  410      = ProcHeading<pn> ";"
-  411        ( "FORWARD"                       (. SymTab.MarkFwd;
-  412                                             SymTab.CloseProc; .)
-  413        | Block<pn>                       (. SymTab.CloseProc;
-  414                                             SemError(230); .) ) .
-  415    Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
-  416      = DeclSeq
-  417        [ "BEGIN"
-  418          [ StatSeq ] ]
-  419        "END"
-  420        GetIdent<m2>                      (. IF ~SymTab.Equal(pn, m2) THEN
-  421                                               SemError(202) END; .) .
-  422    StatSeq
-  423      = Statement { ";" Statement } .
-  424    Statement                             (. VAR lx: QbeGen.QVal; .)
-  425      = Assign
-  426      | IfStat
-  427      | WhileStat
-  428      | RepeatStat
-  429      | LoopStat
-  430      | ForStat
-  431      | CaseStat
-  432      | ReturnStat
-  433      | "EXIT"                            (. IF QbeGen.TopLoop(lx) THEN
-  434                                               QbeGen.Jmp(lx)
-  435                                             ELSE SemError(230) END; .) .
-  436    Assign                                (. VAR dt, et: SymTab.TypeIndex;
-  437                                               dk: INTEGER;
-  438                                               qd, qe, qt: QbeGen.QVal;
-  439                                               qn: SymTab.Name;
-  440                                               isR, conv: BOOLEAN; .)
-  441      = Design<dt, dk, qd, qn> ":="
-  442        Expr<et, qe>                      (. IF (dt # SymTab.InvalidType)
-  443                                             & (dk # SymTab.KindVar)
-  444                                             & (dk # SymTab.KindParam) THEN
-  445                                             SemError(210)
-  446                                           ELSIF ~SymTab.Assignable(et,
-  447                                                    dt) THEN
-  448                                             SemError(210) END;
-  449                                           isR := (dt #
-  450                                                   SymTab.InvalidType)
-  451                                                  & (SymTab.ClassOf(dt)
-  452                                                     = SymTab.ClReal);
-  453                                           conv := isR
-  454                                                   & SymTab.IsIntFamily(et);
-  455                                           IF ((dk = SymTab.KindVar)
-  456                                               OR (dk = SymTab.KindParam))
-  457                                              & (dt # SymTab.InvalidType)
-  458                                              & (et # SymTab.InvalidType) THEN
-  459                                             IF conv THEN
-  460                                               QbeGen.ConvIR(qe, qt);
-  461                                               QbeGen.StoreVar(qn, qt, TRUE)
-  462                                             ELSE
-  463                                               QbeGen.StoreVar(qn, qe, isR)
-  464                                             END
-  465                                           END; .) .
-  466    IfStat                                (. VAR t: SymTab.TypeIndex;
-  467                                               q, lThen, lElse, lEnd:
-  468                                                 QbeGen.QVal;
-  469                                               hasElse: BOOLEAN; .)
-  470      = "IF"                              (. hasElse := FALSE; .)
-  471        Expr<t, q>                        (. IF ~SymTab.BoolCheck(t) THEN
-  472                                               SemError(214) END;
-  473                                             QbeGen.NewLabel(lThen);
-  474                                             QbeGen.NewLabel(lElse);
-  475                                             QbeGen.NewLabel(lEnd);
-  476                                             QbeGen.Jnz(q, lThen, lElse);
-  477                                             QbeGen.EmitLabel(lThen); .)
-  478        "THEN" StatSeq                    (. QbeGen.Jmp(lEnd); .)
-  479        { "ELSIF"                         (. QbeGen.EmitLabel(lElse);
-  480                                             QbeGen.NewLabel(lElse); .)
-  481          Expr<t, q>                      (. IF ~SymTab.BoolCheck(t) THEN
-  482                                               SemError(214) END;
-  483                                             QbeGen.NewLabel(lThen);
-  484                                             QbeGen.Jnz(q, lThen, lElse);
-  485                                             QbeGen.EmitLabel(lThen); .)
-  486          "THEN" StatSeq                  (. QbeGen.Jmp(lEnd); .) }
-  487        [ "ELSE"                          (. QbeGen.EmitLabel(lElse);
-  488                                             hasElse := TRUE; .)
-  489          StatSeq ]
-  490        "END"                             (. IF hasElse THEN
-  491                                               QbeGen.EmitLabel(lEnd)
-  492                                             ELSE QbeGen.EmitLabel(lElse);
-  493                                               QbeGen.EmitLabel(lEnd)
-  494                                             END; .) .
-  495    WhileStat                             (. VAR t: SymTab.TypeIndex;
-  496                                               q, lTop, lBody, lEnd:
-  497                                                 QbeGen.QVal; .)
-  498      = "WHILE"                           (. QbeGen.NewLabel(lTop);
-  499                                             QbeGen.NewLabel(lBody);
-  500                                             QbeGen.NewLabel(lEnd);
-  501                                             QbeGen.EmitLabel(lTop); .)
-  502        Expr<t, q>                        (. IF ~SymTab.BoolCheck(t) THEN
-  503                                               SemError(214) END;
-  504                                             QbeGen.Jnz(q, lBody, lEnd);
-  505                                             QbeGen.EmitLabel(lBody); .)
-  506        "DO" StatSeq                      (. QbeGen.Jmp(lTop); .)
-  507        "END"                             (. QbeGen.EmitLabel(lEnd); .) .
-  508    RepeatStat                            (. VAR t: SymTab.TypeIndex;
-  509                                               q, lTop, lEnd: QbeGen.QVal; .)
-  510      = "REPEAT"                          (. QbeGen.NewLabel(lTop);
-  511                                             QbeGen.NewLabel(lEnd);
-  512                                             QbeGen.EmitLabel(lTop); .)
-  513        StatSeq
-  514        "UNTIL" Expr<t, q>                (. IF ~SymTab.BoolCheck(t) THEN
-  515                                               SemError(214) END;
-  516                                             QbeGen.Jnz(q, lEnd, lTop);
-  517                                             QbeGen.EmitLabel(lEnd); .) .
-  518    LoopStat                              (. VAR lTop, lEnd: QbeGen.QVal; .)
-  519      = "LOOP"                            (. QbeGen.NewLabel(lTop);
-  520                                             QbeGen.NewLabel(lEnd);
-  521                                             QbeGen.PushLoop(lEnd);
-  522                                             QbeGen.EmitLabel(lTop); .)
-  523        StatSeq
-  524        "END"                             (. QbeGen.Jmp(lTop);
-  525                                             QbeGen.PopLoop;
-  526                                             QbeGen.EmitLabel(lEnd); .) .
-  527    (* FOR with static-sign BY (literal, non-zero; 220 otherwise).
-  528       Runtime direction would need a compare-select; the literal
-  529       sign picks cslew/csegew at "DO" time. *)
-  530    ForStat                               (. VAR lv: SymTab.Name;
-  531                                               tlo, thi, tby:
-  532                                                 SymTab.TypeIndex;
-  533                                               qlo, qhi, qby, qt, qk, qb:
-  534                                                 QbeGen.QVal;
-  535                                               lTop, lBody, lEnd:
-  536                                                 QbeGen.QVal;
-  537                                               by: INTEGER;
-  538                                               ok: BOOLEAN; .)
-  539      = "FOR"                             (. by := 1; .)
-  540        GetIdent<lv>                      (. ok := SymTab.Lookup(lv);
-  541                                             IF ~ok THEN
-  542                                               SemError(201)
-  543                                             ELSIF (SymTab.SymKind(lv) #
-  544                                                    SymTab.KindVar)
-  545                                                & (SymTab.SymKind(lv) #
-  546                                                   SymTab.KindParam) THEN
-  547                                               SemError(220); ok := FALSE
-  548                                             ELSIF ~SymTab.IsIntFamily(
-  549                                                     SymTab.SymType(lv)) THEN
-  550                                               SemError(220); ok := FALSE
-  551                                             END; .)
-  552        ":=" Expr<tlo, qlo>               (. IF ~SymTab.IsIntFamily(tlo) THEN
-  553                                               SemError(220); ok := FALSE
-  554                                             END; .)
-  555        "TO" Expr<thi, qhi>               (. IF ~SymTab.IsIntFamily(thi) THEN
-  556                                               SemError(220); ok := FALSE
-  557                                             END; .)
-  558        [ "BY" Expr<tby, qby>             (. IF (tby #
-  559                                               SymTab.InvalidType)
-  560                                              & ~SymTab.IsIntFamily(tby) THEN
-  561                                               SemError(220); ok := FALSE
-  562                                             END;
-  563                                             IF ~SymTab.ConstInt(qby, by) THEN
-  564                                               SemError(230); by := 1
-  565                                             ELSIF by = 0 THEN
-  566                                               SemError(220); by := 1
-  567                                             END; .) ]
-  568        "DO"                              (. IF ok THEN
-  569                                               QbeGen.StoreVar(lv, qlo,
-  570                                                 FALSE) END;
-  571                                             QbeGen.NewLabel(lTop);
-  572                                             QbeGen.NewLabel(lBody);
-  573                                             QbeGen.NewLabel(lEnd);
-  574                                             QbeGen.EmitLabel(lTop);
-  575                                             QbeGen.LoadVar(lv, FALSE, qt);
-  576                                             QbeGen.NewTemp(qk);
-  577                                             IF by > 0 THEN
-  578                                               QbeGen.Op3("cslew", qk,
-  579                                                 qt, qhi, FALSE)
-  580                                             ELSE QbeGen.Op3("csgew", qk,
-  581                                               qt, qhi, FALSE)
-  582                                             END;
-  583                                             QbeGen.Jnz(qk, lBody, lEnd);
-  584                                             QbeGen.EmitLabel(lBody); .)
-  585        StatSeq
-  586        "END"                             (. IF ok THEN
-  587                                               QbeGen.LoadVar(lv, FALSE,
-  588                                                 qt);
-  589                                               QbeGen.IntStr(by, qb);
-  590                                               QbeGen.NewTemp(qk);
-  591                                               QbeGen.Op3("add", qk,
-  592                                                 qt, qb, FALSE);
-  593                                               QbeGen.StoreVar(lv, qk,
-  594                                                 FALSE) END;
-  595                                             QbeGen.Jmp(lTop);
+   30     Table example's _names stay lexically out of reach.
+   31     Step 3.1 arrays: "ARRAY [lo..hi, ...] OF T" (folded literal
+   32     bounds, int/char) and open "ARRAY OF T" formals; index suffixes
+   33     with per-level checks (217/218, trap on breach via $abort);
+   34     whole-array ":=" with runtime count check + blit; string
+   35     literals lower as descriptors (1-char stays CHAR, empty works);
+   36     array/string "=" is 213 (no built-in whole comparison). *)
+   37  
+   38  IMPORT SymTab, QbeGen;
+   39  
+   40  CHARACTERS
+   41    eol      = CHR(13) .
+   42    lf       = CHR(10) .
+   43    letter   = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" .
+   44    digit    = "0123456789" .
+   45    hexDigit = digit + "ABCDEFabcdef" .
+   46    noQuote1 = ANY - "'" - eol .
+   47    noQuote2 = ANY - '"' - eol .
+   48  
+   49  IGNORE CHR(9) .. CHR(13)
+   50  
+   51  COMMENTS FROM "(*" TO "*)" NESTED
+   52  COMMENTS FROM "//" TO lf
+   53  
+   54  TOKENS
+   55    ident   = letter { letter | digit } .
+   56    integer = digit { digit }
+   57            | digit { digit } CONTEXT("..")
+   58            | "0x" hexDigit { hexDigit }
+   59            | "0X" hexDigit { hexDigit } .
+   60    real    = digit { digit } "." { digit }
+   61              [ ( "E" | "e" ) [ "+" | "-" ] digit { digit } ] .
+   62    string  = "'" { noQuote1 } "'"
+   63            | '"' { noQuote2 } '"' .
+   64  
+   65  PRODUCTIONS
+   66    M2
+   67      = Unit "." .
+   68    (* Units: program modules compile fully; DEFINITION and
+   69       IMPLEMENTATION modules parse + check now but lower in step 4
+   70       (each ends with one 230); same for nested local modules. *)
+   71    Unit
+   72      = DefUnit
+   73      | ImplUnit
+   74      | ProgModule .
+   75    DefUnit                               (. VAR m1, m2, pn: SymTab.Name; .)
+   76      = "DEFINITION" "MODULE"
+   77        GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
+   78                                             IF ~SymTab.Enter(m1,
+   79                                                SymTab.KindModule) THEN
+   80                                               SemError(200) END; .)
+   81        ";"
+   82        { Import }
+   83        { ConstBlock | TypeBlock<TRUE> | VarBlock
+   84        | ProcHeading<pn> ";"             (. SymTab.CloseProc; .) }
+   85        "END"
+   86        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
+   87                                               SemError(202) END; .)
+   88                                          (. QbeGen.CloseModule;
+   89                                             SemError(230);
+   90                                             SymTab.PrintTable; .) .
+   91    ImplUnit                              (. VAR m1, m2: SymTab.Name; .)
+   92      = "IMPLEMENTATION" "MODULE"
+   93        GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
+   94                                             IF ~SymTab.Enter(m1,
+   95                                                SymTab.KindModule) THEN
+   96                                               SemError(200) END; .)
+   97        ";"
+   98        { Import }
+   99        DeclSeq
+  100        [ "BEGIN"                         (. QbeGen.BeginBody; .)
+  101          [ StatSeq ] ]
+  102        "END"
+  103        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
+  104                                               SemError(202) END; .)
+  105                                          (. QbeGen.CloseModule;
+  106                                             SemError(230);
+  107                                             SymTab.PrintTable; .) .
+  108    ProgModule                            (. VAR m1, m2: SymTab.Name; .)
+  109      = "MODULE"
+  110        GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
+  111                                             IF ~SymTab.Enter(m1,
+  112                                                SymTab.KindModule) THEN
+  113                                               SemError(200) END; .)
+  114        [ Priority ]
+  115        ";"
+  116        { Import }
+  117        DeclSeq
+  118        [ "BEGIN"                         (. QbeGen.BeginBody; .)
+  119          [ StatSeq ] ]
+  120        "END"
+  121        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
+  122                                               SemError(202) END; .)
+  123                                          (. QbeGen.EndModule;
+  124                                             SymTab.PrintTable; .) .
+  125    DeclSeq
+  126      = { ConstBlock | TypeBlock<FALSE> | VarBlock | ProcDecl ";"
+  127        | NestedModule ";" | ClassItem ";" } .
+  128    (* Local module, Wirth form. Parses + checks fully (own scope);
+  129       lowering waits for step 4, marked by one 230 at END. *)
+  130    NestedModule                          (. VAR m1, m2: SymTab.Name; .)
+  131      = "MODULE"
+  132        GetIdent<m1>                      (. IF ~SymTab.Enter(m1,
+  133                                               SymTab.KindModule) THEN
+  134                                               SemError(200) END;
+  135                                             SymTab.PushScope; .)
+  136        [ Priority ]
+  137        ";"
+  138        { Import }
+  139        [ Export ]
+  140        DeclSeq
+  141        [ "BEGIN"
+  142          [ StatSeq ] ]
+  143        "END"
+  144        GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
+  145                                               SemError(202) END;
+  146                                             SymTab.PopScope;
+  147                                             SemError(230); .) .
+  148    Priority
+  149      = "[" integer "]"                   (. SemError(230); .) .
+  150    Export                                (. VAR nm: SymTab.Name; .)
+  151      = "EXPORT"                          (* names wire up in step 4 *)
+  152        [ "QUALIFIED" ]
+  153        GetIdent<nm>
+  154        { "," GetIdent<nm> }
+  155        ";" .
+  156    Import                                (. VAR n: SymTab.Name; .)
+  157      = "FROM"
+  158        GetIdent<n>                       (. IF ~SymTab.Enter(n,
+  159                                               SymTab.KindImport) THEN
+  160                                               SemError(200) END; .)
+  161        "IMPORT"
+  162        ImpList ";"
+  163      | "IMPORT"
+  164        ImpList ";" .
+  165    ImpList                               (. VAR n: SymTab.Name; .)
+  166      = GetIdent<n>                       (. IF ~SymTab.Enter(n,
+  167                                               SymTab.KindImport) THEN
+  168                                               SemError(200) END; .)
+  169        { ","
+  170          GetIdent<n>                     (. IF ~SymTab.Enter(n,
+  171                                               SymTab.KindImport) THEN
+  172                                               SemError(200) END; .) } .
+  173    (* Opaque TYPE declarations (definition modules). The targetless
+  174       alias resolves to InvalidType until step 4 completes it. *)
+  175    (* Scalar-phase TYPEs: named types, integer subranges, enumerations.
+  176       Opaque "TYPE T;" needs isDef (definition units); elsewhere 231.
+  177       Composite forms (ARRAY/RECORD/SET/POINTER) arrive with step 3. *)
+  178    TypeBlock<isDef: BOOLEAN>
+  179      = "TYPE" { TypeItem<isDef> ";" | ClassItem ";" } .
+  180    TypeItem<isDef: BOOLEAN>              (. VAR n: SymTab.Name;
+  181                                               t: SymTab.TypeIndex; .)
+  182      = GetIdent<n>                       (. IF ~SymTab.Enter(n,
+  183                                               SymTab.KindType) THEN
+  184                                               SemError(200) END; .)
+  185        ( "=" Type<t, FALSE>              (. SymTab.SetSymType(n, t); .)
+  186        |                                 (. IF ~isDef THEN
+  187                                               SemError(231) END;
+  188                                             SymTab.SetSymType(n,
+  189                                               SymTab.NewAlias()); .) ) .
+  190    Type<VAR t: SymTab.TypeIndex; allowOpen: BOOLEAN>
+  191      = TypeIdent<t>
+  192      | Subrange<t>
+  193      | Enum<t>
+  194      | ArrayType<t, allowOpen> .
+  195    (* Arrays: "OF" without bounds is an open formal (allowed only
+  196       where allowOpen); "[lo..hi, ...]" nests bounded levels inside
+  197       out. Bounds are folded literals (int/char); anything else 230.
+  198       Bare-type indices ("ARRAY Color OF") wait for enum ordinals. *)
+  199    ArrayType<VAR t: SymTab.TypeIndex; allowOpen: BOOLEAN>
+  200                                          (. VAR elem: SymTab.TypeIndex;
+  201                                               ok: BOOLEAN; .)
+  202      = "ARRAY"
+  203        ( "OF" Type<elem, FALSE>          (. IF ~allowOpen THEN
+  204                                               SemError(230) END;
+  205                                             t := SymTab.NewOpenArray(elem); .)
+  206        | "["                             (. SymTab.BoundBegin; ok := TRUE; .)
+  207          BoundPair<ok>
+  208          { "," BoundPair<ok> }
+  209          "]" "OF" Type<elem, FALSE>
+  210                                          (. IF ok THEN
+  211                                               t := SymTab.NestArray(elem)
+  212                                             ELSE t := SymTab.InvalidType
+  213                                             END; .) ) .
+  214    BoundPair<VAR ok: BOOLEAN>            (. VAR tlo, thi: SymTab.TypeIndex;
+  215                                               qlo, qhi: QbeGen.QVal;
+  216                                               lo, hi: INTEGER;
+  217                                               cl, cl2: INTEGER; .)
+  218      = Expr<tlo, qlo> ".." Expr<thi, qhi>
+  219                                          (. IF (tlo = SymTab.InvalidType)
+  220                                              OR (thi = SymTab.InvalidType) THEN
+  221                                             ok := FALSE
+  222                                           ELSE cl := SymTab.ClassOf(tlo);
+  223                                             cl2 := SymTab.ClassOf(thi);
+  224                                             IF ((cl # SymTab.ClInt)
+  225                                                & (cl # SymTab.ClChar))
+  226                                                OR ((cl2 # SymTab.ClInt)
+  227                                                & (cl2 # SymTab.ClChar)) THEN
+  228                                               SemError(230); ok := FALSE
+  229                                             ELSIF ~SymTab.ConstInt(qlo, lo)
+  230                                                OR ~SymTab.ConstInt(qhi, hi)
+  231                                                OR (lo > hi) THEN
+  232                                               SemError(230); ok := FALSE
+  233                                             ELSIF ~SymTab.BoundAdd(lo, hi) THEN
+  234                                               SemError(230); ok := FALSE
+  235                                             END; .) .
+  236    TypeIdent<VAR t: SymTab.TypeIndex>    (. VAR n: SymTab.Name;
+  237                                               k: INTEGER; .)
+  238      = GetIdent<n>                       (. IF ~SymTab.Lookup(n) THEN
+  239                                               SemError(201);
+  240                                               t := SymTab.InvalidType
+  241                                             ELSE k := SymTab.SymKind(n);
+  242                                               IF (k = SymTab.KindType)
+  243                                                  OR (k = SymTab.KindPredef) THEN
+  244                                                 t := SymTab.SymType(n)
+  245                                               ELSIF k = SymTab.KindImport THEN
+  246                                                 t := SymTab.InvalidType
+  247                                               ELSE SemError(221);
+  248                                                 t := SymTab.InvalidType
+  249                                               END
+  250                                             END; .) .
+  251    Subrange<VAR t: SymTab.TypeIndex>     (. VAR tlo, thi: SymTab.TypeIndex;
+  252                                               qlo, qhi: QbeGen.QVal;
+  253                                               lo, hi: INTEGER; .)
+  254      = "[" Expr<tlo, qlo> ".." Expr<thi, qhi>
+  255                                          (. IF (tlo = SymTab.InvalidType)
+  256                                              OR (thi = SymTab.InvalidType) THEN
+  257                                             t := SymTab.InvalidType
+  258                                           ELSIF (SymTab.ClassOf(tlo) #
+  259                                                  SymTab.ClInt)
+  260                                              OR (SymTab.ClassOf(thi) #
+  261                                                  SymTab.ClInt) THEN
+  262                                             SemError(230);
+  263                                             t := SymTab.InvalidType
+  264                                           ELSIF ~SymTab.ConstInt(qlo, lo)
+  265                                              OR ~SymTab.ConstInt(qhi, hi)
+  266                                              OR (lo > hi) THEN
+  267                                             SemError(230);
+  268                                             t := SymTab.InvalidType
+  269                                           ELSE t := SymTab.NewSubR(lo, hi)
+  270                                           END; .)
+  271        "]" .
+  272    Enum<VAR t: SymTab.TypeIndex>         (. VAR n: SymTab.Name; .)
+  273      = "("                               (. t := SymTab.NewEnum(); .)
+  274        GetIdent<n>                       (. IF ~SymTab.Enter(n,
+  275                                               SymTab.KindConst) THEN
+  276                                               SemError(200) END;
+  277                                             SymTab.SetSymType(n, t); .)
+  278        { "," GetIdent<n>                 (. IF ~SymTab.Enter(n,
+  279                                               SymTab.KindConst) THEN
+  280                                               SemError(200) END;
+  281                                             SymTab.SetSymType(n, t); .) }
+  282        ")" .
+  283    (* Clarion-form classes (docs/OOP.txt): declaration + single
+  284       inheritance + IMPLEMENTATION blocks. Scopes and member checks
+  285       now; lowering (vtable, dispatch, THIS) later — one 230 per
+  286       class/impl block. Methods end with ";" per the Table example
+  287       (not "," as in the sketch). No underscores in identifiers. *)
+  288    (* Single CLASS item in both loops: separating declaration from
+  289       IMPLEMENTATION at the loop level needs 2-token lookahead
+  290       (CLASS ident vs CLASS IMPLEMENTATION), which LL(1) cannot do.
+  291       The second token decides after CLASS is consumed. A misplaced
+  292       CLASS IMPLEMENTATION inside TYPE still parses (harmless: the
+  293       whole unit ends 230 until lowering). *)
+  294    ClassItem
+  295      = "CLASS" ( "IMPLEMENTATION" ClassImplRest | ClassRest ) .
+  296    ClassRest                             (. VAR cn, m2, pn: SymTab.Name;
+  297                                               ct: SymTab.TypeIndex; .)
+  298      = GetIdent<cn>                      (. IF ~SymTab.Enter(cn,
+  299                                               SymTab.KindType) THEN
+  300                                               SemError(200) END;
+  301                                             ct := SymTab.NewClass();
+  302                                             SymTab.SetSymType(cn, ct);
+  303                                             SymTab.PushClassScope(ct); .)
+  304        [ Parents<ct> ]
+  305        ";"
+  306        { ClassField<ct> ";" }
+  307        { MethodHeading<pn> ";"           (. SymTab.CloseProc; .) }
+  308        "END"
+  309        GetIdent<m2>                      (. IF ~SymTab.Equal(cn, m2) THEN
+  310                                               SemError(202) END;
+  311                                             SymTab.PopScope;
+  312                                             SemError(230); .) .
+  313    Parents<ct: SymTab.TypeIndex>         (. VAR p: SymTab.Name; .)
+  314      = "(" Parent1<ct>
+  315        { "," GetIdent<p>                 (. SemError(230); .) }
+  316        ")" .
+  317    Parent1<ct: SymTab.TypeIndex>         (. VAR p: SymTab.Name;
+  318                                               pt: SymTab.TypeIndex; .)
+  319      = GetIdent<p>                       (. IF ~SymTab.Lookup(p) THEN
+  320                                               SemError(201)
+  321                                             ELSE pt := SymTab.SymType(p);
+  322                                               IF SymTab.ClassOf(pt) #
+  323                                                  SymTab.ClClass THEN
+  324                                                 SemError(230)
+  325                                               ELSE SymTab.SetParent(ct, pt)
+  326                                               END
+  327                                             END; .) .
+  328    ClassField<ct: SymTab.TypeIndex>      (. VAR n, rhs: SymTab.Name;
+  329                                               t: SymTab.TypeIndex; .)
+  330      = GetIdent<n>
+  331        ( "=" GetIdent<rhs>               (. IF ~SymTab.Enter(n,
+  332                                               SymTab.KindConst) THEN
+  333                                               SemError(200) END;
+  334                                             IF SymTab.Lookup(rhs) THEN
+  335                                               SymTab.SetSymType(n,
+  336                                                 SymTab.SymType(rhs))
+  337                                             END; .)
+  338        |                                 (. IF ~SymTab.FieldPending(ct,
+  339                                               n) THEN
+  340                                               SemError(200) END; .)
+  341          { "," GetIdent<n>               (. IF ~SymTab.FieldPending(ct,
+  342                                               n) THEN
+  343                                               SemError(200) END; .) }
+  344          ":" TypeIdent<t>                (. SymTab.FixPendingF(ct, t); .) ) .
+  345    MethodHeading<VAR pn: SymTab.Name>    (. VAR wantVirt: BOOLEAN; .)
+  346      =                                   (. wantVirt := FALSE; .)
+  347        [ "VIRTUAL"                       (. wantVirt := TRUE; .) ]
+  348        ProcHeading<pn>                   (. IF wantVirt THEN
+  349                                               SymTab.MarkVirtual END; .) .
+  350    ClassImplRest                         (. VAR cn, m2: SymTab.Name;
+  351                                               ct: SymTab.TypeIndex; .)
+  352      = GetIdent<cn>                      (. IF ~SymTab.Lookup(cn) THEN
+  353                                               SemError(201);
+  354                                               ct := SymTab.InvalidType
+  355                                             ELSE ct := SymTab.SymType(cn);
+  356                                               IF SymTab.ClassOf(ct) #
+  357                                                  SymTab.ClClass THEN
+  358                                                 SemError(230);
+  359                                                 ct := SymTab.InvalidType
+  360                                               END
+  361                                             END;
+  362                                             IF ct #
+  363                                                SymTab.InvalidType THEN
+  364                                               IF ~SymTab.PushClassMembers(
+  365                                                      ct) THEN
+  366                                                 SemError(230) END
+  367                                             END; .)
+  368        ";" { MethodImpl<ct> ";" }
+  369        [ "BEGIN"
+  370          [ StatSeq ] ]
+  371        "END"
+  372        GetIdent<m2>                      (. IF ~SymTab.Equal(cn, m2) THEN
+  373                                               SemError(202) END;
+  374                                             SymTab.PopScope;
+  375                                             SemError(230); .) .
+  376    MethodImpl<ct: SymTab.TypeIndex>      (. VAR pn: SymTab.Name; .)
+  377      = MethodHeading<pn> ";"
+  378                                          (. IF (ct #
+  379                                                SymTab.InvalidType)
+  380                                               & ~SymTab.MethodExists(ct,
+  381                                                    pn) THEN
+  382                                               SemError(201) END; .)
+  383        ( "FORWARD"                       (. SymTab.MarkFwd;
+  384                                             SymTab.CloseProc; .)
+  385        | Block<pn>                       (. SymTab.CloseProc;
+  386                                             SemError(230); .) ) .
+  387    ConstBlock
+  388      = "CONST" { ConstDecl ";" } .
+  389    ConstDecl                             (. VAR n: SymTab.Name;
+  390                                               t: SymTab.TypeIndex;
+  391                                               qv: QbeGen.QVal;
+  392                                               cls: INTEGER; .)
+  393      = GetIdent<n>                       (. IF ~SymTab.Enter(n,
+  394                                               SymTab.KindConst) THEN
+  395                                               SemError(200) END; .)
+  396        "="
+  397        Expr<t, qv>                       (. SymTab.SetSymType(n, t);
+  398                                             cls := SymTab.ClassOf(t);
+  399                                             IF cls = SymTab.ClStr THEN
+  400                                               SemError(230)
+  401                                             ELSIF ~QbeGen.IsImm(qv) THEN
+  402                                               SemError(230) END;
+  403                                             QbeGen.DeclConst(n, qv, t); .) .
+  404    VarBlock
+  405      = "VAR" { VarDecl ";" } .
+  406    VarDecl                               (. VAR nm: SymTab.Name;
+  407                                               t: SymTab.TypeIndex;
+  408                                               i: CARDINAL;
+  409                                               cls: INTEGER; .)
+  410      = VarIdents ":"
+  411        Type<t, FALSE>                    (. cls := SymTab.ClassOf(t);
+  412                                             IF (t # SymTab.InvalidType)
+  413                                                & (cls # SymTab.ClInt)
+  414                                                & (cls # SymTab.ClBool)
+  415                                                & (cls # SymTab.ClChar)
+  416                                                & (cls # SymTab.ClReal)
+  417                                                & (cls # SymTab.ClArray) THEN
+  418                                               SemError(230) END;
+  419                                             i := 0;
+  420                                             WHILE i < SymTab.PendCount() DO
+  421                                               SymTab.PendName(i, nm);
+  422                                               QbeGen.DeclVar(nm, t);
+  423                                               INC(i)
+  424                                             END;
+  425                                             SymTab.FixPending(t); .) .
+  426    VarIdents                             (. VAR n: SymTab.Name; .)
+  427      = GetIdent<n>                       (. IF ~SymTab.EnterPending(n,
+  428                                               SymTab.KindVar) THEN
+  429                                               SemError(200) END; .)
+  430        { ","
+  431          GetIdent<n>                     (. IF ~SymTab.EnterPending(n,
+  432                                               SymTab.KindVar) THEN
+  433                                               SemError(200) END; .) } .
+  434    ParIdents<isV: BOOLEAN>               (. VAR n: SymTab.Name; .)
+  435      = GetIdent<n>                       (. IF ~SymTab.EnterParam(n, isV) THEN
+  436                                               SemError(200) END; .)
+  437        { "," GetIdent<n>                 (. IF ~SymTab.EnterParam(n, isV) THEN
+  438                                               SemError(200) END; .) } .
+  439    (* Procedure headings enter now (scopes, params, result); bodies
+  440       parse + check with one 230 at END (lowering = step 4).
+  441       FORWARD marks; the body heading re-enters (signature compare
+  442       tightens in step 4). *)
+  443    ProcHeading<VAR pn: SymTab.Name>      (. VAR t: SymTab.TypeIndex; .)
+  444      = "PROCEDURE"
+  445        GetIdent<pn>                      (. IF ~SymTab.EnterProc(pn) THEN
+  446                                             IF ~SymTab.ReenterProc(pn) THEN
+  447                                               SemError(200) END
+  448                                             END; .)
+  449        [ FormalParams ]
+  450        [ ":" TypeIdent<t>                (. SymTab.SetProcRes(t);
+  451                                             IF (t #
+  452                                                SymTab.InvalidType)
+  453                                                & (SymTab.ClassOf(t)
+  454                                                   = SymTab.ClArray) THEN
+  455                                               SemError(230) END; .) ] .
+  456    FormalParams
+  457      = "(" [ ParamSection { ";" ParamSection } ] ")" .
+  458    ParamSection                          (. VAR t: SymTab.TypeIndex;
+  459                                               isV: BOOLEAN; .)
+  460      =                                   (. isV := FALSE; .)
+  461        [ "VAR"                           (. isV := TRUE; .) ]
+  462        ParIdents<isV> ":" Type<t, TRUE>  (. SymTab.FixPending(t); .) .
+  463    ProcDecl                              (. VAR pn, m2: SymTab.Name; .)
+  464      = ProcHeading<pn> ";"
+  465        ( "FORWARD"                       (. SymTab.MarkFwd;
+  466                                             SymTab.CloseProc; .)
+  467        | Block<pn>                       (. SymTab.CloseProc;
+  468                                             SemError(230); .) ) .
+  469    Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
+  470      = DeclSeq
+  471        [ "BEGIN"
+  472          [ StatSeq ] ]
+  473        "END"
+  474        GetIdent<m2>                      (. IF ~SymTab.Equal(pn, m2) THEN
+  475                                               SemError(202) END; .) .
+  476    StatSeq
+  477      = Statement { ";" Statement } .
+  478    Statement                             (. VAR lx: QbeGen.QVal; .)
+  479      = Assign
+  480      | IfStat
+  481      | WhileStat
+  482      | RepeatStat
+  483      | LoopStat
+  484      | ForStat
+  485      | CaseStat
+  486      | ReturnStat
+  487      | "EXIT"                            (. IF QbeGen.TopLoop(lx) THEN
+  488                                               QbeGen.Jmp(lx)
+  489                                             ELSE SemError(230) END; .) .
+  490    Assign                                (. VAR dt, et: SymTab.TypeIndex;
+  491                                               dk: INTEGER;
+  492                                               qd, qe, qt: QbeGen.QVal;
+  493                                               qn: SymTab.Name;
+  494                                               isR, conv: BOOLEAN;
+  495                                               sfx: BOOLEAN; .)
+  496      = Design<dt, dk, qd, qn, sfx> ":="
+  497        Expr<et, qe>                      (. IF (dt # SymTab.InvalidType)
+  498                                             & (dk # SymTab.KindVar)
+  499                                             & (dk # SymTab.KindParam) THEN
+  500                                             SemError(210)
+  501                                           ELSIF ~SymTab.Assignable(et,
+  502                                                    dt) THEN
+  503                                             SemError(210) END;
+  504                                           isR := (dt #
+  505                                                   SymTab.InvalidType)
+  506                                                  & (SymTab.ClassOf(dt)
+  507                                                     = SymTab.ClReal);
+  508                                           conv := isR
+  509                                                   & SymTab.IsIntFamily(et);
+  510                                           IF ((dk = SymTab.KindVar)
+  511                                               OR (dk = SymTab.KindParam))
+  512                                              & (dt # SymTab.InvalidType)
+  513                                              & (et # SymTab.InvalidType) THEN
+  514                                             IF sfx THEN
+  515                                               IF SymTab.ClassOf(dt) =
+  516                                                  SymTab.ClArray THEN
+  517                                                 QbeGen.CopyArray(qd, qe,
+  518                                                   dt)
+  519                                               ELSIF conv THEN
+  520                                                 QbeGen.ConvIR(qe, qt);
+  521                                                 QbeGen.ElemStore(qd, qt,
+  522                                                   dt)
+  523                                               ELSE QbeGen.ElemStore(qd, qe,
+  524                                                 dt)
+  525                                               END
+  526                                             ELSIF SymTab.ClassOf(dt) =
+  527                                                   SymTab.ClArray THEN
+  528                                               QbeGen.CopyArray(qd, qe, dt)
+  529                                             ELSIF conv THEN
+  530                                               QbeGen.ConvIR(qe, qt);
+  531                                               QbeGen.StoreVar(qn, qt, TRUE)
+  532                                             ELSE
+  533                                               QbeGen.StoreVar(qn, qe, isR)
+  534                                             END
+  535                                           END; .) .
+  536    IfStat                                (. VAR t: SymTab.TypeIndex;
+  537                                               q, lThen, lElse, lEnd:
+  538                                                 QbeGen.QVal;
+  539                                               hasElse: BOOLEAN; .)
+  540      = "IF"                              (. hasElse := FALSE; .)
+  541        Expr<t, q>                        (. IF ~SymTab.BoolCheck(t) THEN
+  542                                               SemError(214) END;
+  543                                             QbeGen.NewLabel(lThen);
+  544                                             QbeGen.NewLabel(lElse);
+  545                                             QbeGen.NewLabel(lEnd);
+  546                                             QbeGen.Jnz(q, lThen, lElse);
+  547                                             QbeGen.EmitLabel(lThen); .)
+  548        "THEN" StatSeq                    (. QbeGen.Jmp(lEnd); .)
+  549        { "ELSIF"                         (. QbeGen.EmitLabel(lElse);
+  550                                             QbeGen.NewLabel(lElse); .)
+  551          Expr<t, q>                      (. IF ~SymTab.BoolCheck(t) THEN
+  552                                               SemError(214) END;
+  553                                             QbeGen.NewLabel(lThen);
+  554                                             QbeGen.Jnz(q, lThen, lElse);
+  555                                             QbeGen.EmitLabel(lThen); .)
+  556          "THEN" StatSeq                  (. QbeGen.Jmp(lEnd); .) }
+  557        [ "ELSE"                          (. QbeGen.EmitLabel(lElse);
+  558                                             hasElse := TRUE; .)
+  559          StatSeq ]
+  560        "END"                             (. IF hasElse THEN
+  561                                               QbeGen.EmitLabel(lEnd)
+  562                                             ELSE QbeGen.EmitLabel(lElse);
+  563                                               QbeGen.EmitLabel(lEnd)
+  564                                             END; .) .
+  565    WhileStat                             (. VAR t: SymTab.TypeIndex;
+  566                                               q, lTop, lBody, lEnd:
+  567                                                 QbeGen.QVal; .)
+  568      = "WHILE"                           (. QbeGen.NewLabel(lTop);
+  569                                             QbeGen.NewLabel(lBody);
+  570                                             QbeGen.NewLabel(lEnd);
+  571                                             QbeGen.EmitLabel(lTop); .)
+  572        Expr<t, q>                        (. IF ~SymTab.BoolCheck(t) THEN
+  573                                               SemError(214) END;
+  574                                             QbeGen.Jnz(q, lBody, lEnd);
+  575                                             QbeGen.EmitLabel(lBody); .)
+  576        "DO" StatSeq                      (. QbeGen.Jmp(lTop); .)
+  577        "END"                             (. QbeGen.EmitLabel(lEnd); .) .
+  578    RepeatStat                            (. VAR t: SymTab.TypeIndex;
+  579                                               q, lTop, lEnd: QbeGen.QVal; .)
+  580      = "REPEAT"                          (. QbeGen.NewLabel(lTop);
+  581                                             QbeGen.NewLabel(lEnd);
+  582                                             QbeGen.EmitLabel(lTop); .)
+  583        StatSeq
+  584        "UNTIL" Expr<t, q>                (. IF ~SymTab.BoolCheck(t) THEN
+  585                                               SemError(214) END;
+  586                                             QbeGen.Jnz(q, lEnd, lTop);
+  587                                             QbeGen.EmitLabel(lEnd); .) .
+  588    LoopStat                              (. VAR lTop, lEnd: QbeGen.QVal; .)
+  589      = "LOOP"                            (. QbeGen.NewLabel(lTop);
+  590                                             QbeGen.NewLabel(lEnd);
+  591                                             QbeGen.PushLoop(lEnd);
+  592                                             QbeGen.EmitLabel(lTop); .)
+  593        StatSeq
+  594        "END"                             (. QbeGen.Jmp(lTop);
+  595                                             QbeGen.PopLoop;
   596                                             QbeGen.EmitLabel(lEnd); .) .
-  597    CaseStat                              (. VAR tsel: SymTab.TypeIndex;
-  598                                               qsel, lEnd: QbeGen.QVal; .)
-  599      = "CASE" Expr<tsel, qsel>           (. QbeGen.NewLabel(lEnd); .)
-  600        "OF" CaseAlt<tsel, qsel, lEnd>
-  601        { "|" CaseAlt<tsel, qsel, lEnd> }
-  602        [ "ELSE" StatSeq ]
-  603        "END"                             (. QbeGen.EmitLabel(lEnd); .) .
-  604    (* Compare-chain lowering: each alternative ends its match-tests
-  605       with "jmp lAfter", so the no-match fallthrough skips the body:
-  606       "cmp; jnz(lBody,lF); lF: ... ; jmp lAfter; lBody: S; jmp lEnd;
-  607       lAfter:". Falls into the next alternative, ELSE, or END. *)
-  608    CaseAlt<tsel: SymTab.TypeIndex; qsel: QbeGen.QVal;
-  609            lEnd: QbeGen.QVal>            (. VAR lBody, lAfter: QbeGen.QVal; .)
-  610      =                                   (. QbeGen.NewLabel(lBody);
-  611                                             QbeGen.NewLabel(lAfter); .)
-  612        CaseLabel<tsel, qsel, lBody>
-  613        { "," CaseLabel<tsel, qsel, lBody> }
-  614        ":"                               (. QbeGen.Jmp(lAfter);
-  615                                             QbeGen.EmitLabel(lBody); .)
-  616        StatSeq                           (. QbeGen.Jmp(lEnd);
-  617                                             QbeGen.EmitLabel(lAfter); .) .
-  618    CaseLabel<tsel: SymTab.TypeIndex; qsel: QbeGen.QVal;
-  619              lBody: QbeGen.QVal>         (. VAR t2, t3: SymTab.TypeIndex;
-  620                                               q2, q3, qc, qd, qe:
-  621                                                 QbeGen.QVal;
-  622                                               lNext: QbeGen.QVal; .)
-  623      = Expr<t2, q2>                      (. IF (t2 #
-  624                                               SymTab.InvalidType)
-  625                                              & (tsel #
-  626                                                 SymTab.InvalidType)
-  627                                              & ~SymTab.EqCheck(t2,
-  628                                                   tsel) THEN
-  629                                               SemError(213) END;
-  630                                             IF ~QbeGen.IsImm(q2) THEN
-  631                                               SemError(230);
-  632                                               QbeGen.CopyOp("0", q2)
-  633                                             END;
-  634                                             QbeGen.NewLabel(lNext);
-  635                                             QbeGen.Cmp(SymTab.OpEq,
-  636                                               qsel, q2, qc, FALSE);
-  637                                             QbeGen.Jnz(qc, lBody, lNext);
-  638                                             QbeGen.EmitLabel(lNext); .)
-  639        [ ".." Expr<t3, q3>               (. IF (t3 #
-  640                                               SymTab.InvalidType)
-  641                                              & (tsel #
-  642                                                 SymTab.InvalidType)
-  643                                              & ~SymTab.EqCheck(t3,
-  644                                                   tsel) THEN
-  645                                               SemError(213) END;
-  646                                             IF ~QbeGen.IsImm(q3) THEN
-  647                                               SemError(230);
-  648                                               QbeGen.CopyOp("0", q3)
-  649                                             END;
-  650                                             QbeGen.Cmp(SymTab.OpGe,
-  651                                               qsel, q2, qc, FALSE);
-  652                                             QbeGen.Cmp(SymTab.OpLe,
-  653                                               qsel, q3, qd, FALSE);
-  654                                             QbeGen.NewTemp(qe);
-  655                                             QbeGen.Op3("and", qe, qc, qd,
-  656                                               FALSE);
-  657                                             QbeGen.NewLabel(lNext);
-  658                                             QbeGen.Jnz(qe, lBody, lNext);
-  659                                             QbeGen.EmitLabel(lNext); .) ] .
-  660    ReturnStat                            (. VAR t: SymTab.TypeIndex;
-  661                                               q: QbeGen.QVal;
-  662                                               res: SymTab.TypeIndex;
-  663                                               hadE: BOOLEAN; .)
-  664      = "RETURN"                          (. hadE := FALSE; .)
-  665        [ Expr<t, q>                      (. hadE := TRUE; .) ]
-  666                                          (. IF ~SymTab.InProc() THEN
-  667                                               SemError(232)
-  668                                             ELSE res := SymTab.CurRes();
-  669                                               IF ~hadE THEN
-  670                                                 IF res #
-  671                                                    SymTab.InvalidType THEN
-  672                                                   SemError(232) END
-  673                                               ELSIF (res =
-  674                                                      SymTab.InvalidType)
-  675                                                  OR (t #
-  676                                                      SymTab.InvalidType)
-  677                                                  & ~SymTab.Assignable(t,
-  678                                                       res) THEN
-  679                                                 SemError(232)
-  680                                               END
-  681                                             END; .) .
-  682    Design<VAR t: SymTab.TypeIndex; VAR k: INTEGER;
-  683           VAR q: QbeGen.QVal; VAR qn: SymTab.Name>
-  684                                          (. VAR n: SymTab.Name;
-  685                                               cls: INTEGER; .)
-  686      = GetIdent<n>                       (. QbeGen.CopyOp(n, qn);
-  687                                             IF ~SymTab.Lookup(n) THEN
-  688                                               SemError(201);
-  689                                               t := SymTab.InvalidType;
-  690                                               k := -1;
-  691                                               QbeGen.CopyOp("0", q)
-  692                                             ELSE
-  693                                               t := SymTab.SymType(n);
-  694                                               k := SymTab.SymKind(n);
-  695                                               IF k = SymTab.KindConst THEN
-  696                                                 IF SymTab.Equal(n,
-  697                                                    "TRUE") THEN
-  698                                                   t := SymTab.BoolType();
-  699                                                   QbeGen.CopyOp("1", q)
-  700                                                 ELSIF SymTab.Equal(n,
-  701                                                    "FALSE") THEN
-  702                                                   t := SymTab.BoolType();
-  703                                                   QbeGen.CopyOp("0", q)
-  704                                                 ELSE
-  705                                                   cls :=
-  706                                                     SymTab.ClassOf(t);
-  707                                                   IF (t #
-  708                                                       SymTab.InvalidType)
-  709                                                      & ((cls = SymTab.ClInt)
-  710                                                      OR (cls
-  711                                                          = SymTab.ClChar)
-  712                                                      OR (cls
-  713                                                          = SymTab.ClReal)) THEN
-  714                                                     QbeGen.LoadVar(n,
-  715                                                       cls = SymTab.ClReal, q)
-  716                                                   ELSE
-  717                                                     IF t #
-  718                                                        SymTab.InvalidType THEN
-  719                                                       SemError(230)
-  720                                                     END;
-  721                                                     QbeGen.CopyOp("0", q)
-  722                                                   END
-  723                                                 END
-  724                                                ELSIF (k = SymTab.KindVar)
-  725                                                   OR (k = SymTab.KindParam) THEN
-  726                                                  cls :=
-  727                                                    SymTab.ClassOf(t);
-  728                                                  IF (cls = SymTab.ClInt)
-  729                                                     OR (cls = SymTab.ClBool)
-  730                                                     OR (cls = SymTab.ClChar)
-  731                                                     OR (cls
-  732                                                         = SymTab.ClReal) THEN
-  733                                                    QbeGen.LoadVar(n,
-  734                                                      cls = SymTab.ClReal, q)
-  735                                                  ELSE SemError(230);
-  736                                                    QbeGen.CopyOp("0", q)
-  737                                                  END
-  738                                                ELSE QbeGen.CopyOp("0", q);
-  739                                                  IF (k = SymTab.KindImport)
-  740                                                     OR (k = SymTab.KindProc)
-  741                                                     OR (k
-  742                                                         = SymTab.KindField) THEN
-  743                                                    SemError(230)
-  744                                                  END
-  745                                                END
-  746                                             END; .) .
-  747    Expr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
-  748                                          (. VAR t2: SymTab.TypeIndex;
-  749                                               op: INTEGER;
-  750                                               q2, qt: QbeGen.QVal;
-  751                                               isR: BOOLEAN; .)
-  752      = SimExpr<t, q>
-  753        [ Rel<op> SimExpr<t2, q2>
-  754          (. IF SymTab.RelCheck(t, t2, op) THEN
-  755               isR := SymTab.ClassOf(t) = SymTab.ClReal;
-  756               t := SymTab.BoolType()
-  757             ELSE SemError(213); t := SymTab.InvalidType;
-  758               isR := FALSE END;
-  759             IF t # SymTab.InvalidType THEN
-  760               QbeGen.Cmp(op, q, q2, qt, isR);
-  761               QbeGen.CopyOp(qt, q)
-  762             ELSE QbeGen.CopyOp("0", q)
-  763             END; .) ] .
-  764    Rel<VAR op: INTEGER>
-  765      = "="                               (. op := SymTab.OpEq; .)
-  766      | "#"                               (. op := SymTab.OpNeq1; .)
-  767      | "<"                               (. op := SymTab.OpLt; .)
-  768      | "<="                              (. op := SymTab.OpLe; .)
-  769      | ">"                               (. op := SymTab.OpGt; .)
-  770      | ">="                              (. op := SymTab.OpGe; .) .
-  771    SimExpr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
-  772                                          (. VAR t2, res2: SymTab.TypeIndex;
-  773                                               op: INTEGER;
-  774                                               q2, qt: QbeGen.QVal;
-  775                                               neg, isR: BOOLEAN; .)
-  776      =                                   (. neg := FALSE; .)
-  777        [ "+" | "-"                       (. neg := TRUE; .) ]
-  778        Term<t, q>                        (. IF neg THEN
-  779                                             IF QbeGen.IsImm(q) THEN
-  780                                               QbeGen.NegFold(q, q)
-  781                                             ELSE QbeGen.NewTemp(qt);
-  782                                               QbeGen.NegQ(q, qt,
-  783                                                 SymTab.ClassOf(t)
-  784                                                 = SymTab.ClReal);
-  785                                               QbeGen.CopyOp(qt, q)
-  786                                             END
-  787                                           END; .)
-  788        { AddOp<op> Term<t2, q2>
-  789          (. IF op = SymTab.OpOr THEN
-  790               IF SymTab.BoolCheck(t) & SymTab.BoolCheck(t2) THEN
-  791                 t := SymTab.BoolType()
-  792               ELSE SemError(212); t := SymTab.InvalidType END;
-  793               IF t # SymTab.InvalidType THEN
-  794                 QbeGen.NewTemp(qt);
-  795                 QbeGen.Op3("or", qt, q, q2, FALSE);
-  796                 QbeGen.CopyOp(qt, q)
-  797               ELSE QbeGen.CopyOp("0", q)
-  798               END
-  799             ELSE
-  800               IF SymTab.ArithCheck(t, t2, FALSE, res2) THEN
-  801                 t := res2
-  802               ELSE SemError(211); t := SymTab.InvalidType END;
-  803               isR := SymTab.ClassOf(t) = SymTab.ClReal;
-  804               IF t # SymTab.InvalidType THEN
-  805                 QbeGen.NewTemp(qt);
-  806                 IF op = SymTab.OpAdd THEN
-  807                   QbeGen.Op3("add", qt, q, q2, isR)
-  808                 ELSE
-  809                   QbeGen.Op3("sub", qt, q, q2, isR)
-  810                 END;
-  811                 QbeGen.CopyOp(qt, q)
-  812               ELSE QbeGen.CopyOp("0", q)
-  813               END
-  814             END; .) } .
-  815    AddOp<VAR op: INTEGER>
-  816      = "+"                               (. op := SymTab.OpAdd; .)
-  817      | "-"                               (. op := SymTab.OpSub; .)
-  818      | "OR"                              (. op := SymTab.OpOr; .) .
-  819    Term<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
-  820                                          (. VAR t2, res2: SymTab.TypeIndex;
-  821                                               op: INTEGER;
-  822                                               q2, qt: QbeGen.QVal;
-  823                                               isR: BOOLEAN; .)
-  824      = Fact<t, q> { MulOp<op> Fact<t2, q2>
-  825        (. IF op = SymTab.OpAnd THEN
-  826             IF SymTab.BoolCheck(t) & SymTab.BoolCheck(t2) THEN
-  827               t := SymTab.BoolType()
-  828             ELSE SemError(212); t := SymTab.InvalidType END;
-  829             IF t # SymTab.InvalidType THEN
-  830               QbeGen.NewTemp(qt);
-  831               QbeGen.Op3("and", qt, q, q2, FALSE);
-  832               QbeGen.CopyOp(qt, q)
-  833             ELSE QbeGen.CopyOp("0", q)
-  834             END
-  835           ELSE
-  836             IF SymTab.ArithCheck(t, t2,
-  837                  (op = SymTab.OpDiv) OR (op = SymTab.OpMod),
-  838                  res2) THEN t := res2
-  839             ELSE SemError(211); t := SymTab.InvalidType END;
-  840             isR := SymTab.ClassOf(t) = SymTab.ClReal;
-  841             IF t # SymTab.InvalidType THEN
-  842               QbeGen.NewTemp(qt);
-  843               IF op = SymTab.OpTimes THEN
-  844                 QbeGen.Op3("mul", qt, q, q2, isR)
-  845               ELSIF (op = SymTab.OpDiv)
-  846                  OR (op = SymTab.OpSlash) THEN
-  847                 QbeGen.Op3("div", qt, q, q2, isR)
-  848               ELSE
-  849                 QbeGen.Op3("rem", qt, q, q2, isR)
-  850               END;
-  851               QbeGen.CopyOp(qt, q)
-  852             ELSE QbeGen.CopyOp("0", q)
-  853             END
-  854           END; .) } .
-  855    MulOp<VAR op: INTEGER>
-  856      = "*"                               (. op := SymTab.OpTimes; .)
-  857      | "/"                               (. op := SymTab.OpSlash; .)
-  858      | "DIV"                             (. op := SymTab.OpDiv; .)
-  859      | "MOD"                             (. op := SymTab.OpMod; .)
-  860      | "AND"                             (. op := SymTab.OpAnd; .) .
-  861    Fact<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
-  862                                          (. VAR s: ARRAY [0 .. 255] OF CHAR;
-  863                                               et, dt, t2: SymTab.TypeIndex;
-  864                                               dk: INTEGER;
-  865                                               qd, q2: QbeGen.QVal;
-  866                                               qn: SymTab.Name; .)
-  867      = integer                           (. LexString(s);
-  868                                             QbeGen.NormInt(s, q);
-  869                                             t := SymTab.IntType(); .)
-  870      | real                              (. LexString(s);
-  871                                             QbeGen.NormReal(s, q);
-  872                                             t := SymTab.RealType(); .)
-  873      | string                            (. LexString(s);
-  874                                             IF SymTab.StrLen(s) <= 3 THEN
-  875                                               t := SymTab.CharType();
-  876                                               QbeGen.IntStr(
-  877                                                 QbeGen.CharVal(s), q)
-  878                                             ELSE t := SymTab.NewStr();
-  879                                               SemError(230);
-  880                                               QbeGen.CopyOp("0", q)
-  881                                             END; .)
-  882      | Design<dt, dk, qd, qn>            (. t := dt;
-  883                                             QbeGen.CopyOp(qd, q); .)
-  884      | "(" Expr<et, q> ")"               (. t := et; .)
-  885      | "NOT" Fact<t2, q2>                (. IF SymTab.BoolCheck(t2) THEN
-  886                                               t := SymTab.BoolType()
-  887                                             ELSE SemError(212);
-  888                                               t := SymTab.InvalidType END;
-  889                                             IF t # SymTab.InvalidType THEN
-  890                                               QbeGen.NotQ(q2, q)
-  891                                             ELSE QbeGen.CopyOp("0", q)
-  892                                             END; .) .
-  893    GetIdent<VAR n: SymTab.Name>
-  894      = ident                             (. LexName(n); .) .
-  895  
-  896  END M2.
+  597    (* FOR with static-sign BY (literal, non-zero; 220 otherwise).
+  598       Runtime direction would need a compare-select; the literal
+  599       sign picks cslew/csegew at "DO" time. *)
+  600    ForStat                               (. VAR lv: SymTab.Name;
+  601                                               tlo, thi, tby:
+  602                                                 SymTab.TypeIndex;
+  603                                               qlo, qhi, qby, qt, qk, qb:
+  604                                                 QbeGen.QVal;
+  605                                               lTop, lBody, lEnd:
+  606                                                 QbeGen.QVal;
+  607                                               by: INTEGER;
+  608                                               ok: BOOLEAN; .)
+  609      = "FOR"                             (. by := 1; .)
+  610        GetIdent<lv>                      (. ok := SymTab.Lookup(lv);
+  611                                             IF ~ok THEN
+  612                                               SemError(201)
+  613                                             ELSIF (SymTab.SymKind(lv) #
+  614                                                    SymTab.KindVar)
+  615                                                & (SymTab.SymKind(lv) #
+  616                                                   SymTab.KindParam) THEN
+  617                                               SemError(220); ok := FALSE
+  618                                             ELSIF ~SymTab.IsIntFamily(
+  619                                                     SymTab.SymType(lv)) THEN
+  620                                               SemError(220); ok := FALSE
+  621                                             END; .)
+  622        ":=" Expr<tlo, qlo>               (. IF ~SymTab.IsIntFamily(tlo) THEN
+  623                                               SemError(220); ok := FALSE
+  624                                             END; .)
+  625        "TO" Expr<thi, qhi>               (. IF ~SymTab.IsIntFamily(thi) THEN
+  626                                               SemError(220); ok := FALSE
+  627                                             END; .)
+  628        [ "BY" Expr<tby, qby>             (. IF (tby #
+  629                                               SymTab.InvalidType)
+  630                                              & ~SymTab.IsIntFamily(tby) THEN
+  631                                               SemError(220); ok := FALSE
+  632                                             END;
+  633                                             IF ~SymTab.ConstInt(qby, by) THEN
+  634                                               SemError(230); by := 1
+  635                                             ELSIF by = 0 THEN
+  636                                               SemError(220); by := 1
+  637                                             END; .) ]
+  638        "DO"                              (. IF ok THEN
+  639                                               QbeGen.StoreVar(lv, qlo,
+  640                                                 FALSE) END;
+  641                                             QbeGen.NewLabel(lTop);
+  642                                             QbeGen.NewLabel(lBody);
+  643                                             QbeGen.NewLabel(lEnd);
+  644                                             QbeGen.EmitLabel(lTop);
+  645                                             QbeGen.LoadVar(lv, FALSE, qt);
+  646                                             QbeGen.NewTemp(qk);
+  647                                             IF by > 0 THEN
+  648                                               QbeGen.Op3("cslew", qk,
+  649                                                 qt, qhi, FALSE)
+  650                                             ELSE QbeGen.Op3("csgew", qk,
+  651                                               qt, qhi, FALSE)
+  652                                             END;
+  653                                             QbeGen.Jnz(qk, lBody, lEnd);
+  654                                             QbeGen.EmitLabel(lBody); .)
+  655        StatSeq
+  656        "END"                             (. IF ok THEN
+  657                                               QbeGen.LoadVar(lv, FALSE,
+  658                                                 qt);
+  659                                               QbeGen.IntStr(by, qb);
+  660                                               QbeGen.NewTemp(qk);
+  661                                               QbeGen.Op3("add", qk,
+  662                                                 qt, qb, FALSE);
+  663                                               QbeGen.StoreVar(lv, qk,
+  664                                                 FALSE) END;
+  665                                             QbeGen.Jmp(lTop);
+  666                                             QbeGen.EmitLabel(lEnd); .) .
+  667    CaseStat                              (. VAR tsel: SymTab.TypeIndex;
+  668                                               qsel, lEnd: QbeGen.QVal; .)
+  669      = "CASE" Expr<tsel, qsel>           (. QbeGen.NewLabel(lEnd); .)
+  670        "OF" CaseAlt<tsel, qsel, lEnd>
+  671        { "|" CaseAlt<tsel, qsel, lEnd> }
+  672        [ "ELSE" StatSeq ]
+  673        "END"                             (. QbeGen.EmitLabel(lEnd); .) .
+  674    (* Compare-chain lowering: each alternative ends its match-tests
+  675       with "jmp lAfter", so the no-match fallthrough skips the body:
+  676       "cmp; jnz(lBody,lF); lF: ... ; jmp lAfter; lBody: S; jmp lEnd;
+  677       lAfter:". Falls into the next alternative, ELSE, or END. *)
+  678    CaseAlt<tsel: SymTab.TypeIndex; qsel: QbeGen.QVal;
+  679            lEnd: QbeGen.QVal>            (. VAR lBody, lAfter: QbeGen.QVal; .)
+  680      =                                   (. QbeGen.NewLabel(lBody);
+  681                                             QbeGen.NewLabel(lAfter); .)
+  682        CaseLabel<tsel, qsel, lBody>
+  683        { "," CaseLabel<tsel, qsel, lBody> }
+  684        ":"                               (. QbeGen.Jmp(lAfter);
+  685                                             QbeGen.EmitLabel(lBody); .)
+  686        StatSeq                           (. QbeGen.Jmp(lEnd);
+  687                                             QbeGen.EmitLabel(lAfter); .) .
+  688    CaseLabel<tsel: SymTab.TypeIndex; qsel: QbeGen.QVal;
+  689              lBody: QbeGen.QVal>         (. VAR t2, t3: SymTab.TypeIndex;
+  690                                               q2, q3, qc, qd, qe:
+  691                                                 QbeGen.QVal;
+  692                                               lNext: QbeGen.QVal; .)
+  693      = Expr<t2, q2>                      (. IF (t2 #
+  694                                               SymTab.InvalidType)
+  695                                              & (tsel #
+  696                                                 SymTab.InvalidType)
+  697                                              & ~SymTab.EqCheck(t2,
+  698                                                   tsel) THEN
+  699                                               SemError(213) END;
+  700                                             IF ~QbeGen.IsImm(q2) THEN
+  701                                               SemError(230);
+  702                                               QbeGen.CopyOp("0", q2)
+  703                                             END;
+  704                                             QbeGen.NewLabel(lNext);
+  705                                             QbeGen.Cmp(SymTab.OpEq,
+  706                                               qsel, q2, qc, FALSE);
+  707                                             QbeGen.Jnz(qc, lBody, lNext);
+  708                                             QbeGen.EmitLabel(lNext); .)
+  709        [ ".." Expr<t3, q3>               (. IF (t3 #
+  710                                               SymTab.InvalidType)
+  711                                              & (tsel #
+  712                                                 SymTab.InvalidType)
+  713                                              & ~SymTab.EqCheck(t3,
+  714                                                   tsel) THEN
+  715                                               SemError(213) END;
+  716                                             IF ~QbeGen.IsImm(q3) THEN
+  717                                               SemError(230);
+  718                                               QbeGen.CopyOp("0", q3)
+  719                                             END;
+  720                                             QbeGen.Cmp(SymTab.OpGe,
+  721                                               qsel, q2, qc, FALSE);
+  722                                             QbeGen.Cmp(SymTab.OpLe,
+  723                                               qsel, q3, qd, FALSE);
+  724                                             QbeGen.NewTemp(qe);
+  725                                             QbeGen.Op3("and", qe, qc, qd,
+  726                                               FALSE);
+  727                                             QbeGen.NewLabel(lNext);
+  728                                             QbeGen.Jnz(qe, lBody, lNext);
+  729                                             QbeGen.EmitLabel(lNext); .) ] .
+  730    ReturnStat                            (. VAR t: SymTab.TypeIndex;
+  731                                               q: QbeGen.QVal;
+  732                                               res: SymTab.TypeIndex;
+  733                                               hadE: BOOLEAN; .)
+  734      = "RETURN"                          (. hadE := FALSE; .)
+  735        [ Expr<t, q>                      (. hadE := TRUE; .) ]
+  736                                          (. IF ~SymTab.InProc() THEN
+  737                                               SemError(232)
+  738                                             ELSE res := SymTab.CurRes();
+  739                                               IF ~hadE THEN
+  740                                                 IF res #
+  741                                                    SymTab.InvalidType THEN
+  742                                                   SemError(232) END
+  743                                               ELSIF (res =
+  744                                                      SymTab.InvalidType)
+  745                                                  OR (t #
+  746                                                      SymTab.InvalidType)
+  747                                                  & ~SymTab.Assignable(t,
+  748                                                       res) THEN
+  749                                                 SemError(232)
+  750                                               END
+  751                                             END; .) .
+  752    (* Designator: scalar loads, array addresses, and index suffixes.
+  753       Each index descends one level (bounds-checked, trap on breach);
+  754       nested levels reload the inner descriptor address. q ends as the
+  755       value (scalars), the descriptor address (plain arrays), or the
+  756       element address (indexed); sfx marks the indexed form. *)
+  757    Design<VAR t: SymTab.TypeIndex; VAR k: INTEGER;
+  758           VAR q: QbeGen.QVal; VAR qn: SymTab.Name; VAR sfx: BOOLEAN>
+  759                                          (. VAR n: SymTab.Name;
+  760                                               cls: INTEGER;
+  761                                               it, eT: SymTab.TypeIndex;
+  762                                               iq, ql, qlo, qhi, qe:
+  763                                                 QbeGen.QVal;
+  764                                               lo, hi: INTEGER;
+  765                                               isOpen: BOOLEAN; .)
+  766      = GetIdent<n>                       (. QbeGen.CopyOp(n, qn);
+  767                                             sfx := FALSE;
+  768                                             IF ~SymTab.Lookup(n) THEN
+  769                                               SemError(201);
+  770                                               t := SymTab.InvalidType;
+  771                                               k := -1;
+  772                                               QbeGen.CopyOp("0", q)
+  773                                             ELSE
+  774                                               t := SymTab.SymType(n);
+  775                                               k := SymTab.SymKind(n);
+  776                                               IF k = SymTab.KindConst THEN
+  777                                                 IF SymTab.Equal(n,
+  778                                                    "TRUE") THEN
+  779                                                   t := SymTab.BoolType();
+  780                                                   QbeGen.CopyOp("1", q)
+  781                                                 ELSIF SymTab.Equal(n,
+  782                                                    "FALSE") THEN
+  783                                                   t := SymTab.BoolType();
+  784                                                   QbeGen.CopyOp("0", q)
+  785                                                 ELSE
+  786                                                   cls :=
+  787                                                     SymTab.ClassOf(t);
+  788                                                   IF (t #
+  789                                                       SymTab.InvalidType)
+  790                                                      & ((cls = SymTab.ClInt)
+  791                                                      OR (cls
+  792                                                          = SymTab.ClChar)
+  793                                                      OR (cls
+  794                                                          = SymTab.ClReal)) THEN
+  795                                                     QbeGen.LoadVar(n,
+  796                                                       cls = SymTab.ClReal, q)
+  797                                                   ELSE
+  798                                                     IF t #
+  799                                                        SymTab.InvalidType THEN
+  800                                                       SemError(230)
+  801                                                     END;
+  802                                                     QbeGen.CopyOp("0", q)
+  803                                                   END
+  804                                                 END
+  805                                               ELSIF (k = SymTab.KindVar)
+  806                                                  OR (k = SymTab.KindParam) THEN
+  807                                                 cls :=
+  808                                                   SymTab.ClassOf(t);
+  809                                                 IF (cls = SymTab.ClInt)
+  810                                                    OR (cls = SymTab.ClBool)
+  811                                                    OR (cls = SymTab.ClChar)
+  812                                                    OR (cls
+  813                                                        = SymTab.ClReal) THEN
+  814                                                   QbeGen.LoadVar(n,
+  815                                                     cls = SymTab.ClReal, q)
+  816                                                 ELSIF cls
+  817                                                       = SymTab.ClArray THEN
+  818                                                   QbeGen.AddrOf(n, q)
+  819                                                 ELSE SemError(230);
+  820                                                   QbeGen.CopyOp("0", q)
+  821                                                 END
+  822                                               ELSE QbeGen.CopyOp("0", q);
+  823                                                 IF (k = SymTab.KindImport)
+  824                                                    OR (k = SymTab.KindProc)
+  825                                                    OR (k
+  826                                                        = SymTab.KindField) THEN
+  827                                                   SemError(230)
+  828                                                 END
+  829                                               END
+  830                                             END; .)
+  831        { "[" Expr<it, iq>
+  832                                          (. IF t = SymTab.InvalidType THEN
+  833                                             ELSIF SymTab.ClassOf(t) #
+  834                                                   SymTab.ClArray THEN
+  835                                               SemError(217);
+  836                                               t := SymTab.InvalidType
+  837                                             ELSIF ~SymTab.IsIntFamily(it)
+  838                                                & (SymTab.ClassOf(it) #
+  839                                                   SymTab.ClChar) THEN
+  840                                               SemError(218);
+  841                                               t := SymTab.InvalidType
+  842                                             ELSE
+  843                                               QbeGen.WidenIndex(iq, ql);
+  844                                               isOpen :=
+  845                                                 SymTab.IsOpenArray(t);
+  846                                               IF isOpen THEN
+  847                                                 QbeGen.CopyOp("0", qlo);
+  848                                                 QbeGen.OpenHi(q, qhi)
+  849                                               ELSE
+  850                                                 lo := SymTab.ArrayLo(t);
+  851                                                 hi := SymTab.ArrayHi(t);
+  852                                                 QbeGen.IntStr(lo, qlo);
+  853                                                 QbeGen.IntStr(hi, qhi)
+  854                                               END;
+  855                                               QbeGen.CheckRange(ql, qlo,
+  856                                                 qhi);
+  857                                               eT := SymTab.ArrayElem(t);
+  858                                               QbeGen.ElemAddr(q, ql, qlo,
+  859                                                 t, qe);
+  860                                               IF SymTab.ClassOf(eT) =
+  861                                                  SymTab.ClArray THEN
+  862                                                 QbeGen.ElemLoad(qe, eT, q)
+  863                                               ELSE QbeGen.CopyOp(qe, q)
+  864                                               END;
+  865                                               t := eT; sfx := TRUE
+  866                                             END; .)
+  867          { "," Expr<it, iq>
+  868                                          (. IF t = SymTab.InvalidType THEN
+  869                                             ELSIF SymTab.ClassOf(t) #
+  870                                                   SymTab.ClArray THEN
+  871                                               SemError(217);
+  872                                               t := SymTab.InvalidType
+  873                                             ELSIF ~SymTab.IsIntFamily(it)
+  874                                                & (SymTab.ClassOf(it) #
+  875                                                   SymTab.ClChar) THEN
+  876                                               SemError(218);
+  877                                               t := SymTab.InvalidType
+  878                                             ELSE
+  879                                               QbeGen.WidenIndex(iq, ql);
+  880                                               isOpen :=
+  881                                                 SymTab.IsOpenArray(t);
+  882                                               IF isOpen THEN
+  883                                                 QbeGen.CopyOp("0", qlo);
+  884                                                 QbeGen.OpenHi(q, qhi)
+  885                                               ELSE
+  886                                                 lo := SymTab.ArrayLo(t);
+  887                                                 hi := SymTab.ArrayHi(t);
+  888                                                 QbeGen.IntStr(lo, qlo);
+  889                                                 QbeGen.IntStr(hi, qhi)
+  890                                               END;
+  891                                               QbeGen.CheckRange(ql, qlo,
+  892                                                 qhi);
+  893                                               eT := SymTab.ArrayElem(t);
+  894                                               QbeGen.ElemAddr(q, ql, qlo,
+  895                                                 t, qe);
+  896                                               IF SymTab.ClassOf(eT) =
+  897                                                  SymTab.ClArray THEN
+  898                                                 QbeGen.ElemLoad(qe, eT, q)
+  899                                               ELSE QbeGen.CopyOp(qe, q)
+  900                                               END;
+  901                                               t := eT; sfx := TRUE
+  902                                             END; .) }
+  903          "]" } .
+  904    Expr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
+  905                                          (. VAR t2: SymTab.TypeIndex;
+  906                                               op: INTEGER;
+  907                                               q2, qt: QbeGen.QVal;
+  908                                               isR: BOOLEAN; .)
+  909      = SimExpr<t, q>
+  910        [ Rel<op> SimExpr<t2, q2>
+  911          (. IF SymTab.RelCheck(t, t2, op) THEN
+  912               isR := SymTab.ClassOf(t) = SymTab.ClReal;
+  913               t := SymTab.BoolType()
+  914             ELSE SemError(213); t := SymTab.InvalidType;
+  915               isR := FALSE END;
+  916             IF t # SymTab.InvalidType THEN
+  917               QbeGen.Cmp(op, q, q2, qt, isR);
+  918               QbeGen.CopyOp(qt, q)
+  919             ELSE QbeGen.CopyOp("0", q)
+  920             END; .) ] .
+  921    Rel<VAR op: INTEGER>
+  922      = "="                               (. op := SymTab.OpEq; .)
+  923      | "#"                               (. op := SymTab.OpNeq1; .)
+  924      | "<"                               (. op := SymTab.OpLt; .)
+  925      | "<="                              (. op := SymTab.OpLe; .)
+  926      | ">"                               (. op := SymTab.OpGt; .)
+  927      | ">="                              (. op := SymTab.OpGe; .) .
+  928    SimExpr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
+  929                                          (. VAR t2, res2: SymTab.TypeIndex;
+  930                                               op: INTEGER;
+  931                                               q2, qt: QbeGen.QVal;
+  932                                               neg, isR: BOOLEAN; .)
+  933      =                                   (. neg := FALSE; .)
+  934        [ "+" | "-"                       (. neg := TRUE; .) ]
+  935        Term<t, q>                        (. IF neg THEN
+  936                                             IF QbeGen.IsImm(q) THEN
+  937                                               QbeGen.NegFold(q, q)
+  938                                             ELSE QbeGen.NewTemp(qt);
+  939                                               QbeGen.NegQ(q, qt,
+  940                                                 SymTab.ClassOf(t)
+  941                                                 = SymTab.ClReal);
+  942                                               QbeGen.CopyOp(qt, q)
+  943                                             END
+  944                                           END; .)
+  945        { AddOp<op> Term<t2, q2>
+  946          (. IF op = SymTab.OpOr THEN
+  947               IF SymTab.BoolCheck(t) & SymTab.BoolCheck(t2) THEN
+  948                 t := SymTab.BoolType()
+  949               ELSE SemError(212); t := SymTab.InvalidType END;
+  950               IF t # SymTab.InvalidType THEN
+  951                 QbeGen.NewTemp(qt);
+  952                 QbeGen.Op3("or", qt, q, q2, FALSE);
+  953                 QbeGen.CopyOp(qt, q)
+  954               ELSE QbeGen.CopyOp("0", q)
+  955               END
+  956             ELSE
+  957               IF SymTab.ArithCheck(t, t2, FALSE, res2) THEN
+  958                 t := res2
+  959               ELSE SemError(211); t := SymTab.InvalidType END;
+  960               isR := SymTab.ClassOf(t) = SymTab.ClReal;
+  961               IF t # SymTab.InvalidType THEN
+  962                 QbeGen.NewTemp(qt);
+  963                 IF op = SymTab.OpAdd THEN
+  964                   QbeGen.Op3("add", qt, q, q2, isR)
+  965                 ELSE
+  966                   QbeGen.Op3("sub", qt, q, q2, isR)
+  967                 END;
+  968                 QbeGen.CopyOp(qt, q)
+  969               ELSE QbeGen.CopyOp("0", q)
+  970               END
+  971             END; .) } .
+  972    AddOp<VAR op: INTEGER>
+  973      = "+"                               (. op := SymTab.OpAdd; .)
+  974      | "-"                               (. op := SymTab.OpSub; .)
+  975      | "OR"                              (. op := SymTab.OpOr; .) .
+  976    Term<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
+  977                                          (. VAR t2, res2: SymTab.TypeIndex;
+  978                                               op: INTEGER;
+  979                                               q2, qt: QbeGen.QVal;
+  980                                               isR: BOOLEAN; .)
+  981      = Fact<t, q> { MulOp<op> Fact<t2, q2>
+  982        (. IF op = SymTab.OpAnd THEN
+  983             IF SymTab.BoolCheck(t) & SymTab.BoolCheck(t2) THEN
+  984               t := SymTab.BoolType()
+  985             ELSE SemError(212); t := SymTab.InvalidType END;
+  986             IF t # SymTab.InvalidType THEN
+  987               QbeGen.NewTemp(qt);
+  988               QbeGen.Op3("and", qt, q, q2, FALSE);
+  989               QbeGen.CopyOp(qt, q)
+  990             ELSE QbeGen.CopyOp("0", q)
+  991             END
+  992           ELSE
+  993             IF SymTab.ArithCheck(t, t2,
+  994                  (op = SymTab.OpDiv) OR (op = SymTab.OpMod),
+  995                  res2) THEN t := res2
+  996             ELSE SemError(211); t := SymTab.InvalidType END;
+  997             isR := SymTab.ClassOf(t) = SymTab.ClReal;
+  998             IF t # SymTab.InvalidType THEN
+  999               QbeGen.NewTemp(qt);
+ 1000               IF op = SymTab.OpTimes THEN
+ 1001                 QbeGen.Op3("mul", qt, q, q2, isR)
+ 1002               ELSIF (op = SymTab.OpDiv)
+ 1003                  OR (op = SymTab.OpSlash) THEN
+ 1004                 QbeGen.Op3("div", qt, q, q2, isR)
+ 1005               ELSE
+ 1006                 QbeGen.Op3("rem", qt, q, q2, isR)
+ 1007               END;
+ 1008               QbeGen.CopyOp(qt, q)
+ 1009             ELSE QbeGen.CopyOp("0", q)
+ 1010             END
+ 1011           END; .) } .
+ 1012    MulOp<VAR op: INTEGER>
+ 1013      = "*"                               (. op := SymTab.OpTimes; .)
+ 1014      | "/"                               (. op := SymTab.OpSlash; .)
+ 1015      | "DIV"                             (. op := SymTab.OpDiv; .)
+ 1016      | "MOD"                             (. op := SymTab.OpMod; .)
+ 1017      | "AND"                             (. op := SymTab.OpAnd; .) .
+ 1018    Fact<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
+ 1019                                          (. VAR s: ARRAY [0 .. 255] OF CHAR;
+ 1020                                               et, dt, t2: SymTab.TypeIndex;
+ 1021                                               dk: INTEGER;
+ 1022                                               qd, q2: QbeGen.QVal;
+ 1023                                               qn: SymTab.Name;
+ 1024                                               sfx: BOOLEAN; .)
+ 1025      = integer                           (. LexString(s);
+ 1026                                             QbeGen.NormInt(s, q);
+ 1027                                             t := SymTab.IntType(); .)
+ 1028      | real                              (. LexString(s);
+ 1029                                             QbeGen.NormReal(s, q);
+ 1030                                             t := SymTab.RealType(); .)
+ 1031      | string                            (. LexString(s);
+ 1032                                             IF SymTab.StrLen(s) = 3 THEN
+ 1033                                               t := SymTab.CharType();
+ 1034                                               QbeGen.IntStr(
+ 1035                                                 QbeGen.CharVal(s), q)
+ 1036                                             ELSE t := SymTab.NewStr();
+ 1037                                               QbeGen.DeclStr(s, q)
+ 1038                                             END; .)
+ 1039      | Design<dt, dk, qd, qn, sfx>       (. t := dt;
+ 1040                                             IF sfx THEN
+ 1041                                               IF dt #
+ 1042                                                  SymTab.InvalidType THEN
+ 1043                                                 QbeGen.ElemLoad(qd, dt, q)
+ 1044                                               ELSE QbeGen.CopyOp("0", q)
+ 1045                                               END
+ 1046                                             ELSE QbeGen.CopyOp(qd, q)
+ 1047                                             END; .)
+ 1048      | "(" Expr<et, q> ")"               (. t := et; .)
+ 1049      | "NOT" Fact<t2, q2>                (. IF SymTab.BoolCheck(t2) THEN
+ 1050                                               t := SymTab.BoolType()
+ 1051                                             ELSE SemError(212);
+ 1052                                               t := SymTab.InvalidType END;
+ 1053                                             IF t # SymTab.InvalidType THEN
+ 1054                                               QbeGen.NotQ(q2, q)
+ 1055                                             ELSE QbeGen.CopyOp("0", q)
+ 1056                                             END; .) .
+ 1057    GetIdent<VAR n: SymTab.Name>
+ 1058      = ident                             (. LexName(n); .) .
+ 1059  
+ 1060  END M2.
 
     0 errors
 
 
 Statistics:
 
-  nr of terminals:        64 (limit   400)
-  nr of non-terminals:    57 (limit   210)
-  nr of pragmas:           0 (limit   436)
-  nr of symbolnodes:     121 (limit   500)
-  nr of graphnodes:      521 (limit  1500)
+  nr of terminals:        65 (limit   400)
+  nr of non-terminals:    59 (limit   210)
+  nr of pragmas:           0 (limit   435)
+  nr of symbolnodes:     124 (limit   500)
+  nr of graphnodes:      552 (limit  1500)
   nr of conditionsets:     6 (limit   100)
   nr of charactersets:    11 (limit   250)
 

+ 46 - 0
compiler/src/QbeGen.def

@@ -78,6 +78,8 @@ PROCEDURE ConvIR (a: ARRAY OF CHAR; VAR q: QVal);
 (* q := fresh double holding "swtof a" (INTEGER -> REAL widening). *)
 
 PROCEDURE CopyOp (s: ARRAY OF CHAR; VAR d: ARRAY OF CHAR);
+PROCEDURE AddrOf (name: ARRAY OF CHAR; VAR q: QVal);
+(* q := "$name" descriptor-address operand. *)
 PROCEDURE IntStr (v: INTEGER; VAR s: QVal);
 PROCEDURE NormInt (s: ARRAY OF CHAR; VAR d: QVal);
 (* "0xFF" -> "255"; plain decimals copied through. *)
@@ -106,4 +108,48 @@ PROCEDURE IsImm (s: ARRAY OF CHAR): BOOLEAN;
 PROCEDURE Remark (s: ARRAY OF CHAR);
 (* Emits a "# s" comment line. *)
 
+(* ---------------- arrays (step 3.1: length-prefixed layout) ---------------- *)
+(* Every array value: LONGCARD count + packed elements. Indexes and
+   counts are LONGCARD (l) inside emitted code; immediates pass
+   through, w-temps widen via extsw. *)
+
+PROCEDURE DeclArr (name: ARRAY OF CHAR; t: INTEGER);
+(* Recursive data emission for fixed arrays (`data $name = { l n,
+   ... }`, nested levels as `$name_i`). CHAR→b, int-family/bool/
+   enum→w, REAL→d, nested/pointer→l, all zero. *)
+
+PROCEDURE DeclStr (text: ARRAY OF CHAR; VAR q: QVal);
+(* Emits `data $strN = { l len, b ... }` for a quoted literal and
+   returns its address operand. Deterministic counter. *)
+
+PROCEDURE WidenIndex (idx: ARRAY OF CHAR; VAR q: QVal);
+(* Immediates copy through; w-temps widen via extsw to l. *)
+
+PROCEDURE OpenHi (base: ARRAY OF CHAR; VAR q: QVal);
+(* q := loadl(base) - 1 in l (open-array upper bound). *)
+
+PROCEDURE CheckRange (idx, lo, hi: ARRAY OF CHAR);
+(* Trap unless lo <= idx <= hi (l-domain operands). *)
+
+PROCEDURE ElemAddr (base, idx, lo: ARRAY OF CHAR; elemT: INTEGER;
+                    VAR q: QVal);
+(* q := base + 8 + (idx - lo) * elemSizeOf(elemT's array) in l.
+   elemT is the array descriptor (open or fixed). *)
+
+PROCEDURE ElemLoad (addr: ARRAY OF CHAR; t: INTEGER; VAR q: QVal);
+PROCEDURE ElemStore (addr, v: ARRAY OF CHAR; t: INTEGER);
+(* Element access by element-type index (CHAR→loadub … nested→loadl). *)
+
+PROCEDURE Op3L (mn: ARRAY OF CHAR; res, l, r: ARRAY OF CHAR);
+(* "=l mn l, r" (address/count arithmetic). *)
+
+PROCEDURE CopyArray (dst: ARRAY OF CHAR; src: ARRAY OF CHAR;
+                      t: INTEGER);
+(* Whole-array copy with runtime count check + blit, recursive for
+   nested levels via a runtime loop. Counts mismatch traps. *)
+
+PROCEDURE Trap;
+(* call $abort(); hlt. Interim trap until runtime/syslib/Trap;
+   sets the dead flag (terminator). *)
+
 END QbeGen.

+ 308 - 2
compiler/src/QbeGen.mod

@@ -17,6 +17,9 @@ VAR
   nR     : CARDINAL;  (* non-zero REAL consts flushed at BeginBody *)
   rNames : ARRAY [0 .. 31] OF QVal;
   rVals  : ARRAY [0 .. 31] OF QVal;
+  nStr   : CARDINAL;  (* string-literal data counter *)
+  strNams : ARRAY [0 .. 255] OF QVal;
+  strTexts : ARRAY [0 .. 255] OF ARRAY [0 .. 255] OF CHAR;
 
 (* ---------------- small string utilities ---------------- *)
 
@@ -86,6 +89,12 @@ PROCEDURE CopyOp (s: ARRAY OF CHAR; VAR d: ARRAY OF CHAR);
     Cpy(d, s)
   END CopyOp;
 
+PROCEDURE AddrOf (name: ARRAY OF CHAR; VAR q: QVal);
+  BEGIN
+    Cpy(q, "$");
+    App(q, name)
+  END AddrOf;
+
 PROCEDURE IntStr (v: INTEGER; VAR s: QVal);
   VAR neg : BOOLEAN;
     mag : CARDINAL;
@@ -233,7 +242,7 @@ PROCEDURE OpenModule (name: ARRAY OF CHAR);
     opened := FileIO.Okay;
     inBody := FALSE;
     dead := FALSE;
-    nTemp := 0; nLab := 0; loopTop := 0; nR := 0;
+    nTemp := 0; nLab := 0; loopTop := 0; nR := 0; nStr := 0;
     WL("# QBE IR generated by the V3 step-1 backend");
     WL("")
   END OpenModule;
@@ -251,6 +260,7 @@ PROCEDURE DataLine (name: ARRAY OF CHAR; isReal: BOOLEAN;
 PROCEDURE DeclVar (name: ARRAY OF CHAR; t: INTEGER);
   BEGIN
     IF SymTab.ClassOf(t) = SymTab.ClReal THEN DataLine(name, TRUE, "0")
+    ELSIF SymTab.ClassOf(t) = SymTab.ClArray THEN DeclArr(name, t)
     ELSE DataLine(name, FALSE, "0")
     END
   END DeclVar;
@@ -338,6 +348,7 @@ PROCEDURE EndModule;
       WL("  ret 0")
     END;
     WL("}");
+    FlushStrings;
     FileIO.Close(out);
     opened := FALSE
   END EndModule;
@@ -467,6 +478,301 @@ PROCEDURE Cmp (op: INTEGER; l, r: ARRAY OF CHAR; VAR q: QVal;
     Op3(mn, q, l, r, FALSE)
   END Cmp;
 
+(* ---------------- arrays: length-prefixed layout ---------------- *)
+(* Indexes and counts are LONGCARD (l) in emitted code; immediates
+   pass through, w-temps widen via extsw. Traps call $abort + hlt
+   (interim; runtime/syslib/Trap replaces them later). *)
+
+PROCEDURE ElemCls (t: SymTab.TypeIndex): INTEGER;
+  BEGIN
+    RETURN SymTab.ClassOf(SymTab.ArrayElem(t))
+  END ElemCls;
+
+PROCEDURE ElemSize (t: SymTab.TypeIndex): CARDINAL;
+(* Storage size of t's elements: CHAR 1, REAL 8, nested/pointer 8,
+   else 4. t is an array (or string) descriptor. *)
+  VAR cls: INTEGER;
+  BEGIN
+    cls := SymTab.ClassOf(t);
+    IF cls = SymTab.ClStr THEN RETURN 1 END;
+    cls := ElemCls(t);
+    IF cls = SymTab.ClChar THEN RETURN 1
+    ELSIF cls = SymTab.ClReal THEN RETURN 8
+    ELSIF (cls = SymTab.ClArray) OR (cls = SymTab.ClPtr) THEN RETURN 8
+    ELSE RETURN 4
+    END
+  END ElemSize;
+
+PROCEDURE Op3L (mn: ARRAY OF CHAR; res, l, r: ARRAY OF CHAR);
+  BEGIN
+    Revive;
+    W("  "); W(res);
+    W(" =l ");
+    W(mn); W(" "); W(l); W(", "); WL(r)
+  END Op3L;
+
+PROCEDURE Trap;
+  BEGIN
+    Revive;
+    WL("  call $abort()");
+    WL("  hlt");
+    dead := TRUE
+  END Trap;
+
+PROCEDURE WidenIndex (idx: ARRAY OF CHAR; VAR q: QVal);
+  BEGIN
+    IF IsImm(idx) THEN Cpy(q, idx)
+    ELSE
+      NewTemp(q);
+      Revive;
+      W("  "); W(q); W(" =l extsw "); WL(idx)
+    END
+  END WidenIndex;
+
+PROCEDURE OpenHi (base: ARRAY OF CHAR; VAR q: QVal);
+  VAR c: QVal;
+  BEGIN
+    NewTemp(c);
+    Revive;
+    W("  "); W(c); W(" =l loadl "); WL(base);
+    NewTemp(q);
+    Op3L("sub", q, c, "1")
+  END OpenHi;
+
+PROCEDURE CheckRange (idx, lo, hi: ARRAY OF CHAR);
+(* l-domain operands (widened index, immediates, or open hi temp);
+   comparisons are long (result w). *)
+  VAR c1, c2, c: QVal;
+    lok, lbad: QVal;
+  BEGIN
+    NewTemp(c1);
+    Op3("csgel", c1, idx, lo, FALSE);
+    NewTemp(c2);
+    Op3("cslel", c2, idx, hi, FALSE);
+    NewTemp(c);
+    Op3("and", c, c1, c2, FALSE);
+    NewLabel(lok); NewLabel(lbad);
+    Jnz(c, lok, lbad);
+    EmitLabel(lbad);
+    Trap;
+    EmitLabel(lok)
+  END CheckRange;
+
+PROCEDURE ElemAddr (base, idx, lo: ARRAY OF CHAR; elemT: INTEGER;
+                    VAR q: QVal);
+(* q := base + 8 + (idx - lo) * elemSize in l, fresh temp.
+   elemT is the array descriptor (open or fixed); all operands
+   l-domain (immediates pass, temps pre-widened). *)
+  VAR t1, t2, tb, sb: QVal;
+  BEGIN
+    NewTemp(t1);
+    Op3L("sub", t1, idx, lo);
+    IntStr(VAL(INTEGER, ElemSize(elemT)), sb);
+    NewTemp(t2);
+    Op3L("mul", t2, t1, sb);
+    NewTemp(tb);
+    Op3L("add", tb, base, "8");
+    NewTemp(q);
+    Op3L("add", q, tb, t2)
+  END ElemAddr;
+
+PROCEDURE ElemLoad (addr: ARRAY OF CHAR; t: INTEGER; VAR q: QVal);
+(* Loads one element of (element-)type t. Nested/pointer elements
+   are addresses (loadl). *)
+  VAR cls: INTEGER;
+  BEGIN
+    cls := SymTab.ClassOf(t);
+    NewTemp(q);
+    Revive;
+    W("  "); W(q);
+    IF cls = SymTab.ClReal THEN W(" =d loadd ")
+    ELSIF (cls = SymTab.ClArray) OR (cls = SymTab.ClPtr) THEN
+      W(" =l loadl ")
+    ELSIF cls = SymTab.ClChar THEN W(" =w loadub ")
+    ELSE W(" =w loadw ")
+    END;
+    WL(addr)
+  END ElemLoad;
+
+PROCEDURE ElemStore (addr, v: ARRAY OF CHAR; t: INTEGER);
+  VAR cls: INTEGER;
+  BEGIN
+    cls := SymTab.ClassOf(t);
+    Revive;
+    IF cls = SymTab.ClReal THEN W("  stored ")
+    ELSIF (cls = SymTab.ClArray) OR (cls = SymTab.ClPtr) THEN
+      W("  storel ")
+    ELSIF cls = SymTab.ClChar THEN W("  storeb ")
+    ELSE W("  storew ")
+    END;
+    W(v); W(", "); WL(addr)
+  END ElemStore;
+
+PROCEDURE CopyArray (dst: ARRAY OF CHAR; src: ARRAY OF CHAR;
+                      t: INTEGER);
+(* Whole-array copy with runtime count check + blit. Nested levels
+   recurse through a runtime loop; counts mismatch traps. dst/src
+   are descriptor address operands; t is the destination type. *)
+  VAR dc, sc, ok: QVal;
+    lok, lbad: QVal;
+    ecls: INTEGER;
+    n, da, sa: QVal;
+    sb: QVal;
+    qr: QVal;
+    i, c, de, se, di, si, dd0, doff, sd0, soff: QVal;
+    ltop, lbody, lend: QVal;
+  BEGIN
+    NewTemp(dc);
+    Revive;
+    W("  "); W(dc); W(" =l loadl "); WL(dst);
+    NewTemp(sc);
+    W("  "); W(sc); W(" =l loadl "); WL(src);
+    NewTemp(ok);
+    Op3("ceql", ok, dc, sc, FALSE);
+    NewLabel(lok); NewLabel(lbad);
+    Jnz(ok, lok, lbad);
+    EmitLabel(lbad);
+    Trap;
+    EmitLabel(lok);
+    ecls := ElemCls(t);
+    IF (ecls = SymTab.ClArray) THEN
+      NewTemp(i);
+      W("  "); W(i); W(" =l copy 0"); WL("");
+      NewLabel(ltop); NewLabel(lbody); NewLabel(lend);
+      EmitLabel(ltop);
+      NewTemp(c);
+      Op3("csltl", c, i, dc, FALSE);
+      Jnz(c, lbody, lend);
+      EmitLabel(lbody);
+      NewTemp(dd0); Op3L("add", dd0, dst, "8");
+      NewTemp(doff); Op3L("mul", doff, i, "8");
+      NewTemp(de); Op3L("add", de, dd0, doff);
+      NewTemp(sd0); Op3L("add", sd0, src, "8");
+      NewTemp(soff); Op3L("mul", soff, i, "8");
+      NewTemp(se); Op3L("add", se, sd0, soff);
+      NewTemp(di);
+      W("  "); W(di); W(" =l loadl "); WL(de);
+      NewTemp(si);
+      W("  "); W(si); W(" =l loadl "); WL(se);
+      CopyArray(di, si, SymTab.ArrayElem(t));
+      Op3L("add", i, i, "1");
+      Jmp(ltop);
+      EmitLabel(lend)
+    ELSE
+      IntStr(VAL(INTEGER, ElemSize(t)), sb);
+      NewTemp(n);
+      Op3L("mul", n, dc, sb);
+      NewTemp(da);
+      Op3L("add", da, dst, "8");
+      NewTemp(sa);
+      Op3L("add", sa, src, "8");
+      NewTemp(qr);
+      W("  "); W(qr); W(" =l call $memcpy(l ");
+      W(da); W(", l "); W(sa); W(", l "); W(n); WL(")")
+    END
+  END CopyArray;
+
+PROCEDURE DeclStr (text: ARRAY OF CHAR; VAR q: QVal);
+(* Records a quoted literal for top-level emission at EndModule and
+   returns its address operand ($strN). Data definitions may only
+   appear outside functions, but literals occur mid-body. *)
+  VAR nm: QVal;
+  BEGIN
+    Cpy(nm, "str");
+    AppNum(nm, nStr);
+    IF nStr <= HIGH(strNams) THEN
+      Cpy(strNams[nStr], nm);
+      Cpy(strTexts[nStr], text);
+      INC(nStr)
+    END;
+    Cpy(q, "$");
+    App(q, nm)
+  END DeclStr;
+
+PROCEDURE FlushStrings;
+(* Emits all recorded string literals as top-level data. *)
+  VAR k, i, L: CARDINAL;
+    bv: QVal;
+  BEGIN
+    IF ~opened THEN RETURN END;
+    k := 0;
+    WHILE k < nStr DO
+      L := Len(strTexts[k]);
+      IF L >= 2 THEN
+        W("data $"); W(strNams[k]);
+        W(" = { l ");
+        IntStr(VAL(INTEGER, L - 2), bv);
+        W(bv);
+        i := 1;
+        WHILE i < L - 1 DO
+          W(", b ");
+          IntStr(ORD(strTexts[k][i]), bv);
+          W(bv);
+          INC(i)
+        END;
+        WL(" }")
+      END;
+      INC(k)
+    END
+  END FlushStrings;
+
+PROCEDURE ArrData (name: ARRAY OF CHAR; t: SymTab.TypeIndex);
+(* Recursive data emission; nested levels as $name_i. *)
+  VAR n, i: CARDINAL;
+    elem: SymTab.TypeIndex;
+    ecls: INTEGER;
+    cls: INTEGER;
+    bv: QVal;
+    sub: QVal;
+  BEGIN
+    IF ~opened THEN RETURN END;
+    n := SymTab.ArrayLen(t);
+    elem := SymTab.ArrayElem(t);
+    ecls := SymTab.ClassOf(elem);
+    W("data $"); W(name);
+    W(" = { l ");
+    IntStr(VAL(INTEGER, n), bv);
+    W(bv);
+    IF ecls = SymTab.ClArray THEN
+      i := 0;
+      WHILE i < n DO
+        W(", l $"); W(name); W("_");
+        IntStr(VAL(INTEGER, i), bv);
+        W(bv);
+        INC(i)
+      END;
+      WL(" }");
+      i := 0;
+      WHILE i < n DO
+        Cpy(sub, name); App(sub, "_");
+        IntStr(VAL(INTEGER, i), bv);
+        App(sub, bv);
+        ArrData(sub, elem);
+        INC(i)
+      END
+    ELSE
+      IF ecls = SymTab.ClChar THEN Cpy(bv, "b")
+      ELSIF ecls = SymTab.ClReal THEN Cpy(bv, "d")
+      ELSIF (ecls = SymTab.ClPtr) THEN Cpy(bv, "l")
+      ELSE Cpy(bv, "w")
+      END;
+      i := 0;
+      WHILE i < n DO
+        W(", "); W(bv); W(" 0");
+        INC(i)
+      END;
+      WL(" }")
+    END
+  END ArrData;
+
+PROCEDURE DeclArr (name: ARRAY OF CHAR; t: INTEGER);
+  BEGIN
+    IF SymTab.IsOpenArray(t) OR (SymTab.ArrayDepth(t) = 0) THEN
+      DataLine(name, FALSE, "0"); RETURN
+    END;
+    ArrData(name, t)
+  END DeclArr;
+
 PROCEDURE PushLoop (exit: ARRAY OF CHAR);
   BEGIN
     IF loopTop <= HIGH(loopSt) THEN
@@ -496,5 +802,5 @@ BEGIN
   opened := FALSE;
   inBody := FALSE;
   dead := FALSE;
-  nTemp := 0; nLab := 0; loopTop := 0; nR := 0
+  nTemp := 0; nLab := 0; loopTop := 0; nR := 0; nStr := 0
 END QbeGen.

+ 24 - 1
compiler/src/SymTab.def

@@ -54,6 +54,8 @@ CONST
 TYPE
   Name = ARRAY [0 .. 63] OF CHAR;
   TypeIndex = INTEGER;
+  BoundsArr = ARRAY [0 .. 7] OF INTEGER;
+(* Per-dimension bounds for array index lists (grammar locals). *)
 
 (* ---------------- symbols and scopes ---------------- *)
 
@@ -180,7 +182,19 @@ 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 NewArrayB (elem: TypeIndex; lo, hi: INTEGER): TypeIndex;
+(* Bounded array (one dimension; nest for multi-dim). InvalidType
+   when the table is full. *)
+PROCEDURE NewOpenArray (elem: TypeIndex): TypeIndex;
+(* Open ARRAY OF formal: 0-based, runtime count in the header.
+   Only legal for formal parameters (grammar enforces). *)
+
+PROCEDURE BoundBegin;
+PROCEDURE BoundAdd (lo, hi: INTEGER): BOOLEAN;
+(* Accumulates one index bound pair for the enclosing ArrayType
+   (max 8 dimensions, FALSE past that). *)
+PROCEDURE NestArray (elem: TypeIndex): TypeIndex;
+(* Nests the accumulated bounds inside-out around elem. *)
 PROCEDURE NewRecord (): TypeIndex;
 PROCEDURE NewSet (base: TypeIndex): TypeIndex;
 PROCEDURE NewPtr (base: TypeIndex): TypeIndex;
@@ -211,6 +225,15 @@ PROCEDURE SameType (a, b: TypeIndex): BOOLEAN;
 PROCEDURE FieldExists (rec: TypeIndex; name: ARRAY OF CHAR): BOOLEAN;
 PROCEDURE FieldType (rec: TypeIndex; name: ARRAY OF CHAR): TypeIndex;
 PROCEDURE ArrayElem (t: TypeIndex): TypeIndex;
+PROCEDURE IsOpenArray (t: TypeIndex): BOOLEAN;
+PROCEDURE ArrayLo (t: TypeIndex): INTEGER;
+PROCEDURE ArrayHi (t: TypeIndex): INTEGER;
+(* Static bounds (open arrays: lo 0, hi undefined — backend reads
+   the header instead). InvalidType maps to 0. *)
+PROCEDURE ArrayLen (t: TypeIndex): CARDINAL;
+(* Static element count, 0 for open/invalid. *)
+PROCEDURE ArrayDepth (t: TypeIndex): CARDINAL;
+(* Nesting depth (open/fixed levels); 0 for non-arrays. *)
 PROCEDURE PtrBase (t: TypeIndex): TypeIndex;
 
 (* ---------------- predicates used by grammar checks ---------------- *)

+ 106 - 10
compiler/src/SymTab.mod

@@ -13,7 +13,7 @@ CONST
   FNone = 0; FAlias = 1; FSub = 2; FEnum = 3; FArray = 4;
   FRecord = 5; FSet = 6; FPtr = 7; FStr = 8;
   FInt = 9; FReal = 10; FChar = 11; FBool = 12;
-  FClass = 13;
+  FClass = 13; FOpenArr = 14;
 
 TYPE
   SymPtr = POINTER TO SymNode;
@@ -61,6 +61,11 @@ VAR
   thi : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
   tparent : ARRAY [0 .. MaxTypes - 1] OF TypeIndex;
   tscope : ARRAY [0 .. MaxTypes - 1] OF ScopePtr;
+  alo : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
+  ahi : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
+  blo : ARRAY [0 .. 7] OF INTEGER;
+  bhi : ARRAY [0 .. 7] OF INTEGER;
+  nBounds : CARDINAL;
   nTypes : CARDINAL;
   curProc : SymPtr;   (* heading being declared *)
   curPTail : SymPtr;  (* positional param chain tail *)
@@ -367,10 +372,41 @@ PROCEDURE NewEnum (): TypeIndex;
     RETURN NewDesc(FEnum, InvalidType)
   END NewEnum;
 
-PROCEDURE NewArray (elem: TypeIndex): TypeIndex;
+PROCEDURE NewArrayB (elem: TypeIndex; lo, hi: INTEGER): TypeIndex;
+  VAR t: TypeIndex;
+  BEGIN
+    t := NewDesc(FArray, elem);
+    IF t # InvalidType THEN alo[t] := lo; ahi[t] := hi END;
+    RETURN t
+  END NewArrayB;
+
+PROCEDURE NewOpenArray (elem: TypeIndex): TypeIndex;
+  BEGIN
+    RETURN NewDesc(FOpenArr, elem)
+  END NewOpenArray;
+
+PROCEDURE BoundBegin;
   BEGIN
-    RETURN NewDesc(FArray, elem)
-  END NewArray;
+    nBounds := 0
+  END BoundBegin;
+
+PROCEDURE BoundAdd (lo, hi: INTEGER): BOOLEAN;
+  BEGIN
+    IF nBounds > HIGH(blo) THEN RETURN FALSE END;
+    blo[nBounds] := lo; bhi[nBounds] := hi; INC(nBounds);
+    RETURN TRUE
+  END BoundAdd;
+
+PROCEDURE NestArray (elem: TypeIndex): TypeIndex;
+  VAR i: CARDINAL;
+  BEGIN
+    i := nBounds;
+    WHILE i > 0 DO
+      DEC(i);
+      elem := NewArrayB(elem, blo[i], bhi[i])
+    END;
+    RETURN elem
+  END NestArray;
 
 PROCEDURE NewRecord (): TypeIndex;
   BEGIN
@@ -460,6 +496,7 @@ PROCEDURE ClassOf (t: TypeIndex): INTEGER;
     | FBool : RETURN ClBool
     | FEnum : RETURN ClEnum
     | FArray : RETURN ClArray
+    | FOpenArr : RETURN ClArray
     | FRecord : RETURN ClRecord
     | FSet : RETURN ClSet
     | FPtr : RETURN ClPtr
@@ -499,12 +536,60 @@ PROCEDURE ArrayElem (t: TypeIndex): TypeIndex;
   VAR r: TypeIndex;
   BEGIN
     r := Resolve(t);
-    IF (r = InvalidType) OR (tform[r] # FArray) THEN
+    IF (r = InvalidType) OR ((tform[r] # FArray)
+       & (tform[r] # FOpenArr)) THEN
       RETURN InvalidType
     END;
     RETURN tref[r]
   END ArrayElem;
 
+PROCEDURE IsOpenArray (t: TypeIndex): BOOLEAN;
+  VAR r: TypeIndex;
+  BEGIN
+    r := Resolve(t);
+    IF r = InvalidType THEN RETURN FALSE END;
+    RETURN tform[r] = FOpenArr
+  END IsOpenArray;
+
+PROCEDURE ArrayLo (t: TypeIndex): INTEGER;
+  VAR r: TypeIndex;
+  BEGIN
+    r := Resolve(t);
+    IF (r = InvalidType) OR (tform[r] # FArray) THEN RETURN 0 END;
+    RETURN alo[r]
+  END ArrayLo;
+
+PROCEDURE ArrayHi (t: TypeIndex): INTEGER;
+  VAR r: TypeIndex;
+  BEGIN
+    r := Resolve(t);
+    IF (r = InvalidType) OR (tform[r] # FArray) THEN RETURN 0 END;
+    RETURN ahi[r]
+  END ArrayHi;
+
+PROCEDURE ArrayLen (t: TypeIndex): CARDINAL;
+  VAR r: TypeIndex;
+    lo, hi: INTEGER;
+  BEGIN
+    r := Resolve(t);
+    IF (r = InvalidType) OR (tform[r] # FArray) THEN RETURN 0 END;
+    lo := alo[r]; hi := ahi[r];
+    IF hi < lo THEN RETURN 0 END;
+    RETURN VAL(CARDINAL, hi - lo + 1)
+  END ArrayLen;
+
+PROCEDURE ArrayDepth (t: TypeIndex): CARDINAL;
+  VAR r: TypeIndex;
+    d: CARDINAL;
+  BEGIN
+    d := 0; r := Resolve(t);
+    WHILE (r # InvalidType) & ((tform[r] = FArray)
+           OR (tform[r] = FOpenArr)) DO
+      INC(d); r := Resolve(tref[r])
+    END;
+    RETURN d
+  END ArrayDepth;
+
 PROCEDURE PtrBase (t: TypeIndex): TypeIndex;
   VAR r: TypeIndex;
   BEGIN
@@ -707,15 +792,19 @@ PROCEDURE Assignable (src, dst: TypeIndex): BOOLEAN;
     IF (tform[rs] = FSet) & (tform[rd] = FSet) THEN
       RETURN SetBasesOk(tref[rs], tref[rd])
     END;
+    IF (ClassOf(src) = ClArray) & (ClassOf(dst) = ClArray) THEN
+      RETURN SameType(src, dst)
+    END;
+    IF (ClassOf(src) = ClStr) & (ClassOf(dst) = ClArray) THEN
+      RETURN (ArrayDepth(dst) = 1)
+             & (ClassOf(ArrayElem(dst)) = ClChar)
+    END;
     IF (ClassOf(src) = ClInt) & (ClassOf(dst) = ClInt) THEN
       RETURN TRUE
     END;
     IF (ClassOf(src) = ClInt) & (ClassOf(dst) = ClReal) THEN
       RETURN TRUE
     END;
-    IF (ClassOf(src) = ClStr) & (ClassOf(dst) = ClArray) THEN
-      RETURN TRUE
-    END;
     RETURN FALSE
   END Assignable;
 
@@ -752,7 +841,14 @@ PROCEDURE EqCheck (l, r: TypeIndex): BOOLEAN;
   VAR rl, rr: TypeIndex;
   BEGIN
     IF (l = InvalidType) OR (r = InvalidType) THEN RETURN TRUE END;
-    IF SameType(l, r) THEN RETURN TRUE END;
+    IF SameType(l, r) THEN
+      (* Whole-array and string equality are not built-in (213);
+         compare element-wise or via future memcmp instead. *)
+      IF (ClassOf(l) = ClArray) OR (ClassOf(l) = ClStr) THEN
+        RETURN FALSE
+      END;
+      RETURN TRUE
+    END;
     IF IsIntFamily(l) & IsIntFamily(r) THEN RETURN TRUE END;
     IF (ClassOf(l) = ClReal) & (ClassOf(r) = ClReal) THEN
       RETURN TRUE
@@ -843,7 +939,7 @@ PROCEDURE Init;
     scopeList := NIL; scopeTail := NIL;
     fields := NIL; nFields := 0;
     nPend := 0; nPendF := 0;
-    nTypes := 0; nProc := 0;
+    nTypes := 0; nProc := 0; nBounds := 0;
     curProc := NIL; curPTail := NIL;
     curScope := NewScope(NIL, 0);
     dInt := NewDesc(FInt, InvalidType);

+ 23 - 0
compiler/tests/t_array.mod

@@ -0,0 +1,23 @@
+MODULE TArray;
+// 1-D + nested arrays, whole assign (incl. nested copy), strings,
+// char compare. Exit 108.
+TYPE Vec = ARRAY [0..4] OF INTEGER;
+TYPE Mat = ARRAY [0..1, 0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+VAR v, w : Vec;
+VAR m, m2 : Mat;
+VAR s : ARRAY [0..2] OF CHAR;
+VAR i, sum : INTEGER;
+BEGIN
+  sum := 0;
+  FOR i := 0 TO 4 DO v[i] := i * 10; sum := sum + v[i] END;
+  w := v;
+  m[0, 0] := 1; m[0, 1] := 2; m[0, 2] := 3;
+  m[1, 0] := 4; m[1, 1] := 5; m[1, 2] := 6;
+  sum := sum + m[1, 2];
+  m2 := m;
+  sum := sum + m2[0, 0];
+  s := "abc";
+  IF s[0] = 'a' THEN sum := sum + 1 END;
+  ExitCode := sum
+END TArray.

+ 4 - 0
compiler/tests/t_bad_bounds.mod

@@ -0,0 +1,4 @@
+MODULE TBadBounds;
+TYPE Bad = ARRAY [5..1] OF INTEGER;
+BEGIN
+END TBadBounds.

+ 7 - 0
compiler/tests/t_bad_index.mod

@@ -0,0 +1,7 @@
+MODULE TBadIndex;
+VAR a : ARRAY [0..4] OF INTEGER;
+VAR b : BOOLEAN;
+VAR ExitCode : INTEGER;
+BEGIN
+  a[b] := 1
+END TBadIndex.

+ 4 - 0
compiler/tests/t_bad_open.mod

@@ -0,0 +1,4 @@
+MODULE TBadOpen;
+VAR x : ARRAY OF INTEGER;
+BEGIN
+END TBadOpen.

+ 6 - 0
compiler/tests/t_openformal.mod

@@ -0,0 +1,6 @@
+MODULE TOpenFormal;
+PROCEDURE P(a : ARRAY OF INTEGER);
+BEGIN
+END P;
+BEGIN
+END TOpenFormal.

+ 7 - 0
compiler/tests/t_trap_count.mod

@@ -0,0 +1,7 @@
+MODULE TTrapCount;
+VAR s : ARRAY [0..2] OF CHAR;
+VAR ExitCode : INTEGER;
+BEGIN
+  s := "abcd";
+  ExitCode := 0
+END TTrapCount.

+ 7 - 0
compiler/tests/t_trap_index.mod

@@ -0,0 +1,7 @@
+MODULE TTrapIndex;
+VAR a : ARRAY [0..4] OF INTEGER;
+VAR ExitCode : INTEGER;
+BEGIN
+  a[5] := 1;
+  ExitCode := 0
+END TTrapIndex.

+ 68 - 0
docs/summary_step3.1.md

@@ -0,0 +1,68 @@
+# V3 step 3.1 — arrays: bounded/open, strings, whole assign (done 2026-09-20)
+
+First of four phased composite substeps (arrays → records → sets →
+pointers). Suite 42/42 (16 run — incl. 2 trap-aborts — + 26 reject),
+LL(1)-clean (only the known-benign TypeBlock warning).
+
+## Type syntax (`M2.atg`)
+
+- `Type` gains `allowOpen` and `ArrayType`: `"ARRAY" "OF" Type`
+  (open formal; 230 elsewhere incl. named open aliases) and
+  `"ARRAY" "[" bounds "]" "OF" Type` (multi-index sugar nests
+  inside-out via `NestArray`). Bounds are folded int/char literals
+  (`ConstInt`, inverted → 230); bare-type indices wait for enum
+  ordinals; `VarDecl` takes full `Type<FALSE>` (anonymous arrays
+  in `VAR`); formals take `Type<TRUE>`; composite function results
+  are 230 (V2 parity).
+- Coco/R V1.53 cannot parse dotted types (`SymTab.BoundsArr`) in
+  *attribute* declarations (generates garbage), so bounds accumulate
+  SymTab-side (`BoundBegin/BoundAdd/NestArray`, max 8).
+- `Design` grows index suffixes (`[i, j]` descends nested levels;
+  217/218) plus an `sfx` out-flag; `Fact` loads indexed elements
+  (`ElemLoad`) and lowers string literals to descriptors
+  (1-char stays CHAR, `''` works); `Assign` copies whole arrays
+  (`CopyArray`) or stores elements (`ElemStore`, with `ConvIR`
+  for `r[i] := 3`); array/string `=` is 213 (strict Wirth; done in
+  `EqCheck`, so CASE labels agree).
+
+## Layout (locked rule, now real)
+
+`DeclArr` emits `data $a = { l n, … }` with CHAR→b, int/bool/enum→w,
+REAL→d, nested/pointer→l, all zero; nested levels are separate
+`$a_i` objects linked by address (probes proved `l $sym` in data,
+`copy $sym` addressing, `loadub`, `blit`-class ops). Indexes widen
+to LONGCARD (`extsw`), check `lo..hi` (static, or `0..count-1`
+for open via `OpenHi`), trap via `call $abort()` + `hlt`
+(rc=134; interim until `runtime/syslib/Trap`).
+
+## Backend lessons (all probed, not guessed)
+
+- QBE `blit` needs a *constant* length → whole copies use
+  `call $memcpy(l dst, l src, l n)` (runtime length, uniform for
+  fixed/open/nested; nested recurses through a runtime loop).
+- QBE `call` needs per-argument classes (`call $f(l a, …)`);
+  bare `$abort()` is fine.
+- Single-letter `%p` temporaries mislink — always `%tN` (already
+  the convention; now recorded).
+- String `data` must be top-level: literals buffer during the body
+  and flush at `EndModule` (a `data` line mid-function is a QBE
+  syntax error — found the hard way).
+
+## SymTab (additive)
+
+Bounded/open descriptors (`NewArrayB/OpenArray`, `alo/ahi`),
+`IsOpenArray/ArrayLo/Hi/Len/Depth`, `ConstInt` reuse;
+`Assignable` tightened (array↔array by `SameType`,
+string→1-D-CHAR-array only); `EqCheck` rejects array/string `=`.
+
+## Tests
+
+`t_array` → 108 (named + anonymous arrays, nested `m[i,j]`,
+whole + nested copies, strings, char compare); `t_bad_index`
+(218), `t_bad_bounds` (inverted → 230), `t_openformal`/`t_bad_open`
+(open rules); new `expect_trap` harness (rc=134): index breach
+and `"abcd"`-into-3 count mismatch.
+
+Deferred: `HIGH`/`LEN` (need calls, step 4), string `=`/`+`,
+string CONSTs, enum/char-subrange bounds as index *types*,
+`CHAR`-bound subtleties, open-formal *reads* (parse-only now).