Explorar el Código

array constructors T{...} (codegen)

- QbeGen: CtorBegin/CtorElem/CtorElemStr/CtorEnd build a pooled static
  descriptor (data $ctoN = { l len, ... }), matching the declared-array
  layout; FlushCtors emits them at EndModule.  Elements encode b/w/d/l by
  class; nested constructors and string literals for nested ARRAY OF CHAR
  become sub-descriptors referenced by 'l'; CtorEnd pads to ArrayLen and
  adds the CHAR/UCHAR terminator slot.
- M2.atg: TypedSetLit generalized to TypedBraceLit (BITSET{...} still a
  set; ArrayName{...} an array constructor) with BraceElem handling the
  scalar/nested/string elements and the 'expr BY n' repeat form.
  CONST of an array type stores the descriptor address (no scalar data);
  Design resolves an aggregate constant to that address.
- Tests t_ctor{1,2,3} (CHAR copy, BY repeat, nested strings).
- Suite 155/155; fixpoint OK (2,442,496 bytes).  gm2 testsuite corpus
  272 -> 277 compile-OK (0 CRASH).
Eric Streit hace 1 semana
padre
commit
c03563e82c

+ 3 - 0
compiler/run_tests.sh

@@ -62,6 +62,9 @@ expect_run t_case.mod 103
 expect_run t_bool.mod 42
 expect_run t_real.mod 31
 expect_run t_array.mod 108
+expect_run t_ctor1.mod 0
+expect_run t_ctor2.mod 0
+expect_run t_ctor3.mod 0
 expect_run t_nestidx.mod 42
 expect_run t_proctype.mod 42
 expect_run t_compat.mod 42

+ 143 - 18
compiler/src/M2.atg

@@ -58,6 +58,9 @@ VAR
      (InvalidType when the callee is an ordinary procedure). Set by
      Design, consumed by the following ArgList. *)
   methCls: SymTab.TypeIndex;
+  (* Class of the type of the innermost `TypeName{...}` brace
+     constructor (ClSet or ClArray); dispatches BraceElem. *)
+  braceCls: INTEGER;
 
 CHARACTERS
   eol      = CHR(13) .
@@ -650,12 +653,19 @@ PRODUCTIONS
       "="
       Expr<t, qv>                       (. SymTab.SetSymType(n, t);
                                            cls := SymTab.ClassOf(t);
-                                           IF cls = SymTab.ClStr THEN
+                                           IF cls = SymTab.ClArray THEN
+                                             (* an array constructor: qv is
+                                                its pooled descriptor
+                                                address; no scalar data *)
+                                             SymTab.SetSymVal(n, qv)
+                                           ELSIF cls = SymTab.ClStr THEN
                                              SemError(230)
                                            ELSIF NOT QbeGen.IsImm(qv) THEN
-                                             SemError(230) END;
-                                           SymTab.SetSymVal(n, qv);
-                                           QbeGen.DeclConst(n, qv, t); .) .
+                                             SemError(230)
+                                           ELSE
+                                             SymTab.SetSymVal(n, qv);
+                                             QbeGen.DeclConst(n, qv, t)
+                                           END; .) .
   VarBlock
     = "VAR" { VarDecl ";" } .
   VarDecl                               (. VAR nm: SymTab.Name;
@@ -1625,6 +1635,17 @@ PRODUCTIONS
                                                        cls = SymTab.ClReal,
                                                        q)
                                                    END
+                                                 ELSIF (cls = SymTab.ClArray)
+                                                    OR (cls = SymTab.ClStr)
+                                                    OR (cls = SymTab.ClUStr) THEN
+                                                   (* aggregate constant:
+                                                      its value IS the
+                                                      descriptor address *)
+                                                   IF SymTab.GetSymVal(n, cv) THEN
+                                                     QbeGen.CopyOp(cv, q)
+                                                   ELSE
+                                                     QbeGen.CopyOp("0", q)
+                                                   END
                                                  ELSE
                                                    IF t #
                                                       SymTab.InvalidType THEN
