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

v3 step 1.9 — statements + boolean core, QBE block discipline (28/28 tests green)

Eric Streit 3 недель назад
Родитель
Сommit
fe297e2ed2

+ 12 - 0
compiler/run_tests.sh

@@ -52,6 +52,14 @@ expect_run t_arith.mod 25
 expect_run showcase1.mod 50
 expect_run t_types.mod 8
 expect_run showcase2.mod 71
+expect_run t_if.mod 25
+expect_run t_while.mod 55
+expect_run t_repeat.mod 55
+expect_run t_loop.mod 55
+expect_run t_for.mod 85
+expect_run t_case.mod 103
+expect_run t_bool.mod 42
+expect_run showcase3.mod 183
 expect_fail t_enumdecl.mod "not supported yet"
 expect_fail t_proc.mod "not supported yet"
 expect_fail t_forward.mod "not supported yet"
@@ -59,6 +67,10 @@ expect_fail t_bad_undecl.mod "undeclared identifier"
 expect_fail t_bad_dup.mod "duplicate identifier"
 expect_fail t_bad_mismatch.mod "module name mismatch"
 expect_fail t_bad_opaque.mod "opaque type outside definition"
+expect_fail t_bad_while.mod "BOOLEAN condition required"
+expect_fail t_bad_exit.mod "not supported yet"
+expect_fail t_bad_return.mod "bad RETURN"
+expect_fail t_bad_for.mod "FOR needs integer variable and bounds"
 expect_fail t_defunit.mod "not supported yet"
 expect_fail t_implunit.mod "not supported yet"
 expect_fail t_nested.mod "not supported yet"

+ 334 - 45
compiler/src/M2.atg

@@ -13,9 +13,15 @@ COMPILER M2
    211 bad arithmetic, 221 not a type, 230 not supported yet,
    231 opaque type outside definition.
    Scalar-phase TYPEs (named, integer subrange, enum) check fully;
-   composite forms wait for step 3. Procedure headings (formals,
+   composite forms wait for step 3.    Procedure headings (formals,
    result, FORWARD) enter scopes now; bodies parse + check with one
-   230 at END (lowering = step 4). *)
+   230 at END (lowering = step 4).
+   Statements: assignment, IF/ELSIF/ELSE, WHILE, REPEAT/UNTIL,
+   LOOP/EXIT (230 outside LOOP), FOR/TO/static-sign-BY, full CASE
+   (labels, ranges, ELSE; compare-chain), RETURN with 232 checks
+   (outside proc / value mismatch / missing value). WITH waits for
+   records (step 3). Boolean connectives are eager (or/and/xor);
+   relations yield 0/1 via cXXw. *)
 
 IMPORT SymTab, QbeGen;
 
@@ -63,7 +69,7 @@ PRODUCTIONS
       ";"
       { Import }
       { ConstBlock | TypeBlock<TRUE> | VarBlock
-      | ProcHeading<pn> ";"             (. SymTab.PopScope; .) }
+      | ProcHeading<pn> ";"             (. SymTab.CloseProc; .) }
       "END"
       GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
                                              SemError(202) END; .)
