Преглед изворни кода

m2comp step 4 — MC64 tree-walking backend (41/41 tests green)

Eric Streit пре 3 недеља
родитељ
комит
a781af2287
70 измењених фајлова са 2960 додато и 118 уклоњено
  1. BIN
      M2comp
  2. BIN
      OkMinimal.MC4
  3. BIN
      OkProc.MC4
  4. BIN
      RArith.MC4
  5. BIN
      RArr.MC4
  6. BIN
      RBool.MC4
  7. BIN
      RConst.MC4
  8. BIN
      RFlow.MC4
  9. BIN
      RMod.MC4
  10. BIN
      RNested.MC4
  11. BIN
      RProc.MC4
  12. BIN
      RPtr.MC4
  13. BIN
      RReal.MC4
  14. BIN
      RRec.MC4
  15. BIN
      RSet.MC4
  16. BIN
      RStr.MC4
  17. BIN
      RVarPar.MC4
  18. BIN
      Showcase.MC4
  19. 3 3
      build.sh
  20. 107 0
      docs/summary_m2comp_step4.md
  21. 65 0
      run_tests.sh
  22. 2 0
      src/AST.def
  23. 2 0
      src/AST.mod
  24. BIN
      src/AST.o
  25. 182 43
      src/M2comp.atg
  26. 4 3
      src/M2comp.err
  27. 1 1
      src/M2comp.lst
  28. 18 8
      src/M2comp.mod
  29. BIN
      src/M2comp.o
  30. 194 44
      src/M2compP.mod
  31. BIN
      src/M2compP.o
  32. 21 0
      src/MGen.def
  33. 1622 0
      src/MGen.mod
  34. BIN
      src/MGen.o
  35. 45 6
      src/SymTab.def
  36. 190 2
      src/SymTab.mod
  37. BIN
      src/SymTab.o
  38. 14 5
      src/compiler.frm
  39. 2 1
      src/modules.lst
  40. 36 0
      tests/ok_proc.LST
  41. 1 1
      tests/ok_proc.mod
  42. 11 0
      tests/r_arith.LST
  43. 5 0
      tests/r_arith.mod
  44. 19 0
      tests/r_array.LST
  45. 13 0
      tests/r_array.mod
  46. 18 0
      tests/r_bool.LST
  47. 12 0
      tests/r_bool.mod
  48. 19 0
      tests/r_const.LST
  49. 13 0
      tests/r_const.mod
  50. 26 0
      tests/r_flow.LST
  51. 20 0
      tests/r_flow.mod
  52. 21 0
      tests/r_module.LST
  53. 15 0
      tests/r_module.mod
  54. 21 0
      tests/r_nested.LST
  55. 15 0
      tests/r_nested.mod
  56. 20 0
      tests/r_proc.LST
  57. 14 0
      tests/r_proc.mod
  58. 17 0
      tests/r_ptr.LST
  59. 11 0
      tests/r_ptr.mod
  60. 19 0
      tests/r_real.LST
  61. 13 0
      tests/r_real.mod
  62. 15 0
      tests/r_record.LST
  63. 9 0
      tests/r_record.mod
  64. 19 0
      tests/r_set.LST
  65. 13 0
      tests/r_set.mod
  66. 17 0
      tests/r_string.LST
  67. 11 0
      tests/r_string.mod
  68. 25 0
      tests/r_varpar.LST
  69. 19 0
      tests/r_varpar.mod
  70. 1 1
      tests/showcase.mod


















+ 3 - 3
build.sh