@@ -2291,7 +2312,7 @@ PRODUCTIONS
                                                      SymTab.ClRecord)) THEN
                                              QbeGen.NoteAddr(qd, qd)
                                            END; .)
-      [ TypedSetLit<dt, q>              (. t := dt; .) ]
+      [ TypedBraceLit<dt, q>            (. t := dt; .) ]
       [ ArgList<qn, dt, qd, TRUE, methCls, ct2, q2, called>
                                         (. t := ct2;
                                            QbeGen.CopyOp(q2, q); .) ]
@@ -2599,21 +2620,125 @@ PRODUCTIONS
                                            QbeGen.SetZero(q, 8); .)
       [ SetElem<t, q> { "," SetElem<t, q> } ]
       "}" .
-  (* Typed set constructor: TypeName{ elems } — e.g. BITSET{0},
-     BITSET{}.  The declared type (not SET OF [0..255]) sets the
-     width and element span. *)
-  TypedSetLit<vt: SymTab.TypeIndex; VAR q: QbeGen.QVal>
+  (* Typed brace constructor: TypeName{ elems } — BITSET{0} (a set)
+     or ArrayName{...} (an array constructor, GNU Modula-2).  The
+     declared type sets the width (set) or element type (array). *)
+  TypedBraceLit<vt: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR nw: CARDINAL; .)
-    = "{"                               (. IF SymTab.ClassOf(vt) #
-                                              SymTab.ClSet THEN
-                                             SemError(230); nw := 8
-                                           ELSE nw := SymTab.SetWords(vt);
-                                             IF nw = 0 THEN nw := 8 END
+    = "{"                               (. IF vt = SymTab.InvalidType THEN
+                                             braceCls := -1
+                                           ELSE braceCls :=
+                                                  SymTab.ClassOf(vt)
                                            END;
-                                           QbeGen.NewSetTemp(nw, q);
-                                           QbeGen.SetZero(q, nw); .)
-      [ SetElem<vt, q> { "," SetElem<vt, q> } ]
-      "}" .
+                                           IF braceCls = SymTab.ClSet THEN
+                                             IF vt = SymTab.InvalidType THEN
+                                               nw := 8
+                                             ELSE nw := SymTab.SetWords(vt);
+                                               IF nw = 0 THEN nw := 8 END
+                                             END;
+                                             QbeGen.NewSetTemp(nw, q);
+                                             QbeGen.SetZero(q, nw)
+                                           ELSIF braceCls = SymTab.ClArray THEN
+                                             QbeGen.CtorBegin(vt)
+                                           ELSE
+                                             IF vt # SymTab.InvalidType THEN
+                                               SemError(230) END;
+                                             braceCls := -1
+                                           END; .)
+      [ BraceElem<vt, q> { "," BraceElem<vt, q> } ]
+      "}"                               (. IF braceCls = SymTab.ClArray THEN
+                                             QbeGen.CtorEnd(q)
+                                           ELSIF braceCls # SymTab.ClSet THEN
+                                             QbeGen.CopyOp("0", q)
+                                           END; .) .
+  BraceElem<vt: SymTab.TypeIndex; VAR sq: QbeGen.QVal>
+                                        (. VAR et, et2: SymTab.TypeIndex;
+                                             qe, q2: QbeGen.QVal;
+                                             v, v2, reps, k: INTEGER;
+                                             elem: SymTab.TypeIndex;
+                                             lo: INTEGER;
+                                             span: CARDINAL;
+                                             cl, cl2: INTEGER;
+                                             hasR, hasB: BOOLEAN; .)
+    =                                   (. hasR := FALSE; hasB := FALSE; .)
+      Expr<et, qe>
+      [ ".." Expr<et2, q2>              (. hasR := TRUE; .) ]
+      [ "BY" Expr<et2, q2>              (. hasB := TRUE; .) ]
+                                        (. IF braceCls = SymTab.ClSet THEN
+                                             IF hasB THEN SemError(230) END;
+                                             lo := SymTab.SetBaseLo(vt);
+                                             span := SymTab.SetCount(vt);
+                                             IF (et = SymTab.InvalidType)
+                                                OR (hasR AND (et2 =
+                                                   SymTab.InvalidType)) THEN
+                                             ELSE cl :=
+                                                    SymTab.ClassOf(et);
+                                               IF hasR THEN
+                                                 cl2 :=
+                                                   SymTab.ClassOf(et2)
+                                               ELSE cl2 := SymTab.ClInt
+                                               END;
+                                               IF ((cl # SymTab.ClInt)
+ AND (cl # SymTab.ClChar)
+ AND (cl # SymTab.ClBool))
+                                                  OR (hasR AND 
+                                                     ((cl2
+                                                       # SymTab.ClInt)
+ AND (cl2
+                                                        # SymTab.ClChar)
+ AND (cl2
+                                                        # SymTab.ClBool))) THEN
+                                                 SemError(222)
+                                               ELSIF hasR
+ AND SymTab.ConstInt(qe, v)
+ AND SymTab.ConstInt(q2,
+                                                     v2)
+ AND ((v < lo)
+                                                     OR (v2 < lo)
+                                                     OR (v >= lo +
+                                                        VAL(INTEGER, span))
+                                                     OR (v2 >= lo +
+                                                        VAL(INTEGER, span))
+                                                     OR (v > v2)) THEN
+                                                 SemError(222)
+                                                ELSIF hasR THEN
+                                                  QbeGen.SetRange(sq, qe, q2,
+                                                    lo, span)
+                                                ELSIF SymTab.ConstInt(qe,
+                                                        v)
+ AND ((v < lo)
+                                                      OR (v >= lo +
+                                                         VAL(INTEGER,
+                                                           span))) THEN
+                                                  SemError(222)
+                                                ELSE QbeGen.SetBit(sq, qe,
+                                                  lo, span)
+                                               END
+                                             END
+                                           ELSIF braceCls = SymTab.ClArray THEN
+                                             IF hasR THEN SemError(230) END;
+                                             reps := 1;
+                                             IF hasB THEN
+                                               IF SymTab.ConstInt(q2, v2)
+ AND (v2 >= 1) THEN
+                                                 reps := v2
+                                               ELSE SemError(230)
+                                               END
+                                             END;
+                                             elem := SymTab.ArrayElem(vt);
+                                             IF (SymTab.ClassOf(elem) =
+                                                 SymTab.ClArray)
+ AND (SymTab.ClassOf(et) =
+                                                 SymTab.ClStr) THEN
+                                               QbeGen.CtorElemStr(qe, elem)
+                                             ELSE
+                                               k := 0;
+                                               WHILE k < reps DO
+                                                 QbeGen.CtorElem(qe, elem);
+                                                 INC(k)
+                                               END
+                                             END
+                                           END; .) .
   SetElem<st: SymTab.TypeIndex; sq: QbeGen.QVal>         (. VAR et, et2: SymTab.TypeIndex;
                                              qe, q2: QbeGen.QVal;
                                              v, v2: INTEGER;

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2730 - 2605
compiler/src/M2.lst


+ 14 - 0
compiler/src/QbeGen.def

@@ -280,6 +280,19 @@ PROCEDURE DeclArr (name: ARRAY OF CHAR; t: INTEGER);
    ... }`, nested levels as `$name_i`). CHAR→b, int-family/bool/
    enum→w, REAL→d, nested/pointer→l, all zero. *)
 
+PROCEDURE CtorBegin (t: INTEGER);
+(* Begin an array-constructor descriptor (`T{...}`): t is the
+   (fixed) array type.  Elements accumulate via CtorElem. *)
+PROCEDURE CtorElem (v: ARRAY OF CHAR; elemT: INTEGER);
+(* Append one constructor element: v is the (constant) QBE operand,
+   elemT the array's element type (encodes b/w/d/l by class). *)
+PROCEDURE CtorElemStr (v: ARRAY OF CHAR; elemT: INTEGER);
+(* Append a string-literal element ($strN) destined for a nested
+   CHAR-array elemT: builds a matching sub-descriptor. *)
+PROCEDURE CtorEnd (VAR q: QVal);
+(* Finish the constructor; q := the descriptor address `$ctoN` (the
+   pooled `data` flushes at EndModule). *)
+
 PROCEDURE DeclStr (text: ARRAY OF CHAR; VAR q: QVal);
 (* Emits `data $strN = { l len, b ... }` for a quoted literal and
    returns its address operand. Deterministic counter. *)
@@ -445,6 +458,7 @@ PROCEDURE HApp (s: ARRAY OF CHAR);
 PROCEDURE UpAddrOf (flat: INTEGER; levels: CARDINAL; VAR q: QVal);
 PROCEDURE Revive;
 PROCEDURE FlushStrings;
+PROCEDURE FlushCtors;
 
 PROCEDURE EmitVTables;
 (* Emit a data array per class that has virtual methods. *)

+ 139 - 0
compiler/src/QbeGen.mod

@@ -27,6 +27,14 @@ VAR
   ustrCount : ARRAY [0 .. 255] OF CARDINAL;
   ustrPool : ARRAY [0 .. 8191] OF INTEGER;
   ustrUsed : CARDINAL;
+  (* array constructors: pooled static descriptors *)
+  ctorN : CARDINAL;
+  ctorNam : ARRAY [0 .. 255] OF QVal;
+  ctorTxt : ARRAY [0 .. 255] OF ARRAY [0 .. 4095] OF CHAR;
+  ctorTop : CARDINAL;
+  ctorTyp : ARRAY [0 .. 7] OF INTEGER;
+  ctorCnt : ARRAY [0 .. 7] OF CARDINAL;
+  ctorBuf : ARRAY [0 .. 7] OF ARRAY [0 .. 4095] OF CHAR;
   withTop : CARDINAL;
   withSt  : ARRAY [0 .. 7] OF QVal;
   noEmit : BOOLEAN;  (* TRUE while parsing nested procedures in 4.1:
@@ -511,6 +519,7 @@ PROCEDURE OpenModule (name: ARRAY OF CHAR);
     nTemp := 0; nLab := 0; loopTop := 0; nR := 0; nStr := 0;
     withTop := 0;
     noEmit := FALSE; inFunc := FALSE; useStack := FALSE;
+    ctorN := 0; ctorTop := 0;
     nLoc := 0; nPar := 0; nArg := 0; nn := 0; callDepth := 0;
     recvArmed := FALSE;
     funcDepth := 0; scopeTop := 0; scopeBase[0] := 0;
@@ -704,6 +713,7 @@ PROCEDURE EndModule (name: ARRAY OF CHAR);
     END;
     FlushStrings;
     FlushUStrings;
+    FlushCtors;
     EmitVTables;
     (* the image is named after the program module *)
     fname[0] := CHR(0);
@@ -2713,6 +2723,135 @@ PROCEDURE DeclArr (name: ARRAY OF CHAR; t: INTEGER);
     ArrData(name, t)
   END DeclArr;
 
+(* ---------------- array constructors (step: `T{...}`) ---------------- *)
+(* A constructor becomes a pooled static descriptor with the same
+   layout as a declared array: "l <ArrayLen>, <items>" (CHAR/UCHAR
+   get a trailing zero terminator slot, like ArrBodyItems).  Nested
+   arrays are emitted as their own pooled descriptors and referenced
+   by `l $ctoK` so the runtime descriptor pointers stay valid. *)
+
+PROCEDURE CtorItem (VAR buf: ARRAY OF CHAR; v: ARRAY OF CHAR;
+                    cls: INTEGER);
+  BEGIN
+    App(buf, ", ");
+    IF cls = SymTab.ClChar THEN
+      App(buf, "b ")
+    ELSIF cls = SymTab.ClReal THEN
+      App(buf, "d ")
+    ELSIF (cls = SymTab.ClPtr) OR (cls = SymTab.ClProc)
+       OR (cls = SymTab.ClLong) OR (cls = SymTab.ClArray) THEN
+      App(buf, "l ")
+    ELSE
+      App(buf, "w ")
+    END;
+    App(buf, v)
+  END CtorItem;
+
+PROCEDURE CtorBegin (t: INTEGER);
+  BEGIN
+    IF ctorTop > HIGH(ctorTyp) THEN RETURN END;
+    ctorTyp[ctorTop] := t;
+    ctorCnt[ctorTop] := 0;
+    ctorBuf[ctorTop][0] := CHR(0);
+    INC(ctorTop)
+  END CtorBegin;
+
+PROCEDURE CtorElem (v: ARRAY OF CHAR; elemT: INTEGER);
+  BEGIN
+    IF ctorTop = 0 THEN RETURN END;
+    CtorItem(ctorBuf[ctorTop - 1], v, SymTab.ClassOf(elemT));
+    INC(ctorCnt[ctorTop - 1])
+  END CtorElem;
+
+PROCEDURE CtorElemStr (v: ARRAY OF CHAR; elemT: INTEGER);
+(* v is a $strN descriptor; build a CHAR-array descriptor of the
+   element type elemT from the recorded string text, then reference
+   it as a nested element. *)
+  VAR k: CARDINAL;
+    found: BOOLEAN;
+    txt, bv, sv: QVal;
+    buf: ARRAY [0 .. 4095] OF CHAR;
+    n, i, L: CARDINAL;
+  BEGIN
+    IF (ctorTop = 0) OR (ctorN > HIGH(ctorNam)) THEN RETURN END;
+    found := FALSE; k := 0;
+    WHILE (k < nStr) AND NOT found DO
+      Cpy(sv, "$"); App(sv, strNams[k]);
+      IF SymTab.Equal(sv, v) THEN
+        Cpy(txt, strTexts[k]); found := TRUE
+      ELSE INC(k)
+      END
+    END;
+    IF NOT found THEN RETURN END;
+    L := Len(txt);
+    n := SymTab.ArrayLen(elemT);
+    buf[0] := CHR(0);
+    App(buf, "l ");
+    IntStr(VAL(INTEGER, n), bv);
+    App(buf, bv);
+    i := 0;
+    WHILE i < n DO
+      IF (i + 1 < L - 1) AND ((i + 1) <= HIGH(txt)) THEN
+        IntStr(ORD(txt[i + 1]), bv)
+      ELSE Cpy(bv, "0")
+      END;
+      CtorItem(buf, bv, SymTab.ClChar);
+      INC(i)
+    END;
+    App(buf, ", b 0");
+    Cpy(ctorNam[ctorN], "cto"); AppNum(ctorNam[ctorN], ctorN);
+    Cpy(ctorTxt[ctorN], buf);
+    INC(ctorN);
+    Cpy(buf, "$"); App(buf, ctorNam[ctorN - 1]);
+    CtorItem(ctorBuf[ctorTop - 1], buf, SymTab.ClArray);
+    INC(ctorCnt[ctorTop - 1])
+  END CtorElemStr;
+
+PROCEDURE CtorEnd (VAR q: QVal);
+  VAR t, elem, ecls, n, cnt, k: INTEGER;
+    bv: QVal;
+  BEGIN
+    IF ctorTop = 0 THEN Cpy(q, "0"); RETURN END;
+    DEC(ctorTop);
+    IF noEmit THEN Cpy(q, "0"); RETURN END;
+    t := ctorTyp[ctorTop];
+    cnt := VAL(INTEGER, ctorCnt[ctorTop]);
+    elem := SymTab.ArrayElem(t);
+    ecls := SymTab.ClassOf(elem);
+    n := VAL(INTEGER, SymTab.ArrayLen(t));
+    IF ecls = SymTab.ClChar THEN
+      k := cnt;
+      WHILE k < n DO CtorItem(ctorBuf[ctorTop], "0", SymTab.ClChar); INC(k) END;
+      App(ctorBuf[ctorTop], ", b 0")
+    ELSIF ecls = SymTab.ClUChar THEN
+      k := cnt;
+      WHILE k < n DO CtorItem(ctorBuf[ctorTop], "0", SymTab.ClUChar); INC(k) END;
+      App(ctorBuf[ctorTop], ", w 0")
+    ELSE
+      k := cnt;
+      WHILE k < n DO CtorItem(ctorBuf[ctorTop], "0", ecls); INC(k) END
+    END;
+    IF ctorN > HIGH(ctorNam) THEN Cpy(q, "0"); RETURN END;
+    Cpy(ctorNam[ctorN], "cto"); AppNum(ctorNam[ctorN], ctorN);
+    Cpy(ctorTxt[ctorN], "l ");
+    IntStr(n, bv);
+    App(ctorTxt[ctorN], bv);
+    App(ctorTxt[ctorN], ctorBuf[ctorTop]);
+    INC(ctorN);
+    Cpy(q, "$"); App(q, ctorNam[ctorN - 1])
+  END CtorEnd;
+
+PROCEDURE FlushCtors;
+  VAR k: CARDINAL;
+  BEGIN
+    IF NOT opened THEN RETURN END;
+    k := 0;
+    WHILE k < ctorN DO
+      W("data $"); W(ctorNam[k]); W(" = { "); W(ctorTxt[k]); WL(" }");
+      INC(k)
+    END
+  END FlushCtors;
+
 PROCEDURE PushLoop (exit: ARRAY OF CHAR);
   BEGIN
     IF loopTop <= HIGH(loopSt) THEN

+ 10 - 0
compiler/tests/t_ctor1.mod

@@ -0,0 +1,10 @@
+MODULE tctor1;
+// GNU Modula-2 array constructor: a CHAR-array constant copied whole.
+TYPE arr = ARRAY [0..4] OF CHAR;
+CONST my = arr {'h','e','l','l','o'};
+VAR a : arr;
+VAR ExitCode : INTEGER;
+BEGIN
+  a := my;
+  IF (a[0] = 'h') AND (a[4] = 'o') THEN ExitCode := 0 ELSE ExitCode := 1 END
+END tctor1.

+ 11 - 0
compiler/tests/t_ctor2.mod

@@ -0,0 +1,11 @@
+MODULE tctor2;
+// Constructor with the BY repeat form: {1,2,3,4 BY 3} fills 1..6.
+TYPE arr = ARRAY [0..5] OF CARDINAL;
+CONST c = arr {1, 2, 3, 4 BY 3};
+VAR a : arr;
+VAR ExitCode : INTEGER;
+BEGIN
+  a := c;
+  IF (a[0]=1) AND (a[2]=3) AND (a[3]=4) AND (a[5]=4) THEN ExitCode := 0
+  ELSE ExitCode := 1 END
+END tctor2.

+ 11 - 0
compiler/tests/t_ctor3.mod

@@ -0,0 +1,11 @@
+MODULE tctor3;
+// Nested constructor: an array of CHAR arrays, elements given as strings.
+TYPE name = ARRAY [0..3] OF CHAR;
+     names = ARRAY [0..1] OF name;
+CONST t = names { "Thu", "Fri" };
+VAR a : names;
+VAR ExitCode : INTEGER;
+BEGIN
+  a := t;
+  IF (a[0][0] = 'T') AND (a[1][2] = 'i') THEN ExitCode := 0 ELSE ExitCode := 1 END
+END tctor3.

+ 4 - 0
docs/features.md

@@ -21,6 +21,10 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
   enumerations (literals carry ordinals; usable in expressions,
   `CASE` labels, `CONST` and as parameter/value types).
 - ✅ `ARRAY [lo..hi,…]` (multi-dim) and open `ARRAY OF` formals.
+- ✅ Array constructors `T{…}` (GNU Modula-2): a `CONST` of an array
+  type fills a pooled static descriptor (copied/indexed like a
+  declared array); scalar elements, nested `Sub{…}`, string elements
+  for nested `ARRAY OF CHAR`, and the `expr BY n` repeat form.
 - ✅ `RECORD` (flat blobs, nested inline, array fields as pointers),
   including variant records (`RECORD … ; CASE tag : T OF … END`): plain
   fields pack before the tag, each branch's fields overlay after it.

+ 22 - 7
docs/summary_v3-corpus.md

@@ -1,6 +1,6 @@
 # Step: V3 compiler corpus runs
 
-Tag `v3-corpus-run`. V3 suite **152/152**, fixpoint OK.
+Tag `v3-corpus-run`. V3 suite **155/155**, fixpoint OK.
 
 ## Why
 
@@ -22,10 +22,10 @@ gap).
 
 | | no libs | with `$V3_LIBS` |
 | --- | --- | --- |
-| **compile-OK** | 242 | **272** |
+| **compile-OK** | 242 | **277** |
 | CRASH | 0 | 0 |
 | LIB | 185 | **9** |
-| LANG | 185 | **331** |
+| LANG | 185 | **326** |
 
 **2. gm2 standard libraries** (`m2pim`/`m2iso`/`m2log`/`m2cor`/`m2min`,
 336 files):
@@ -57,6 +57,16 @@ are visible.)
    procedure with an open-array parameter to a procedure variable works.
 6. **Harness import resolution** — `corpus.sh` now finds and passes the
    `.def` of each imported module (`$V3_LIBS`), removing the LIB noise.
+7. **Array constructors** `T{…}` (GNU Modula-2).  A `CONST` whose value
+   is a constructor (`my = arr {'h','e','l','l','o'}`, `arr {1,2,3,4 BY
+   3}`, nested `names { "Thu", "Fri" }`) now lowers to a pooled static
+   descriptor (`data $ctoN = { l len, … }`) with the same layout as a
+   declared array, and the constant resolves to its address; whole-array
+   copy, indexing and `LEN` then work unchanged.  Elements: scalars
+   (`b`/`w`/`d`/`l` by class), nested constructors, string literals for
+   nested `ARRAY OF CHAR`, and the `expr BY n` repeat form.  New
+   `QbeGen.Ctor{Begin,Elem,ElemStr,End}`; the old `TypedSetLit` became
+   the unified `TypedBraceLit` (`BITSET{…}` still a set).
 
 ## Remaining LANG gaps
 
@@ -67,7 +77,8 @@ are visible.)
   above, and small library holes.
 - **gm2-specific syntax** — `PROCEDURE __BUILTIN__ name(…)`, C-name
   pragmas, ISO module attributes (not V3's dialect; skip).
-- **`not supported yet`** — deliberate `230`s (array constructors, …).
+- **`not supported yet`** — deliberate `230`s (records in
+  constructors, non-numeric `BY`, …).
 
 ## Reproduce
 
@@ -84,12 +95,16 @@ V3_LIBS="$G/m2pim:$G/m2iso:$G/m2log:$G/m2cor:$G/m2min" \
 
 1. **`ARRAY <type> OF T`** (index-type arrays) — a real PIM feature.
 2. More `SYSTEM` facilities / library holes.
-3. Array constructors `array{…}` (codegen).
+3. Records in array constructors, and constructors as non-`CONST`
+   expressions (runtime element values).
 
 ## Files
 
 `tools/v3-corpus/{corpus.sh,README.md}`, `compiler/src/compiler.frm`
 (long-line), `compiler/src/M2.atg` (`EXPORT QUALIFIED`, anchored
-subranges, unnamed proc-type params), `compiler/src/SymTab.def`/`.mod`
-(SYSTEM predefs, `ParamTypesOk`), `compiler/tests/t_longline.mod`,
+subranges, unnamed proc-type params, `TypedBraceLit` array
+constructors), `compiler/src/SymTab.def`/`.mod` (SYSTEM predefs,
+`ParamTypesOk`), `compiler/src/QbeGen.def`/`.mod`
+(`CtorBegin`/`CtorElem`/`CtorElemStr`/`CtorEnd`, `FlushCtors`),
+`compiler/tests/t_ctor{1,2,3}.mod`, `compiler/tests/t_longline.mod`,
 `compiler/run_tests.sh`, this doc.

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio