|
|
@@ -0,0 +1,88 @@
|
|
|
+# Step: Clarion CLASS lowering — design (not yet implemented)
|
|
|
+
|
|
|
+Status: **not started** (scoped here). Suite **137/137**; fixpoint OK.
|
|
|
+
|
|
|
+## Current state
|
|
|
+
|
|
|
+Classes *parse, scope and check* — fields, methods, `=` consts,
|
|
|
+single/multiple parent lists, `VIRTUAL`, and `CLASS IMPLEMENTATION`
|
|
|
+blocks with nested procedures — but every class/method ends in error
|
|
|
+**230** and method bodies are suppressed (`QbeGen.SetNoEmit(TRUE)` +
|
|
|
+`AbortFunc` in `MethodImpl`). See `docs/OOP.txt` for the grammar.
|
|
|
+
|
|
|
+## Goal
|
|
|
+
|
|
|
+Make a class a usable **value** with callable methods:
|
|
|
+
|
|
|
+```modula-2
|
|
|
+TYPE
|
|
|
+ CLASS Point;
|
|
|
+ x, y : INTEGER;
|
|
|
+ PROCEDURE Set(a, b : INTEGER);
|
|
|
+ PROCEDURE Get() : INTEGER;
|
|
|
+ END Point;
|
|
|
+CLASS IMPLEMENTATION Point;
|
|
|
+ PROCEDURE Set(a, b : INTEGER);
|
|
|
+ BEGIN x := a; y := b END Set;
|
|
|
+ PROCEDURE Get() : INTEGER;
|
|
|
+ BEGIN RETURN x + y END Get;
|
|
|
+END Point;
|
|
|
+VAR p : Point;
|
|
|
+BEGIN p.Set(3, 4); ExitCode := p.Get() END
|
|
|
+```
|
|
|
+
|
|
|
+## Design sketch
|
|
|
+
|
|
|
+1. **Layout** — a class is a record: fields get declaration-order
|
|
|
+ offsets via the existing `ComputeOffsets`/`FieldOffset` machinery
|
|
|
+ (already shared with `RECORD`; `PushRecord` already accepts
|
|
|
+ `FClass`). `obj.field` then works through the record field path.
|
|
|
+ Single inheritance prepends the parent's fields (offset by the
|
|
|
+ parent's size); multiple parents stay 230.
|
|
|
+
|
|
|
+2. **Receiver** — each method gets a **hidden first parameter** `THIS`
|
|
|
+ of type `POINTER TO <class>`. Inside a method body, a bare field
|
|
|
+ name `f` is an implicit `THIS^.f`; the class scope already holds the
|
|
|
+ fields as `KindField`, so `Design` needs a "current receiver"
|
|
|
+ addressing mode (like `WITH`): push the receiver register at method
|
|
|
+ entry, add `FieldOffset` to it, load/store.
|
|
|
+
|
|
|
+3. **Calls** — `obj.M(args)` binds statically to the method's mangled
|
|
|
+ symbol and passes `ADR obj` as the hidden first argument, then the
|
|
|
+ user arguments. `QbeGen.CallBegin`/`ArgList` gain a flag for the
|
|
|
+ implicit receiver. Non-virtual first; `VIRTUAL` accepts but
|
|
|
+ dispatches statically with a `230`-free path.
|
|
|
+
|
|
|
+4. **Dispatch (later)** — a per-class vtable (one static array of code
|
|
|
+ pointers, parent entries first) stored as a hidden first field;
|
|
|
+ `VIRTUAL` calls load the slot and call indirectly (procedure types
|
|
|
+ + indirect calls already exist from step 8.5). This can be a
|
|
|
+ follow-on increment.
|
|
|
+
|
|
|
+5. **`WITH`-style field binding** — reuse `PushRecord`/`PushWith`; the
|
|
|
+ receiver is the `WITH` base, so `WITH p DO x := 1 END` works for
|
|
|
+ free once (2) lands.
|
|
|
+
|
|
|
+## Acceptance (planned)
|
|
|
+
|
|
|
+- The `Point` program above compiles and exits 7.
|
|
|
+- `obj.field` read/write, method calls with value and `VAR` params,
|
|
|
+ methods returning values.
|
|
|
+- Single inheritance: child methods see parent fields.
|
|
|
+- Suite green; fixpoint still byte-identical.
|
|
|
+
|
|
|
+## Risks
|
|
|
+
|
|
|
+- `Design`'s addressing is threaded through several productions;
|
|
|
+ adding a receiver mode must not change existing `RECORD`/`WITH`
|
|
|
+ codegen (fixpoint is the guard).
|
|
|
+- `VIRTUAL`/vtable must not perturb layout for non-virtual classes.
|
|
|
+
|
|
|
+## Files (planned)
|
|
|
+
|
|
|
+`compiler/src/M2.atg` (`ClassRest`/`ClassImplRest`/`MethodImpl`
|
|
|
+lowering, `Design` receiver mode, call-site receiver),
|
|
|
+`compiler/src/SymTab.def`/`.mod` (receiver scope, parent field
|
|
|
+offsets, vtable), `compiler/src/QbeGen.def`/`.mod` (receiver param,
|
|
|
+vtable emission, indirect `VIRTUAL` calls),
|
|
|
+`compiler/tests/t_class*.mod`, `docs/OOP.txt`.
|