Scalar-subset Modula-2 compiler: SimpleQ.atg (Coco/R) + SymTab
(type checking, reused from Test1) + new QbeGen backend emitting
QBE SSA to gen_qbe/<Module>.ssa, assembled with qbe and linked
with cc. All work lives in Test2/, binary Test2/SimpleQ.
Full SimpleMod2 syntax parses and type-checks with Test1's exact codes (200/201/202, 210–224). Only scalar data is lowered:
w; REAL / LONGREAL → QBE d (widened).Test convention (the language has no I/O): a global
VAR ExitCode : INTEGER makes generated main return its value;
otherwise the program returns 0.
SimpleQ.atg (856 lines) — grammar; single-pass QBE emission.
Every expression synthesizes a QBE operand (immediate like 42,
-5, d_3.14 or fresh %tN); statements emit jumps/labels.
Code emits only on type-correct subtrees, so backend 230s never
cascade with type errors.QbeGen.def/.mod (120/448 lines) — SSA writer: data globals,
export function w $main, temporaries (%tN), labels (@LN),
ceqw/cnew/csltw/cslew/csgtw/csgew + ceqd/cned/cltd/cled/cgtd/cged,
swtof for INTEGER→REAL assignment, loop-exit stack, CASE as
compare-chains, NoQbe guard (single 230 for WITH bodies).SymTab.def/.mod — Test1 copy + 2 accessors (PendCount,
PendName) so VAR-list storage is emitted before FixPending.FileIO, scanner.frm, parser.frm, compiler.frm (+230 text),
compiler.gpm — copied from Test1.build_qbe.sh — regenerates (CRFRAMES=. ../CR -m -C SimpleQ.atg),
compiles with gm2 -fiso, links via the two-phase module-list
workaround, then runs the suite: SimpleQ → qbe → cc → execute,
checking exit codes; negatives must be rejected with the right message.tests/ — 14 positive + 6 negative .mod sources (with .LST
listings); gen_qbe/ — backend output (.ssa, plus .s,
binaries and empty .qbeerr/.ccerr logs from the last run).| test | exit | covers |
|---|---|---|
| q_minimal | 0 | empty body, default ret 0 |
| q_arith | 7 | + - * DIV MOD, precedence, parens |
| q_const | 15 | CONST, unary minus, hex 0AH |
| q_bool | 42 | AND/OR/NOT, comparisons |
| q_if | 20 | IF/ELSIF/ELSE, empty-IF fallthrough |
| q_case | 103 | labels, ranges, ELSE, expr selector |
| q_while | 55 | WHILE sum 1..10 |
| q_repeat | 55 | REPEAT..UNTIL |
| q_loop | 7 | LOOP + EXIT |
| q_for | 85 | FOR, BY -2 negative step |
| q_real | 31 | REAL arith, int→real assign, real CONST (runtime init) |
| q_char | 97 | CHAR literals/comparison |
| q_enum | 1 | enum ordinals, CASE on enum |
| q_full | 74 | combined demo (FOR/MOD/WHILE/REPEAT/LOOP/CASE) |
| q_bad_array | rej/230 | ARRAY var + indexing |
| q_bad_with | rej/230 | RECORD var + WITH (single 230) |
| q_bad_set | rej/230 | SET var + set literal |
| q_bad_exit | rej/230 | EXIT outside LOOP |
| q_bad_const | rej/230 | non-literal CONST (B = A + 1) |
| q_bad_asn | rej/210 | REAL→INTEGER (type parity with Test1) |
Ad-hoc edge probe also verified: unary-minus temps (-y, -r),
empty FOR (5 TO 1 skipped), boolean CASE, 10H, nested LOOP/EXIT
(inner EXIT targets inner loop), AND-ed WHILE-style conditions.
IntStr/NegFold wrote the string terminator one past the end
(double Len() evaluation) → random garbage bytes in .ssa.LabelList took body/next labels as value params, so NewLabel
results were lost → garbage labels. Made them VAR.LoopStat missed the back-edge jmp → loop body ran once.data (d 0 ok,
d 1.5 rejected) → non-zero REAL consts emit d 0 plus a
runtime copy flushed at function start (BeginBody).r*2.0 + x is a
211 error — convert via assignment first).DIV/MOD truncate toward zero (C/QBE semantics, not Modula-2 floor)../build_qbe.sh (from Test2/)../SimpleQ tests/<file>.mod (listing next to the
source, SSA in gen_qbe/), then
qbe -o gen_qbe/<M>.s gen_qbe/<M>.ssa && cc gen_qbe/<M>.s -o gen_qbe/<M> && gen_qbe/<M>; echo $?.CR binary (repo root, ./build.sh), gm2 -fiso,
qbe, cc.