Explorar o código

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

Eric Streit hai 3 semanas
pai
achega
a781af2287
Modificáronse 70 ficheiros con 2960 adicións e 118 borrados
  1. BIN=BIN
      M2comp
  2. BIN=BIN
      OkMinimal.MC4
  3. BIN=BIN
      OkProc.MC4
  4. BIN=BIN
      RArith.MC4
  5. BIN=BIN
      RArr.MC4
  6. BIN=BIN
      RBool.MC4
  7. BIN=BIN
      RConst.MC4
  8. BIN=BIN
      RFlow.MC4
  9. BIN=BIN
      RMod.MC4
  10. BIN=BIN
      RNested.MC4
  11. BIN=BIN
      RProc.MC4
  12. BIN=BIN
      RPtr.MC4
  13. BIN=BIN
      RReal.MC4
  14. BIN=BIN
      RRec.MC4
  15. BIN=BIN
      RSet.MC4
  16. BIN=BIN
      RStr.MC4
  17. BIN=BIN
      RVarPar.MC4
  18. BIN=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=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=BIN
      src/M2comp.o
  30. 194 44
      src/M2compP.mod
  31. BIN=BIN
      src/M2compP.o
  32. 21 0
      src/MGen.def
  33. 1622 0
      src/MGen.mod
  34. BIN=BIN
      src/MGen.o
  35. 45 6
      src/SymTab.def
  36. 190 2
      src/SymTab.mod
  37. BIN=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

BIN=BIN
M2comp


BIN=BIN
OkMinimal.MC4


BIN=BIN
OkProc.MC4


BIN=BIN
RArith.MC4


BIN=BIN
RArr.MC4


BIN=BIN
RBool.MC4


BIN=BIN
RConst.MC4


BIN=BIN
RFlow.MC4


BIN=BIN
RMod.MC4


BIN=BIN
RNested.MC4


BIN=BIN
RProc.MC4


BIN=BIN
RPtr.MC4


BIN=BIN
RReal.MC4


BIN=BIN
RRec.MC4


BIN=BIN
RSet.MC4


BIN=BIN
RStr.MC4


BIN=BIN
RVarPar.MC4


BIN=BIN
Showcase.MC4


+ 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;

BIN=BIN
src/AST.o


+ 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);

BIN=BIN
src/M2comp.o


+ 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;
 

BIN=BIN
src/M2compP.o


+ 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.

BIN=BIN
src/MGen.o


+ 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 ---------------- *)

BIN=BIN
src/SymTab.o


+ 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.