@@ -246,7 +252,9 @@ PRODUCTIONS
     = VarIdents ":"
       TypeIdent<t>                      (. cls := SymTab.ClassOf(t);
                                            IF (t # SymTab.InvalidType)
-                                              & (cls # SymTab.ClInt) THEN
+                                              & (cls # SymTab.ClInt)
+                                              & (cls # SymTab.ClBool)
+                                              & (cls # SymTab.ClChar) THEN
                                              SemError(230) END;
                                            i := 0;
                                            WHILE i < SymTab.PendCount() DO
@@ -290,8 +298,8 @@ PRODUCTIONS
   ProcDecl                              (. VAR pn, m2: SymTab.Name; .)
     = ProcHeading<pn> ";"
       ( "FORWARD"                       (. SymTab.MarkFwd;
-                                           SymTab.PopScope; .)
-      | Block<pn>                       (. SymTab.PopScope;
+                                           SymTab.CloseProc; .)
+      | Block<pn>                       (. SymTab.CloseProc;
                                            SemError(230); .) ) .
   Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
     = DeclSeq
@@ -302,8 +310,18 @@ PRODUCTIONS
                                              SemError(202) END; .) .
   StatSeq
     = Statement { ";" Statement } .
-  Statement
-    = Assign .
+  Statement                             (. VAR lx: QbeGen.QVal; .)
+    = Assign
+    | IfStat
+    | WhileStat
+    | RepeatStat
+    | LoopStat
+    | ForStat
+    | CaseStat
+    | ReturnStat
+    | "EXIT"                            (. IF QbeGen.TopLoop(lx) THEN
+                                             QbeGen.Jmp(lx)
+                                           ELSE SemError(230) END; .) .
   Assign                                (. VAR dt, et: SymTab.TypeIndex;
                                              dk: INTEGER;
                                              qd, qe: QbeGen.QVal;
@@ -322,8 +340,222 @@ PRODUCTIONS
                                             & (et # SymTab.InvalidType) THEN
                                            QbeGen.StoreVar(qn, qe, FALSE)
                                          END; .) .
-  (* Designator, step-1 form: scalar variables and constants only.
-     No suffixes (field/index/deref are syntax errors in step 1). *)
+  IfStat                                (. VAR t: SymTab.TypeIndex;
+                                             q, lThen, lElse, lEnd:
+                                               QbeGen.QVal;
+                                             hasElse: BOOLEAN; .)
+    = "IF"                              (. hasElse := FALSE; .)
+      Expr<t, q>                        (. IF ~SymTab.BoolCheck(t) THEN
+                                             SemError(214) END;
+                                           QbeGen.NewLabel(lThen);
+                                           QbeGen.NewLabel(lElse);
+                                           QbeGen.NewLabel(lEnd);
+                                           QbeGen.Jnz(q, lThen, lElse);
+                                           QbeGen.EmitLabel(lThen); .)
+      "THEN" StatSeq                    (. QbeGen.Jmp(lEnd); .)
+      { "ELSIF"                         (. QbeGen.EmitLabel(lElse);
+                                           QbeGen.NewLabel(lElse); .)
+        Expr<t, q>                      (. IF ~SymTab.BoolCheck(t) THEN
+                                             SemError(214) END;
+                                           QbeGen.NewLabel(lThen);
+                                           QbeGen.Jnz(q, lThen, lElse);
+                                           QbeGen.EmitLabel(lThen); .)
+        "THEN" StatSeq                  (. QbeGen.Jmp(lEnd); .) }
+      [ "ELSE"                          (. QbeGen.EmitLabel(lElse);
+                                           hasElse := TRUE; .)
+        StatSeq ]
+      "END"                             (. IF hasElse THEN
+                                             QbeGen.EmitLabel(lEnd)
+                                           ELSE QbeGen.EmitLabel(lElse);
+                                             QbeGen.EmitLabel(lEnd)
+                                           END; .) .
+  WhileStat                             (. VAR t: SymTab.TypeIndex;
+                                             q, lTop, lBody, lEnd:
+                                               QbeGen.QVal; .)
+    = "WHILE"                           (. QbeGen.NewLabel(lTop);
+                                           QbeGen.NewLabel(lBody);
+                                           QbeGen.NewLabel(lEnd);
+                                           QbeGen.EmitLabel(lTop); .)
+      Expr<t, q>                        (. IF ~SymTab.BoolCheck(t) THEN
+                                             SemError(214) END;
+                                           QbeGen.Jnz(q, lBody, lEnd);
+                                           QbeGen.EmitLabel(lBody); .)
+      "DO" StatSeq                      (. QbeGen.Jmp(lTop); .)
+      "END"                             (. QbeGen.EmitLabel(lEnd); .) .
+  RepeatStat                            (. VAR t: SymTab.TypeIndex;
+                                             q, lTop, lEnd: QbeGen.QVal; .)
+    = "REPEAT"                          (. QbeGen.NewLabel(lTop);
+                                           QbeGen.NewLabel(lEnd);
+                                           QbeGen.EmitLabel(lTop); .)
+      StatSeq
+      "UNTIL" Expr<t, q>                (. IF ~SymTab.BoolCheck(t) THEN
+                                             SemError(214) END;
+                                           QbeGen.Jnz(q, lEnd, lTop);
+                                           QbeGen.EmitLabel(lEnd); .) .
+  LoopStat                              (. VAR lTop, lEnd: QbeGen.QVal; .)
+    = "LOOP"                            (. QbeGen.NewLabel(lTop);
+                                           QbeGen.NewLabel(lEnd);
+                                           QbeGen.PushLoop(lEnd);
+                                           QbeGen.EmitLabel(lTop); .)
+      StatSeq
+      "END"                             (. QbeGen.Jmp(lTop);
+                                           QbeGen.PopLoop;
+                                           QbeGen.EmitLabel(lEnd); .) .
+  (* FOR with static-sign BY (literal, non-zero; 220 otherwise).
+     Runtime direction would need a compare-select; the literal
+     sign picks cslew/csegew at "DO" time. *)
+  ForStat                               (. VAR lv: SymTab.Name;
+                                             tlo, thi, tby:
+                                               SymTab.TypeIndex;
+                                             qlo, qhi, qby, qt, qk, qb:
+                                               QbeGen.QVal;
+                                             lTop, lBody, lEnd:
+                                               QbeGen.QVal;
+                                             by: INTEGER;
+                                             ok: BOOLEAN; .)
+    = "FOR"                             (. by := 1; .)
+      GetIdent<lv>                      (. ok := SymTab.Lookup(lv);
+                                           IF ~ok THEN
+                                             SemError(201)
+                                           ELSIF (SymTab.SymKind(lv) #
+                                                  SymTab.KindVar)
+                                              & (SymTab.SymKind(lv) #
+                                                 SymTab.KindParam) THEN
+                                             SemError(220); ok := FALSE
+                                           ELSIF ~SymTab.IsIntFamily(
+                                                   SymTab.SymType(lv)) THEN
+                                             SemError(220); ok := FALSE
+                                           END; .)
+      ":=" Expr<tlo, qlo>               (. IF ~SymTab.IsIntFamily(tlo) THEN
+                                             SemError(220); ok := FALSE
+                                           END; .)
+      "TO" Expr<thi, qhi>               (. IF ~SymTab.IsIntFamily(thi) THEN
+                                             SemError(220); ok := FALSE
+                                           END; .)
+      [ "BY" Expr<tby, qby>             (. IF (tby #
+                                             SymTab.InvalidType)
+                                            & ~SymTab.IsIntFamily(tby) THEN
+                                             SemError(220); ok := FALSE
+                                           END;
+                                           IF ~SymTab.ConstInt(qby, by) THEN
+                                             SemError(230); by := 1
+                                           ELSIF by = 0 THEN
+                                             SemError(220); by := 1
+                                           END; .) ]
+      "DO"                              (. IF ok THEN
+                                             QbeGen.StoreVar(lv, qlo,
+                                               FALSE) END;
+                                           QbeGen.NewLabel(lTop);
+                                           QbeGen.NewLabel(lBody);
+                                           QbeGen.NewLabel(lEnd);
+                                           QbeGen.EmitLabel(lTop);
+                                           QbeGen.LoadVar(lv, FALSE, qt);
+                                           QbeGen.NewTemp(qk);
+                                           IF by > 0 THEN
+                                             QbeGen.Op3("cslew", qk,
+                                               qt, qhi, FALSE)
+                                           ELSE QbeGen.Op3("csgew", qk,
+                                             qt, qhi, FALSE)
+                                           END;
+                                           QbeGen.Jnz(qk, lBody, lEnd);
+                                           QbeGen.EmitLabel(lBody); .)
+      StatSeq
+      "END"                             (. IF ok THEN
+                                             QbeGen.LoadVar(lv, FALSE,
+                                               qt);
+                                             QbeGen.IntStr(by, qb);
+                                             QbeGen.NewTemp(qk);
+                                             QbeGen.Op3("add", qk,
+                                               qt, qb, FALSE);
+                                             QbeGen.StoreVar(lv, qk,
+                                               FALSE) END;
+                                           QbeGen.Jmp(lTop);
+                                           QbeGen.EmitLabel(lEnd); .) .
+  CaseStat                              (. VAR tsel: SymTab.TypeIndex;
+                                             qsel, lEnd: QbeGen.QVal; .)
+    = "CASE" Expr<tsel, qsel>           (. QbeGen.NewLabel(lEnd); .)
+      "OF" CaseAlt<tsel, qsel, lEnd>
+      { "|" CaseAlt<tsel, qsel, lEnd> }
+      [ "ELSE" StatSeq ]
+      "END"                             (. QbeGen.EmitLabel(lEnd); .) .
+  (* Compare-chain lowering: each alternative ends its match-tests
+     with "jmp lAfter", so the no-match fallthrough skips the body:
+     "cmp; jnz(lBody,lF); lF: ... ; jmp lAfter; lBody: S; jmp lEnd;
+     lAfter:". Falls into the next alternative, ELSE, or END. *)
+  CaseAlt<tsel: SymTab.TypeIndex; qsel: QbeGen.QVal;
+          lEnd: QbeGen.QVal>            (. VAR lBody, lAfter: QbeGen.QVal; .)
+    =                                   (. QbeGen.NewLabel(lBody);
+                                           QbeGen.NewLabel(lAfter); .)
+      CaseLabel<tsel, qsel, lBody>
+      { "," CaseLabel<tsel, qsel, lBody> }
+      ":"                               (. QbeGen.Jmp(lAfter);
+                                           QbeGen.EmitLabel(lBody); .)
+      StatSeq                           (. QbeGen.Jmp(lEnd);
+                                           QbeGen.EmitLabel(lAfter); .) .
+  CaseLabel<tsel: SymTab.TypeIndex; qsel: QbeGen.QVal;
+            lBody: QbeGen.QVal>         (. VAR t2, t3: SymTab.TypeIndex;
+                                             q2, q3, qc, qd, qe:
+                                               QbeGen.QVal;
+                                             lNext: QbeGen.QVal; .)
+    = Expr<t2, q2>                      (. IF (t2 #
+                                             SymTab.InvalidType)
+                                            & (tsel #
+                                               SymTab.InvalidType)
+                                            & ~SymTab.EqCheck(t2,
+                                                 tsel) THEN
+                                             SemError(213) END;
+                                           IF ~QbeGen.IsImm(q2) THEN
+                                             SemError(230);
+                                             QbeGen.CopyOp("0", q2)
+                                           END;
+                                           QbeGen.NewLabel(lNext);
+                                           QbeGen.Cmp(SymTab.OpEq,
+                                             qsel, q2, qc, FALSE);
+                                           QbeGen.Jnz(qc, lBody, lNext);
+                                           QbeGen.EmitLabel(lNext); .)
+      [ ".." Expr<t3, q3>               (. IF (t3 #
+                                             SymTab.InvalidType)
+                                            & (tsel #
+                                               SymTab.InvalidType)
+                                            & ~SymTab.EqCheck(t3,
+                                                 tsel) THEN
+                                             SemError(213) END;
+                                           IF ~QbeGen.IsImm(q3) THEN
+                                             SemError(230);
+                                             QbeGen.CopyOp("0", q3)
+                                           END;
+                                           QbeGen.Cmp(SymTab.OpGe,
+                                             qsel, q2, qc, FALSE);
+                                           QbeGen.Cmp(SymTab.OpLe,
+                                             qsel, q3, qd, FALSE);
+                                           QbeGen.NewTemp(qe);
+                                           QbeGen.Op3("and", qe, qc, qd,
+                                             FALSE);
+                                           QbeGen.NewLabel(lNext);
+                                           QbeGen.Jnz(qe, lBody, lNext);
+                                           QbeGen.EmitLabel(lNext); .) ] .
+  ReturnStat                            (. VAR t: SymTab.TypeIndex;
+                                             q: QbeGen.QVal;
+                                             res: SymTab.TypeIndex;
+                                             hadE: BOOLEAN; .)
+    = "RETURN"                          (. hadE := FALSE; .)
+      [ Expr<t, q>                      (. hadE := TRUE; .) ]
+                                        (. IF ~SymTab.InProc() THEN
+                                             SemError(232)
+                                           ELSE res := SymTab.CurRes();
+                                             IF ~hadE THEN
+                                               IF res #
+                                                  SymTab.InvalidType THEN
+                                                 SemError(232) END
+                                             ELSIF (res =
+                                                    SymTab.InvalidType)
+                                                OR (t #
+                                                    SymTab.InvalidType)
+                                                & ~SymTab.Assignable(t,
+                                                     res) THEN
+                                               SemError(232)
+                                             END
+                                           END; .) .
   Design<VAR t: SymTab.TypeIndex; VAR k: INTEGER;
          VAR q: QbeGen.QVal; VAR qn: SymTab.Name>
                                         (. VAR n: SymTab.Name;
@@ -351,7 +583,9 @@ PRODUCTIONS
                                                    SymTab.ClassOf(t);
                                                  IF (t #
                                                      SymTab.InvalidType)
-                                                    & (cls = SymTab.ClInt) THEN
+                                                    & ((cls = SymTab.ClInt)
+                                                    OR (cls
+                                                        = SymTab.ClChar)) THEN
                                                    QbeGen.LoadVar(n,
                                                      FALSE, q)
                                                  ELSE
@@ -366,7 +600,9 @@ PRODUCTIONS
                                                  OR (k = SymTab.KindParam) THEN
                                                 cls :=
                                                   SymTab.ClassOf(t);
-                                                IF cls = SymTab.ClInt THEN
+                                                IF (cls = SymTab.ClInt)
+                                                   OR (cls = SymTab.ClBool)
+                                                   OR (cls = SymTab.ClChar) THEN
                                                   QbeGen.LoadVar(n,
                                                     FALSE, q)
                                                 ELSE SemError(230);
@@ -380,7 +616,26 @@ PRODUCTIONS
                                               END
                                            END; .) .
   Expr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
-    = SimExpr<t, q> .
+                                        (. VAR t2: SymTab.TypeIndex;
+                                             op: INTEGER;
+                                             q2, qt: QbeGen.QVal; .)
+    = SimExpr<t, q>
+      [ Rel<op> SimExpr<t2, q2>
+        (. IF SymTab.RelCheck(t, t2, op) THEN
+             t := SymTab.BoolType()
+           ELSE SemError(213); t := SymTab.InvalidType END;
+           IF t # SymTab.InvalidType THEN
+             QbeGen.Cmp(op, q, q2, qt, FALSE);
+             QbeGen.CopyOp(qt, q)
+           ELSE QbeGen.CopyOp("0", q)
+           END; .) ] .
+  Rel<VAR op: INTEGER>
+    = "="                               (. op := SymTab.OpEq; .)
+    | "#"                               (. op := SymTab.OpNeq1; .)
+    | "<"                               (. op := SymTab.OpLt; .)
+    | "<="                              (. op := SymTab.OpLe; .)
+    | ">"                               (. op := SymTab.OpGt; .)
+    | ">="                              (. op := SymTab.OpGe; .) .
   SimExpr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR t2, res2: SymTab.TypeIndex;
                                              op: INTEGER;
@@ -397,52 +652,78 @@ PRODUCTIONS
                                            END
                                          END; .)
       { AddOp<op> Term<t2, q2>
-        (. IF SymTab.ArithCheck(t, t2, FALSE, res2) THEN
-             t := res2
-           ELSE SemError(211); t := SymTab.InvalidType END;
-           IF t # SymTab.InvalidType THEN
-             QbeGen.NewTemp(qt);
-             IF op = SymTab.OpAdd THEN
-               QbeGen.Op3("add", qt, q, q2, FALSE)
-             ELSE
-               QbeGen.Op3("sub", qt, q, q2, FALSE)
-             END;
-             QbeGen.CopyOp(qt, q)
-           ELSE QbeGen.CopyOp("0", q)
+        (. IF op = SymTab.OpOr THEN
+             IF SymTab.BoolCheck(t) & SymTab.BoolCheck(t2) THEN
+               t := SymTab.BoolType()
+             ELSE SemError(212); t := SymTab.InvalidType END;
+             IF t # SymTab.InvalidType THEN
+               QbeGen.NewTemp(qt);
+               QbeGen.Op3("or", qt, q, q2, FALSE);
+               QbeGen.CopyOp(qt, q)
+             ELSE QbeGen.CopyOp("0", q)
+             END
+           ELSE
+             IF SymTab.ArithCheck(t, t2, FALSE, res2) THEN
+               t := res2
+             ELSE SemError(211); t := SymTab.InvalidType END;
+             IF t # SymTab.InvalidType THEN
+               QbeGen.NewTemp(qt);
+               IF op = SymTab.OpAdd THEN
+                 QbeGen.Op3("add", qt, q, q2, FALSE)
+               ELSE
+                 QbeGen.Op3("sub", qt, q, q2, FALSE)
+               END;
+               QbeGen.CopyOp(qt, q)
+             ELSE QbeGen.CopyOp("0", q)
+             END
            END; .) } .
   AddOp<VAR op: INTEGER>
     = "+"                               (. op := SymTab.OpAdd; .)
-    | "-"                               (. op := SymTab.OpSub; .) .
+    | "-"                               (. op := SymTab.OpSub; .)
+    | "OR"                              (. op := SymTab.OpOr; .) .
   Term<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR t2, res2: SymTab.TypeIndex;
                                              op: INTEGER;
                                              q2, qt: QbeGen.QVal; .)
     = Fact<t, q> { MulOp<op> Fact<t2, q2>
-      (. IF SymTab.ArithCheck(t, t2,
-            (op = SymTab.OpDiv) OR (op = SymTab.OpMod),
-            res2) THEN t := res2
-         ELSE SemError(211); t := SymTab.InvalidType END;
-         IF t # SymTab.InvalidType THEN
-           QbeGen.NewTemp(qt);
-           IF op = SymTab.OpTimes THEN
-             QbeGen.Op3("mul", qt, q, q2, FALSE)
-           ELSIF op = SymTab.OpDiv THEN
-             QbeGen.Op3("div", qt, q, q2, FALSE)
-           ELSE
-             QbeGen.Op3("rem", qt, q, q2, FALSE)
-           END;
-           QbeGen.CopyOp(qt, q)
-         ELSE QbeGen.CopyOp("0", q)
+      (. IF op = SymTab.OpAnd THEN
+           IF SymTab.BoolCheck(t) & SymTab.BoolCheck(t2) THEN
+             t := SymTab.BoolType()
+           ELSE SemError(212); t := SymTab.InvalidType END;
+           IF t # SymTab.InvalidType THEN
+             QbeGen.NewTemp(qt);
+             QbeGen.Op3("and", qt, q, q2, FALSE);
+             QbeGen.CopyOp(qt, q)
+           ELSE QbeGen.CopyOp("0", q)
+           END
+         ELSE
+           IF SymTab.ArithCheck(t, t2,
+                (op = SymTab.OpDiv) OR (op = SymTab.OpMod),
+                res2) THEN t := res2
+           ELSE SemError(211); t := SymTab.InvalidType END;
+           IF t # SymTab.InvalidType THEN
+             QbeGen.NewTemp(qt);
+             IF op = SymTab.OpTimes THEN
+               QbeGen.Op3("mul", qt, q, q2, FALSE)
+             ELSIF op = SymTab.OpDiv THEN
+               QbeGen.Op3("div", qt, q, q2, FALSE)
+             ELSE
+               QbeGen.Op3("rem", qt, q, q2, FALSE)
+             END;
+             QbeGen.CopyOp(qt, q)
+           ELSE QbeGen.CopyOp("0", q)
+           END
          END; .) } .
   MulOp<VAR op: INTEGER>
     = "*"                               (. op := SymTab.OpTimes; .)
     | "DIV"                             (. op := SymTab.OpDiv; .)
-    | "MOD"                             (. op := SymTab.OpMod; .) .
+    | "MOD"                             (. op := SymTab.OpMod; .)
+    | "AND"                             (. op := SymTab.OpAnd; .) .
   Fact<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR s: ARRAY [0 .. 255] OF CHAR;
-                                             et, dt: SymTab.TypeIndex;
+                                             et, dt, t2: SymTab.TypeIndex;
                                              dk: INTEGER;
-                                             qd: QbeGen.QVal;
+                                             qd, q2: QbeGen.QVal;
                                              qn: SymTab.Name; .)
     = integer                           (. LexString(s);
                                            QbeGen.NormInt(s, q);
@@ -462,7 +743,15 @@ PRODUCTIONS
                                            END; .)
     | Design<dt, dk, qd, qn>            (. t := dt;
                                            QbeGen.CopyOp(qd, q); .)
-    | "(" Expr<et, q> ")"               (. t := et; .) .
+    | "(" Expr<et, q> ")"               (. t := et; .)
+    | "NOT" Fact<t2, q2>                (. IF SymTab.BoolCheck(t2) THEN
+                                             t := SymTab.BoolType()
+                                           ELSE SemError(212);
+                                             t := SymTab.InvalidType END;
+                                           IF t # SymTab.InvalidType THEN
+                                             QbeGen.NotQ(q2, q)
+                                           ELSE QbeGen.CopyOp("0", q)
+                                           END; .) .
   GetIdent<VAR n: SymTab.Name>
     = ident                             (. LexName(n); .) .
 

+ 6 - 6
compiler/src/M2.lst

@@ -14,12 +14,12 @@ LL(1) conditions:         --  ok  --
 
 Statistics:
 
-  nr of terminals:        36 (limit   400)
-  nr of non-terminals:    39 (limit   210)
-  nr of pragmas:           0 (limit   464)
-  nr of symbolnodes:      75 (limit   500)
-  nr of graphnodes:      295 (limit  1500)
-  nr of conditionsets:     2 (limit   100)
+  nr of terminals:        61 (limit   400)
+  nr of non-terminals:    49 (limit   210)
+  nr of pragmas:           0 (limit   439)
+  nr of symbolnodes:     110 (limit   500)
+  nr of graphnodes:      439 (limit  1500)
+  nr of conditionsets:     5 (limit   100)
   nr of charactersets:    11 (limit   250)
 
 

+ 22 - 0
compiler/src/QbeGen.def

@@ -39,6 +39,28 @@ PROCEDURE CloseModule;
 PROCEDURE NewTemp (VAR t: QVal);
 (* Fresh "%tN" operand (deterministic counter: fixpoint-safe). *)
 
+PROCEDURE NewLabel (VAR l: QVal);
+(* Fresh "@LN" label (deterministic counter). *)
+
+PROCEDURE EmitLabel (l: ARRAY OF CHAR);
+PROCEDURE Jmp (l: ARRAY OF CHAR);
+PROCEDURE Jnz (c, t, f: ARRAY OF CHAR);
+(* "jnz c, @t, @f". *)
+
+PROCEDURE Cmp (op: INTEGER; l, r: ARRAY OF CHAR; VAR q: QVal;
+              isReal: BOOLEAN);
+(* Integer comparison selected by a SymTab.OpEq..OpGe code
+   (ceqw/cnew/csltw/cslew/csgtw/csgew); q := fresh w holding 0/1.
+   isReal reserved; step 2 passes FALSE. *)
+
+PROCEDURE NotQ (a: ARRAY OF CHAR; VAR q: QVal);
+(* q := fresh w holding "NOT a" (xor 1, a). *)
+
+PROCEDURE PushLoop (exit: ARRAY OF CHAR);
+PROCEDURE PopLoop;
+PROCEDURE TopLoop (VAR exit: QVal): BOOLEAN;
+(* Innermost LOOP exit label; FALSE when outside any LOOP. *)
+
 PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
 (* q := fresh temp holding $name (isReal reserved; step 1: FALSE). *)
 

+ 102 - 3
compiler/src/QbeGen.mod

@@ -6,7 +6,14 @@ VAR
   out    : FileIO.File;
   opened : BOOLEAN;
   inBody : BOOLEAN;
+  dead   : BOOLEAN;  (* TRUE past a terminator: next instruction opens
+                         an unreachable block with a fresh label, so
+                         the .ssa stays valid QBE (no dangling temps,
+                         no instruction outside a block). *)
   nTemp  : CARDINAL;
+  nLab   : CARDINAL;
+  loopTop : CARDINAL;
+  loopSt  : ARRAY [0 .. 15] OF QVal;
 
 (* ---------------- small string utilities ---------------- *)
 
@@ -171,7 +178,8 @@ PROCEDURE OpenModule (name: ARRAY OF CHAR);
     FileIO.Open(out, fname, TRUE);
     opened := FileIO.Okay;
     inBody := FALSE;
-    nTemp := 0;
+    dead := FALSE;
+    nTemp := 0; nLab := 0; loopTop := 0;
     WL("# QBE IR generated by the V3 step-1 backend");
     WL("")
   END OpenModule;
@@ -208,7 +216,8 @@ PROCEDURE BeginBody;
     WL("");
     WL("export function w $main() {");
     WL("@start");
-    inBody := TRUE
+    inBody := TRUE;
+    dead := FALSE
   END BeginBody;
 
 PROCEDURE CloseModule;
@@ -249,6 +258,7 @@ PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
   BEGIN
     (* isReal reserved for step 2+; step 1 always passes FALSE. *)
     NewTemp(q);
+    Revive;
     W("  "); W(q);
     W(" =w loadw $");
     WL(name)
@@ -256,6 +266,7 @@ PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
 
 PROCEDURE StoreVar (name: ARRAY OF CHAR; q: ARRAY OF CHAR; isReal: BOOLEAN);
   BEGIN
+    Revive;
     W("  storew ");
     W(q); W(", $"); WL(name)
   END StoreVar;
@@ -263,6 +274,7 @@ PROCEDURE StoreVar (name: ARRAY OF CHAR; q: ARRAY OF CHAR; isReal: BOOLEAN);
 PROCEDURE Op3 (mn: ARRAY OF CHAR; res, l, r: ARRAY OF CHAR;
                isReal: BOOLEAN);
   BEGIN
+    Revive;
     W("  "); W(res);
     W(" =w ");
     W(mn); W(" "); W(l); W(", "); WL(r)
@@ -274,13 +286,100 @@ PROCEDURE NegQ (a: ARRAY OF CHAR; VAR q: QVal; isReal: BOOLEAN);
     Op3("sub", q, "0", a, FALSE)
   END NegQ;
 
+PROCEDURE NotQ (a: ARRAY OF CHAR; VAR q: QVal);
+  BEGIN
+    NewTemp(q);
+    Op3("xor", q, "1", a, FALSE)
+  END NotQ;
+
+PROCEDURE NewLabel (VAR l: QVal);
+  BEGIN
+    Cpy(l, "@L");
+    AppNum(l, nLab);
+    INC(nLab)
+  END NewLabel;
+
+PROCEDURE Revive;
+(* Opens an unreachable block if past a terminator. Keeps every
+   temporary defined and every instruction inside a block, even for
+   dead source tails (EXIT followed by more statements) and CASE
+   chains whose compares follow a Jmp. Counter-driven: fixpoint-safe. *)
+  VAR b: QVal;
+  BEGIN
+    IF opened & dead THEN
+      NewLabel(b);
+      WL(b);
+      dead := FALSE
+    END
+  END Revive;
+
+PROCEDURE EmitLabel (l: ARRAY OF CHAR);
+  BEGIN
+    WL(l);
+    dead := FALSE
+  END EmitLabel;
+
+PROCEDURE Jmp (l: ARRAY OF CHAR);
+  BEGIN
+    IF dead THEN RETURN END;  (* unreachable: a terminator with no
+      intervening label can never be reached; emitting it would end
+      the block and orphan whatever follows. *)
+    W("  jmp "); WL(l);
+    dead := TRUE
+  END Jmp;
+
+PROCEDURE Jnz (c, t, f: ARRAY OF CHAR);
+  BEGIN
+    IF dead THEN RETURN END;
+    W("  jnz "); W(c); W(", "); W(t); W(", "); WL(f);
+    dead := TRUE
+  END Jnz;
+
+PROCEDURE Cmp (op: INTEGER; l, r: ARRAY OF CHAR; VAR q: QVal;
+               isReal: BOOLEAN);
+  VAR mn : ARRAY [0 .. 7] OF CHAR;
+  BEGIN
+    mn[0] := 0C;
+    (* Step 2: integer comparisons only (OpEq..OpGe). *)
+    IF op = SymTab.OpEq THEN Cpy(mn, "ceqw")
+    ELSIF (op = SymTab.OpNeq1) OR (op = SymTab.OpNeq2) THEN Cpy(mn, "cnew")
+    ELSIF op = SymTab.OpLt THEN Cpy(mn, "csltw")
+    ELSIF op = SymTab.OpLe THEN Cpy(mn, "cslew")
+    ELSIF op = SymTab.OpGt THEN Cpy(mn, "csgtw")
+    ELSE Cpy(mn, "csgew")
+    END;
+    NewTemp(q);
+    Op3(mn, q, l, r, FALSE)
+  END Cmp;
+
+PROCEDURE PushLoop (exit: ARRAY OF CHAR);
+  BEGIN
+    IF loopTop <= HIGH(loopSt) THEN
+      Cpy(loopSt[loopTop], exit); INC(loopTop)
+    END
+  END PushLoop;
+
+PROCEDURE PopLoop;
+  BEGIN
+    IF loopTop > 0 THEN DEC(loopTop) END
+  END PopLoop;
+
+PROCEDURE TopLoop (VAR exit: QVal): BOOLEAN;
+  BEGIN
+    IF loopTop = 0 THEN RETURN FALSE END;
+    Cpy(exit, loopSt[loopTop - 1]);
+    RETURN TRUE
+  END TopLoop;
+
 PROCEDURE Remark (s: ARRAY OF CHAR);
   BEGIN
+    Revive;
     W("# "); WL(s)
   END Remark;
 
 BEGIN
   opened := FALSE;
   inBody := FALSE;
-  nTemp := 0
+  dead := FALSE;
+  nTemp := 0; nLab := 0; loopTop := 0
 END QbeGen.

+ 12 - 0
compiler/src/SymTab.def

@@ -137,6 +137,18 @@ PROCEDURE ProcRes (name: ARRAY OF CHAR): TypeIndex;
 PROCEDURE MarkFwd;
 (* Marks the current procedure FORWARD (body must follow). *)
 
+PROCEDURE CloseProc;
+(* Pops the current procedure's scope and its proc-stack entry.
+   Replaces bare PopScope at the end of headings (DefUnit),
+   FORWARD clauses and bodies. *)
+
+PROCEDURE InProc (): BOOLEAN;
+(* TRUE inside a procedure heading/body (RETURN legal). *)
+
+PROCEDURE CurRes (): TypeIndex;
+(* Result type of the innermost open procedure,
+   InvalidType outside procedures or for proper procedures. *)
+
 (* ---------------- type descriptors ---------------- *)
 
 PROCEDURE NewAlias (): TypeIndex;

+ 29 - 1
compiler/src/SymTab.mod

@@ -60,6 +60,8 @@ VAR
   nTypes : CARDINAL;
   curProc : SymPtr;   (* heading being declared *)
   curPTail : SymPtr;  (* positional param chain tail *)
+  procStk : ARRAY [0 .. 15] OF SymPtr;
+  nProc : CARDINAL;
   dInt, dCard, dReal, dChar, dBool : TypeIndex;
 
 (* ---------------- strings ---------------- *)
@@ -504,6 +506,13 @@ PROCEDURE PushRecord (t: TypeIndex): BOOLEAN;
 
 (* ---------------- procedures ---------------- *)
 
+PROCEDURE PushProc (node: SymPtr);
+  BEGIN
+    IF nProc <= HIGH(procStk) THEN
+      procStk[nProc] := node; INC(nProc)
+    END
+  END PushProc;
+
 PROCEDURE EnterProc (name: ARRAY OF CHAR): BOOLEAN;
   VAR node: SymPtr;
   BEGIN
@@ -515,6 +524,7 @@ PROCEDURE EnterProc (name: ARRAY OF CHAR): BOOLEAN;
     node^.fwd := FALSE;
     curProc := node;
     curPTail := NIL;
+    PushProc(node);
     PushScope;
     RETURN TRUE
   END EnterProc;
@@ -530,6 +540,7 @@ PROCEDURE ReenterProc (name: ARRAY OF CHAR): BOOLEAN;
     node^.plink := NIL;  (* fresh signature; step 4 compares old vs new *)
     curProc := node;
     curPTail := NIL;
+    PushProc(node);
     PushScope;
     RETURN TRUE
   END ReenterProc;
@@ -573,6 +584,23 @@ PROCEDURE MarkFwd;
     IF curProc # NIL THEN curProc^.fwd := TRUE END
   END MarkFwd;
 
+PROCEDURE CloseProc;
+  BEGIN
+    IF nProc > 0 THEN DEC(nProc) END;
+    PopScope
+  END CloseProc;
+
+PROCEDURE InProc (): BOOLEAN;
+  BEGIN
+    RETURN nProc > 0
+  END InProc;
+
+PROCEDURE CurRes (): TypeIndex;
+  BEGIN
+    IF nProc = 0 THEN RETURN InvalidType END;
+    RETURN procStk[nProc - 1]^.rslt
+  END CurRes;
+
 (* ---------------- predicates (unchanged) ---------------- *)
 
 PROCEDURE SetBasesOk (a, b: TypeIndex): BOOLEAN;
@@ -732,7 +760,7 @@ PROCEDURE Init;
     scopeList := NIL; scopeTail := NIL;
     fields := NIL; nFields := 0;
     nPend := 0; nPendF := 0;
-    nTypes := 0;
+    nTypes := 0; nProc := 0;
     curProc := NIL; curPTail := NIL;
     curScope := NewScope(NIL, 0);
     dInt := NewDesc(FInt, InvalidType);

+ 1 - 0
compiler/src/compiler.frm

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

+ 33 - 0
compiler/tests/showcase3.mod

@@ -0,0 +1,33 @@
+MODULE Showcase3;
+// Stage 1.9 tour: WHILE factorial, FOR with negative BY, CASE with
+// range + ELSE, REPEAT, LOOP/EXIT with nested IF, BOOLEAN + CHAR.
+// Expected ExitCode: 183 (hand-computed).
+VAR ExitCode, i, f : INTEGER;
+VAR b : BOOLEAN;
+VAR c : CHAR;
+BEGIN
+  (* 5! = 120 *)
+  f := 1; i := 2;
+  WHILE i <= 5 DO f := f * i; i := i + 1 END;
+  (* 120 + 10 + 8 + 6 + 4 + 2 = 150 *)
+  FOR i := 10 TO 2 BY -2 DO f := f + i END;
+  (* 150 MOD 3 = 0 -> 157 *)
+  CASE f MOD 3 OF
+    0 : f := f + 7
+  | 1..2 : f := f + 70
+  ELSE f := f + 700
+  END;
+  (* 157 + 1 + 2 + 3 = 163 *)
+  i := 0;
+  REPEAT i := i + 1; f := f + i UNTIL i >= 3;
+  (* 163 + 10 + 10 = 183 *)
+  i := 0;
+  LOOP
+    i := i + 1;
+    IF i > 2 THEN EXIT END;
+    f := f + 10
+  END;
+  c := 'x';
+  b := (f = 183) AND (c = 'x');
+  IF b THEN ExitCode := f ELSE ExitCode := 0 END
+END Showcase3.

+ 6 - 0
compiler/tests/t_bad_exit.mod

@@ -0,0 +1,6 @@
+MODULE TBadExit;
+VAR ExitCode : INTEGER;
+BEGIN
+  EXIT;
+  ExitCode := 1
+END TBadExit.

+ 5 - 0
compiler/tests/t_bad_for.mod

@@ -0,0 +1,5 @@
+MODULE TBadFor;
+VAR ExitCode, i : INTEGER;
+BEGIN
+  FOR i := 1 TO 10 BY 0 DO ExitCode := i END
+END TBadFor.

+ 5 - 0
compiler/tests/t_bad_return.mod

@@ -0,0 +1,5 @@
+MODULE TBadReturn;
+VAR ExitCode : INTEGER;
+BEGIN
+  RETURN 1
+END TBadReturn.

+ 5 - 0
compiler/tests/t_bad_while.mod

@@ -0,0 +1,5 @@
+MODULE TBadWhile;
+VAR ExitCode : INTEGER;
+BEGIN
+  WHILE 1 DO ExitCode := 1 END
+END TBadWhile.

+ 9 - 0
compiler/tests/t_bool.mod

@@ -0,0 +1,9 @@
+MODULE TBool;
+VAR ExitCode : INTEGER;
+VAR b : BOOLEAN;
+VAR c : CHAR;
+BEGIN
+  b := (3 < 5) AND NOT (2 = 2) OR (1 # 0);
+  c := 'a';
+  IF b AND (c = 'a') THEN ExitCode := 42 ELSE ExitCode := 0 END
+END TBool.

+ 11 - 0
compiler/tests/t_case.mod

@@ -0,0 +1,11 @@
+MODULE TCase;
+VAR ExitCode, x : INTEGER;
+BEGIN
+  x := 7;
+  CASE x OF
+    1, 2 : ExitCode := 100
+  | 3..6 : ExitCode := 200
+  | 7..9 : ExitCode := 103
+  ELSE ExitCode := 300
+  END
+END TCase.

+ 9 - 0
compiler/tests/t_for.mod

@@ -0,0 +1,9 @@
+MODULE TFor;
+VAR ExitCode, i, s : INTEGER;
+BEGIN
+  s := 0;
+  FOR i := 1 TO 10 DO s := s + i END;
+  FOR i := 10 TO 1 BY -2 DO s := s + i END;
+  FOR i := 5 TO 1 DO s := s + 1000 END;
+  ExitCode := s
+END TFor.

+ 10 - 0
compiler/tests/t_if.mod

@@ -0,0 +1,10 @@
+MODULE TIf;
+VAR ExitCode : INTEGER;
+BEGIN
+  ExitCode := 0;
+  IF 1 > 2 THEN ExitCode := 1
+  ELSIF 2 > 1 THEN ExitCode := 20
+  ELSE ExitCode := 30
+  END;
+  IF ExitCode = 20 THEN ExitCode := ExitCode + 5 END
+END TIf.

+ 10 - 0
compiler/tests/t_loop.mod

@@ -0,0 +1,10 @@
+MODULE TLoop;
+VAR ExitCode, i, s : INTEGER;
+BEGIN
+  s := 0; i := 1;
+  LOOP
+    IF i > 10 THEN EXIT END;
+    s := s + i; i := i + 1
+  END;
+  ExitCode := s
+END TLoop.

+ 7 - 0
compiler/tests/t_repeat.mod

@@ -0,0 +1,7 @@
+MODULE TRepeat;
+VAR ExitCode, i, s : INTEGER;
+BEGIN
+  s := 0; i := 1;
+  REPEAT s := s + i; i := i + 1 UNTIL i > 10;
+  ExitCode := s
+END TRepeat.

+ 7 - 0
compiler/tests/t_while.mod

@@ -0,0 +1,7 @@
+MODULE TWhile;
+VAR ExitCode, i, s : INTEGER;
+BEGIN
+  s := 0; i := 1;
+  WHILE i <= 10 DO s := s + i; i := i + 1 END;
+  ExitCode := s
+END TWhile.

+ 61 - 0
docs/summary_step1.9.md

@@ -0,0 +1,61 @@
+# V3 step 1.9 — statements + boolean/relational core (done 2026-09-19)
+
+Interactive grammar §4 (last big section before composites).
+Suite 28/28 (13 run + 15 reject), LL(1)-clean throughout.
+
+## Grammar (`compiler/src/M2.atg`)
+
+- `Expr = SimExpr [Rel SimExpr]`: `= # < <= > >=` (no `<>`, no `IN`
+  yet), `RelCheck` → 213, result BOOLEAN via new `QbeGen.Cmp`
+  (ceqw/cnew/csltw/cslew/csgtw/csgew, eager 0/1).
+- `OR`/`AND` (BoolCheck → 212) eager via or/and; `NOT` via xor-1.
+- `BOOLEAN`/`CHAR` variables load like integers now (still `w`);
+  named CHAR consts load too; other named-const use stays 230.
+  `VAR x: BOOLEAN/CHAR` accepted (enums/subranges still 230).
+- Statements: IF/ELSIF/ELSE, WHILE, REPEAT/UNTIL, LOOP/EXIT
+  (230 outside LOOP via loop-label stack), FOR/TO/static-sign-BY,
+  full CASE (labels, `a..b` ranges, ELSE), RETURN with 232 checks
+  (outside proc / value-vs-proper mismatch / missing value).
+- FOR: integer var + integer bounds (220), literal non-zero BY
+  (ConstInt; non-literal → 230, zero → 220), sign picks cslew/csgew.
+- WITH waits for records (step 3).
+
+## Backend (`QbeGen`: labels, jumps, dead positions)
+
+New: deterministic `@LN` labels, EmitLabel/Jmp/Jnz, Cmp, NotQ,
+LOOP-exit stack. Plus the section's hard lesson, handled once in
+the backend instead of every action:
+
+- QBE forbids instructions after a terminator with no intervening
+  label. Two rules fix all shapes: (1) value-emitters auto-open an
+  unreachable block past a terminator (keeps every temporary
+  defined — a *skipped* definition would dangle later uses);
+  (2) Jmp/Jnz past a terminator are skipped (provably unreachable).
+- CASE needed a real layout fix on top: each alternative now ends
+  its tests with `jmp lAfter`, so no-match fallthrough skips the
+  body (`cmp; jnz(lBody,lF); lF: ...; jmp lAfter; lBody: S; ...
+  lAfter:`). The first attempt (fallthrough into the body) ran the
+  wrong branch and is recorded here so it isn't reinvented.
+
+## SymTab (small)
+
+Proc-stack: EnterProc/ReenterProc push, new CloseProc pops both
+scope and stack (all three heading sites converted); InProc/CurRes
+drive the RETURN 232s. Frame gains 232 "bad RETURN".
+
+## Bugs the tests caught
+
+1. Missing LOOP back-edge — the exact bug Test2's own summary
+   records ("body ran once"); t_loop proved it again.
+2. CASE no-match fallthrough executed the first body (100 vs 103).
+3. Double-terminator QBE rejections (EXIT inside IF bodies).
+
+## Tests
+
+Run: t_if/while/repeat/loop/for (incl. `BY -2` and empty-range
+skip)/case (labels+ranges+ELSE)/bool (vars, chars, `#`, NOT) +
+`showcase3` tour → 183. Reject: 214 non-boolean WHILE, stray EXIT,
+module-level `RETURN 1`, `BY 0`. Total 28 (13 run + 15 reject).
+
+Open (step 2/3/4): REAL lowering, `WITH`, `IN`, CHAR arithmetic
+policy (currently 211), unmatched-FORWARD 231, call emission.