Parcourir la source

v3 step 1.4 — module structure: 3 units, imports, opaque, nesting (9/9 tests green)

Eric Streit il y a 3 semaines
Parent
commit
135b314214

+ 3 - 0
compiler/run_tests.sh

@@ -52,5 +52,8 @@ expect_run t_arith.mod 25
 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_defunit.mod "not supported yet"
+expect_fail t_implunit.mod "not supported yet"
+expect_fail t_nested.mod "not supported yet"
 echo "--- $pass passed, $fail failed ---"
 [ "$fail" -eq 0 ]

+ 103 - 3
compiler/src/M2.atg

@@ -42,21 +42,121 @@ TOKENS
           | '"' { noQuote2 } '"' .
 
 PRODUCTIONS
-  M2                                    (. VAR m1, m2: SymTab.Name; .)
+  M2
+    = Unit "." .
+  (* Units: program modules compile fully; DEFINITION and
+     IMPLEMENTATION modules parse + check now but lower in step 4
+     (each ends with one 230); same for nested local modules. *)
+  Unit
+    = DefUnit
+    | ImplUnit
+    | ProgModule .
+  DefUnit                               (. VAR m1, m2: SymTab.Name; .)
+    = "DEFINITION" "MODULE"
+      GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
+                                           IF ~SymTab.Enter(m1,
+                                              SymTab.KindModule) THEN
+                                             SemError(200) END; .)
+      ";"
+      { Import }
+      { ConstBlock | OpaqueBlock | VarBlock }
+      "END"
+      GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
+                                             SemError(202) END; .)
+                                        (. QbeGen.CloseModule;
+                                           SemError(230);
+                                           SymTab.PrintTable; .) .
+  ImplUnit                              (. VAR m1, m2: SymTab.Name; .)
+    = "IMPLEMENTATION" "MODULE"
+      GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
+                                           IF ~SymTab.Enter(m1,
+                                              SymTab.KindModule) THEN
+                                             SemError(200) END; .)
+      ";"
+      { Import }
+      DeclSeq
+      [ "BEGIN"                         (. QbeGen.BeginBody; .)
+        [ StatSeq ] ]
+      "END"
+      GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
+                                             SemError(202) END; .)
+                                        (. QbeGen.CloseModule;
+                                           SemError(230);
+                                           SymTab.PrintTable; .) .
+  ProgModule                            (. VAR m1, m2: SymTab.Name; .)
     = "MODULE"
       GetIdent<m1>                      (. SymTab.Init; QbeGen.OpenModule(m1);
                                            IF ~SymTab.Enter(m1,
                                               SymTab.KindModule) THEN
                                              SemError(200) END; .)
+      [ Priority ]
       ";"
-      { ConstBlock | VarBlock }
+      { Import }
+      DeclSeq
       [ "BEGIN"                         (. QbeGen.BeginBody; .)
         [ StatSeq ] ]
       "END"
       GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
                                              SemError(202) END; .)