@@ -22,17 +22,17 @@ echo "=== Deleting all o files ==="
 rm -f ./*.o
 
 echo "=== Compiling the needed modules ==="
-for m in FileIO SymTab AST M2compS M2compP M2comp; do
+for m in FileIO SymTab AST MGen M2compS M2compP M2comp; do
   gm2 -fiso -c "$m.mod" || exit 1
 done
 
 echo "=== Phase 1: generating the module list ==="
 gm2 -fiso -fgen-module-list=modules.lst -o /dev/null \
-    M2compS.o M2compP.o FileIO.o SymTab.o AST.o M2comp.mod || exit 1
+    M2compS.o M2compP.o FileIO.o SymTab.o AST.o MGen.o M2comp.mod || exit 1
 
 echo "=== Phase 2: compiling main module and linking using the module list ==="
 gm2 -fiso -fuse-list=modules.lst -o ../M2comp \
-    M2compS.o M2compP.o FileIO.o SymTab.o AST.o M2comp.mod || exit 1
+    M2compS.o M2compP.o FileIO.o SymTab.o AST.o MGen.o M2comp.mod || exit 1
 cd ..
 
 echo "=== M2comp built ==="

+ 107 - 0
docs/summary_m2comp_step4.md

@@ -0,0 +1,107 @@
+# m2comp step 4 — MC64 tree-walking backend (tag: `m2comp-step4`)
+
+The compiler is end-to-end: Modula-2 source → Coco/R frontend →
+SymTab checks → AST → MGen MC64 `.MC4` images runnable under
+`mcint`, with execution tests. Follows the Blaise front/back split
+(frontend builds the tree; backend walks it) rather than m2c's
+single-pass emit, reusing m2c's proven lowering recipes and the
+`mc64-spec.md` contract.
+
+## What was built
+
+- `src/MGen.def/.mod` (~1300 lines, single module): `EmitModule`
+  walks the checked AST and writes `<Module>.MC4`. Globals from
+  slot 4 (0..3 are the print-helper temps), ENTER frames with
+  ED/EC/EE calls, E0/E1 jumps with fixups, ExitCode print
+  convention (copied `EmitPrint`), full image assembly
+  (magic/descriptor/proc table/checksum).
+  - Scalars: 32-bit ops in slots, signed comparisons for the whole
+    int family (m2c parity, incl. the past-MAXINT edge), eager
+    AND/OR/NOT, `DIV`/`MOD` (truncating synthesis), int→real
+    widening, REAL (binary64) arithmetic/comparisons.
+  - Control flow: IF/ELSIF (deepest-else nesting), WHILE, REPEAT,
+    LOOP/EXIT, FOR with runtime BY incl. negative direction.
+  - Procedures: value/`VAR` params (addresses in slots), functions,
+    recursion, nested procedures with display access, module
+    procedures, init bodies called once at startup (m2c parity).
+  - Composites: slot-per-element arrays (multi-dim), records
+    (declaration-order offsets), whole assign via `copy_block`,
+    char arrays (literals materialized slot-wise), sets
+    (masks/`IN`/`=`), pointers (`^`, `NIL`), open-array formals
+    (unchecked, lo=0).
+  - CONSTs inline (folded at walk: chained, hex, bool, char, real,
+    string-text); no slots, no startup inits.
+- `src/SymTab` layout enrichment: `NewSubB/NewArrayB/WrapArrayB`
+  with folded bounds, `IndexBounds` (CHAR 0..255, BOOLEAN 0..1,
+  subranges; INTEGER unbounded → 230), `ArrayLo/Hi/Len`,
+  `TypeSlots`/`FieldOffset` (cycle-guarded), const-value table
+  with chained `ConstFold` (`IMPORT AST`, acyclic).
+- Grammar updates: bound folding (non-literal → 230, cascade-safe),
+  `aux` (symbol kind) + `tag` (module qualifier) stamps on
+  name/field nodes (scopes pop after parsing, so the backend
+  resolves through its own scope stack), `num` stamp on `nkProc`,
+  grammar-time 230s for value composite params and composite
+  function returns (m2c parity).
+- `src/compiler.frm` driver hook: after a successful parse,
+  `MGen.EmitModule(AST.GetRoot())` runs *before* the listing, so
+  backend 230s (via `M2compP.SemError`) still reach the `.LST`.
+- `run_tests.sh`: `expect_run` (ExitCode compare) and
+  `expect_run_none` (silent, terminating), both with `timeout`
+  guards and VM-rc checks so diverging programs fail fast
+  instead of hanging the suite.
+
+## Tests — 41/41
+
+4 accept + 15 reject + 6 AST dumps (steps 1–3 intact), 2 silent
+runs, 14 execution runs: arith 8, flow 69, bool 11, real 1111,
+proc 162 (fact), varpar 48, nested-display 213, array 39,
+record 15, set 12, string 111, ptr 111, module-init 83,
+const-chains 1102.
+
+## Bugs found (all fixed, all covered)
+
+- gm2 is multipass: `PROCEDURE ... FORWARD;` is rejected with a
+  cryptic "too many errors in pass 3" (CR's `-m` flag comments
+  forwards out for this reason; m2c has zero forwards). Deleted.
+- Op-code overlap ×2, fatal only to tree-walk dispatch: `OpAdd =
+  OpTimes` made `2*3` emit `ADD`; then `OpEq = OpAdd` made `=`
+  emit `ADD` (`1=1` passed by accident). Mul codes → 10–14,
+  relation codes → 20–27 (all uses symbolic).
+- `51H`/`70H` store orders differ (addr-on-top vs value-on-top);
+  m2c never emits `51H`. Ours uses `70H` everywhere.
+- Nested proc bodies emitted inline need jump-over
+  (`Jmp(endL)`/`DefLabel`, m2c parity) — else fall-through
+  executes them with the wrong frame.
+- VAR-param stores need the address *under* the value.
+- Module-qualified addresses must resolve via the export tag,
+  not the module name as a variable.
+- `CHAR` consts never folded (`Q` → 230); multi-index chains
+  emitted only the first index (wrong code, now looped).
+- Harness: `| head` masked VM hangs (`rc` was head's); silent
+  divergences now fail via `timeout` + rc checks.
+
+## Non-bugs established by bisection
+
+- `showcase.mod` and the old `ok_proc.mod` DIVERGE by construction
+  (`Work` restarts its own `FOR 1..10`; `P(i,i)` mutates its loop
+  control `1→2→-1→…`). Any correct implementation hangs; both stay
+  check-only. `ok_proc`'s body was fixed to `v := v + 1` so it
+  terminates and runs silent (constructs unchanged).
+- Negative-vs-zero `D5` real comparisons misbehave identically
+  under byte-identical m2c output — VM-domain quirk, avoided in
+  tests, not a backend bug.
+
+## Deviations from m2c (documented)
+
+Slot-per-element composites (no byte packing, no 0D/1D ops);
+`BY` may be a runtime expression; open-array bounds unchecked;
+module inits run once at startup even when nested (m2c parity);
+`NEW`/`DISPOSE` unreachable (no grammar productions).
+
+## Next
+
+Step 5 candidates: `DEFINITION`/`IMPLEMENTATION` separate
+compilation (multi-`.MC4` + `depCount` loader path), opaque
+types, procedure types, `WITH`/`CASE`, `LONGINT`/`LONGREAL`
+quads, debugger listings. `SymTab`/`AST`/`MGen` APIs are ready
+(`ByNum` queries, export tables, pnum/typ on nodes).

+ 65 - 0
run_tests.sh

@@ -23,6 +23,55 @@ expect_dump() {
     fail=$((fail+1)); echo "FAIL(ast): $1 [$2]"
   fi
 }
+MCINT="${MCINT:-../mc64/mcint}"
+MCTIMEOUT="${MCTIMEOUT:-10}"
+expect_run() {
+  # $1 = test file, $2 = expected printed ExitCode value
+  name="$1"
+  want="$2"
+  mod=$(head -n 1 "tests/$name" | cut -d' ' -f2 | cut -d';' -f1)
+  mcd="$mod.MC4"
+  rm -f "$mcd"
+  if ./M2comp "tests/$name" 2>&1 | grep -q "Incorrect source"; then
+    fail=$((fail+1)); echo "FAIL(run): $name rejected"; return
+  fi
+  if [ ! -f "$mcd" ]; then
+    fail=$((fail+1)); echo "FAIL(run): $name no $mcd"; return
+  fi
+  timeout "$MCTIMEOUT" "$MCINT" "$mcd" > /tmp/m2comp_out.txt 2>&1; rc=$?
+  if [ "$rc" -ne 0 ]; then
+    fail=$((fail+1)); echo "FAIL(run): $name vm rc=$rc (diverges?)"; return
+  fi
+  got=$(head -n 1 /tmp/m2comp_out.txt | tr -d '\r')
+  if [ "$got" = "$want" ]; then
+    pass=$((pass+1)); echo "PASS(run): $name -> $got"
+  else
+    fail=$((fail+1)); echo "FAIL(run): $name printed [$got], want [$want]"
+  fi
+}
+expect_run_none() {
+  # $1 = test file — must compile and run silent (terminating)
+  name="$1"
+  mod=$(head -n 1 "tests/$name" | cut -d' ' -f2 | cut -d';' -f1)
+  mcd="$mod.MC4"
+  rm -f "$mcd"
+  if ./M2comp "tests/$name" 2>&1 | grep -q "Incorrect source"; then
+    fail=$((fail+1)); echo "FAIL(silent): $name rejected"; return
+  fi
+  if [ ! -f "$mcd" ]; then
+    fail=$((fail+1)); echo "FAIL(silent): $name no $mcd"; return
+  fi
+  timeout "$MCTIMEOUT" "$MCINT" "$mcd" > /tmp/m2comp_out.txt 2>&1; rc=$?
+  if [ "$rc" -ne 0 ]; then
+    fail=$((fail+1)); echo "FAIL(silent): $name vm rc=$rc (diverges?)"; return
+  fi
+  got=$(head -n 1 /tmp/m2comp_out.txt)
+  if [ -z "$got" ]; then
+    pass=$((pass+1)); echo "PASS(silent): $name runs silent"
+  else
+    fail=$((fail+1)); echo "FAIL(silent): $name output [$got]"
+  fi
+}
 expect_ok tests/ok_minimal.mod
 expect_ok tests/ok_proc.mod
 expect_ok tests/showcase.mod
@@ -48,5 +97,21 @@ expect_dump tests/showcase.mod "CallExpr"
 expect_dump tests/showcase.mod "Field q"
 expect_dump tests/showcase.mod "Int 255"
 expect_dump tests/ok_proc.mod "Proc P"
+expect_run_none ok_minimal.mod
+expect_run_none ok_proc.mod
+expect_run r_arith.mod 8
+expect_run r_flow.mod 69
+expect_run r_bool.mod 11
+expect_run r_real.mod 1111
+expect_run r_proc.mod 162
+expect_run r_varpar.mod 48
+expect_run r_nested.mod 213
+expect_run r_array.mod 39
+expect_run r_record.mod 15
+expect_run r_set.mod 12
+expect_run r_string.mod 111
+expect_run r_ptr.mod 111
+expect_run r_module.mod 83
+expect_run r_const.mod 1102
 echo "--- $pass passed, $fail failed ---"
 [ "$fail" -eq 0 ]

+ 2 - 0
src/AST.def

@@ -58,7 +58,9 @@ TYPE
     typ: INTEGER;
     op: INTEGER;
     num: INTEGER;
+    aux: INTEGER;
     name: ARRAY [0 .. 63] OF CHAR;
+    tag: ARRAY [0 .. 63] OF CHAR;
     text: ARRAY [0 .. 63] OF CHAR;
     left, right, extra, more, next: Node;
   END;

+ 2 - 0
src/AST.mod

@@ -25,7 +25,9 @@ PROCEDURE New (kind: INTEGER): Node;
     n^.typ := -1;
     n^.op := 0;
     n^.num := 0;
+    n^.aux := -1;
     n^.name[0] := 0C;
+    n^.tag[0] := 0C;
     n^.text[0] := 0C;
     n^.left := NIL; n^.right := NIL; n^.extra := NIL;
     n^.more := NIL; n^.next := NIL;


+ 182 - 43
src/M2comp.atg

@@ -108,12 +108,18 @@ PRODUCTIONS
       ";" .
   ConstDecl<VAR n: AST.Node>          (. VAR nm: SymTab.Name;
                                            t: SymTab.TypeIndex;
-                                           en: AST.Node; .)
+                                           en: AST.Node;
+                                           cv: INTEGER;
+                                           ok: BOOLEAN; .)
     = GetIdent<nm>                    (. IF ~SymTab.Enter(nm,
                                            SymTab.KindConst)
                                          THEN SemError(200) END; .)
       "="
       ConstExpr<t, en>                (. SymTab.SetSymType(nm, t);
+                                        ok := SymTab.ConstFold(en,
+                                          cv);
+                                        SymTab.NoteConst(nm, cv,
+                                          ok);
                                         n := AST.MkConst(nm, en,
                                           t); .) .
   ConstExpr<VAR t: SymTab.TypeIndex; VAR e: AST.Node>
@@ -156,20 +162,31 @@ PRODUCTIONS
                                           SymTab.InvalidType)); .) } .
   ProcDecl<VAR n: AST.Node>           (. VAR m1, m2: SymTab.Name;
                                            rt, ret: SymTab.TypeIndex;
-                                           p, bd, bs: AST.Node; .)
+                                           p, bd, bs: AST.Node;
+                                           pn: INTEGER; .)
     =                                 (. ret := SymTab.InvalidType;
                                          p := NIL; .)
       "PROCEDURE"
       GetIdent<m1>                    (. IF ~SymTab.EnterProc(m1) THEN
                                          SemError(200) END;
+                                        pn := SymTab.ProcNum(m1);
                                         SymTab.OpenProcScope; .)
       [ FormalParams<p> ]
       [ ":"
         QualIdent<rt>                 (. ret := rt;
-                                        SymTab.SetProcRet(rt); .) ]
+                                        SymTab.SetProcRet(rt);
+                                        IF (rt #
+                                           SymTab.InvalidType)
+                                           & ((SymTab.ClassOf(rt) =
+                                              SymTab.ClArray)
+                                              OR (SymTab.ClassOf(
+                                                 rt) =
+                                                 SymTab.ClRecord)) THEN
+                                          SemError(230) END; .) ]
       ";"
       Block<bd, bs>                   (. n := AST.MkProc(m1, ret, p,
-                                          AST.MkBlock(bd, bs)); .)
+                                          AST.MkBlock(bd, bs));
+                                        n^.num := pn; .)
       GetIdent<m2>                    (. IF ~SymTab.Equal(m1, m2) THEN
                                          SemError(202) END;
                                         SymTab.CloseProc; .) .
@@ -184,7 +201,15 @@ PRODUCTIONS
       [ "VAR"                         (. isV := TRUE; .) ]
       FPIdents<isV, p> ":"
       Type<t>                         (. IF SymTab.IsOpen(t) & ~isV THEN
-                                         SemError(230) END;
+                                         SemError(230)
+                                        ELSIF ~isV
+                                           & (t #
+                                              SymTab.InvalidType)
+                                           & ((SymTab.ClassOf(t) =
+                                              SymTab.ClArray)
+                                              OR (SymTab.ClassOf(t) =
+                                                 SymTab.ClRecord)) THEN
+                                          SemError(230) END;
                                         AST.SetChainType(p, t);
                                         IF ~SymTab.FixParamPending(t) THEN
                                           SemError(200) END; .) .
@@ -244,15 +269,23 @@ PRODUCTIONS
   Type<VAR t: SymTab.TypeIndex>
                                       (. VAR c: CARDINAL;
                                            e, s: SymTab.TypeIndex;
-                                           fh: AST.Node; .)
+                                           los, his: SymTab.BoundsArr;
+                                           fh: AST.Node;
+                                           i: CARDINAL; .)
     = BoundedBase<t>
     | "ARRAY"                         (* abbreviated form only (3.1);
                                          nested ARRAY OF ARRAY long form
                                          flagged in step 4 sizing *)
-                                      (. c := 0; .)
-      [ IdxList<c> ]
+                                      (. c := 0; i := 0;
+                                        WHILE i <= 7 DO
+                                          los[i] := 0;
+                                          his[i] := -1;
+                                          INC(i)
+                                        END; .)
+      [ IdxList<c, los, his> ]
       "OF"
-      Type<e>                         (. t := SymTab.WrapArray(e, c); .)
+      Type<e>                         (. t := SymTab.WrapArrayB(e, los,
+                                          his, c); .)
     | "RECORD"                        (. t := SymTab.NewRecord(); .)
       FieldSeq<t, fh>
       "END"
@@ -273,7 +306,8 @@ PRODUCTIONS
   BoundedBase<VAR t: SymTab.TypeIndex>
                                       (. VAR t1, t2: SymTab.TypeIndex;
                                            vD, vD2: BOOLEAN;
-                                           en: AST.Node; .)
+                                           en, en2: AST.Node;
+                                           lo, hi: INTEGER; .)
     = QualIdent<t>
       [ "["
         Expr<t1, vD, en>              (. IF (t1 # SymTab.InvalidType)
@@ -285,15 +319,29 @@ PRODUCTIONS
                                             SymTab.ClEnum) THEN
                                          SemError(224) END; .)
         ".."
-        Expr<t2, vD2, en>             (. IF (t2 # SymTab.InvalidType)
+        Expr<t2, vD2, en2>            (. IF (t2 # SymTab.InvalidType)
                                          & (SymTab.ClassOf(t2) #
                                             SymTab.ClInt)
                                          & (SymTab.ClassOf(t2) #
                                             SymTab.ClChar)
                                          & (SymTab.ClassOf(t2) #
                                             SymTab.ClEnum) THEN
-                                         SemError(224) END; .)
-        "]"                           (. t := SymTab.NewSub(t1); .) ]
+                                         SemError(224) END;
+                                        IF (t1 = SymTab.InvalidType)
+                                           OR (t2 =
+                                              SymTab.InvalidType) THEN
+                                          t := SymTab.NewSub(t1)
+                                        ELSIF SymTab.ConstFold(en,
+                                           lo)
+                                           & SymTab.ConstFold(en2,
+                                              hi) THEN
+                                          t := SymTab.NewSubB(t1,
+                                            lo, hi)
+                                        ELSE
+                                          SemError(230);
+                                          t := SymTab.NewSub(t1)
+                                        END; .)
+        "]" ]
     | "["
       Expr<t1, vD, en>                (. IF (t1 # SymTab.InvalidType)
                                          & (SymTab.ClassOf(t1) #
@@ -304,37 +352,120 @@ PRODUCTIONS
                                             SymTab.ClEnum) THEN
                                          SemError(224) END; .)
       ".."
-      Expr<t2, vD2, en>               (. IF (t2 # SymTab.InvalidType)
+      Expr<t2, vD2, en2>              (. IF (t2 # SymTab.InvalidType)
                                          & (SymTab.ClassOf(t2) #
                                             SymTab.ClInt)
                                          & (SymTab.ClassOf(t2) #
                                             SymTab.ClChar)
                                          & (SymTab.ClassOf(t2) #
                                             SymTab.ClEnum) THEN
-                                         SemError(224) END; .)
-      "]"                             (. t := SymTab.NewSub(t1); .) .
-  IdxList<VAR c: CARDINAL>            (. VAR s: SymTab.TypeIndex; .)
-    = IndexType<s>                    (. c := 1;
-                                        IF (s # SymTab.InvalidType)
-                                           & (SymTab.ClassOf(s) #
-                                              SymTab.ClInt)
-                                           & (SymTab.ClassOf(s) #
-                                              SymTab.ClChar)
-                                           & (SymTab.ClassOf(s) #
-                                              SymTab.ClEnum) THEN
-                                          SemError(224) END; .)
-      { ","
-        IndexType<s>                  (. IF (s # SymTab.InvalidType)
-                                           & (SymTab.ClassOf(s) #
-                                              SymTab.ClInt)
-                                           & (SymTab.ClassOf(s) #
-                                              SymTab.ClChar)
-                                           & (SymTab.ClassOf(s) #
-                                              SymTab.ClEnum) THEN
-                                          SemError(224) END;
+                                         SemError(224) END;
+                                        IF (t1 = SymTab.InvalidType)
+                                           OR (t2 =
+                                              SymTab.InvalidType) THEN
+                                          t := SymTab.NewSub(t1)
+                                        ELSIF SymTab.ConstFold(en,
+                                           lo)
+                                           & SymTab.ConstFold(en2,
+                                              hi) THEN
+                                          t := SymTab.NewSubB(t1,
+                                            lo, hi)
+                                        ELSE
+                                          SemError(230);
+                                          t := SymTab.NewSub(t1)
+                                        END; .)
+      "]" .
+  IdxList<VAR c: CARDINAL; VAR los, his: SymTab.BoundsArr>
+                                      (. VAR s: SymTab.TypeIndex;
+                                           lo, hi: INTEGER;
+                                           ok: BOOLEAN; .)
+    = IndexType<s, lo, hi, ok>        (. c := 1;
+                                        los[0] := lo; his[0] := hi; .)
+      { "," IndexType<s, lo, hi, ok>  (. IF c <= 7 THEN
+                                          los[c] := lo;
+                                          his[c] := hi
+                                        END;
                                         INC(c); .) } .
-  IndexType<VAR t: SymTab.TypeIndex>
-    = BoundedBase<t> .
+  IndexType<VAR t: SymTab.TypeIndex; VAR lo, hi: INTEGER;
+            VAR ok: BOOLEAN>          (. VAR t1, t2: SymTab.TypeIndex;
+                                           vD, vD2: BOOLEAN;
+                                           en, en2: AST.Node;
+                                           l2, h2: INTEGER; .)
+    = QualIdent<t>                    (. ok := SymTab.IndexBounds(t,
+                                           lo, hi); .)
+      [ "["
+        Expr<t1, vD, en>              (. IF (t1 # SymTab.InvalidType)
+                                         & (SymTab.ClassOf(t1) #
+                                            SymTab.ClInt)
+                                         & (SymTab.ClassOf(t1) #
+                                            SymTab.ClChar)
+                                         & (SymTab.ClassOf(t1) #
+                                            SymTab.ClEnum) THEN
+                                         SemError(224) END; .)
+        ".."
+        Expr<t2, vD2, en2>            (. IF (t2 # SymTab.InvalidType)
+                                         & (SymTab.ClassOf(t2) #
+                                            SymTab.ClInt)
+                                         & (SymTab.ClassOf(t2) #
+                                            SymTab.ClChar)
+                                         & (SymTab.ClassOf(t2) #
+                                            SymTab.ClEnum) THEN
+                                         SemError(224) END;
+                                        IF (t1 = SymTab.InvalidType)
+                                           OR (t2 =
+                                              SymTab.InvalidType) THEN
+                                          t := SymTab.NewSub(t1)
+                                        ELSIF SymTab.ConstFold(en,
+                                           l2)
+                                           & SymTab.ConstFold(en2,
+                                              h2) THEN
+                                          t := SymTab.NewSubB(t1,
+                                            l2, h2);
+                                          lo := l2; hi := h2;
+                                          ok := TRUE
+                                        ELSE
+                                          t := SymTab.NewSub(t1);
+                                          ok := FALSE
+                                        END; .)
+        "]" ]
+                                      (. IF (t # SymTab.InvalidType)
+                                           & ~ok THEN
+                                          SemError(230) END; .)
+    | "["
+      Expr<t1, vD, en>                (. IF (t1 # SymTab.InvalidType)
+                                         & (SymTab.ClassOf(t1) #
+                                            SymTab.ClInt)
+                                         & (SymTab.ClassOf(t1) #
+                                            SymTab.ClChar)
+                                         & (SymTab.ClassOf(t1) #
+                                            SymTab.ClEnum) THEN
+                                         SemError(224) END;
+                                        ok := FALSE; .)
+      ".."
+      Expr<t2, vD2, en2>              (. IF (t2 # SymTab.InvalidType)
+                                         & (SymTab.ClassOf(t2) #
+                                            SymTab.ClInt)
+                                         & (SymTab.ClassOf(t2) #
+                                            SymTab.ClChar)
+                                         & (SymTab.ClassOf(t2) #
+                                            SymTab.ClEnum) THEN
+                                         SemError(224) END;
+                                        IF (t1 = SymTab.InvalidType)
+                                           OR (t2 =
+                                              SymTab.InvalidType) THEN
+                                          t := SymTab.NewSub(t1)
+                                        ELSIF SymTab.ConstFold(en,
+                                           lo)
+                                           & SymTab.ConstFold(en2,
+                                              hi) THEN
+                                          t := SymTab.NewSubB(t1,
+                                            lo, hi);
+                                          ok := TRUE
+                                        ELSE
+                                          SemError(230);
+                                          t := SymTab.NewSub(t1)
+                                        END; .)
+      "]" .
   FieldSeq<rt: SymTab.TypeIndex; VAR h: AST.Node>
                                       (. VAR fn: AST.Node; .)
     = Field<rt, fn>                   (. h := fn; .)
@@ -460,7 +591,7 @@ PRODUCTIONS
                                            it, it2: SymTab.TypeIndex;
                                            iv, iv2: BOOLEAN;
                                            ek, sk, p2: INTEGER;
-                                           modHead: BOOLEAN;
+                                           modHead, wasMod: BOOLEAN;
                                            en, en2, ih: AST.Node; .)
     = GetIdent<n>                     (. modHead := FALSE;
                                         IF ~SymTab.Lookup(n) THEN
@@ -480,8 +611,10 @@ PRODUCTIONS
                                           ELSE pn := -1
                                           END
                                         END;
-                                        dn := AST.MkName(n, t); .)
-      { "." GetIdent<m>               (. IF modHead THEN
+                                        dn := AST.MkName(n, t);
+                                        dn^.aux := k; .)
+      { "." GetIdent<m>               (. wasMod := modHead;
+                                         IF modHead THEN
                                           modHead := FALSE;
                                           ek := SymTab.ExpKind(n, m);
                                           sk := SymTab.SelfKind(n, m);
@@ -542,7 +675,11 @@ PRODUCTIONS
                                           k := SymTab.KindField;
                                           pn := -1
                                         END;
-                                        dn := AST.MkField(dn, m, t); .)
+                                        dn := AST.MkField(dn, m, t);
+                                        dn^.aux := k;
+                                        IF wasMod THEN
+                                          dn^.tag := n
+                                        END; .)
       | "[" Expr<it, iv, en>            (. IF t = SymTab.InvalidType THEN
                                           (* cascade *)
                                         ELSIF SymTab.ClassOf(t) #
@@ -578,7 +715,8 @@ PRODUCTIONS
                                             t) END;
                                         ih := AST.Append(ih, en2); .) }
         "]"                             (. dn := AST.MkIndex(dn, ih,
-                                          t); .)
+                                          t);
+                                        dn^.aux := k; .)
       | "^"                           (. IF t = SymTab.InvalidType THEN
                                           (* cascade *)
                                         ELSIF SymTab.ClassOf(t) #
@@ -589,7 +727,8 @@ PRODUCTIONS
                                         ELSE t := SymTab.PtrBase(t);
                                           pn := -1
                                         END;
-                                        dn := AST.MkDeref(dn, t); .) } .
+                                        dn := AST.MkDeref(dn, t);
+                                        dn^.aux := k; .) } .
   ActParams<VAR h: AST.Node; VAR n: CARDINAL>
                                       (. VAR at: SymTab.TypeIndex;
                                            av: BOOLEAN;

+ 4 - 3
src/M2comp.err

@@ -68,6 +68,7 @@
 | 67: Msg("invalid Relation")
 | 68: Msg("invalid SimExpr")
 | 69: Msg("invalid AssignOrCall")
-| 70: Msg("invalid BoundedBase")
-| 71: Msg("invalid Type")
-| 72: Msg("invalid Declaration")
+| 70: Msg("invalid IndexType")
+| 71: Msg("invalid BoundedBase")
+| 72: Msg("invalid Type")
+| 73: Msg("invalid Declaration")

+ 1 - 1
src/M2comp.lst

@@ -21,7 +21,7 @@ Statistics:
   nr of non-terminals:    45 (limit   210)
   nr of pragmas:           0 (limit   436)
   nr of symbolnodes:     109 (limit   500)
-  nr of graphnodes:      457 (limit  1500)
+  nr of graphnodes:      474 (limit  1500)
   nr of conditionsets:     6 (limit   100)
   nr of charactersets:     9 (limit   250)
 

+ 18 - 8
src/M2comp.mod

@@ -1,12 +1,13 @@
 MODULE M2comp;
-(* Driver for the M2comp Modula-2 compiler (Coco/R, step 1: syntax only).
-   Generated <Grammar>S (scanner) + <Grammar>P (parser) do lexing/parsing;
-   no hand-written lexer. SymTab/MGen plug in at step 2. *)
+(* Driver for the M2comp Modula-2 compiler (Coco/R frontend).
+   Generated <Grammar>S (scanner) + <Grammar>P (parser) do lexing,
+   parsing, SymTab checking and AST building; on success the MGen
+   tree-walking backend emits a single-module MC64 .MC4 image. *)
 
   FROM M2compS IMPORT lst, src, errors, Error, CharAt;
   FROM M2compP IMPORT Parse, Successful;
   IMPORT
-    Strings, Storage, SYSTEM, FileIO;
+    Strings, Storage, SYSTEM, FileIO, AST, MGen;
 
   TYPE
     INT32 = FileIO.INT32 (* 32 bit integers needed *);
@@ -165,9 +166,10 @@ MODULE M2comp;
         | 67: Msg("invalid Relation")
         | 68: Msg("invalid SimExpr")
         | 69: Msg("invalid AssignOrCall")
-        | 70: Msg("invalid BoundedBase")
-        | 71: Msg("invalid Type")
-        | 72: Msg("invalid Declaration")
+        | 70: Msg("invalid IndexType")
+        | 71: Msg("invalid BoundedBase")
+        | 72: Msg("invalid Type")
+        | 73: Msg("invalid Declaration")
         
         (* add customized cases here *)
         | 200: Msg("duplicate identifier")
@@ -261,6 +263,7 @@ MODULE M2comp;
   VAR
     sourceName, listName: ARRAY [0 .. 255] OF CHAR;
     failed: BOOLEAN;
+    emitOk: BOOLEAN;
 
   BEGIN
     (* check on correct parameter usage *)
@@ -302,12 +305,19 @@ MODULE M2comp;
       FileIO.WriteLn(FileIO.StdOut);
       Parse;
 
+      (* backend: emit the MC64 image while errors still reach
+         the listing below *)
+      IF Successful() THEN
+        emitOk := MGen.EmitModule(AST.GetRoot())
+      ELSE emitOk := FALSE
+      END;
+
       (* generate the source listing on lst file *)
       PrintListing;
       IF lst # FileIO.StdOut THEN FileIO.Close(lst) END;
 
       (* fail fast: later files build on this one's tables *)
-      IF NOT Successful()
+      IF NOT (Successful() & emitOk)
         THEN
           FileIO.WriteString(FileIO.StdOut, "Incorrect source");
           FileIO.WriteLn(FileIO.StdOut);


+ 194 - 44
src/M2compP.mod

@@ -136,9 +136,10 @@ PROCEDURE AssignOrCall (VAR s: AST.Node); FORWARD;
 PROCEDURE Stat (VAR s: AST.Node); FORWARD;
 PROCEDURE FieldIdents (rt: SymTab.TypeIndex; VAR h: AST.Node); FORWARD;
 PROCEDURE Field (rt: SymTab.TypeIndex; VAR n: AST.Node); FORWARD;
-PROCEDURE IndexType (VAR t: SymTab.TypeIndex); FORWARD;
+PROCEDURE IndexType (VAR t: SymTab.TypeIndex; VAR lo, hi: INTEGER;
+                      VAR ok: BOOLEAN); FORWARD;
 PROCEDURE FieldSeq (rt: SymTab.TypeIndex; VAR h: AST.Node); FORWARD;
-PROCEDURE IdxList (VAR c: CARDINAL); FORWARD;
+PROCEDURE IdxList (VAR c: CARDINAL; VAR los, his: SymTab.BoundsArr); FORWARD;
 PROCEDURE BoundedBase (VAR t: SymTab.TypeIndex); FORWARD;
 PROCEDURE Export (VAR n: AST.Node); FORWARD;
 PROCEDURE Priority; FORWARD;
@@ -459,7 +460,7 @@ PROCEDURE Designator (VAR t: SymTab.TypeIndex; VAR k: INTEGER;
   it, it2: SymTab.TypeIndex;
   iv, iv2: BOOLEAN;
   ek, sk, p2: INTEGER;
-  modHead: BOOLEAN;
+  modHead, wasMod: BOOLEAN;
   en, en2, ih: AST.Node;
   BEGIN
     GetIdent(n);
@@ -481,11 +482,13 @@ PROCEDURE Designator (VAR t: SymTab.TypeIndex; VAR k: INTEGER;
      ELSE pn := -1
      END
     END;
-    dn := AST.MkName(n, t);;
+    dn := AST.MkName(n, t);
+    dn^.aux := k;;
     WHILE (sym = 5) OR (sym = 21) OR (sym = 34) DO
       IF (sym = 5) THEN
         Get;
         GetIdent(m);
+        wasMod := modHead;
         IF modHead THEN
          modHead := FALSE;
          ek := SymTab.ExpKind(n, m);
@@ -547,7 +550,11 @@ PROCEDURE Designator (VAR t: SymTab.TypeIndex; VAR k: INTEGER;
          k := SymTab.KindField;
          pn := -1
         END;
-        dn := AST.MkField(dn, m, t);;
+        dn := AST.MkField(dn, m, t);
+        dn^.aux := k;
+        IF wasMod THEN
+         dn^.tag := n
+        END;;
       ELSIF (sym = 21) THEN
         Get;
         Expr(it, iv, en);
@@ -591,7 +598,8 @@ PROCEDURE Designator (VAR t: SymTab.TypeIndex; VAR k: INTEGER;
         END;
         Expect(22);
         dn := AST.MkIndex(dn, ih,
-        t);;
+        t);
+        dn^.aux := k;;
       ELSE
         Get;
         IF t = SymTab.InvalidType THEN
@@ -604,7 +612,8 @@ PROCEDURE Designator (VAR t: SymTab.TypeIndex; VAR k: INTEGER;
         ELSE t := SymTab.PtrBase(t);
          pn := -1
         END;
-        dn := AST.MkDeref(dn, t);;
+        dn := AST.MkDeref(dn, t);
+        dn^.aux := k;;
       END;
     END;
   END Designator;
@@ -861,9 +870,99 @@ PROCEDURE Field (rt: SymTab.TypeIndex; VAR n: AST.Node);
     END;
   END Field;
 
-PROCEDURE IndexType (VAR t: SymTab.TypeIndex);
+PROCEDURE IndexType (VAR t: SymTab.TypeIndex; VAR lo, hi: INTEGER;
+                      VAR ok: BOOLEAN);
+  VAR t1, t2: SymTab.TypeIndex;
+    vD, vD2: BOOLEAN;
+    en, en2: AST.Node;
+    l2, h2: INTEGER;
   BEGIN
-    BoundedBase(t);
+    IF (sym = 1) THEN
+      QualIdent(t);
+      ok := SymTab.IndexBounds(t,
+        lo, hi);;
+      IF (sym = 21) THEN
+        Get;
+        Expr(t1, vD, en);
+        IF (t1 # SymTab.InvalidType)
+        & (SymTab.ClassOf(t1) #
+           SymTab.ClInt)
+        & (SymTab.ClassOf(t1) #
+           SymTab.ClChar)
+        & (SymTab.ClassOf(t1) #
+           SymTab.ClEnum) THEN
+        SemError(224) END;;
+        Expect(31);
+        Expr(t2, vD2, en2);
+        IF (t2 # SymTab.InvalidType)
+        & (SymTab.ClassOf(t2) #
+           SymTab.ClInt)
+        & (SymTab.ClassOf(t2) #
+           SymTab.ClChar)
+        & (SymTab.ClassOf(t2) #
+           SymTab.ClEnum) THEN
+        SemError(224) END;
+        IF (t1 = SymTab.InvalidType)
+          OR (t2 =
+             SymTab.InvalidType) THEN
+         t := SymTab.NewSub(t1)
+        ELSIF SymTab.ConstFold(en,
+          l2)
+          & SymTab.ConstFold(en2,
+             h2) THEN
+         t := SymTab.NewSubB(t1,
+           l2, h2);
+         lo := l2; hi := h2;
+         ok := TRUE
+        ELSE
+         t := SymTab.NewSub(t1);
+         ok := FALSE
+        END;;
+        Expect(22);
+      END;
+      IF (t # SymTab.InvalidType)
+        & ~ok THEN
+       SemError(230) END;;
+    ELSIF (sym = 21) THEN
+      Get;
+      Expr(t1, vD, en);
+      IF (t1 # SymTab.InvalidType)
+      & (SymTab.ClassOf(t1) #
+         SymTab.ClInt)
+      & (SymTab.ClassOf(t1) #
+         SymTab.ClChar)
+      & (SymTab.ClassOf(t1) #
+         SymTab.ClEnum) THEN
+      SemError(224) END;
+      ok := FALSE;;
+      Expect(31);
+      Expr(t2, vD2, en2);
+      IF (t2 # SymTab.InvalidType)
+      & (SymTab.ClassOf(t2) #
+         SymTab.ClInt)
+      & (SymTab.ClassOf(t2) #
+         SymTab.ClChar)
+      & (SymTab.ClassOf(t2) #
+         SymTab.ClEnum) THEN
+      SemError(224) END;
+      IF (t1 = SymTab.InvalidType)
+        OR (t2 =
+           SymTab.InvalidType) THEN
+       t := SymTab.NewSub(t1)
+      ELSIF SymTab.ConstFold(en,
+        lo)
+        & SymTab.ConstFold(en2,
+           hi) THEN
+       t := SymTab.NewSubB(t1,
+         lo, hi);
+       ok := TRUE
+      ELSE
+       SemError(230);
+       t := SymTab.NewSub(t1)
+      END;;
+      Expect(22);
+    ELSE SynError(70);
+    END;
   END IndexType;
 
 PROCEDURE FieldSeq (rt: SymTab.TypeIndex; VAR h: AST.Node);
@@ -878,30 +977,21 @@ PROCEDURE FieldSeq (rt: SymTab.TypeIndex; VAR h: AST.Node);
     END;
   END FieldSeq;
 
-PROCEDURE IdxList (VAR c: CARDINAL);
+PROCEDURE IdxList (VAR c: CARDINAL; VAR los, his: SymTab.BoundsArr);
   VAR s: SymTab.TypeIndex;
+    lo, hi: INTEGER;
+    ok: BOOLEAN;
   BEGIN
-    IndexType(s);
+    IndexType(s, lo, hi, ok);
     c := 1;
-    IF (s # SymTab.InvalidType)
-      & (SymTab.ClassOf(s) #
-         SymTab.ClInt)
-      & (SymTab.ClassOf(s) #
-         SymTab.ClChar)
-      & (SymTab.ClassOf(s) #
-         SymTab.ClEnum) THEN
-     SemError(224) END;;
+    los[0] := lo; his[0] := hi;;
     WHILE (sym = 10) DO
       Get;
-      IndexType(s);
-      IF (s # SymTab.InvalidType)
-        & (SymTab.ClassOf(s) #
-           SymTab.ClInt)
-        & (SymTab.ClassOf(s) #
-           SymTab.ClChar)
-        & (SymTab.ClassOf(s) #
-           SymTab.ClEnum) THEN
-       SemError(224) END;
+      IndexType(s, lo, hi, ok);
+      IF c <= 7 THEN
+       los[c] := lo;
+       his[c] := hi
+      END;
       INC(c);;
     END;
   END IdxList;
@@ -909,7 +999,8 @@ PROCEDURE IdxList (VAR c: CARDINAL);
 PROCEDURE BoundedBase (VAR t: SymTab.TypeIndex);
   VAR t1, t2: SymTab.TypeIndex;
     vD, vD2: BOOLEAN;
-    en: AST.Node;
+    en, en2: AST.Node;
+    lo, hi: INTEGER;
   BEGIN
     IF (sym = 1) THEN
       QualIdent(t);
@@ -925,7 +1016,7 @@ PROCEDURE BoundedBase (VAR t: SymTab.TypeIndex);
            SymTab.ClEnum) THEN
         SemError(224) END;;
         Expect(31);
-        Expr(t2, vD2, en);
+        Expr(t2, vD2, en2);
         IF (t2 # SymTab.InvalidType)
         & (SymTab.ClassOf(t2) #
            SymTab.ClInt)
@@ -933,9 +1024,22 @@ PROCEDURE BoundedBase (VAR t: SymTab.TypeIndex);
            SymTab.ClChar)
         & (SymTab.ClassOf(t2) #
            SymTab.ClEnum) THEN
-        SemError(224) END;;
+        SemError(224) END;
+        IF (t1 = SymTab.InvalidType)
+          OR (t2 =
+             SymTab.InvalidType) THEN
+         t := SymTab.NewSub(t1)
+        ELSIF SymTab.ConstFold(en,
+          lo)
+          & SymTab.ConstFold(en2,
+             hi) THEN
+         t := SymTab.NewSubB(t1,
+           lo, hi)
+        ELSE
+         SemError(230);
+         t := SymTab.NewSub(t1)
+        END;;
         Expect(22);
-        t := SymTab.NewSub(t1);;
       END;
     ELSIF (sym = 21) THEN
       Get;
@@ -949,7 +1053,7 @@ PROCEDURE BoundedBase (VAR t: SymTab.TypeIndex);
          SymTab.ClEnum) THEN
       SemError(224) END;;
       Expect(31);
-      Expr(t2, vD2, en);
+      Expr(t2, vD2, en2);
       IF (t2 # SymTab.InvalidType)
       & (SymTab.ClassOf(t2) #
          SymTab.ClInt)
@@ -957,10 +1061,23 @@ PROCEDURE BoundedBase (VAR t: SymTab.TypeIndex);
          SymTab.ClChar)
       & (SymTab.ClassOf(t2) #
          SymTab.ClEnum) THEN
-      SemError(224) END;;
+      SemError(224) END;
+      IF (t1 = SymTab.InvalidType)
+        OR (t2 =
+           SymTab.InvalidType) THEN
+       t := SymTab.NewSub(t1)
+      ELSIF SymTab.ConstFold(en,
+        lo)
+        & SymTab.ConstFold(en2,
+           hi) THEN
+       t := SymTab.NewSubB(t1,
+         lo, hi)
+      ELSE
+       SemError(230);
+       t := SymTab.NewSub(t1)
+      END;;
       Expect(22);
-      t := SymTab.NewSub(t1);;
-    ELSE SynError(70);
+    ELSE SynError(71);
     END;
   END BoundedBase;
 
@@ -1034,7 +1151,15 @@ PROCEDURE FPSection (VAR p: AST.Node);
     Expect(17);
     Type(t);
     IF SymTab.IsOpen(t) & ~isV THEN
-    SemError(230) END;
+    SemError(230)
+    ELSIF ~isV
+      & (t #
+         SymTab.InvalidType)
+      & ((SymTab.ClassOf(t) =
+         SymTab.ClArray)
+         OR (SymTab.ClassOf(t) =
+            SymTab.ClRecord)) THEN
+     SemError(230) END;
     AST.SetChainType(p, t);
     IF ~SymTab.FixParamPending(t) THEN
      SemError(200) END;;
@@ -1133,19 +1258,27 @@ PROCEDURE VarIdents (VAR h: AST.Node);
 PROCEDURE Type (VAR t: SymTab.TypeIndex);
   VAR c: CARDINAL;
     e, s: SymTab.TypeIndex;
+    los, his: SymTab.BoundsArr;
     fh: AST.Node;
+    i: CARDINAL;
   BEGIN
     IF (sym = 1) OR (sym = 21) THEN
       BoundedBase(t);
     ELSIF (sym = 25) THEN
       Get;
-      c := 0;;
+      c := 0; i := 0;
+      WHILE i <= 7 DO
+       los[i] := 0;
+       his[i] := -1;
+       INC(i)
+      END;;
       IF (sym = 1) OR (sym = 21) THEN
-        IdxList(c);
+        IdxList(c, los, his);
       END;
       Expect(26);
       Type(e);
-      t := SymTab.WrapArray(e, c);;
+      t := SymTab.WrapArrayB(e, los,
+       his, c);;
     ELSIF (sym = 27) THEN
       Get;
       t := SymTab.NewRecord();;
@@ -1169,7 +1302,7 @@ PROCEDURE Type (VAR t: SymTab.TypeIndex);
       Expect(30);
       Type(e);
       t := SymTab.NewPtr(e);;
-    ELSE SynError(71);
+    ELSE SynError(72);
     END;
   END Type;
 
@@ -1241,6 +1374,7 @@ PROCEDURE ProcDecl (VAR n: AST.Node);
   VAR m1, m2: SymTab.Name;
     rt, ret: SymTab.TypeIndex;
     p, bd, bs: AST.Node;
+    pn: INTEGER;
   BEGIN
     ret := SymTab.InvalidType;
     p := NIL;;
@@ -1248,6 +1382,7 @@ PROCEDURE ProcDecl (VAR n: AST.Node);
     GetIdent(m1);
     IF ~SymTab.EnterProc(m1) THEN
     SemError(200) END;
+    pn := SymTab.ProcNum(m1);
     SymTab.OpenProcScope;;
     IF (sym = 19) THEN
       FormalParams(p);
@@ -1256,12 +1391,21 @@ PROCEDURE ProcDecl (VAR n: AST.Node);
       Get;
       QualIdent(rt);
       ret := rt;
-      SymTab.SetProcRet(rt);;
+      SymTab.SetProcRet(rt);
+      IF (rt #
+        SymTab.InvalidType)
+        & ((SymTab.ClassOf(rt) =
+           SymTab.ClArray)
+           OR (SymTab.ClassOf(
+              rt) =
+              SymTab.ClRecord)) THEN
+       SemError(230) END;;
     END;
     Expect(7);
     Block(bd, bs);
     n := AST.MkProc(m1, ret, p,
-     AST.MkBlock(bd, bs));;
+     AST.MkBlock(bd, bs));
+    n^.num := pn;;
     GetIdent(m2);
     IF ~SymTab.Equal(m1, m2) THEN
     SemError(202) END;
@@ -1306,6 +1450,8 @@ PROCEDURE ConstDecl (VAR n: AST.Node);
   VAR nm: SymTab.Name;
     t: SymTab.TypeIndex;
     en: AST.Node;
+    cv: INTEGER;
+    ok: BOOLEAN;
   BEGIN
     GetIdent(nm);
     IF ~SymTab.Enter(nm,
@@ -1314,6 +1460,10 @@ PROCEDURE ConstDecl (VAR n: AST.Node);
     Expect(16);
     ConstExpr(t, en);
     SymTab.SetSymType(nm, t);
+    ok := SymTab.ConstFold(en,
+     cv);
+    SymTab.NoteConst(nm, cv,
+     ok);
     n := AST.MkConst(nm, en,
      t);;
   END ConstDecl;
@@ -1363,7 +1513,7 @@ PROCEDURE Declaration (VAR n: AST.Node);
     ELSIF (sym = 6) THEN
       ModuleDecl(n);
       Expect(7);
-    ELSE SynError(72);
+    ELSE SynError(73);
     END;
   END Declaration;
 


+ 21 - 0
src/MGen.def

@@ -0,0 +1,21 @@
+DEFINITION MODULE MGen;
+(* MC64 tree-walking backend for M2comp step 4.
+   Consumes the checked AST (SymTab scopes are popped after parsing,
+   so names resolve through MGen's own scope stack, built during the
+   walk) plus the persistent SymTab tables (types, proc signatures,
+   exports, bounds). Emits a single-module .MC4 image runnable under
+   mcint, following mc64-spec.md and the m2c lowering recipes:
+   32-bit scalar ops in slots, slot-per-element composites (no byte
+   packing — documented deviation from m2c), ENTER frames with
+   ED/EC/EE calls, ExitCode print convention.
+
+   PROCEDURE EmitModule returns TRUE when fully emitted. Residual
+   unsupported corners record SemError(230) via M2compP and return
+   FALSE (driver reports Incorrect source). Grammar-time 230s cover
+   value composite params and composite function returns. *)
+
+IMPORT AST;
+
+PROCEDURE EmitModule (root: AST.Node): BOOLEAN;
+
+END MGen.

+ 1622 - 0
src/MGen.mod

@@ -0,0 +1,1622 @@
+IMPLEMENTATION MODULE MGen;
+
+IMPORT FileIO, SymTab, AST, M2compP;
+
+FROM M2compP IMPORT SemError;
+
+CONST
+  MaxCode = 8191;
+  MaxImg  = 16383;
+  MaxLab  = 255;
+  MaxFix  = 2047;
+  MaxGlb  = 255;
+  MaxProc = 64;
+  MaxScope = 64;
+  MaxVar  = 2048;
+  MaxCst  = 512;
+  MaxLoop = 15;
+  MaxInit = 8;
+
+  (* opcodes, see mc64-spec.md §11 *)
+  OPdup = 20H;  OPswap = 21H;
+  OPloadGlb = 2DH;  OPstoreGlb = 3DH;
+  OPloadLocal = 2CH;  OPstoreLocal = 3CH;
+  OPloadOuterN = 11H;
+  OPloadIndir0 = 60H;  OPstoreIndir0 = 70H;
+  OPloadIndir = 41H;  OPstoreIndir = 51H;
+  OPlocalAddr = 80H;  OPglobalAddr = 81H;  OPstkAddr = 82H;
+  OPext = 40H;  SUBdrop = 00H;
+  OPenter = 0D4H;  OPprocLeave = 84H;  OPfctLeave = 85H;
+  OPprocCall = 0EDH;  OPnestedCall = 0ECH;  OPcallFrame = 0EEH;
+  OPimmB = 8DH;  OPimmW = 8EH;  OPimm0 = 90H;
+  OPadd = 0A6H;  OPsub = 0A7H;  OPumul = 0A8H;
+  OPudiv = 0A9H;  OPumod = 0AAH;
+  OPaeq0 = 0ABH;  OPinc = 0ACH;  OPdec = 0ADH;
+  OPeq = 0A0H;  OPne = 0A1H;
+  OPilt = 0B2H;  OPigt = 0B3H;  OPile = 0B4H;  OPige = 0B5H;
+  OPnot = 0B6H;
+  OPimul = 0B8H;  OPidiv = 0B9H;
+  OPintToLong = 0BDH;  OPlongToReal = 0BEH;
+  OPrCmp = 0D5H;  OPrAdd = 0D6H;  OPrSub = 0D7H;
+  OPrMul = 0D8H;  OPrDiv = 0D9H;
+  OPor = 0E6H;  OPbitIn = 0E7H;  OPand = 0E8H;
+  OPbitXor = 0E9H;  OPpower2 = 0EAH;
+  OPjp = 0E0H;  OPjz = 0E1H;
+  OPsys = 0C3H;  OPend = 50H;
+  OPcallRel = 8CH;  OPcopyBlock = 30H;  OPstrComp = 0C4H;
+
+  (* image layout *)
+  HeadSize = 64;
+  DName = 264;  DChecksum = 288;  DFlags = 292;
+  DVarCount = 293;  DDepCount = 294;  DProcs = 296;
+  DVarSizes = 304;
+
+TYPE
+  FixRec = RECORD
+    pos : CARDINAL;
+    lab : INTEGER;
+  END;
+  RealView = RECORD CASE : BOOLEAN OF
+               | TRUE : r : REAL;
+               | FALSE : w : LONGCARD;
+             END;
+           END;
+  ScopeRec = RECORD
+    tag : SymTab.Name;
+    pnum : INTEGER;
+    depth : CARDINAL;
+    parent : INTEGER;
+    isMod : BOOLEAN;
+  END;
+  VarRec = RECORD
+    scope : INTEGER;
+    name : SymTab.Name;
+    kind : INTEGER;
+    typ : INTEGER;
+    slot : INTEGER;
+    size : CARDINAL;
+    depth : CARDINAL;
+    global : BOOLEAN;
+    isAddr : BOOLEAN;
+  END;
+  CstRec = RECORD
+    scope : INTEGER;
+    name : SymTab.Name;
+    ck : INTEGER;  (* 0 int, 1 real bits, 2 string text *)
+    ival : INTEGER;
+    bits : LONGCARD;
+    text : SymTab.Name;
+    ok : BOOLEAN;
+  END;
+
+VAR
+  code : ARRAY [0 .. MaxCode] OF CHAR;
+  nCode : CARDINAL;
+  img : ARRAY [0 .. MaxImg] OF CHAR;
+  modName : SymTab.Name;
+  labs : ARRAY [0 .. MaxLab] OF INTEGER;
+  nLab : CARDINAL;
+  fixs : ARRAY [0 .. MaxFix] OF FixRec;
+  nFix : CARDINAL;
+  gBase : ARRAY [0 .. MaxGlb] OF CARDINAL;
+  gSize : ARRAY [0 .. MaxGlb] OF CARDINAL;
+  nG : CARDINAL;
+  nGlb : CARDINAL;
+  procAddr : ARRAY [0 .. MaxProc] OF INTEGER;
+  procDepth : ARRAY [0 .. MaxProc] OF CARDINAL;
+  procParent : ARRAY [0 .. MaxProc] OF INTEGER;
+  maxNum : CARDINAL;
+  mainAddr : CARDINAL;
+  initNums : ARRAY [0 .. MaxInit - 1] OF INTEGER;
+  nInits : CARDINAL;
+  nextInit : CARDINAL;
+  scopes : ARRAY [0 .. MaxScope - 1] OF ScopeRec;
+  nScopes : CARDINAL;
+  curScope : INTEGER;
+  vars : ARRAY [0 .. MaxVar - 1] OF VarRec;
+  nV : CARDINAL;
+  csts : ARRAY [0 .. MaxCst - 1] OF CstRec;
+  nC : CARDINAL;
+  curDepth : CARDINAL;
+  curProc : INTEGER;
+  loopSt : ARRAY [0 .. MaxLoop - 1] OF INTEGER;
+  loopTop : CARDINAL;
+  ok : BOOLEAN;
+
+(* ---------------- small helpers ---------------- *)
+
+PROCEDURE StrLen (s: ARRAY OF CHAR): CARDINAL;
+  VAR i : CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE (i < HIGH(s)) & (s[i] # 0C) DO INC(i) END;
+    RETURN i
+  END StrLen;
+
+PROCEDURE StrCpy (VAR d: ARRAY OF CHAR; s: ARRAY OF CHAR);
+  VAR i : CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE (i < HIGH(d)) & (i < HIGH(s)) & (s[i] # 0C) DO
+      d[i] := s[i]; INC(i)
+    END;
+    IF i <= HIGH(d) THEN d[i] := 0C END
+  END StrCpy;
+
+PROCEDURE Fail230 ();
+  BEGIN
+    M2compP.SemError(230);
+    ok := FALSE
+  END Fail230;
+
+(* ---------------- emission primitives ---------------- *)
+
+PROCEDURE EmitByte (b: CARDINAL);
+  BEGIN
+    IF nCode <= MaxCode THEN
+      code[nCode] := CHR(b MOD 256); INC(nCode)
+    END
+  END EmitByte;
+
+PROCEDURE EmitOp (o: CARDINAL);
+  BEGIN
+    EmitByte(o)
+  END EmitOp;
+
+PROCEDURE EmitOpB (o, b: CARDINAL);
+  BEGIN
+    EmitByte(o); EmitByte(b)
+  END EmitOpB;
+
+PROCEDURE EmitW64 (v: LONGCARD);
+  VAR j : CARDINAL;
+  BEGIN
+    FOR j := 0 TO 7 DO
+      EmitByte(VAL(CARDINAL, v MOD 256)); v := v DIV 256
+    END
+  END EmitW64;
+
+PROCEDURE EmitS64 (rel: INTEGER);
+  VAR mag : CARDINAL;
+  BEGIN
+    IF rel >= 0 THEN EmitW64(VAL(LONGCARD, VAL(CARDINAL, rel)))
+    ELSE
+      IF rel = -2147483647 - 1 THEN mag := 80000000H
+      ELSE mag := VAL(CARDINAL, -rel)
+      END;
+      EmitW64(0FFFFFFFFFFFFFFFFH - VAL(LONGCARD, mag) + 1H)
+    END
+  END EmitS64;
+
+PROCEDURE WriteS64At (off: CARDINAL; rel: INTEGER);
+  VAR mag : CARDINAL;
+    bits : LONGCARD;
+    j : CARDINAL;
+  BEGIN
+    IF rel >= 0 THEN bits := VAL(LONGCARD, VAL(CARDINAL, rel))
+    ELSE
+      IF rel = -2147483647 - 1 THEN mag := 80000000H
+      ELSE mag := VAL(CARDINAL, -rel)
+      END;
+      bits := 0FFFFFFFFFFFFFFFFH - VAL(LONGCARD, mag) + 1H
+    END;
+    FOR j := 0 TO 7 DO
+      code[off + j] := CHR(VAL(CARDINAL, bits MOD 256));
+      bits := bits DIV 256
+    END
+  END WriteS64At;
+
+PROCEDURE EmitMag (mag: CARDINAL);
+  BEGIN
+    IF mag <= 15 THEN EmitOp(OPimm0 + mag)
+    ELSE EmitOp(OPimmW); EmitW64(VAL(LONGCARD, mag))
+    END
+  END EmitMag;
+
+PROCEDURE PushInt (v: INTEGER);
+  VAR mag : CARDINAL;
+  BEGIN
+    IF (v >= 0) & (v <= 15) THEN EmitOp(OPimm0 + VAL(CARDINAL, v))
+    ELSIF v < 0 THEN
+      IF v = -2147483647 - 1 THEN mag := 80000000H
+      ELSE mag := VAL(CARDINAL, -v)
+      END;
+      EmitOp(OPimm0); EmitMag(mag); EmitOp(OPsub)
+    ELSE EmitMag(VAL(CARDINAL, v))
+    END
+  END PushInt;
+
+PROCEDURE PushBits (b: LONGCARD);
+  BEGIN
+    EmitOp(OPimmW); EmitW64(b)
+  END PushBits;
+
+PROCEDURE EmitSlotB (op: CARDINAL; sl: INTEGER);
+  BEGIN
+    IF sl >= 0 THEN EmitOpB(op, VAL(CARDINAL, sl))
+    ELSE EmitOpB(op, 256 - VAL(CARDINAL, -sl))
+    END
+  END EmitSlotB;
+
+PROCEDURE LoadLocal (sl: INTEGER);
+  BEGIN
+    EmitSlotB(OPloadLocal, sl)
+  END LoadLocal;
+
+PROCEDURE StoreLocal (sl: INTEGER);
+  BEGIN
+    EmitSlotB(OPstoreLocal, sl)
+  END StoreLocal;
+
+PROCEDURE LoadIndir0;
+  BEGIN
+    EmitOp(OPloadIndir0)
+  END LoadIndir0;
+
+PROCEDURE StoreIndir0;
+  BEGIN
+    EmitOp(OPstoreIndir0)
+  END StoreIndir0;
+
+PROCEDURE LoadIndir;
+  BEGIN
+    EmitOp(OPloadIndir)
+  END LoadIndir;
+
+PROCEDURE StoreIndir;
+  BEGIN
+    EmitOp(OPstoreIndir)
+  END StoreIndir;
+
+PROCEDURE FrameAddr (sl: INTEGER; np: CARDINAL);
+  BEGIN
+    EmitOpB(OPloadOuterN, np);
+    IF sl >= 0 THEN EmitOpB(OPstkAddr, VAL(CARDINAL, sl))
+    ELSE
+      PushInt(sl * 8);
+      EmitOp(OPadd)
+    END
+  END FrameAddr;
+
+PROCEDURE NewLabel (): INTEGER;
+  BEGIN
+    IF nLab > MaxLab THEN Fail230(); RETURN 0 END;
+    labs[nLab] := -1;
+    INC(nLab);
+    RETURN VAL(INTEGER, nLab - 1)
+  END NewLabel;
+
+PROCEDURE DefLabel (id: INTEGER);
+  VAR i : CARDINAL;
+    rel : INTEGER;
+  BEGIN
+    IF (id < 0) OR (id >= VAL(INTEGER, nLab)) THEN RETURN END;
+    labs[id] := VAL(INTEGER, nCode);
+    i := 0;
+    WHILE i < nFix DO
+      IF fixs[i].lab = id THEN
+        rel := labs[id] - VAL(INTEGER, fixs[i].pos + 9);
+        WriteS64At(fixs[i].pos + 1, rel);
+        fixs[i].lab := -1
+      END;
+      INC(i)
+    END
+  END DefLabel;
+
+PROCEDURE Jump (op: CARDINAL; id: INTEGER);
+  VAR pos : CARDINAL;
+    j : CARDINAL;
+  BEGIN
+    pos := nCode;
+    EmitOp(op);
+    IF (id >= 0) & (id < VAL(INTEGER, nLab)) & (labs[id] >= 0) THEN
+      EmitS64(labs[id] - VAL(INTEGER, pos + 9))
+    ELSE
+      FOR j := 0 TO 7 DO EmitByte(0) END;
+      IF (nFix <= MaxFix) & (id >= 0) & (id < VAL(INTEGER, nLab)) THEN
+        fixs[nFix].pos := pos; fixs[nFix].lab := id; INC(nFix)
+      END
+    END
+  END Jump;
+
+PROCEDURE Jmp (id: INTEGER);
+  BEGIN
+    Jump(OPjp, id)
+  END Jmp;
+
+PROCEDURE Jz (id: INTEGER);
+  BEGIN
+    Jump(OPjz, id)
+  END Jz;
+
+PROCEDURE TempGlobal (): INTEGER;
+  VAR idx : INTEGER;
+  BEGIN
+    IF (nG > MaxGlb) OR (nGlb > MaxGlb) THEN Fail230(); RETURN 4 END;
+    idx := VAL(INTEGER, nGlb);
+    gBase[nG] := VAL(CARDINAL, idx);
+    gSize[nG] := 1;
+    INC(nG);
+    nGlb := nGlb + 1;
+    RETURN idx
+  END TempGlobal;
+
+PROCEDURE LoadTemp (t: INTEGER);
+  BEGIN
+    EmitOpB(OPloadGlb, VAL(CARDINAL, t))
+  END LoadTemp;
+
+PROCEDURE StoreTemp (t: INTEGER);
+  BEGIN
+    EmitOpB(OPstoreGlb, VAL(CARDINAL, t))
+  END StoreTemp;
+
+(* ---------------- literal parsing ---------------- *)
+
+PROCEDURE DigVal (ch: CHAR): INTEGER;
+  BEGIN
+    IF (ch >= "0") & (ch <= "9") THEN RETURN ORD(ch) - ORD("0") END;
+    IF (ch >= "A") & (ch <= "F") THEN RETURN ORD(ch) - ORD("A") + 10 END;
+    IF (ch >= "a") & (ch <= "f") THEN RETURN ORD(ch) - ORD("a") + 10 END;
+    RETURN -1
+  END DigVal;
+
+PROCEDURE ParseReal (s: ARRAY OF CHAR; VAR b: LONGCARD): BOOLEAN;
+  VAR i : CARDINAL;
+    neg, esign : BOOLEAN;
+    m, scale : REAL;
+    e, d : CARDINAL;
+    rv : RealView;
+  BEGIN
+    b := 0H;
+    i := 0; neg := FALSE;
+    IF s[0] = "-" THEN neg := TRUE; INC(i) END;
+    IF (s[i] < "0") OR (s[i] > "9") THEN RETURN FALSE END;
+    m := 0.0;
+    WHILE (s[i] >= "0") & (s[i] <= "9") DO
+      m := m * 10.0 + VAL(REAL, VAL(CARDINAL, ORD(s[i]) - ORD("0")));
+      INC(i)
+    END;
+    IF s[i] = "." THEN
+      INC(i);
+      IF (s[i] < "0") OR (s[i] > "9") THEN RETURN FALSE END;
+      scale := 10.0;
+      WHILE (s[i] >= "0") & (s[i] <= "9") DO
+        m := m + VAL(REAL, VAL(CARDINAL, ORD(s[i]) - ORD("0"))) / scale;
+        scale := scale * 10.0;
+        INC(i)
+      END
+    END;
+    IF (s[i] = "E") OR (s[i] = "e") THEN
+      INC(i); esign := FALSE;
+      IF s[i] = "+" THEN INC(i)
+      ELSIF s[i] = "-" THEN esign := TRUE; INC(i)
+      END;
+      IF (s[i] < "0") OR (s[i] > "9") THEN RETURN FALSE END;
+      e := 0;
+      WHILE (s[i] >= "0") & (s[i] <= "9") DO
+        d := VAL(CARDINAL, ORD(s[i]) - ORD("0"));
+        IF e <= 9999 THEN e := e * 10 + d END;
+        INC(i)
+      END;
+      WHILE e > 0 DO
+        IF esign THEN m := m / 10.0 ELSE m := m * 10.0 END;
+        DEC(e)
+      END
+    END;
+    IF s[i] # 0C THEN RETURN FALSE END;
+    IF neg THEN m := -m END;
+    rv.r := m;
+    b := rv.w;
+    RETURN TRUE
+  END ParseReal;
+
+(* ---------------- scopes, variables, constants ---------------- *)
+
+PROCEDURE PushScope (tag: ARRAY OF CHAR; pnum: INTEGER;
+                     depth: CARDINAL; isMod: BOOLEAN);
+  BEGIN
+    IF nScopes >= MaxScope THEN Fail230(); RETURN END;
+    StrCpy(scopes[nScopes].tag, tag);
+    scopes[nScopes].pnum := pnum;
+    scopes[nScopes].depth := depth;
+    scopes[nScopes].parent := curScope;
+    scopes[nScopes].isMod := isMod;
+    curScope := VAL(INTEGER, nScopes);
+    INC(nScopes)
+  END PushScope;
+
+PROCEDURE PopScope;
+  BEGIN
+    IF curScope < 0 THEN RETURN END;
+    curScope := scopes[curScope].parent
+  END PopScope;
+
+PROCEDURE EnterVar (name: ARRAY OF CHAR; kind, typ, slot: INTEGER;
+                    size: CARDINAL; depth: CARDINAL;
+                    global, isAddr: BOOLEAN);
+  BEGIN
+    IF nV >= MaxVar THEN Fail230(); RETURN END;
+    vars[nV].scope := curScope;
+    StrCpy(vars[nV].name, name);
+    vars[nV].kind := kind;
+    vars[nV].typ := typ;
+    vars[nV].slot := slot;
+    vars[nV].size := size;
+    vars[nV].depth := depth;
+    vars[nV].global := global;
+    vars[nV].isAddr := isAddr;
+    INC(nV)
+  END EnterVar;
+
+PROCEDURE FindVar (name: ARRAY OF CHAR): INTEGER;
+(* Innermost visible variable (scope chain walk). *)
+  VAR sc, i: INTEGER;
+  BEGIN
+    sc := curScope;
+    WHILE sc >= 0 DO
+      i := VAL(INTEGER, nV);
+      WHILE i > 0 DO
+        DEC(i);
+        IF (vars[i].scope = sc) & SymTab.Equal(vars[i].name, name) THEN
+          RETURN i
+        END
+      END;
+      sc := scopes[sc].parent
+    END;
+    RETURN -1
+  END FindVar;
+
+PROCEDURE FindModVar (mod, name: ARRAY OF CHAR): INTEGER;
+(* Innermost (highest scope index) variable of a module scope. *)
+  VAR i: INTEGER;
+    best: INTEGER;
+  BEGIN
+    best := -1;
+    i := 0;
+    WHILE VAL(CARDINAL, i) < nV DO
+      IF scopes[vars[i].scope].isMod
+         & SymTab.Equal(scopes[vars[i].scope].tag, mod)
+         & SymTab.Equal(vars[i].name, name) THEN
+        best := i
+      END;
+      INC(i)
+    END;
+    RETURN best
+  END FindModVar;
+
+PROCEDURE NoteConst (name: ARRAY OF CHAR; ck, ival: INTEGER;
+                     bits: LONGCARD; tx: ARRAY OF CHAR; isOk: BOOLEAN);
+  BEGIN
+    IF nC >= MaxCst THEN Fail230(); RETURN END;
+    csts[nC].scope := curScope;
+    StrCpy(csts[nC].name, name);
+    csts[nC].ck := ck;
+    csts[nC].ival := ival;
+    csts[nC].bits := bits;
+    StrCpy(csts[nC].text, tx);
+    csts[nC].ok := isOk;
+    INC(nC)
+  END NoteConst;
+
+PROCEDURE ConstFind (name: ARRAY OF CHAR): INTEGER;
+  VAR sc, i: INTEGER;
+  BEGIN
+    sc := curScope;
+    WHILE sc >= 0 DO
+      i := VAL(INTEGER, nC);
+      WHILE i > 0 DO
+        DEC(i);
+        IF (csts[i].scope = sc) & SymTab.Equal(csts[i].name, name) THEN
+          RETURN i
+        END
+      END;
+      sc := scopes[sc].parent
+    END;
+    RETURN -1
+  END ConstFind;
+
+PROCEDURE EvalConstInt (n: AST.Node; VAR v: INTEGER): BOOLEAN;
+  VAR a, b: INTEGER;
+    ci: INTEGER;
+  BEGIN
+    v := 0;
+    IF (n = NIL) OR ~ok THEN RETURN FALSE END;
+    IF n^.kind = AST.nkInt THEN
+      v := n^.num; RETURN TRUE
+    ELSIF n^.kind = AST.nkChar THEN
+      v := n^.num; RETURN TRUE
+    ELSIF n^.kind = AST.nkUn THEN
+      IF ~EvalConstInt(n^.left, a) THEN RETURN FALSE END;
+      IF n^.op = AST.opNeg THEN v := -a
+      ELSIF n^.op = AST.opPos THEN v := a
+      ELSE RETURN FALSE
+      END;
+      RETURN TRUE
+    ELSIF n^.kind = AST.nkBin THEN
+      IF ~EvalConstInt(n^.left, a) THEN RETURN FALSE END;
+      IF ~EvalConstInt(n^.right, b) THEN RETURN FALSE END;
+      IF n^.op = SymTab.OpAdd THEN v := a + b
+      ELSIF n^.op = SymTab.OpSub THEN v := a - b
+      ELSIF n^.op = SymTab.OpTimes THEN v := a * b
+      ELSIF n^.op = SymTab.OpDiv THEN
+        IF b = 0 THEN RETURN FALSE END;
+        v := a DIV b
+      ELSIF n^.op = SymTab.OpMod THEN
+        IF b = 0 THEN RETURN FALSE END;
+        v := a MOD b
+      ELSIF n^.op = SymTab.OpEq THEN v := ORD(a = b)
+      ELSIF (n^.op = SymTab.OpNeq1) OR (n^.op = SymTab.OpNeq2) THEN
+        v := ORD(a # b)
+      ELSIF n^.op = SymTab.OpLt THEN v := ORD(a < b)
+      ELSIF n^.op = SymTab.OpLe THEN v := ORD(a <= b)
+      ELSIF n^.op = SymTab.OpGt THEN v := ORD(a > b)
+      ELSIF n^.op = SymTab.OpGe THEN v := ORD(a >= b)
+      ELSIF n^.op = SymTab.OpAnd THEN v := ORD(ODD(a) & ODD(b))
+      ELSIF n^.op = SymTab.OpOr THEN v := ORD(ODD(a) OR ODD(b))
+      ELSE RETURN FALSE
+      END;
+      RETURN TRUE
+    ELSIF n^.kind = AST.nkName THEN
+      ci := ConstFind(n^.name);
+      IF (ci < 0) OR (csts[ci].ck # 0) THEN RETURN FALSE END;
+      v := csts[ci].ival;
+      RETURN TRUE
+    END;
+    RETURN FALSE
+  END EvalConstInt;
+
+PROCEDURE EvalConstBits (n: AST.Node; VAR b: LONGCARD): BOOLEAN;
+  VAR rv, r2: RealView;
+    a: INTEGER;
+    ci: INTEGER;
+  BEGIN
+    b := 0H;
+    IF (n = NIL) OR ~ok THEN RETURN FALSE END;
+    IF n^.kind = AST.nkReal THEN
+      RETURN ParseReal(n^.text, b)
+    ELSIF n^.kind = AST.nkUn THEN
+      IF ~EvalConstBits(n^.left, b) THEN RETURN FALSE END;
+      IF n^.op = AST.opNeg THEN
+        IF b DIV 8000000000000000H = 1 THEN
+          b := b - 8000000000000000H
+        ELSE b := b + 8000000000000000H
+        END
+      ELSIF n^.op # AST.opPos THEN RETURN FALSE
+      END;
+      RETURN TRUE
+    ELSIF n^.kind = AST.nkBin THEN
+      IF ~EvalConstBits(n^.left, rv.w) THEN RETURN FALSE END;
+      IF ~EvalConstBits(n^.right, r2.w) THEN RETURN FALSE END;
+      IF n^.op = SymTab.OpAdd THEN rv.r := rv.r + r2.r
+      ELSIF n^.op = SymTab.OpSub THEN rv.r := rv.r - r2.r
+      ELSIF n^.op = SymTab.OpTimes THEN rv.r := rv.r * r2.r
+      ELSIF n^.op = SymTab.OpSlash THEN
+        IF r2.r = 0.0 THEN RETURN FALSE END;
+        rv.r := rv.r / r2.r
+      ELSE RETURN FALSE
+      END;
+      b := rv.w;
+      RETURN TRUE
+    ELSIF n^.kind = AST.nkName THEN
+      ci := ConstFind(n^.name);
+      IF (ci < 0) OR (csts[ci].ck # 1) THEN RETURN FALSE END;
+      b := csts[ci].bits;
+      RETURN TRUE
+    ELSIF n^.kind = AST.nkInt THEN
+      IF ~EvalConstInt(n, a) THEN RETURN FALSE END;
+      rv.r := VAL(REAL, a);
+      b := rv.w;
+      RETURN TRUE
+    END;
+    RETURN FALSE
+  END EvalConstBits;
+
+PROCEDURE EvalConstText (n: AST.Node; VAR tx: ARRAY OF CHAR): BOOLEAN;
+  VAR ci: INTEGER;
+  BEGIN
+    tx[0] := 0C;
+    IF (n = NIL) OR ~ok THEN RETURN FALSE END;
+    IF n^.kind = AST.nkStr THEN
+      StrCpy(tx, n^.text);
+      RETURN TRUE
+    ELSIF n^.kind = AST.nkName THEN
+      ci := ConstFind(n^.name);
+      IF (ci < 0) OR (csts[ci].ck # 2) THEN RETURN FALSE END;
+      StrCpy(tx, csts[ci].text);
+      RETURN TRUE
+    END;
+    RETURN FALSE
+  END EvalConstText;
+
+(* ---------------- procedures ---------------- *)
+
+PROCEDURE OpenModule (name: ARRAY OF CHAR);
+  VAR i : CARDINAL;
+  BEGIN
+    StrCpy(modName, name);
+    nCode := 0;
+    nLab := 0; nFix := 0;
+    nG := 0; nGlb := 4;
+    nScopes := 0; curScope := -1;
+    nV := 0; nC := 0;
+    maxNum := 0; mainAddr := 0;
+    nInits := 0;
+    curDepth := 0; curProc := 0;
+    loopTop := 0;
+    ok := TRUE;
+    i := 0;
+    WHILE i <= MaxProc DO
+      procAddr[i] := -1;
+      procDepth[i] := 0;
+      procParent[i] := -1;
+      INC(i)
+    END
+  END OpenModule;
+
+PROCEDURE ProcEntry (num: INTEGER; nLoc: CARDINAL);
+  VAR k : CARDINAL;
+  BEGIN
+    IF (num >= 0) & (num <= MaxProc) THEN
+      procAddr[num] := VAL(INTEGER, nCode);
+      IF VAL(CARDINAL, num) > maxNum THEN
+        maxNum := VAL(CARDINAL, num)
+      END
+    END;
+    IF nLoc > 255 THEN k := 255 ELSE k := nLoc END;
+    EmitOpB(OPenter, 255 - k)
+  END ProcEntry;
+
+PROCEDURE Leave (nPar: CARDINAL; func: BOOLEAN);
+  BEGIN
+    IF nPar > 255 THEN nPar := 255 END;
+    IF func THEN EmitOpB(OPfctLeave, nPar)
+    ELSE EmitOpB(OPprocLeave, nPar)
+    END
+  END Leave;
+
+PROCEDURE CallProc (num: INTEGER);
+  BEGIN
+    EmitOpB(OPprocCall, VAL(CARDINAL, num))
+  END CallProc;
+
+PROCEDURE CallNested (num: INTEGER);
+  BEGIN
+    EmitOpB(OPnestedCall, VAL(CARDINAL, num))
+  END CallNested;
+
+PROCEDURE CallDisplay (num: INTEGER; np: CARDINAL);
+  BEGIN
+    EmitOpB(OPloadOuterN, np);
+    EmitOpB(OPcallFrame, VAL(CARDINAL, num))
+  END CallDisplay;
+
+PROCEDURE CallByRule (pnum: INTEGER);
+(* ED for frameless-parent callees, EC for direct children,
+   EE with display walk otherwise. *)
+  VAR pd, pp, np: INTEGER;
+  BEGIN
+    IF (pnum < 1) OR (pnum > MaxProc) THEN Fail230(); RETURN END;
+    pd := VAL(INTEGER, procDepth[pnum]);
+    pp := procParent[pnum];
+    IF pd <= 1 THEN CallProc(pnum)
+    ELSIF pp = curProc THEN CallNested(pnum)
+    ELSIF (pp < 0) OR (curProc = 0) THEN Fail230()
+    ELSE
+      np := VAL(INTEGER, curDepth) - 1 - VAL(INTEGER, procDepth[pp]);
+      IF np < 0 THEN Fail230(); RETURN END;
+      CallDisplay(pnum, VAL(CARDINAL, np))
+    END
+  END CallByRule;
+
+(* ---------------- image assembly ---------------- *)
+
+PROCEDURE Put32At (off: CARDINAL; v: CARDINAL);
+  BEGIN
+    img[off] := CHR(v MOD 256);
+    img[off + 1] := CHR((v DIV 256) MOD 256);
+    img[off + 2] := CHR((v DIV 65536) MOD 256);
+    img[off + 3] := CHR(v DIV 16777216)
+  END Put32At;
+
+PROCEDURE Put64At (off: CARDINAL; v: LONGCARD);
+  VAR j : CARDINAL;
+  BEGIN
+    FOR j := 0 TO 7 DO
+      img[off + j] := CHR(VAL(CARDINAL, v MOD 256));
+      v := v DIV 256
+    END
+  END Put64At;
+
+PROCEDURE PutS64At (off, addr, slot: CARDINAL);
+  VAR mag : LONGCARD;
+    bits : LONGCARD;
+    j : CARDINAL;
+  BEGIN
+    IF addr >= slot THEN bits := VAL(LONGCARD, addr - slot)
+    ELSE
+      mag := VAL(LONGCARD, slot - addr);
+      bits := 0FFFFFFFFFFFFFFFFH - mag + 1H
+    END;
+    FOR j := 0 TO 7 DO
+      img[off + j] := CHR(VAL(CARDINAL, bits MOD 256));
+      bits := bits DIV 256
+    END
+  END PutS64At;
+
+PROCEDURE EmitPrint;
+  VAR l1, l2 : INTEGER;
+  BEGIN
+    EmitOpB(OPenter, 250);
+    EmitOpB(OPimmB, 16); EmitOp(0D2H); EmitOpB(OPstoreGlb, 0);
+    EmitOp(OPimm0); EmitOp(OPimm0);
+    EmitOpB(OPstoreGlb, 1); EmitOpB(OPstoreGlb, 2);
+    EmitOp(03H);
+    l1 := NewLabel(); DefLabel(l1);
+    EmitOpB(OPloadGlb, 1); EmitOp(OPinc); EmitOpB(OPstoreGlb, 1);
+    EmitOp(OPdup); EmitOpB(OPimmB, 10); EmitOp(OPumod);
+    EmitOp(OPswap); EmitOpB(OPimmB, 10); EmitOp(OPudiv);
+    EmitOp(OPdup); EmitOp(OPaeq0);
+    Jz(l1);
+    EmitOp(OPext); EmitOp(SUBdrop);
+    l2 := NewLabel(); DefLabel(l2);
+    EmitOpB(OPimmB, 48); EmitOp(OPadd); EmitOpB(OPstoreGlb, 3);
+    EmitOpB(OPloadGlb, 0); EmitOpB(OPloadGlb, 2); EmitOp(OPadd);
+    EmitOp(OPimm0); EmitOpB(OPloadGlb, 3); EmitOp(1DH);
+    EmitOpB(OPloadGlb, 2); EmitOp(OPinc); EmitOpB(OPstoreGlb, 2);
+    EmitOpB(OPloadGlb, 1); EmitOp(OPdec); EmitOpB(OPstoreGlb, 1);
+    EmitOpB(OPloadGlb, 1); EmitOp(OPaeq0);
+    Jz(l2);
+    EmitOpB(OPloadGlb, 0); EmitOpB(OPloadGlb, 2); EmitOp(OPadd);
+    EmitOp(OPimm0); EmitOp(OPimm0); EmitOp(1DH);
+    EmitOpB(OPloadGlb, 0);
+    EmitOpB(OPimmB, 1); EmitOp(OPsys);
+    EmitOpB(OPimmB, 3); EmitOp(0D2H);
+    EmitOp(OPdup); EmitOp(OPimm0); EmitOpB(OPimmB, 13); EmitOp(1DH);
+    EmitOp(OPdup); EmitOpB(OPimmB, 1); EmitOpB(OPimmB, 10); EmitOp(1DH);
+    EmitOp(OPdup); EmitOpB(OPimmB, 2); EmitOp(OPimm0); EmitOp(1DH);
+    EmitOpB(OPimmB, 1); EmitOp(OPsys);
+    EmitOpB(OPfctLeave, 0)
+  END EmitPrint;
+
+PROCEDURE EndModule;
+  VAR i : CARDINAL;
+    codeOff, p1, pt, prNum, tabBytes : CARDINAL;
+    k : CARDINAL;
+    addr : CARDINAL;
+    exitIdx : INTEGER;
+    sum : CARDINAL;
+    fname : ARRAY [0 .. 127] OF CHAR;
+    f : FileIO.File;
+    ch : CHAR;
+  BEGIN
+    prNum := maxNum + 1;
+    exitIdx := -1;
+    i := 0;
+    WHILE i < VAL(CARDINAL, nV) DO
+      IF (vars[i].scope = 0) & (vars[i].kind = SymTab.KindVar)
+         & SymTab.Equal(vars[i].name, "ExitCode")
+         & SymTab.IsIntFamily(vars[i].typ) THEN
+        exitIdx := vars[i].slot
+      END;
+      INC(i)
+    END;
+    IF exitIdx >= 0 THEN
+      EmitOpB(OPloadGlb, VAL(CARDINAL, exitIdx));
+      EmitOpB(OPprocCall, prNum);
+      EmitOp(OPext); EmitOp(SUBdrop)
+    END;
+    EmitOp(OPend);
+    p1 := nCode;
+    EmitPrint;
+    i := 0;
+    WHILE i <= MaxImg DO img[i] := 0C; INC(i) END;
+    img[0] := "M"; img[1] := "C"; img[2] := "6"; img[3] := "4";
+    i := 0;
+    WHILE (i < 16) & (modName[i] # 0C) DO
+      img[HeadSize + DName + i] := modName[i]; INC(i)
+    END;
+    img[HeadSize + DFlags] := CHR(4);
+    img[HeadSize + DVarCount] := CHR(nG MOD 256);
+    img[HeadSize + DDepCount] := 0C;
+    codeOff := DVarSizes + nG * 8;
+    i := 0;
+    WHILE i < nCode DO
+      img[HeadSize + codeOff + i] := code[i]; INC(i)
+    END;
+    pt := codeOff + nCode;
+    tabBytes := (maxNum + 2) * 8;
+    Put64At(HeadSize + DProcs, VAL(LONGCARD, pt));
+    k := 0;
+    WHILE k <= maxNum + 1 DO
+      IF k = 0 THEN addr := codeOff + mainAddr
+      ELSIF k <= maxNum THEN
+        IF procAddr[k] < 0 THEN addr := pt + k * 8
+        ELSE addr := VAL(CARDINAL, procAddr[k]) + codeOff
+        END
+      ELSE addr := codeOff + p1
+      END;
+      PutS64At(HeadSize + pt + k * 8, addr, pt + k * 8);
+      INC(k)
+    END;
+    i := 0;
+    WHILE i < nG DO
+      Put64At(HeadSize + DVarSizes + i * 8,
+              VAL(LONGCARD, gSize[i] * 8));
+      INC(i)
+    END;
+    sum := 0;
+    i := HeadSize;
+    WHILE i < HeadSize + pt + tabBytes DO
+      IF ~((i >= 352) & (i <= 355)) THEN
+        sum := sum + ORD(img[i])
+      END;
+      INC(i)
+    END;
+    Put32At(HeadSize + DChecksum, sum);
+    fname[0] := 0C;
+    StrCpy(fname, modName);
+    i := StrLen(fname);
+    fname[i] := "."; fname[i + 1] := "M"; fname[i + 2] := "C";
+    fname[i + 3] := "4"; fname[i + 4] := 0C;
+    FileIO.Open(f, fname, TRUE);
+    IF FileIO.Okay THEN
+      i := 0;
+      WHILE i < HeadSize + pt + tabBytes DO
+        ch := img[i];
+        FileIO.Write(f, ch);
+        INC(i)
+      END;
+      FileIO.Close(f)
+    END
+  END EndModule;
+
+(* ---------------- expression lowering ---------------- *)
+
+
+PROCEDURE IsRealTyp (t: INTEGER): BOOLEAN;
+  BEGIN
+    RETURN SymTab.ClassOf(t) = SymTab.ClReal
+  END IsRealTyp;
+
+PROCEDURE EmitLoadEntry (vi: INTEGER);
+(* Pushes a scalar variable value. *)
+  VAR np: CARDINAL;
+  BEGIN
+    IF (vi < 0) OR (vi >= VAL(INTEGER, nV)) THEN
+      Fail230(); PushInt(0); RETURN
+    END;
+    IF vars[vi].global THEN
+      EmitOpB(OPloadGlb, VAL(CARDINAL, vars[vi].slot))
+    ELSIF vars[vi].depth = curDepth THEN
+      IF vars[vi].isAddr THEN
+        LoadLocal(vars[vi].slot); LoadIndir0
+      ELSE LoadLocal(vars[vi].slot)
+      END
+    ELSE
+      np := curDepth - 1 - vars[vi].depth;
+      FrameAddr(vars[vi].slot, np);
+      LoadIndir;
+      IF vars[vi].isAddr THEN LoadIndir0 END
+    END
+  END EmitLoadEntry;
+
+PROCEDURE EmitStoreEntry (vi: INTEGER);
+(* Pops into a scalar variable (direct only). *)
+  BEGIN
+    IF (vi < 0) OR (vi >= VAL(INTEGER, nV)) THEN
+      Fail230(); EmitOp(OPext); EmitOp(SUBdrop); RETURN
+    END;
+    IF vars[vi].global THEN
+      EmitOpB(OPstoreGlb, VAL(CARDINAL, vars[vi].slot))
+    ELSIF vars[vi].depth = curDepth THEN
+      IF vars[vi].isAddr THEN StoreIndir0
+      ELSE StoreLocal(vars[vi].slot)
+      END
+    ELSE Fail230()
+    END
+  END EmitStoreEntry;
+
+PROCEDURE EmitAddrEntry (vi: INTEGER);
+(* Pushes a variable address (for VAR actuals and indirect stores). *)
+  VAR np: CARDINAL;
+  BEGIN
+    IF (vi < 0) OR (vi >= VAL(INTEGER, nV)) THEN
+      Fail230(); PushInt(0); RETURN
+    END;
+    IF vars[vi].global THEN
+      EmitOpB(OPglobalAddr, VAL(CARDINAL, vars[vi].slot))
+    ELSIF vars[vi].depth = curDepth THEN
+      IF vars[vi].isAddr THEN LoadLocal(vars[vi].slot)
+      ELSE EmitSlotB(OPlocalAddr, vars[vi].slot)
+      END
+    ELSE
+      np := curDepth - 1 - vars[vi].depth;
+      FrameAddr(vars[vi].slot, np);
+      IF vars[vi].isAddr THEN LoadIndir END
+    END
+  END EmitAddrEntry;
+
+
+PROCEDURE ResolveName (n: AST.Node): INTEGER;
+(* Variable entry for a designator head (tag-aware). *)
+  BEGIN
+    IF n = NIL THEN Fail230(); RETURN -1 END;
+    IF n^.tag[0] # 0C THEN
+      RETURN FindModVar(n^.tag, n^.name)
+    END;
+    RETURN FindVar(n^.name)
+  END ResolveName;
+
+PROCEDURE EmitAddr (n: AST.Node);
+(* Pushes the address of a designator (byte address). *)
+  VAR vi: INTEGER;
+    arrTyp, elemTyp: INTEGER;
+    ix: AST.Node;
+    lo: INTEGER;
+    eb: CARDINAL;
+  BEGIN
+    IF (n = NIL) OR ~ok THEN RETURN END;
+    IF n^.kind = AST.nkName THEN
+      vi := ResolveName(n);
+      IF vi < 0 THEN Fail230(); PushInt(0); RETURN END;
+      EmitAddrEntry(vi)
+    ELSIF n^.kind = AST.nkField THEN
+      IF n^.tag[0] # 0C THEN
+        vi := FindModVar(n^.tag, n^.name);
+        IF vi < 0 THEN Fail230(); PushInt(0); RETURN END;
+        EmitOpB(OPglobalAddr, VAL(CARDINAL, vars[vi].slot))
+      ELSE
+        EmitAddr(n^.left);
+        IF SymTab.FieldOffset(n^.left^.typ, n^.name) > 0 THEN
+          PushInt(SymTab.FieldOffset(n^.left^.typ, n^.name) * 8);
+          EmitOp(OPadd)
+        END
+      END
+    ELSIF n^.kind = AST.nkIndex THEN
+      EmitAddr(n^.left);
+      arrTyp := n^.left^.typ;
+      ix := n^.right;
+      WHILE (ix # NIL) & ok DO
+        elemTyp := SymTab.ArrayElem(arrTyp);
+        lo := SymTab.ArrayLo(arrTyp);
+        eb := SymTab.TypeSlots(elemTyp) * 8;
+        EmitExpr(ix);
+        PushInt(lo);
+        EmitOp(OPsub);
+        PushInt(VAL(INTEGER, eb));
+        EmitOp(OPumul);
+        EmitOp(OPadd);
+        arrTyp := elemTyp;
+        ix := ix^.next
+      END
+    ELSIF n^.kind = AST.nkDeref THEN
+      EmitExpr(n^.left)
+    ELSE Fail230()
+    END
+  END EmitAddr;
+
+PROCEDURE PushConstName (n: AST.Node);
+(* Pushes a CONST value (inlined; strings handled by caller path). *)
+  VAR ci: INTEGER;
+  BEGIN
+    ci := ConstFind(n^.name);
+    IF (ci < 0) OR ~csts[ci].ok THEN
+      IF SymTab.Equal(n^.name, "NIL") THEN PushInt(0)
+      ELSE Fail230(); PushInt(0)
+      END;
+      RETURN
+    END;
+    IF csts[ci].ck = 0 THEN PushInt(csts[ci].ival)
+    ELSIF csts[ci].ck = 1 THEN PushBits(csts[ci].bits)
+    ELSE Fail230(); PushBits(0H)
+    END
+  END PushConstName;
+
+PROCEDURE ConstTextOf (n: AST.Node; VAR tx: ARRAY OF CHAR): BOOLEAN;
+(* TRUE + text when n names a defined string CONST. *)
+  VAR ci: INTEGER;
+  BEGIN
+    tx[0] := 0C;
+    IF (n = NIL) OR (n^.kind # AST.nkName) THEN RETURN FALSE END;
+    ci := ConstFind(n^.name);
+    IF (ci < 0) OR ~csts[ci].ok OR (csts[ci].ck # 2) THEN
+      RETURN FALSE
+    END;
+    StrCpy(tx, csts[ci].text);
+    RETURN TRUE
+  END ConstTextOf;
+
+PROCEDURE EmitCall (callee, actuals: AST.Node; pnum: INTEGER;
+                    wantValue: BOOLEAN);
+  VAR ts: ARRAY [0 .. 63] OF INTEGER;
+    n, i: CARDINAL;
+    a: AST.Node;
+    ft: INTEGER;
+    fv: BOOLEAN;
+    ret: INTEGER;
+  BEGIN
+    IF (pnum < 1) OR (pnum > MaxProc) THEN Fail230(); RETURN END;
+    n := 0;
+    a := actuals;
+    WHILE (a # NIL) & (n <= 63) & ok DO
+      ft := SymTab.ParamTypeByNum(pnum, n);
+      fv := SymTab.ParamIsVarByNum(pnum, n);
+      ts[n] := TempGlobal();
+      IF fv THEN EmitAddr(a)
+      ELSE
+        EmitExpr(a);
+        IF IsRealTyp(ft) & ~IsRealTyp(a^.typ) THEN
+          EmitOp(OPintToLong); EmitOp(OPlongToReal)
+        END
+      END;
+      StoreTemp(ts[n]);
+      INC(n);
+      a := a^.next
+    END;
+    IF a # NIL THEN Fail230(); RETURN END;
+    i := n;
+    WHILE i > 0 DO
+      DEC(i);
+      LoadTemp(ts[i])
+    END;
+    CallByRule(pnum);
+    ret := SymTab.ProcRetByNum(pnum);
+    IF ~wantValue & (ret # SymTab.InvalidType) THEN
+      EmitOp(OPext); EmitOp(SUBdrop)
+    END
+  END EmitCall;
+
+PROCEDURE EmitExpr (n: AST.Node);
+  VAR vi: INTEGER;
+    isR: BOOLEAN;
+    t: INTEGER;
+    b: LONGCARD;
+  BEGIN
+    IF (n = NIL) OR ~ok THEN RETURN END;
+    t := n^.typ;
+    IF n^.kind = AST.nkInt THEN PushInt(n^.num)
+    ELSIF n^.kind = AST.nkChar THEN PushInt(n^.num)
+    ELSIF n^.kind = AST.nkReal THEN
+      IF ~ParseReal(n^.text, b) THEN Fail230(); PushBits(0H)
+      ELSE PushBits(b)
+      END
+    ELSIF n^.kind = AST.nkStr THEN Fail230()
+    ELSIF n^.kind = AST.nkName THEN
+      vi := ResolveName(n);
+      IF vi >= 0 THEN EmitLoadEntry(vi)
+      ELSE PushConstName(n)
+      END
+    ELSIF n^.kind = AST.nkBin THEN
+      isR := IsRealTyp(t);
+      IF (SymTab.ClassOf(n^.left^.typ) = SymTab.ClArray)
+         OR (SymTab.ClassOf(n^.right^.typ) = SymTab.ClArray) THEN
+        Fail230(); PushInt(0); RETURN
+      END;
+      EmitExpr(n^.left);
+      EmitExpr(n^.right);
+      IF (n^.op = SymTab.OpEq) OR (n^.op = SymTab.OpNeq1)
+         OR (n^.op = SymTab.OpNeq2) OR (n^.op = SymTab.OpLt)
+         OR (n^.op = SymTab.OpLe) OR (n^.op = SymTab.OpGt)
+         OR (n^.op = SymTab.OpGe) THEN
+        isR := IsRealTyp(n^.left^.typ)
+      END;
+      IF n^.op = SymTab.OpAdd THEN
+        IF isR THEN EmitOp(OPrAdd) ELSE EmitOp(OPadd) END
+      ELSIF n^.op = SymTab.OpSub THEN
+        IF isR THEN EmitOp(OPrSub) ELSE EmitOp(OPsub) END
+      ELSIF n^.op = SymTab.OpTimes THEN
+        IF isR THEN EmitOp(OPrMul) ELSE EmitOp(OPumul) END
+      ELSIF n^.op = SymTab.OpSlash THEN
+        IF isR THEN EmitOp(OPrDiv) ELSE EmitOp(OPidiv) END
+      ELSIF n^.op = SymTab.OpDiv THEN EmitOp(OPidiv)
+      ELSIF n^.op = SymTab.OpMod THEN
+        vi := TempGlobal();
+        StoreTemp(vi);
+        EmitOp(OPdup);
+        LoadTemp(vi);
+        EmitOp(OPidiv);
+        LoadTemp(vi);
+        EmitOp(OPimul);
+        EmitOp(OPsub)
+      ELSIF n^.op = SymTab.OpOr THEN EmitOp(OPor)
+      ELSIF n^.op = SymTab.OpAnd THEN EmitOp(OPand)
+      ELSIF n^.op = SymTab.OpEq THEN
+        IF isR THEN
+          EmitOp(OPrCmp); EmitOp(OPor); EmitOp(OPnot)
+        ELSE EmitOp(OPeq)
+        END
+      ELSIF (n^.op = SymTab.OpNeq1) OR (n^.op = SymTab.OpNeq2) THEN
+        IF isR THEN EmitOp(OPrCmp); EmitOp(OPor)
+        ELSE EmitOp(OPne)
+        END
+      ELSIF n^.op = SymTab.OpLt THEN
+        IF isR THEN
+          EmitOp(OPswap); EmitOp(OPext); EmitOp(SUBdrop)
+        ELSE EmitOp(OPilt)
+        END
+      ELSIF n^.op = SymTab.OpLe THEN
+        IF isR THEN
+          EmitOp(OPext); EmitOp(SUBdrop); EmitOp(OPnot)
+        ELSE EmitOp(OPile)
+        END
+      ELSIF n^.op = SymTab.OpGt THEN
+        IF isR THEN EmitOp(OPext); EmitOp(SUBdrop)
+        ELSE EmitOp(OPigt)
+        END
+      ELSIF n^.op = SymTab.OpGe THEN
+        IF isR THEN
+          EmitOp(OPswap); EmitOp(OPext); EmitOp(SUBdrop);
+          EmitOp(OPnot)
+        ELSE EmitOp(OPige)
+        END
+      ELSIF n^.op = SymTab.OpIn THEN EmitOp(OPbitIn)
+      ELSE Fail230()
+      END
+    ELSIF n^.kind = AST.nkUn THEN
+      EmitExpr(n^.left);
+      IF n^.op = AST.opNeg THEN
+        IF IsRealTyp(t) THEN
+          PushBits(0H); EmitOp(OPswap); EmitOp(OPrSub)
+        ELSE
+          EmitOp(OPimm0); EmitOp(OPswap); EmitOp(OPsub)
+        END
+      ELSIF n^.op = AST.opNot THEN EmitOp(OPnot)
+      END
+    ELSIF (n^.kind = AST.nkField) OR (n^.kind = AST.nkIndex)
+       OR (n^.kind = AST.nkDeref) THEN
+      IF SymTab.TypeSlots(t) # 1 THEN Fail230(); PushInt(0); RETURN END;
+      EmitAddr(n);
+      LoadIndir
+    ELSIF n^.kind = AST.nkCallExpr THEN
+      EmitCall(n^.left, n^.right, n^.num, TRUE)
+    ELSE Fail230()
+    END
+  END EmitExpr;
+
+PROCEDURE EmitStrCopy (dst: AST.Node; tx: ARRAY OF CHAR);
+(* dst[i] := char slots + NUL terminator for a literal. *)
+  VAR L, i: CARDINAL;
+    q: CHAR;
+  BEGIN
+    L := StrLen(tx);
+    IF L < 2 THEN Fail230(); RETURN END;
+    q := tx[0];
+    i := 1;
+    WHILE (i < L) & (tx[i] # q) & (tx[i] # 0C) DO
+      EmitAddr(dst);
+      PushInt(VAL(INTEGER, i - 1) * 8);
+      EmitOp(OPadd);
+      PushInt(ORD(tx[i]));
+      StoreIndir0;
+      INC(i)
+    END;
+    EmitAddr(dst);
+    PushInt(VAL(INTEGER, i - 1) * 8);
+    EmitOp(OPadd);
+    PushInt(0);
+    StoreIndir0
+  END EmitStrCopy;
+
+PROCEDURE EmitAssign (dst, src: AST.Node);
+  VAR dt, st: INTEGER;
+    sz: CARDINAL;
+    vi: INTEGER;
+    tx: ARRAY [0 .. 63] OF CHAR;
+  BEGIN
+    IF (dst = NIL) OR (src = NIL) OR ~ok THEN RETURN END;
+    dt := dst^.typ; st := src^.typ;
+    IF SymTab.TypeSlots(dt) > 1 THEN
+      IF src^.kind = AST.nkStr THEN
+        EmitStrCopy(dst, src^.text)
+      ELSIF (src^.kind = AST.nkName) & ConstTextOf(src, tx) THEN
+        EmitStrCopy(dst, tx)
+      ELSIF SymTab.TypeSlots(st) > 1 THEN
+        EmitAddr(dst);
+        EmitAddr(src);
+        sz := SymTab.TypeSlots(dt) * 8;
+        PushInt(VAL(INTEGER, sz));
+        EmitOp(OPcopyBlock)
+      ELSE Fail230()
+      END;
+      RETURN
+    END;
+    IF (dst^.kind = AST.nkName) THEN
+      vi := ResolveName(dst);
+      IF (vi >= 0)
+         & (vars[vi].global OR (vars[vi].depth = curDepth)) THEN
+        IF ~vars[vi].global & vars[vi].isAddr THEN
+          LoadLocal(vars[vi].slot)
+        END;
+        EmitExpr(src);
+        IF IsRealTyp(dt) & ~IsRealTyp(st) THEN
+          EmitOp(OPintToLong); EmitOp(OPlongToReal)
+        END;
+        EmitStoreEntry(vi);
+        RETURN
+      END
+    END;
+    EmitAddr(dst);
+    EmitExpr(src);
+    IF IsRealTyp(dt) & ~IsRealTyp(st) THEN
+      EmitOp(OPintToLong); EmitOp(OPlongToReal)
+    END;
+    StoreIndir0
+  END EmitAssign;
+
+
+PROCEDURE EmitStoreName (n: AST.Node);
+  VAR vi: INTEGER;
+  BEGIN
+    vi := ResolveName(n);
+    IF vi < 0 THEN
+      Fail230();
+      EmitOp(OPext); EmitOp(SUBdrop);
+      RETURN
+    END;
+    EmitStoreEntry(vi)
+  END EmitStoreName;
+
+(* ---------------- statement lowering ---------------- *)
+
+
+PROCEDURE EmitStmts (n: AST.Node);
+  BEGIN
+    WHILE (n # NIL) & ok DO
+      EmitStmt(n);
+      n := n^.next
+    END
+  END EmitStmts;
+
+PROCEDURE EmitStmt (n: AST.Node);
+  VAR l1, l2: INTEGER;
+  BEGIN
+    IF (n = NIL) OR ~ok THEN RETURN END;
+    IF n^.kind = AST.nkAssign THEN
+      EmitAssign(n^.left, n^.right)
+    ELSIF n^.kind = AST.nkCall THEN
+      EmitCall(n^.left, n^.right, n^.num, FALSE)
+    ELSIF n^.kind = AST.nkIf THEN
+      l1 := NewLabel(); l2 := NewLabel();
+      EmitExpr(n^.left);
+      Jz(l1);
+      EmitStmts(n^.right);
+      Jmp(l2);
+      DefLabel(l1);
+      EmitStmts(n^.extra);
+      DefLabel(l2)
+    ELSIF n^.kind = AST.nkWhile THEN
+      l1 := NewLabel(); l2 := NewLabel();
+      DefLabel(l1);
+      EmitExpr(n^.left);
+      Jz(l2);
+      EmitStmts(n^.right);
+      Jmp(l1);
+      DefLabel(l2)
+    ELSIF n^.kind = AST.nkRepeat THEN
+      l1 := NewLabel();
+      DefLabel(l1);
+      EmitStmts(n^.left);
+      EmitExpr(n^.right);
+      Jz(l1)
+    ELSIF n^.kind = AST.nkLoop THEN
+      l1 := NewLabel(); l2 := NewLabel();
+      IF loopTop > MaxLoop THEN Fail230(); RETURN END;
+      loopSt[loopTop] := l2; INC(loopTop);
+      DefLabel(l1);
+      EmitStmts(n^.left);
+      Jmp(l1);
+      DefLabel(l2);
+      DEC(loopTop)
+    ELSIF n^.kind = AST.nkExit THEN
+      IF loopTop = 0 THEN Fail230(); RETURN END;
+      Jmp(loopSt[loopTop - 1])
+    ELSIF n^.kind = AST.nkFor THEN EmitFor(n)
+    ELSIF n^.kind = AST.nkReturn THEN
+      IF curProc < 1 THEN Fail230(); RETURN END;
+      IF n^.left # NIL THEN EmitExpr(n^.left) END;
+      IF SymTab.ProcRetByNum(curProc) = SymTab.InvalidType THEN
+        Leave(SymTab.ProcNParByNum(curProc), FALSE)
+      ELSE
+        Leave(SymTab.ProcNParByNum(curProc), TRUE)
+      END
+    ELSE Fail230()
+    END
+  END EmitStmt;
+
+(* ---------------- declaration walking ---------------- *)
+
+
+
+PROCEDURE AllocGlobal (size: CARDINAL): INTEGER;
+  BEGIN
+    IF (nG > MaxGlb) OR (nGlb + size - 1 > MaxGlb) THEN
+      Fail230(); RETURN 4
+    END;
+    gBase[nG] := nGlb;
+    gSize[nG] := size;
+    INC(nG);
+    nGlb := nGlb + size;
+    RETURN VAL(INTEGER, gBase[nG - 1])
+  END AllocGlobal;
+
+PROCEDURE EmitDecls (n: AST.Node);
+  VAR sz: CARDINAL;
+    sl: INTEGER;
+  BEGIN
+    WHILE (n # NIL) & ok DO
+      IF n^.kind = AST.nkVar THEN
+        IF scopes[curScope].isMod THEN
+          sz := SymTab.TypeSlots(n^.typ);
+          IF sz = 0 THEN sz := 1 END;
+          sl := AllocGlobal(sz);
+          EnterVar(n^.name, SymTab.KindVar, n^.typ, sl, sz, 0,
+                   TRUE, FALSE)
+        END
+        (* proc-scope vars are laid out by LayoutLocals *)
+      ELSIF n^.kind = AST.nkProc THEN
+        EmitProc(n)
+      ELSIF (n^.kind = AST.nkConst) OR (n^.kind = AST.nkTypeDecl)
+         OR (n^.kind = AST.nkImport) OR (n^.kind = AST.nkExport)
+         OR (n^.kind = AST.nkParam) OR (n^.kind = AST.nkMember) THEN
+        (* no code; consts fold via NoteDeclConst *)
+        IF n^.kind = AST.nkConst THEN NoteDeclConst(n) END
+      ELSIF n^.kind = AST.nkModule THEN
+        EmitModuleDecl(n)
+      ELSE Fail230()
+      END;
+      n := n^.next
+    END
+  END EmitDecls;
+
+
+PROCEDURE NoteDeclConst (n: AST.Node);
+(* Records a foldable CONST for inline use (ok=FALSE when dynamic). *)
+  VAR v: INTEGER;
+    b: LONGCARD;
+    tx: ARRAY [0 .. 63] OF CHAR;
+    cls: INTEGER;
+  BEGIN
+    cls := SymTab.ClassOf(n^.typ);
+    IF cls = SymTab.ClReal THEN
+      IF EvalConstBits(n^.left, b) THEN
+        NoteConst(n^.name, 1, 0, b, "", TRUE)
+      ELSE NoteConst(n^.name, 1, 0, 0H, "", FALSE)
+      END
+    ELSIF cls = SymTab.ClStr THEN
+      IF EvalConstText(n^.left, tx) THEN
+        NoteConst(n^.name, 2, 0, 0H, tx, TRUE)
+      ELSE NoteConst(n^.name, 2, 0, 0H, "", FALSE)
+      END
+    ELSE
+      IF EvalConstInt(n^.left, v) THEN
+        NoteConst(n^.name, 0, v, 0H, "", TRUE)
+      ELSE NoteConst(n^.name, 0, 0, 0H, "", FALSE)
+      END
+    END
+  END NoteDeclConst;
+
+PROCEDURE EmitProc (n: AST.Node);
+  VAR pnum, saveProc: INTEGER;
+    saveDepth: CARDINAL;
+    p: AST.Node;
+    prm: CARDINAL;
+    loc: INTEGER;
+    blk: AST.Node;
+    endL: INTEGER;
+  BEGIN
+    IF (n = NIL) OR ~ok THEN RETURN END;
+    pnum := n^.num;
+    IF (pnum < 1) OR (pnum > MaxProc) THEN Fail230(); RETURN END;
+    saveProc := curProc;
+    saveDepth := curDepth;
+    procDepth[pnum] := curDepth + 1;
+    IF curProc = 0 THEN procParent[pnum] := -1
+    ELSE procParent[pnum] := curProc
+    END;
+    curProc := pnum;
+    curDepth := curDepth + 1;
+    PushScope("", pnum, curDepth, FALSE);
+    endL := NewLabel();
+    Jmp(endL);
+    prm := 3;
+    p := n^.left;
+    WHILE (p # NIL) & ok DO
+      EnterVar(p^.name, SymTab.KindParam, p^.typ,
+               VAL(INTEGER, prm), 1, curDepth, FALSE,
+               p^.op # 0);
+      INC(prm);
+      p := p^.next
+    END;
+    blk := n^.right;
+    loc := 1;
+    IF (blk # NIL) & (blk^.left # NIL) THEN
+      loc := LayoutLocals(blk^.left, 1)
+    END;
+    ProcEntry(pnum, VAL(CARDINAL, loc - 1));
+    IF blk # NIL THEN
+      EmitDecls(blk^.left);
+      EmitStmts(blk^.right);
+      IF SymTab.ProcRetByNum(pnum) # SymTab.InvalidType THEN
+        PushInt(0)
+      END;
+      Leave(SymTab.ProcNParByNum(pnum),
+            SymTab.ProcRetByNum(pnum) # SymTab.InvalidType)
+    ELSE
+      Leave(SymTab.ProcNParByNum(pnum), FALSE)
+    END;
+    DefLabel(endL);
+    PopScope;
+    curProc := saveProc;
+    curDepth := saveDepth
+  END EmitProc;
+
+
+PROCEDURE LayoutLocals (n: AST.Node; sl: INTEGER): INTEGER;
+(* Assigns negative frame slots to VAR decls; returns next free. *)
+  VAR sz: CARDINAL;
+  BEGIN
+    WHILE (n # NIL) & ok DO
+      IF n^.kind = AST.nkVar THEN
+        sz := SymTab.TypeSlots(n^.typ);
+        IF sz = 0 THEN sz := 1 END;
+        EnterVar(n^.name, SymTab.KindVar, n^.typ, -sl, sz,
+                 curDepth, FALSE, FALSE);
+        sl := sl + VAL(INTEGER, sz)
+      ELSIF (n^.kind = AST.nkConst) OR (n^.kind = AST.nkTypeDecl)
+         OR (n^.kind = AST.nkImport) OR (n^.kind = AST.nkExport)
+         OR (n^.kind = AST.nkParam) OR (n^.kind = AST.nkMember) THEN
+      ELSIF (n^.kind = AST.nkProc) OR (n^.kind = AST.nkModule) THEN
+      ELSE Fail230()
+      END;
+      n := n^.next
+    END;
+    RETURN sl
+  END LayoutLocals;
+
+PROCEDURE EmitModuleDecl (n: AST.Node);
+  VAR blk: AST.Node;
+    inum: INTEGER;
+    endL: INTEGER;
+  BEGIN
+    IF (n = NIL) OR ~ok THEN RETURN END;
+    PushScope(n^.name, -1, curDepth, TRUE);
+    EmitDecls(n^.left);
+    blk := n^.right;
+    IF (blk # NIL) & (blk^.right # NIL) THEN
+      IF (nextInit > 63) OR (nInits >= MaxInit) THEN
+        Fail230(); PopScope; RETURN
+      END;
+      inum := VAL(INTEGER, nextInit);
+      INC(nextInit);
+      IF VAL(CARDINAL, inum) > maxNum THEN
+        maxNum := VAL(CARDINAL, inum)
+      END;
+      initNums[nInits] := inum; INC(nInits);
+      endL := NewLabel();
+      Jmp(endL);
+      ProcEntry(inum, 0);
+      EmitStmts(blk^.right);
+      Leave(0, FALSE);
+      DefLabel(endL)
+    END;
+    PopScope
+  END EmitModuleDecl;
+
+PROCEDURE EmitBlock (blk: AST.Node; pnum: INTEGER);
+  BEGIN
+    IF (blk = NIL) OR ~ok THEN RETURN END;
+    EmitDecls(blk^.left);
+    EmitStmts(blk^.right)
+  END EmitBlock;
+
+PROCEDURE EmitFor (n: AST.Node);
+  VAR vi: INTEGER;
+    lTop, lChk, lNeg, lEnd: INTEGER;
+    tH, tB: INTEGER;
+  BEGIN
+    IF (n = NIL) OR ~ok THEN RETURN END;
+    vi := FindVar(n^.name);
+    IF (vi < 0) OR vars[vi].isAddr THEN Fail230(); RETURN END;
+    IF vars[vi].global THEN
+    ELSIF vars[vi].depth # curDepth THEN Fail230(); RETURN
+    END;
+    EmitExpr(n^.left);
+    EmitStoreEntry(vi);
+    EmitExpr(n^.right);
+    tH := TempGlobal();
+    StoreTemp(tH);
+    IF n^.extra # NIL THEN EmitExpr(n^.extra)
+    ELSE PushInt(1)
+    END;
+    tB := TempGlobal();
+    StoreTemp(tB);
+    lTop := NewLabel(); lChk := NewLabel();
+    lNeg := NewLabel(); lEnd := NewLabel();
+    Jmp(lChk);
+    DefLabel(lTop);
+    EmitStmts(n^.more);
+    EmitLoadEntry(vi);
+    LoadTemp(tB);
+    EmitOp(OPadd);
+    EmitStoreEntry(vi);
+    DefLabel(lChk);
+    LoadTemp(tB);
+    PushInt(0);
+    EmitOp(OPige);
+    Jz(lNeg);
+    EmitLoadEntry(vi);
+    LoadTemp(tH);
+    EmitOp(OPile);
+    Jz(lEnd);
+    Jmp(lTop);
+    DefLabel(lNeg);
+    EmitLoadEntry(vi);
+    LoadTemp(tH);
+    EmitOp(OPige);
+    Jz(lEnd);
+    Jmp(lTop);
+    DefLabel(lEnd)
+  END EmitFor;
+
+PROCEDURE Prescan (n: AST.Node; VAR maxP, nIni: CARDINAL);
+  BEGIN
+    WHILE (n # NIL) & ok DO
+      IF n^.kind = AST.nkProc THEN
+        IF VAL(CARDINAL, n^.num) > maxP THEN
+          maxP := VAL(CARDINAL, n^.num)
+        END;
+        IF n^.right # NIL THEN Prescan(n^.right^.left, maxP, nIni) END
+      ELSIF n^.kind = AST.nkModule THEN
+        IF (n^.right # NIL) & (n^.right^.right # NIL) THEN
+          INC(nIni)
+        END;
+        Prescan(n^.left, maxP, nIni)
+      END;
+      n := n^.next
+    END
+  END Prescan;
+
+PROCEDURE EmitModule (root: AST.Node): BOOLEAN;
+  VAR blk: AST.Node;
+    i: CARDINAL;
+    maxP, nIni: CARDINAL;
+  BEGIN
+    IF root = NIL THEN RETURN FALSE END;
+    IF root^.kind # AST.nkModule THEN Fail230(); RETURN FALSE END;
+    OpenModule(root^.name);
+    maxP := 0; nIni := 0;
+    Prescan(root^.left, maxP, nIni);
+    IF maxP + nIni + 1 > 63 THEN Fail230(); RETURN FALSE END;
+    maxNum := maxP;
+    nextInit := maxP + 1;
+    PushScope("", 0, 0, TRUE);
+    NoteConst("TRUE", 0, 1, 0H, "", TRUE);
+    NoteConst("FALSE", 0, 0, 0H, "", TRUE);
+    EmitDecls(root^.left);
+    blk := root^.right;
+    mainAddr := nCode;
+    i := 0;
+    WHILE i < nInits DO
+      CallProc(initNums[i]);
+      INC(i)
+    END;
+    IF blk # NIL THEN EmitStmts(blk^.right) END;
+    PopScope;
+    EndModule;
+    RETURN ok
+  END EmitModule;
+
+BEGIN
+  nCode := 0;
+  nLab := 0; nFix := 0;
+  nG := 0; nGlb := 4;
+  nScopes := 0; curScope := -1;
+  nV := 0; nC := 0;
+  maxNum := 0; mainAddr := 0;
+  nInits := 0;
+  curDepth := 0; curProc := 0;
+  loopTop := 0;
+  ok := TRUE;
+  modName[0] := 0C
+END MGen.


+ 45 - 6
src/SymTab.def

@@ -1,4 +1,6 @@
 DEFINITION MODULE SymTab;
+
+IMPORT AST;
 (* Symbol table with static type checking for the M2comp compiler
    (Modula-2 program modules, step 2: semantic analysis, no codegen).
 
@@ -57,17 +59,20 @@ CONST
   ClPtr     = 9;
   ClStr     = 10;
 
-  (* operator codes for RelCheck *)
-  OpEq = 0; OpNeq1 = 1; OpNeq2 = 2;
-  OpLt = 3; OpLe  = 4; OpGt   = 5; OpGe = 6;
-  OpIn = 7;
-  (* operator codes for AddOp / MulOp *)
+  (* operator codes; all groups use distinct ranges so a
+     tree-walking backend can dispatch on the code alone *)
+  OpEq = 20; OpNeq1 = 21; OpNeq2 = 22;
+  OpLt = 23; OpLe  = 24; OpGt   = 25; OpGe = 26;
+  OpIn = 27;
+  (* operator codes for AddOp / MulOp; MulOp codes are offset *)
   OpAdd = 0; OpSub = 1; OpOr = 2;
-  OpTimes = 0; OpSlash = 1; OpDiv = 2; OpMod = 3; OpAnd = 4;
+  OpTimes = 10; OpSlash = 11; OpDiv = 12; OpMod = 13; OpAnd = 14;
 
 TYPE
   Name = ARRAY [0 .. 63] OF CHAR;
   TypeIndex = INTEGER;
+  BoundsArr = ARRAY [0 .. 7] OF INTEGER;
+  (* Per-dimension bounds for WrapArrayB (Coco/R attribute type). *)
 
 (* ---------------- symbols and scopes ---------------- *)
 
@@ -251,6 +256,24 @@ PROCEDURE WrapArray (elem: TypeIndex; dims: CARDINAL): TypeIndex;
 (* WrapArray nests elem in dims ARRAY levels (dims = 0 gives an open
    array). Open arrays are formal-only (grammar reports 230). *)
 PROCEDURE IsOpen (t: TypeIndex): BOOLEAN;
+PROCEDURE NewSubB (base: TypeIndex; lo, hi: INTEGER): TypeIndex;
+PROCEDURE NewArrayB (elem: TypeIndex; lo, hi: INTEGER): TypeIndex;
+PROCEDURE WrapArrayB (elem: TypeIndex; los, his: BoundsArr;
+                      dims: CARDINAL): TypeIndex;
+(* Bounded nesting for ARRAY index lists (dims = 0 gives an open
+   array). Bounds come from folded index expressions (grammar). *)
+PROCEDURE IndexBounds (t: TypeIndex; VAR lo, hi: INTEGER): BOOLEAN;
+(* Finite bounds of an index type: subrange (stored), CHAR (0..255),
+   BOOLEAN (0..1). FALSE for INTEGER/CARDINAL (unbounded) and
+   anything else (grammar reports 230). *)
+PROCEDURE ArrayLo (t: TypeIndex): INTEGER;
+PROCEDURE ArrayHi (t: TypeIndex): INTEGER;
+PROCEDURE ArrayLen (t: TypeIndex): CARDINAL;
+PROCEDURE TypeSlots (t: TypeIndex): CARDINAL;
+(* Stack slots for a value: 1 per scalar/pointer/set, len*elem for
+   arrays, summed members for records. Cycle-guarded. *)
+PROCEDURE FieldOffset (rec: TypeIndex; name: ARRAY OF CHAR): INTEGER;
+(* Slot offset of a record field in declaration order. *)
 PROCEDURE NewRecord (): TypeIndex;
 PROCEDURE NewSet (base: TypeIndex): TypeIndex;
 PROCEDURE NewPtr (base: TypeIndex): TypeIndex;
@@ -309,4 +332,20 @@ PROCEDURE SetFor (elem: TypeIndex): TypeIndex;
 
 PROCEDURE StrLen (s: ARRAY OF CHAR): CARDINAL;
 
+(* ---------------- compile-time constants ---------------- *)
+(* Integer values of CONST declarations, recorded at parse time for
+   array/subrange bound folding (grammar) and MGen lookups. Chained
+   consts fold (Neg = -N + 2 with N = 10 gives -8); anything else
+   stays undefined (bounds then report 230). *)
+
+PROCEDURE NoteConst (name: ARRAY OF CHAR; v: INTEGER; ok: BOOLEAN);
+(* Records the folded value of a just-declared CONST (ok = foldable). *)
+
+PROCEDURE ConstVal (name: ARRAY OF CHAR; VAR v: INTEGER): BOOLEAN;
+(* Value of the visible integer CONST (TRUE/FALSE predefs included);
+   FALSE if absent, non-const, or undefined. *)
+
+PROCEDURE ConstFold (n: AST.Node; VAR v: INTEGER): BOOLEAN;
+(* Folds nkInt / nkUn(+/-) / const nkName nodes. Needs AST import. *)
+
 END SymTab.

+ 190 - 2
src/SymTab.mod

@@ -1,6 +1,6 @@
 IMPLEMENTATION MODULE SymTab;
 
-IMPORT FileIO;
+IMPORT FileIO, AST;
 
 CONST
   MaxTypes  = 256;
@@ -60,6 +60,8 @@ VAR
   nPendF : CARDINAL;
   tform : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
   tref : ARRAY [0 .. MaxTypes - 1] OF TypeIndex;
+  tLo : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
+  tHi : ARRAY [0 .. MaxTypes - 1] OF INTEGER;
   nTypes : CARDINAL;
   fields : ARRAY [0 .. MaxFields - 1] OF Field;
   nFields : CARDINAL;
@@ -92,6 +94,8 @@ VAR
   callTop : CARDINAL;
   callFull : BOOLEAN;
   loopDep : CARDINAL;
+  cval : ARRAY [0 .. MaxSyms - 1] OF INTEGER;
+  cdef : ARRAY [0 .. MaxSyms - 1] OF BOOLEAN;
 
 (* ---------------- strings ---------------- *)
 
@@ -124,6 +128,49 @@ PROCEDURE StrLen (s: ARRAY OF CHAR): CARDINAL;
     RETURN i
   END StrLen;
 
+(* ---------------- compile-time constants ---------------- *)
+
+PROCEDURE NoteConst (name: ARRAY OF CHAR; v: INTEGER; ok: BOOLEAN);
+  VAR idx: INTEGER;
+  BEGIN
+    idx := Find(name);
+    IF idx = -1 THEN RETURN END;
+    cval[idx] := v;
+    cdef[idx] := ok
+  END NoteConst;
+
+PROCEDURE ConstVal (name: ARRAY OF CHAR; VAR v: INTEGER): BOOLEAN;
+  VAR idx: INTEGER;
+  BEGIN
+    v := 0;
+    idx := Find(name);
+    IF idx = -1 THEN RETURN FALSE END;
+    IF syms[idx].kind # KindConst THEN RETURN FALSE END;
+    IF ~cdef[idx] THEN RETURN FALSE END;
+    v := cval[idx];
+    RETURN TRUE
+  END ConstVal;
+
+PROCEDURE ConstFold (n: AST.Node; VAR v: INTEGER): BOOLEAN;
+  VAR a: INTEGER;
+  BEGIN
+    v := 0;
+    IF n = NIL THEN RETURN FALSE END;
+    IF n^.kind = AST.nkInt THEN
+      v := n^.num; RETURN TRUE
+    ELSIF n^.kind = AST.nkUn THEN
+      IF ~ConstFold(n^.left, a) THEN RETURN FALSE END;
+      IF n^.op = AST.opNeg THEN v := -a
+      ELSIF n^.op = AST.opPos THEN v := a
+      ELSE RETURN FALSE
+      END;
+      RETURN TRUE
+    ELSIF n^.kind = AST.nkName THEN
+      RETURN ConstVal(n^.name, v)
+    END;
+    RETURN FALSE
+  END ConstFold;
+
 (* ---------------- symbols and scopes ---------------- *)
 
 PROCEDURE Find (name: ARRAY OF CHAR): INTEGER;
@@ -147,6 +194,8 @@ PROCEDURE RawEnter (name: ARRAY OF CHAR; kind: INTEGER): INTEGER;
     syms[nSyms].typ := InvalidType;
     syms[nSyms].lev := curLev;
     syms[nSyms].pnum := -1;
+    cval[nSyms] := 0;
+    cdef[nSyms] := FALSE;
     INC(nSyms);
     RETURN VAL(INTEGER, nSyms - 1)
   END RawEnter;
@@ -282,6 +331,8 @@ PROCEDURE NewDesc (form: INTEGER; ref: TypeIndex): TypeIndex;
     IF nTypes >= MaxTypes THEN RETURN InvalidType END;
     tform[nTypes] := form;
     tref[nTypes] := ref;
+    tLo[nTypes] := 0;
+    tHi[nTypes] := -1;
     INC(nTypes);
     RETURN VAL(INTEGER, nTypes - 1)
   END NewDesc;
@@ -296,6 +347,141 @@ PROCEDURE NewSub (base: TypeIndex): TypeIndex;
     RETURN NewDesc(FSub, base)
   END NewSub;
 
+PROCEDURE NewSubB (base: TypeIndex; lo, hi: INTEGER): TypeIndex;
+  VAR t: TypeIndex;
+  BEGIN
+    t := NewDesc(FSub, base);
+    IF t = InvalidType THEN RETURN InvalidType END;
+    tLo[t] := lo;
+    tHi[t] := hi;
+    RETURN t
+  END NewSubB;
+
+PROCEDURE NewArrayB (elem: TypeIndex; lo, hi: INTEGER): TypeIndex;
+  VAR t: TypeIndex;
+  BEGIN
+    t := NewDesc(FArray, elem);
+    IF t = InvalidType THEN RETURN InvalidType END;
+    tLo[t] := lo;
+    tHi[t] := hi;
+    RETURN t
+  END NewArrayB;
+
+PROCEDURE WrapArrayB (elem: TypeIndex; los, his: BoundsArr;
+                      dims: CARDINAL): TypeIndex;
+  VAR t: TypeIndex;
+  BEGIN
+    t := elem;
+    IF dims = 0 THEN RETURN NewOpen(t) END;
+    WHILE dims > 0 DO
+      DEC(dims);
+      IF dims > 7 THEN RETURN InvalidType END;
+      t := NewArrayB(t, los[dims], his[dims]);
+      IF t = InvalidType THEN RETURN InvalidType END
+    END;
+    RETURN t
+  END WrapArrayB;
+
+PROCEDURE IndexBounds (t: TypeIndex; VAR lo, hi: INTEGER): BOOLEAN;
+  VAR r: TypeIndex;
+  BEGIN
+    lo := 0; hi := -1;
+    r := Resolve(t);
+    IF r = InvalidType THEN RETURN FALSE END;
+    IF tform[r] = FSub THEN
+      lo := tLo[r]; hi := tHi[r]; RETURN TRUE
+    ELSIF tform[r] = FChar THEN
+      lo := 0; hi := 255; RETURN TRUE
+    ELSIF tform[r] = FBool THEN
+      lo := 0; hi := 1; RETURN TRUE
+    END;
+    RETURN FALSE
+  END IndexBounds;
+
+PROCEDURE ArrayLo (t: TypeIndex): INTEGER;
+  VAR r: TypeIndex;
+  BEGIN
+    r := Resolve(t);
+    IF (r = InvalidType) OR (tform[r] # FArray) THEN RETURN 0 END;
+    RETURN tLo[r]
+  END ArrayLo;
+
+PROCEDURE ArrayHi (t: TypeIndex): INTEGER;
+  VAR r: TypeIndex;
+  BEGIN
+    r := Resolve(t);
+    IF (r = InvalidType) OR (tform[r] # FArray) THEN RETURN -1 END;
+    RETURN tHi[r]
+  END ArrayHi;
+
+PROCEDURE ArrayLen (t: TypeIndex): CARDINAL;
+  VAR lo, hi: INTEGER;
+  BEGIN
+    lo := ArrayLo(t); hi := ArrayHi(t);
+    IF hi < lo THEN RETURN 0 END;
+    RETURN VAL(CARDINAL, hi - lo) + 1
+  END ArrayLen;
+
+PROCEDURE SlotsRec (t: TypeIndex; depth: CARDINAL): CARDINAL;
+  VAR r: TypeIndex;
+    i, n: INTEGER;
+    acc: CARDINAL;
+  BEGIN
+    IF depth > 64 THEN RETURN 1 END;
+    r := Resolve(t);
+    IF r = InvalidType THEN RETURN 1 END;
+    IF tform[r] = FArray THEN
+      RETURN ArrayLen(r) * SlotsRec(tref[r], depth + 1)
+    ELSIF tform[r] = FRecord THEN
+      acc := 0;
+      i := tref[r];
+      WHILE i # -1 DO
+        acc := acc + SlotsRec(fields[i].typ, depth + 1);
+        i := fields[i].next
+      END;
+      RETURN acc
+    ELSIF (tform[r] = FAlias) OR (tform[r] = FSub) THEN
+      RETURN SlotsRec(tref[r], depth + 1)
+    ELSIF tform[r] = FOpen THEN
+      RETURN 0
+    END;
+    RETURN 1
+  END SlotsRec;
+
+PROCEDURE TypeSlots (t: TypeIndex): CARDINAL;
+  BEGIN
+    RETURN SlotsRec(t, 0)
+  END TypeSlots;
+
+PROCEDURE FieldOffset (rec: TypeIndex; name: ARRAY OF CHAR): INTEGER;
+(* Declaration-order slot offset: the field chain runs last-declared
+   first, so collect then walk from the end. *)
+  VAR r: TypeIndex;
+    ts: ARRAY [0 .. 255] OF TypeIndex;
+    n, i: CARDINAL;
+    fi: INTEGER;
+    off: CARDINAL;
+  BEGIN
+    r := Resolve(rec);
+    IF (r = InvalidType) OR (tform[r] # FRecord) THEN RETURN 0 END;
+    n := 0;
+    fi := tref[r];
+    WHILE (fi # -1) & (n <= 255) DO
+      ts[n] := fi; INC(n);
+      fi := fields[fi].next
+    END;
+    off := 0;
+    i := n;
+    WHILE i > 0 DO
+      DEC(i);
+      IF Equal(fields[ts[i]].name, name) THEN
+        RETURN VAL(INTEGER, off)
+      END;
+      off := off + SlotsRec(fields[ts[i]].typ, 0)
+    END;
+    RETURN 0
+  END FieldOffset;
+
 PROCEDURE NewEnum (): TypeIndex;
   BEGIN
     RETURN NewDesc(FEnum, InvalidType)
@@ -1058,7 +1244,9 @@ PROCEDURE Init;
     Predef("BOOLEAN", KindPredef, dBool);
     Predef("TRUE", KindConst, dBool);
     Predef("FALSE", KindConst, dBool);
-    Predef("NIL", KindConst, InvalidType)
+    Predef("NIL", KindConst, InvalidType);
+    NoteConst("TRUE", 1, TRUE);
+    NoteConst("FALSE", 0, TRUE)
   END Init;
 
 (* ---------------- listing ---------------- *)


+ 14 - 5
src/compiler.frm

@@ -1,12 +1,13 @@
 MODULE -->Grammar;
-(* Driver for the M2comp Modula-2 compiler (Coco/R, step 1: syntax only).
-   Generated <Grammar>S (scanner) + <Grammar>P (parser) do lexing/parsing;
-   no hand-written lexer. SymTab/MGen plug in at step 2. *)
+(* Driver for the M2comp Modula-2 compiler (Coco/R frontend).
+   Generated <Grammar>S (scanner) + <Grammar>P (parser) do lexing,
+   parsing, SymTab checking and AST building; on success the MGen
+   tree-walking backend emits a single-module MC64 .MC4 image. *)
 
   FROM -->Scanner IMPORT lst, src, errors, Error, CharAt;
   FROM -->Parser IMPORT Parse, Successful;
   IMPORT
-    Strings, Storage, SYSTEM, FileIO;
+    Strings, Storage, SYSTEM, FileIO, AST, MGen;
 
   TYPE
     INT32 = FileIO.INT32 (* 32 bit integers needed *);
@@ -188,6 +189,7 @@ MODULE -->Grammar;
   VAR
     sourceName, listName: ARRAY [0 .. 255] OF CHAR;
     failed: BOOLEAN;
+    emitOk: BOOLEAN;
 
   BEGIN
     (* check on correct parameter usage *)
@@ -229,12 +231,19 @@ MODULE -->Grammar;
       FileIO.WriteLn(FileIO.StdOut);
       Parse;
 
+      (* backend: emit the MC64 image while errors still reach
+         the listing below *)
+      IF Successful() THEN
+        emitOk := MGen.EmitModule(AST.GetRoot())
+      ELSE emitOk := FALSE
+      END;
+
       (* generate the source listing on lst file *)
       PrintListing;
       IF lst # FileIO.StdOut THEN FileIO.Close(lst) END;
 
       (* fail fast: later files build on this one's tables *)
-      IF NOT Successful()
+      IF NOT (Successful() & emitOk)
         THEN
           FileIO.WriteString(FileIO.StdOut, "Incorrect source");
           FileIO.WriteLn(FileIO.StdOut);

+ 2 - 1
src/modules.lst

@@ -61,9 +61,10 @@ RndFile
 TermFile
 FileIO
 M2compS
-SymTab
 STextIO
 SWholeIO
 AST
+SymTab
 M2compP
+MGen
 M2comp

+ 36 - 0
tests/ok_proc.LST

@@ -0,0 +1,36 @@
+Listing:
+
+    1  MODULE OkProc;
+    2  FROM In IMPORT x;
+    3  IMPORT y, z;
+    4  VAR
+    5    i : INTEGER;
+    6    a : ARRAY [0..9], [0..3] OF INTEGER;
+    7    r : RECORD f : INTEGER; END;
+    8  
+    9  PROCEDURE P(VAR v : INTEGER; n : CARDINAL) : BOOLEAN;
+   10  BEGIN
+   11    IF n > 0 THEN v := v + 1 ELSE v := 0 END;
+   12    RETURN TRUE
+   13  END P;
+   14  
+   15  MODULE Local;
+   16  EXPORT q;
+   17  VAR q : INTEGER;
+   18  BEGIN
+   19    q := 1
+   20  END Local;
+   21  
+   22  BEGIN
+   23    i := 0;
+   24    WHILE i < 10 DO
+   25      i := i + 1
+   26    END;
+   27    FOR i := 1 TO 10 BY 2 DO
+   28      P(i, i)
+   29    END
+   30  END OkProc.
+
+    0 errors
+
+

+ 1 - 1
tests/ok_proc.mod

@@ -8,7 +8,7 @@ VAR
 
 PROCEDURE P(VAR v : INTEGER; n : CARDINAL) : BOOLEAN;
 BEGIN
-  IF n > 0 THEN v := -v + 1 ELSE v := 0 END;
+  IF n > 0 THEN v := v + 1 ELSE v := 0 END;
   RETURN TRUE
 END P;
 

+ 11 - 0
tests/r_arith.LST

@@ -0,0 +1,11 @@
+Listing:
+
+    1  MODULE RArith;
+    2  VAR ExitCode : INTEGER;
+    3  BEGIN
+    4    ExitCode := 2 * 3 + 4 - 10 DIV 3 + 7 MOD 3
+    5  END RArith.
+
+    0 errors
+
+

+ 5 - 0
tests/r_arith.mod

@@ -0,0 +1,5 @@
+MODULE RArith;
+VAR ExitCode : INTEGER;
+BEGIN
+  ExitCode := 2 * 3 + 4 - 10 DIV 3 + 7 MOD 3
+END RArith.

+ 19 - 0
tests/r_array.LST

@@ -0,0 +1,19 @@
+Listing:
+
+    1  MODULE RArr;
+    2  VAR ExitCode : INTEGER;
+    3      v, w : ARRAY [0 .. 9] OF INTEGER;
+    4      m : ARRAY [0 .. 2], [0 .. 3] OF INTEGER;
+    5      i, j : INTEGER;
+    6  BEGIN
+    7    FOR i := 0 TO 9 DO v[i] := i * 2 END;
+    8    w := v;
+    9    FOR i := 0 TO 2 DO
+   10      FOR j := 0 TO 3 DO m[i, j] := i * 10 + j END
+   11    END;
+   12    ExitCode := v[5] + w[3] + m[2, 3]
+   13  END RArr.
+
+    0 errors
+
+

+ 13 - 0
tests/r_array.mod

@@ -0,0 +1,13 @@
+MODULE RArr;
+VAR ExitCode : INTEGER;
+    v, w : ARRAY [0 .. 9] OF INTEGER;
+    m : ARRAY [0 .. 2], [0 .. 3] OF INTEGER;
+    i, j : INTEGER;
+BEGIN
+  FOR i := 0 TO 9 DO v[i] := i * 2 END;
+  w := v;
+  FOR i := 0 TO 2 DO
+    FOR j := 0 TO 3 DO m[i, j] := i * 10 + j END
+  END;
+  ExitCode := v[5] + w[3] + m[2, 3]
+END RArr.

+ 18 - 0
tests/r_bool.LST

@@ -0,0 +1,18 @@
+Listing:
+
+    1  MODULE RBool;
+    2  VAR ExitCode : INTEGER;
+    3      b : BOOLEAN;
+    4      i : INTEGER;
+    5  BEGIN
+    6    i := 0;
+    7    b := (3 <= 4) AND (4 >= 5) OR NOT FALSE;
+    8    IF b THEN i := i + 1 END;
+    9    IF (1 = 1) AND (2 # 3) AND NOT (4 < 3) THEN i := i + 10 END;
+   10    IF (5 <> 5) OR (6 <= 5) THEN i := i + 100 END;
+   11    ExitCode := i
+   12  END RBool.
+
+    0 errors
+
+

+ 12 - 0
tests/r_bool.mod

@@ -0,0 +1,12 @@
+MODULE RBool;
+VAR ExitCode : INTEGER;
+    b : BOOLEAN;
+    i : INTEGER;
+BEGIN
+  i := 0;
+  b := (3 <= 4) AND (4 >= 5) OR NOT FALSE;
+  IF b THEN i := i + 1 END;
+  IF (1 = 1) AND (2 # 3) AND NOT (4 < 3) THEN i := i + 10 END;
+  IF (5 <> 5) OR (6 <= 5) THEN i := i + 100 END;
+  ExitCode := i
+END RBool.

+ 19 - 0
tests/r_const.LST

@@ -0,0 +1,19 @@
+Listing:
+
+    1  MODULE RConst;
+    2  CONST N = 10;
+    3        H = 0FFH;
+    4        Neg = -N + 2;
+    5        Big = 1000;
+    6  VAR ExitCode : INTEGER;
+    7      i : INTEGER;
+    8  BEGIN
+    9    ExitCode := 0;
+   10    FOR i := 1 TO N DO ExitCode := ExitCode + 1 END;
+   11    IF H = 255 THEN ExitCode := ExitCode + 100 END;
+   12    ExitCode := ExitCode + Neg + Big
+   13  END RConst.
+
+    0 errors
+
+

+ 13 - 0
tests/r_const.mod

@@ -0,0 +1,13 @@
+MODULE RConst;
+CONST N = 10;
+      H = 0FFH;
+      Neg = -N + 2;
+      Big = 1000;
+VAR ExitCode : INTEGER;
+    i : INTEGER;
+BEGIN
+  ExitCode := 0;
+  FOR i := 1 TO N DO ExitCode := ExitCode + 1 END;
+  IF H = 255 THEN ExitCode := ExitCode + 100 END;
+  ExitCode := ExitCode + Neg + Big
+END RConst.

+ 26 - 0
tests/r_flow.LST

@@ -0,0 +1,26 @@
+Listing:
+
+    1  MODULE RFlow;
+    2  VAR ExitCode : INTEGER;
+    3      i, s : INTEGER;
+    4  BEGIN
+    5    s := 0;
+    6    IF 1 > 2 THEN s := 1
+    7    ELSIF 2 > 3 THEN s := 2
+    8    ELSE s := 3
+    9    END;
+   10    i := 0;
+   11    WHILE i < 10 DO i := i + 1; s := s + i END;
+   12    REPEAT s := s - 1 UNTIL s < 60;
+   13    LOOP
+   14      s := s + 1;
+   15      IF s >= 60 THEN EXIT END
+   16    END;
+   17    FOR i := 1 TO 5 BY 2 DO s := s + i END;
+   18    FOR i := 10 TO 1 DO s := s + 0 END;
+   19    ExitCode := s
+   20  END RFlow.
+
+    0 errors
+
+

+ 20 - 0
tests/r_flow.mod

@@ -0,0 +1,20 @@
+MODULE RFlow;
+VAR ExitCode : INTEGER;
+    i, s : INTEGER;
+BEGIN
+  s := 0;
+  IF 1 > 2 THEN s := 1
+  ELSIF 2 > 3 THEN s := 2
+  ELSE s := 3
+  END;
+  i := 0;
+  WHILE i < 10 DO i := i + 1; s := s + i END;
+  REPEAT s := s - 1 UNTIL s < 60;
+  LOOP
+    s := s + 1;
+    IF s >= 60 THEN EXIT END
+  END;
+  FOR i := 1 TO 5 BY 2 DO s := s + i END;
+  FOR i := 10 TO 1 DO s := s + 0 END;
+  ExitCode := s
+END RFlow.

+ 21 - 0
tests/r_module.LST

@@ -0,0 +1,21 @@
+Listing:
+
+    1  MODULE RMod;
+    2  VAR ExitCode : INTEGER;
+    3  MODULE M;
+    4  EXPORT q, Get;
+    5  VAR q : INTEGER;
+    6  PROCEDURE Get() : INTEGER;
+    7  BEGIN
+    8    RETURN q + 1
+    9  END Get;
+   10  BEGIN
+   11    q := 41
+   12  END M;
+   13  BEGIN
+   14    ExitCode := M.q + M.Get()
+   15  END RMod.
+
+    0 errors
+
+

+ 15 - 0
tests/r_module.mod

@@ -0,0 +1,15 @@
+MODULE RMod;
+VAR ExitCode : INTEGER;
+MODULE M;
+EXPORT q, Get;
+VAR q : INTEGER;
+PROCEDURE Get() : INTEGER;
+BEGIN
+  RETURN q + 1
+END Get;
+BEGIN
+  q := 41
+END M;
+BEGIN
+  ExitCode := M.q + M.Get()
+END RMod.

+ 21 - 0
tests/r_nested.LST

@@ -0,0 +1,21 @@
+Listing:
+
+    1  MODULE RNested;
+    2  VAR ExitCode : INTEGER;
+    3  PROCEDURE Outer(n : INTEGER) : INTEGER;
+    4  VAR loc : INTEGER;
+    5    PROCEDURE Nested(t : INTEGER) : INTEGER;
+    6    BEGIN
+    7      RETURN t + loc + n
+    8    END Nested;
+    9  BEGIN
+   10    loc := 100;
+   11    RETURN Nested(1) + Nested(2)
+   12  END Outer;
+   13  BEGIN
+   14    ExitCode := Outer(5)
+   15  END RNested.
+
+    0 errors
+
+

+ 15 - 0
tests/r_nested.mod

@@ -0,0 +1,15 @@
+MODULE RNested;
+VAR ExitCode : INTEGER;
+PROCEDURE Outer(n : INTEGER) : INTEGER;
+VAR loc : INTEGER;
+  PROCEDURE Nested(t : INTEGER) : INTEGER;
+  BEGIN
+    RETURN t + loc + n
+  END Nested;
+BEGIN
+  loc := 100;
+  RETURN Nested(1) + Nested(2)
+END Outer;
+BEGIN
+  ExitCode := Outer(5)
+END RNested.

+ 20 - 0
tests/r_proc.LST

@@ -0,0 +1,20 @@
+Listing:
+
+    1  MODULE RProc;
+    2  VAR ExitCode : INTEGER;
+    3  PROCEDURE Add(a, b : INTEGER) : INTEGER;
+    4  BEGIN
+    5    RETURN a + b
+    6  END Add;
+    7  PROCEDURE Fact(n : INTEGER) : INTEGER;
+    8  BEGIN
+    9    IF n <= 1 THEN RETURN 1 END;
+   10    RETURN n * Fact(n - 1)
+   11  END Fact;
+   12  BEGIN
+   13    ExitCode := Add(20, 22) + Fact(5)
+   14  END RProc.
+
+    0 errors
+
+

+ 14 - 0
tests/r_proc.mod

@@ -0,0 +1,14 @@
+MODULE RProc;
+VAR ExitCode : INTEGER;
+PROCEDURE Add(a, b : INTEGER) : INTEGER;
+BEGIN
+  RETURN a + b
+END Add;
+PROCEDURE Fact(n : INTEGER) : INTEGER;
+BEGIN
+  IF n <= 1 THEN RETURN 1 END;
+  RETURN n * Fact(n - 1)
+END Fact;
+BEGIN
+  ExitCode := Add(20, 22) + Fact(5)
+END RProc.

+ 17 - 0
tests/r_ptr.LST

@@ -0,0 +1,17 @@
+Listing:
+
+    1  MODULE RPtr;
+    2  VAR ExitCode : INTEGER;
+    3      p, q : POINTER TO INTEGER;
+    4  BEGIN
+    5    ExitCode := 0;
+    6    p := NIL;
+    7    q := p;
+    8    IF p = NIL THEN ExitCode := ExitCode + 1 END;
+    9    IF q = NIL THEN ExitCode := ExitCode + 10 END;
+   10    IF p = q THEN ExitCode := ExitCode + 100 END
+   11  END RPtr.
+
+    0 errors
+
+

+ 11 - 0
tests/r_ptr.mod

@@ -0,0 +1,11 @@
+MODULE RPtr;
+VAR ExitCode : INTEGER;
+    p, q : POINTER TO INTEGER;
+BEGIN
+  ExitCode := 0;
+  p := NIL;
+  q := p;
+  IF p = NIL THEN ExitCode := ExitCode + 1 END;
+  IF q = NIL THEN ExitCode := ExitCode + 10 END;
+  IF p = q THEN ExitCode := ExitCode + 100 END
+END RPtr.

+ 19 - 0
tests/r_real.LST

@@ -0,0 +1,19 @@
+Listing:
+
+    1  MODULE RReal;
+    2  VAR ExitCode : INTEGER;
+    3      r : REAL;
+    4  BEGIN
+    5    ExitCode := 0;
+    6    r := 1.5 + 2.5;
+    7    IF r = 4.0 THEN ExitCode := ExitCode + 1 END;
+    8    r := 10.0 / 4.0;
+    9    IF (r > 2.4) AND (r < 2.6) THEN ExitCode := ExitCode + 10 END;
+   10    r := 3.5 - 1.5 * 2.0;
+   11    IF r = 0.5 THEN ExitCode := ExitCode + 100 END;
+   12    IF -r < -0.25 THEN ExitCode := ExitCode + 1000 END
+   13  END RReal.
+
+    0 errors
+
+

+ 13 - 0
tests/r_real.mod

@@ -0,0 +1,13 @@
+MODULE RReal;
+VAR ExitCode : INTEGER;
+    r : REAL;
+BEGIN
+  ExitCode := 0;
+  r := 1.5 + 2.5;
+  IF r = 4.0 THEN ExitCode := ExitCode + 1 END;
+  r := 10.0 / 4.0;
+  IF (r > 2.4) AND (r < 2.6) THEN ExitCode := ExitCode + 10 END;
+  r := 3.5 - 1.5 * 2.0;
+  IF r = 0.5 THEN ExitCode := ExitCode + 100 END;
+  IF -r < -0.25 THEN ExitCode := ExitCode + 1000 END
+END RReal.

+ 15 - 0
tests/r_record.LST

@@ -0,0 +1,15 @@
+Listing:
+
+    1  MODULE RRec;
+    2  VAR ExitCode : INTEGER;
+    3      r, q : RECORD a : INTEGER; b : INTEGER END;
+    4  BEGIN
+    5    r.a := 7;
+    6    r.b := 8;
+    7    q := r;
+    8    ExitCode := q.a + q.b
+    9  END RRec.
+
+    0 errors
+
+

+ 9 - 0
tests/r_record.mod

@@ -0,0 +1,9 @@
+MODULE RRec;
+VAR ExitCode : INTEGER;
+    r, q : RECORD a : INTEGER; b : INTEGER END;
+BEGIN
+  r.a := 7;
+  r.b := 8;
+  q := r;
+  ExitCode := q.a + q.b
+END RRec.

+ 19 - 0
tests/r_set.LST

@@ -0,0 +1,19 @@
+Listing:
+
+    1  MODULE RSet;
+    2  TYPE R = [0 .. 9];
+    3  VAR ExitCode : INTEGER;
+    4      s, t : SET OF R;
+    5      b : BOOLEAN;
+    6  BEGIN
+    7    ExitCode := 0;
+    8    s := s;
+    9    t := s;
+   10    b := 3 IN s;
+   11    IF b THEN ExitCode := 1 ELSE ExitCode := 2 END;
+   12    IF s = t THEN ExitCode := ExitCode + 10 END
+   13  END RSet.
+
+    0 errors
+
+

+ 13 - 0
tests/r_set.mod

@@ -0,0 +1,13 @@
+MODULE RSet;
+TYPE R = [0 .. 9];
+VAR ExitCode : INTEGER;
+    s, t : SET OF R;
+    b : BOOLEAN;
+BEGIN
+  ExitCode := 0;
+  s := s;
+  t := s;
+  b := 3 IN s;
+  IF b THEN ExitCode := 1 ELSE ExitCode := 2 END;
+  IF s = t THEN ExitCode := ExitCode + 10 END
+END RSet.

+ 17 - 0
tests/r_string.LST

@@ -0,0 +1,17 @@
+Listing:
+
+    1  MODULE RStr;
+    2  VAR ExitCode : INTEGER;
+    3      s, t : ARRAY [0 .. 7] OF CHAR;
+    4  BEGIN
+    5    ExitCode := 0;
+    6    s := "hi";
+    7    t := s;
+    8    IF s[0] = "h" THEN ExitCode := ExitCode + 1 END;
+    9    IF s[1] = "i" THEN ExitCode := ExitCode + 10 END;
+   10    IF t[0] = "h" THEN ExitCode := ExitCode + 100 END
+   11  END RStr.
+
+    0 errors
+
+

+ 11 - 0
tests/r_string.mod

@@ -0,0 +1,11 @@
+MODULE RStr;
+VAR ExitCode : INTEGER;
+    s, t : ARRAY [0 .. 7] OF CHAR;
+BEGIN
+  ExitCode := 0;
+  s := "hi";
+  t := s;
+  IF s[0] = "h" THEN ExitCode := ExitCode + 1 END;
+  IF s[1] = "i" THEN ExitCode := ExitCode + 10 END;
+  IF t[0] = "h" THEN ExitCode := ExitCode + 100 END
+END RStr.

+ 25 - 0
tests/r_varpar.LST

@@ -0,0 +1,25 @@
+Listing:
+
+    1  MODULE RVarPar;
+    2  TYPE Vec = ARRAY [0 .. 9] OF INTEGER;
+    3  VAR ExitCode : INTEGER;
+    4      v : Vec;
+    5  PROCEDURE Incr(VAR x : INTEGER);
+    6  BEGIN
+    7    x := x + 1
+    8  END Incr;
+    9  PROCEDURE Fill(VAR a : Vec; n : INTEGER);
+   10  VAR i : INTEGER;
+   11  BEGIN
+   12    FOR i := 0 TO n DO a[i] := i + 1 END
+   13  END Fill;
+   14  BEGIN
+   15    ExitCode := 41;
+   16    Incr(ExitCode);
+   17    Fill(v, 4);
+   18    ExitCode := ExitCode + v[0] + v[4]
+   19  END RVarPar.
+
+    0 errors
+
+

+ 19 - 0
tests/r_varpar.mod

@@ -0,0 +1,19 @@
+MODULE RVarPar;
+TYPE Vec = ARRAY [0 .. 9] OF INTEGER;
+VAR ExitCode : INTEGER;
+    v : Vec;
+PROCEDURE Incr(VAR x : INTEGER);
+BEGIN
+  x := x + 1
+END Incr;
+PROCEDURE Fill(VAR a : Vec; n : INTEGER);
+VAR i : INTEGER;
+BEGIN
+  FOR i := 0 TO n DO a[i] := i + 1 END
+END Fill;
+BEGIN
+  ExitCode := 41;
+  Incr(ExitCode);
+  Fill(v, 4);
+  ExitCode := ExitCode + v[0] + v[4]
+END RVarPar.

+ 1 - 1
tests/showcase.mod

@@ -109,6 +109,6 @@ BEGIN
   i := Min(3, 4) + (2 * 3 - 4 / 2);
   Work(v, N, TRUE);
   Local.q := i;
-  y(i, sa, x);
+  y(i, sa, N);
   z
 END Showcase.