-      "."                               (. QbeGen.EndModule;
+                                        (. QbeGen.EndModule;
                                            SymTab.PrintTable; .) .
+  DeclSeq
+    = { ConstBlock | VarBlock | NestedModule ";" } .
+  (* Local module, Wirth form. Parses + checks fully (own scope);
+     lowering waits for step 4, marked by one 230 at END. *)
+  NestedModule                          (. VAR m1, m2: SymTab.Name; .)
+    = "MODULE"
+      GetIdent<m1>                      (. IF ~SymTab.Enter(m1,
+                                             SymTab.KindModule) THEN
+                                             SemError(200) END;
+                                           SymTab.PushScope; .)
+      [ Priority ]
+      ";"
+      { Import }
+      [ Export ]
+      DeclSeq
+      [ "BEGIN"
+        [ StatSeq ] ]
+      "END"
+      GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
+                                             SemError(202) END;
+                                           SymTab.PopScope;
+                                           SemError(230); .) .
+  Priority
+    = "[" integer "]"                   (. SemError(230); .) .
+  Export                                (. VAR nm: SymTab.Name; .)
+    = "EXPORT"                          (* names wire up in step 4 *)
+      [ "QUALIFIED" ]
+      GetIdent<nm>
+      { "," GetIdent<nm> }
+      ";" .
+  Import                                (. VAR n: SymTab.Name; .)
+    = "FROM"
+      GetIdent<n>                       (. IF ~SymTab.Enter(n,
+                                             SymTab.KindImport) THEN
+                                             SemError(200) END; .)
+      "IMPORT"
+      ImpList ";"
+    | "IMPORT"
+      ImpList ";" .
+  ImpList                               (. VAR n: SymTab.Name; .)
+    = GetIdent<n>                       (. IF ~SymTab.Enter(n,
+                                             SymTab.KindImport) THEN
+                                             SemError(200) END; .)
+      { ","
+        GetIdent<n>                     (. IF ~SymTab.Enter(n,
+                                             SymTab.KindImport) THEN
+                                             SemError(200) END; .) } .
+  (* Opaque TYPE declarations (definition modules). The targetless
+     alias resolves to InvalidType until step 4 completes it. *)
+  OpaqueBlock
+    = "TYPE" { OpaqueDecl ";" } .
+  OpaqueDecl                            (. VAR n: SymTab.Name; .)
+    = GetIdent<n>                       (. IF ~SymTab.Enter(n,
+                                             SymTab.KindType) THEN
+                                             SemError(200) END;
+                                           SymTab.SetSymType(n,
+                                             SymTab.NewAlias()); .) .
   ConstBlock
     = "CONST" { ConstDecl ";" } .
   ConstDecl                             (. VAR n: SymTab.Name;

+ 7 - 6
compiler/src/M2.lst

@@ -4,7 +4,8 @@ Source file: M2.atg
 
 Grammar Tests:
 
-Deletable symbols:        -- none --
+Deletable symbols:
+     DeclSeq
 Undefined nonterminals:   -- none --
 Unreachable nonterminals: -- none --
 Circular derivations:     -- none --
@@ -13,11 +14,11 @@ LL(1) conditions:         --  ok  --
 
 Statistics:
 
-  nr of terminals:        24 (limit   400)
-  nr of non-terminals:    17 (limit   210)
-  nr of pragmas:           0 (limit   476)
-  nr of symbolnodes:      41 (limit   500)
-  nr of graphnodes:      108 (limit  1500)
+  nr of terminals:        33 (limit   400)
+  nr of non-terminals:    29 (limit   210)
+  nr of pragmas:           0 (limit   467)
+  nr of symbolnodes:      62 (limit   500)
+  nr of graphnodes:      211 (limit  1500)
   nr of conditionsets:     1 (limit   100)
   nr of charactersets:    11 (limit   250)
 

+ 5 - 0
compiler/src/QbeGen.def

@@ -31,6 +31,11 @@ PROCEDURE BeginBody;
 PROCEDURE EndModule;
 (* Return sequence (via ExitCode when present), close file. *)
 
+PROCEDURE CloseModule;
+(* Closes the .ssa without emitting a function (units whose
+   lowering waits for step 4: definitions, implementations,
+   and any unit containing nested modules). *)
+
 PROCEDURE NewTemp (VAR t: QVal);
 (* Fresh "%tN" operand (deterministic counter: fixpoint-safe). *)
 

+ 8 - 0
compiler/src/QbeGen.mod

@@ -211,6 +211,14 @@ PROCEDURE BeginBody;
     inBody := TRUE
   END BeginBody;
 
+PROCEDURE CloseModule;
+  BEGIN
+    IF ~opened THEN RETURN END;
+    WL("# no code: unit lowering waits for step 4");
+    FileIO.Close(out);
+    opened := FALSE
+  END CloseModule;
+
 PROCEDURE EndModule;
   BEGIN
     IF ~opened THEN RETURN END;

+ 1 - 1
compiler/src/compiler.frm

@@ -118,7 +118,7 @@ MODULE -->Grammar;
         | 222: Msg("set operand mismatch")
         | 223: Msg("cyclical type definition")
         | 224: Msg("ordinal type required")
-        | 230: Msg("not supported in step 1 (integers only)")
+        | 230: Msg("not supported yet")
         ELSE         Msg("Error: "); WriteInt(lst, nr, 0);
         END;
         WriteLn(lst)

+ 5 - 0
compiler/tests/t_defunit.mod

@@ -0,0 +1,5 @@
+DEFINITION MODULE TDef;
+CONST C = 10;
+TYPE T;
+VAR x : INTEGER;
+END TDef.

+ 5 - 0
compiler/tests/t_implunit.mod

@@ -0,0 +1,5 @@
+IMPLEMENTATION MODULE TImpl;
+VAR ExitCode : INTEGER;
+BEGIN
+  ExitCode := 1
+END TImpl.

+ 11 - 0
compiler/tests/t_nested.mod

@@ -0,0 +1,11 @@
+MODULE TNested;
+VAR ExitCode : INTEGER;
+MODULE Inner;
+VAR y : INTEGER;
+BEGIN
+  y := 2
+END Inner;
+BEGIN
+  ExitCode := 3;
+  y := 4
+END TNested.

+ 46 - 0
docs/summary_step1.4.md

@@ -0,0 +1,46 @@
+# V3 step 1.4 — module structure: three units + imports + opaque + nesting (done 2026-09-19)
+
+Interactive grammar §2, landed as syntax-now/backend-later: everything
+parses and type-checks, but only program modules lower to QBE.
+Suite 9/9 (3 run + 6 reject), LL(1)-clean first try.
+
+## Grammar (`compiler/src/M2.atg`)
+
+- `M2 = Unit "."`, `Unit = DefUnit | ImplUnit | ProgModule`
+  (FIRSTs `DEFINITION`/`IMPLEMENTATION`/`MODULE` — no conflicts).
+- `DefUnit`: `DEFINITION MODULE` + imports + `CONST`/`TYPE`(opaque)/
+  `VAR`, ends with one 230 (lowering = step 4). Procedure headings
+  arrive with the declarations section (§next).
+- `ImplUnit`: full `DeclSeq` + optional `BEGIN`, ends with one 230.
+- `ProgModule`: as before + optional `Priority` (`"[" integer "]"`
+  → 230) + `{ Import }`; only this path calls `QbeGen.EndModule`.
+- `DeclSeq = { ConstBlock | VarBlock | NestedModule ";" }`.
+- `NestedModule`: Wirth form — own scope via `PushScope`/`PopScope`,
+  imports, `EXPORT [QUALIFIED] list ;` (parse-only, wires in step 4),
+  optional `Priority`, decls, optional body **without** `BeginBody`
+  (no fake `main` in the outer file), one 230 at `END`.
+- `Import`: both `FROM A IMPORT x, y;` and `IMPORT A, B;` (V2 parity);
+  names entered as `KindImport`, use-as-value already 230 via Design.
+- `OpaqueBlock`: `TYPE T;` enters a targetless `NewAlias()` which
+  resolves to `InvalidType` until step 4 completes it — cascades
+  suppressed by the standard Invalid rules.
+
+## Support changes
+
+- `QbeGen.CloseModule` (def+mod): closes the `.ssa` with an honest
+  `# no code` remark instead of a bogus `main`.
+- 230 text reworded `"not supported in step 1 (integers only)"` →
+  `"not supported yet"` (no tests referenced the old text).
+
+## Tests
+
+`t_defunit` (CONST+opaque+VAR definition → single 230, no cascades),
+`t_implunit` (→ single 230), `t_nested` (inner `y := 2` checks clean
+in its scope, `y` after `END Inner` → 201 proves the pop, + 230).
+
+## Decisions locked (§2)
+
+Three units now; both IMPORT forms; classic opaque `TYPE T;`;
+nesting kept (Wirth form + EXPORT); priority accepted-but-230.
+Next: § declarations (TYPE syntax, CONST expressions, procedure
+headings — including `ProcHeading ";"` for DefUnit).