Bladeren bron

TP3: nested records, record copy, enum subranges, REAL division

Eric Streit 2 dagen geleden
bovenliggende
commit
c1ebf22270

+ 7 - 7
CALC-COMPILE.md

@@ -97,7 +97,7 @@ Current rung (measured today, normalised position):
 
 
 | position | blocker |
 | position | blocker |
 |---|---|
 |---|---|
-| **compiles** | `CALC.PAS` reaches its final `.` — code **27,498** B, data **19,694** B |
+| **compiles** | `CALC.PAS` reaches its final `.` — code **27,484** B, data **19,694** B |
 
 
 Rungs behind it (now passed):
 Rungs behind it (now passed):
 
 
@@ -164,7 +164,7 @@ Rungs ahead:
 | 51322 | CASE/ELSE closing `end;` - DONE |
 | 51322 | CASE/ELSE closing `end;` - DONE |
 | 52316 | string vs CHAR comparison (`S <> Chr($FF)`) - DONE |
 | 52316 | string vs CHAR comparison (`S <> Chr($FF)`) - DONE |
 | 56540 | code-image ceiling - DONE (DataStart moved to 0xA000) |
 | 56540 | code-image ceiling - DONE (DataStart moved to 0xA000) |
-| **OK** | **CALC.PAS compiles: code 27,498, data 19,694** |
+| **OK** | **CALC.PAS compiles: code 27,484, data 19,694** |
 
 
 Corrections the measurements force, worth stating plainly because the stage list
 Corrections the measurements force, worth stating plainly because the stage list
 below was written before them:
 below was written before them:
@@ -1262,15 +1262,15 @@ include directory, boots it under qemu and feeds the default welcome/quit keys.
 `--keys '\r...'`, `--keys-file`, `--com`, `--raw`, `--timeout` and `--verbose`
 `--keys '\r...'`, `--keys-file`, `--com`, `--raw`, `--timeout` and `--verbose`
 are available; input is a static boot-time key stream, not a live terminal.
 are available; input is a static boot-time key stream, not a live terminal.
 
 
-**CALC.PAS compiles and runs.**  The runtime is 9,570 bytes; the generated
-program is 27,498 bytes of code and 19,694 bytes of data, with
+**CALC.PAS compiles and runs.**  The runtime is 9,597 bytes; the generated
+program is 27,484 bytes of code and 19,694 bytes of data, with
 `DataStart=0xA000` and `CodeMax=0xA000`.  The boot harness now loads images at
 `DataStart=0xA000` and `CodeMax=0xA000`.  The boot harness now loads images at
 segment `0x1000` and puts its input descriptor at `0x9800`, so the larger image
 segment `0x1000` and puts its input descriptor at `0x9800`, so the larger image
 no longer collides with the boot sector/BIOS areas.  The loader reads 77
 no longer collides with the boot sector/BIOS areas.  The loader reads 77
 sectors and accepts images up to 38,656 bytes.  `tests/calc_smoke.py`
 sectors and accepts images up to 38,656 bytes.  `tests/calc_smoke.py`
 boots CALC.COM under qemu, enters `1+2` in cell A1, checks the resulting
 boots CALC.COM under qemu, enters `1+2` in cell A1, checks the resulting
 `3.00` and the welcome/grid/command menu, sends `/Q`, and requires exit code 0.  The original CALC.COM is 27,901 bytes; ours is
 `3.00` and the welcome/grid/command menu, sends `/Q`, and requires exit code 0.  The original CALC.COM is 27,901 bytes; ours is
-37,071 bytes because the runtime, static links and code layout differ.
+37,084 bytes because the runtime, static links and code layout differ.
 
 
 **Stage 14 — the CRT identifiers.** `GotoXY` (25), `LowVideo`/`NormVideo`
 **Stage 14 — the CRT identifiers.** `GotoXY` (25), `LowVideo`/`NormVideo`
 (16 + 16), `ClrEol` (10), `ClrScr` (6), `KeyPressed` (3), `Delay` (1),
 (16 + 16), `ClrEol` (10), `ClrScr` (6), `KeyPressed` (3), `Delay` (1),
@@ -1317,8 +1317,8 @@ remaining run-time capacity note.
 | limit | value | CALC needs | verdict |
 | limit | value | CALC needs | verdict |
 |---|---|---|---|
 |---|---|---|---|
 | `TextLimit` (source buffer) | 62,903 | `CALC.PAS` + `CALC.INC`, normalised = **62,415** | **488 bytes of slack** |
 | `TextLimit` (source buffer) | 62,903 | `CALC.PAS` + `CALC.INC`, normalised = **62,415** | **488 bytes of slack** |
-| `MaxCode` | 0A000h | 27,498 B program + 9,570 B runtime = **37,068 B** | **OK, 3,892 B spare** |
-| code window | `CodeMax = 0A000h`, capped by `MaxCode` | 27,498 B | **OK**; no data gap (§3.2) |
+| `MaxCode` | 0A000h | 27,484 B program + 9,597 B runtime = **37,081 B** | **OK, 3,879 B spare** |
+| code window | `CodeMax = 0A000h`, capped by `MaxCode` | 27,484 B | **OK**; no data gap (§3.2) |
 | data area | fixed image `0A000h`, zeroed by `initmem` | **19,694 B** (generated) | **OK**; CALC boots under qemu |
 | data area | fixed image `0A000h`, zeroed by `initmem` | **19,694 B** (generated) | **OK**; CALC boots under qemu |
 | stack | 16,384 min / 655,360 max | 16,384 min | matches the original's `minstk`/`maxstk` |
 | stack | 16,384 min / 655,360 max | 16,384 min | matches the original's `minstk`/`maxstk` |
 | `MaxSym` | 3,000 | 47 routines + locals + params | **inferred** OK, verify |
 | `MaxSym` | 3,000 | 47 routines + locals + params | **inferred** OK, verify |

+ 15 - 5
CORPUS-TEST.md

@@ -78,8 +78,18 @@ TP3 examples — were deliberately kept.
 Before cleanup: **6,810 files**, **4,919 `.pas`**, **907 zips**.
 Before cleanup: **6,810 files**, **4,919 `.pas`**, **907 zips**.
 After cleanup: **1,731 files**, **71 `.pas`**, **858 zips**.
 After cleanup: **1,731 files**, **71 `.pas`**, **858 zips**.
 
 
-The remaining 71 Pascal files compile as **54 OK / 17 errors** after the first
-compiler upgrades (long identifiers, named labels, `Addr`, field widths,
-nested arrays, negative bounds, forward pointer types, `NIL`, and forward
-procedure parameters).  The remaining 17 are TP3-feature gaps, not
-unit/Delphi noise.
+The remaining 71 Pascal files compile as **57 OK / 14 errors** with the
+current compiler.  The recent upgrades cover long identifiers, named labels,
+`Addr`, field widths, nested arrays, negative bounds, forward pointer types,
+`NIL`, forward procedure parameters, nested record fields, whole-record/array
+assignment, integer `/` as REAL division, `Ord`, `Succ`/`Pred` on enums, and
+enum subranges.  The remaining 14 are TP3-feature gaps, not unit/Delphi noise:
+
+`BINOUT`, `CONVERT`, `DYNREC`, `FINDCHRS`, `LIST3`, `OT3`, `READARRY`,
+`READDATA`, `READINTS`, `READSTOR`, `SETS`, `STRARRAY`, `TIMEDATE`, `VARREC`.
+
+They reduce to four groups: `Round`/`Trunc` REAL-to-integer conversion
+(`CONVERT`), pointer-array dereference and variant/`absolute` records
+(`DYNREC`, `LIST3`, `OT3`, `VARREC`), set operations with a literal on the
+left (`FINDCHRS`, `SETS`), and text-file `Read`/`Readln` plus character-array
+and typed-file I/O (`BINOUT`, `READ*`, `STRARRAY`).

+ 3 - 3
SUMMARY.md

@@ -2044,7 +2044,7 @@ the compiler *emitting* a 16,560-byte program). The ratchet and front-end
 features through **file control-block operations and the original memory
 features through **file control-block operations and the original memory
 layout** are landed: the ladder (`tests/calc_ladder.sh` +
 layout** are landed: the ladder (`tests/calc_ladder.sh` +
 `tests/fixtures/calc_ladder.tsv`, run by `run_all.sh`; CALC.PAS now stops at
 `tests/fixtures/calc_ladder.tsv`, run by `run_all.sh`; CALC.PAS now stops at
-**CALC.PAS compiles and runs** (code 27,498, data 19,694), the `{$I}` include,
+**CALC.PAS compiles and runs** (code 27,484, data 19,694), the `{$I}` include,
 `^X`/`#nn`, scalar typed constants, sets, enums, records (CellRec = 80),
 `^X`/`#nn`, scalar typed constants, sets, enums, records (CellRec = 80),
 subranges, arrays, element/field access, file types, string value parameters,
 subranges, arrays, element/field access, file types, string value parameters,
 `Length`, the CRT identifiers (`ClrScr`/`GotoXY`/`ClrEol`/`LowVideo`/
 `Length`, the CRT identifiers (`ClrScr`/`GotoXY`/`ClrEol`/`LowVideo`/
@@ -2070,7 +2070,7 @@ standard functions (`Abs`, `Sqr`, `Sqrt`, `Ln`, `Exp`, `Sin`, `Cos`,
 landed, along with `Delete` and local `set of Char` variables.  The math runtime
 landed, along with `Delete` and local `set of Char` variables.  The math runtime
 was compacted to guest Horner/square loops, reducing it from 14,063 to 9,203
 was compacted to guest Horner/square loops, reducing it from 14,063 to 9,203
 bytes.  `Insert`, CASE/ELSE trailing semicolons and string-vs-CHAR comparisons
 bytes.  `Insert`, CASE/ELSE trailing semicolons and string-vs-CHAR comparisons
-are also landed.  **CALC.PAS now compiles to its final `.`** with code 27,498
+are also landed.  **CALC.PAS now compiles to its final `.`** with code 27,484
 and data 19,694.  Nested procedures now use a real static link, so a cell can
 and data 19,694.  Nested procedures now use a real static link, so a cell can
 be entered and its formula/text edited (the first cell-entry path no longer
 be entered and its formula/text edited (the first cell-entry path no longer
 restarts or corrupts the screen).  The boot loader now uses segment 0x1000
 restarts or corrupts the screen).  The boot loader now uses segment 0x1000
@@ -2079,7 +2079,7 @@ in DL as real DOS requires, not only in AL as the private boot handler did;
 CALC is verified under the FreeDOS 1.4 LiveCD (welcome, `1+2` -> `3.00`,
 CALC is verified under the FreeDOS 1.4 LiveCD (welcome, `1+2` -> `3.00`,
 menu, `/Q`).  `tests/calc_smoke.py` still boots the project harness, enters
 menu, `/Q`).  `tests/calc_smoke.py` still boots the project harness, enters
 `1+2` in cell A1, checks the resulting `3.00` and quits via `/Q`.  `tests/calc_cli.py` is the manual launcher
 `1+2` in cell A1, checks the resulting `3.00` and quits via `/Q`.  `tests/calc_cli.py` is the manual launcher
-(`--keys`, `--keys-file`, `--com`, `--raw`, `--timeout`).  The runtime is 9,570 bytes / 59 entries; the
+(`--keys`, `--keys-file`, `--com`, `--raw`, `--timeout`).  The runtime is 9,597 bytes / 60 entries; the
 rt_exec scaffold is at HDR=0x4000 / data 0x5000.  Items 1-4 below are
 rt_exec scaffold is at HDR=0x4000 / data 0x5000.  Items 1-4 below are
 independent of both tracks.
 independent of both tracks.
 
 

+ 177 - 23
shell/Compiler.mod

@@ -146,7 +146,7 @@ CONST
    BI_New = 23 ; BI_Dispose = 24 ; BI_Eof = 25 ; BI_Val = 26 ;
    BI_New = 23 ; BI_Dispose = 24 ; BI_Eof = 25 ; BI_Val = 26 ;
    BI_Copy = 27 ; BI_Abs = 28 ; BI_Sqrt = 29 ; BI_Sqr = 30 ;
    BI_Copy = 27 ; BI_Abs = 28 ; BI_Sqrt = 29 ; BI_Sqr = 30 ;
    BI_Ln = 31 ; BI_Exp = 32 ; BI_Sin = 33 ; BI_Cos = 34 ; BI_Arctan = 35 ;
    BI_Ln = 31 ; BI_Exp = 32 ; BI_Sin = 33 ; BI_Cos = 34 ; BI_Arctan = 35 ;
-   BI_Int = 36 ; BI_Delete = 37 ; BI_Insert = 38 ;
+   BI_Int = 36 ; BI_Delete = 37 ; BI_Insert = 38 ; BI_Ord = 39 ;
    (* the sub-codes EmCrtSeq takes *)
    (* the sub-codes EmCrtSeq takes *)
    CrtClr = 0 ; CrtDl = 1 ; CrtDh = 2 ; CrtCaret = 3 ; CrtEol = 4 ;
    CrtClr = 0 ; CrtDl = 1 ; CrtDh = 2 ; CrtCaret = 3 ; CrtEol = 4 ;
    CrtLow = 5 ; CrtNorm = 6 ; CrtDelay = 7 ; CrtKey = 8 ;
    CrtLow = 5 ; CrtNorm = 6 ; CrtDelay = 7 ; CrtKey = 8 ;
@@ -252,6 +252,8 @@ TYPE
          setsize : CARDINAL ;       (* ... and how many bytes there are *)
          setsize : CARDINAL ;       (* ... and how many bytes there are *)
          sz      : CARDINAL ;       (* width of a kind-1 value: an element or
          sz      : CARDINAL ;       (* width of a kind-1 value: an element or
                                        field's size, not its variable's *)
                                        field's size, not its variable's *)
+         fldFirst, fldLast : CARDINAL ;  (* current record metadata *)
+         arrIdx  : CARDINAL ;       (* current array shape, if any *)
       END ;
       END ;
 
 
    DirRec = RECORD  rng, chk : BOOLEAN  END ;
    DirRec = RECORD  rng, chk : BOOLEAN  END ;
@@ -264,6 +266,8 @@ TYPE
          elem   : CARDINAL ;
          elem   : CARDINAL ;
          off    : CARDINAL ;        (* byte offset within the record *)
          off    : CARDINAL ;        (* byte offset within the record *)
          lo, hi : LONGINT ;         (* bounds, for a subrange/enum field *)
          lo, hi : LONGINT ;         (* bounds, for a subrange/enum field *)
+         fldFirst, fldLast : CARDINAL ;  (* nested record metadata *)
+         arrIdx : CARDINAL ;
       END ;
       END ;
 
 
    ArrRec =
    ArrRec =
@@ -272,6 +276,7 @@ TYPE
          lo, hi : ARRAY [0..3] OF LONGINT ;
          lo, hi : ARRAY [0..3] OF LONGINT ;
          elem   : CARDINAL ;        (* element size in bytes *)
          elem   : CARDINAL ;        (* element size in bytes *)
          ecls   : CARDINAL ;        (* element CLASS, for a subscripted value *)
          ecls   : CARDINAL ;        (* element CLASS, for a subscripted value *)
+         fldFirst, fldLast : CARDINAL ;  (* leaf record metadata *)
       END ;
       END ;
 
 
 (* ---------------------------------------------------------------- *)
 (* ---------------------------------------------------------------- *)
@@ -395,10 +400,12 @@ VAR
    TU_WrInt    : CARDINAL ;  TU_WrIntW : CARDINAL ;
    TU_WrInt    : CARDINAL ;  TU_WrIntW : CARDINAL ;
    TU_WrChar   : CARDINAL ;  TU_WrBool : CARDINAL ;
    TU_WrChar   : CARDINAL ;  TU_WrBool : CARDINAL ;
    TU_WrCharW  : CARDINAL ;  TU_WrBoolW : CARDINAL ;  TU_WrStrW : CARDINAL ;
    TU_WrCharW  : CARDINAL ;  TU_WrBoolW : CARDINAL ;  TU_WrStrW : CARDINAL ;
+   TU_MemCopy  : CARDINAL ;
    TU_WrReal   : CARDINAL ;  TU_RealWrite : CARDINAL ;  TU_RealFmt : CARDINAL ;
    TU_WrReal   : CARDINAL ;  TU_RealWrite : CARDINAL ;  TU_RealFmt : CARDINAL ;
    TU_RealVal   : CARDINAL ;  TU_RealCmp : CARDINAL ;
    TU_RealVal   : CARDINAL ;  TU_RealCmp : CARDINAL ;
    TU_RealAdd   : CARDINAL ;  TU_RealSub : CARDINAL ;
    TU_RealAdd   : CARDINAL ;  TU_RealSub : CARDINAL ;
    TU_RealMul   : CARDINAL ;  TU_RealDiv : CARDINAL ;
    TU_RealMul   : CARDINAL ;  TU_RealDiv : CARDINAL ;
+   TU_RealFromInt : CARDINAL ;
    TU_WrLn     : CARDINAL ;
    TU_WrLn     : CARDINAL ;
    TU_RdInt    : CARDINAL ;  TU_RdChar : CARDINAL ;  TU_RdBool : CARDINAL ;
    TU_RdInt    : CARDINAL ;  TU_RdChar : CARDINAL ;  TU_RdBool : CARDINAL ;
    TU_RdLn     : CARDINAL ;  TU_Halt   : CARDINAL ;
    TU_RdLn     : CARDINAL ;  TU_Halt   : CARDINAL ;
@@ -2711,7 +2718,10 @@ BEGIN
          r.kind := 5 ;
          r.kind := 5 ;
          r.boff := 0 ;
          r.boff := 0 ;
          r.cls := symtab [p].cls ;
          r.cls := symtab [p].cls ;
-         r.sz := symtab [p].size
+         r.sz := symtab [p].size ;
+         r.fldFirst := symtab [p].fldFirst ;
+         r.fldLast := symtab [p].fldLast ;
+         r.arrIdx := symtab [p].arrIdx
       ELSIF CurCh () = '.' THEN
       ELSIF CurCh () = '.' THEN
          (* FIELD selection: `r.f'.  The field's offset adds to the record
          (* FIELD selection: `r.f'.  The field's offset adds to the record
             base's, and the value's class and WIDTH become the field's - which
             base's, and the value's class and WIDTH become the field's - which
@@ -2723,13 +2733,13 @@ BEGIN
             RETURN
             RETURN
          END ;
          END ;
          GetWord () ;
          GetWord () ;
-         IF symtab [r.idx].fldFirst >= symtab [r.idx].fldLast THEN
+         IF r.fldFirst >= r.fldLast THEN
             Err (ESimpType) ;               (* not a record *)
             Err (ESimpType) ;               (* not a record *)
             RETURN
             RETURN
          END ;
          END ;
-         fi := symtab [r.idx].fldFirst ;
+         fi := r.fldFirst ;
          found := FALSE ;
          found := FALSE ;
-         WHILE (fi < symtab [r.idx].fldLast) AND NOT found DO
+         WHILE (fi < r.fldLast) AND NOT found DO
             IF NameEq (fields [fi].name, wrd) THEN
             IF NameEq (fields [fi].name, wrd) THEN
                found := TRUE
                found := TRUE
             ELSE
             ELSE
@@ -2754,6 +2764,9 @@ BEGIN
          END ;
          END ;
          r.cls := fields [fi].cls ;
          r.cls := fields [fi].cls ;
          r.sz := fields [fi].size ;
          r.sz := fields [fi].size ;
+         r.fldFirst := fields [fi].fldFirst ;
+         r.fldLast := fields [fi].fldLast ;
+         r.arrIdx := fields [fi].arrIdx ;
          IF r.cls = TSet THEN
          IF r.cls = TSet THEN
             (* ParseVar set setoff only when the SYMBOL itself was a set; for
             (* ParseVar set setoff only when the SYMBOL itself was a set; for
                an array/record the class was still TArray/TRecord then, so the
                an array/record the class was still TArray/TRecord then, so the
@@ -2764,7 +2777,7 @@ BEGIN
          END
          END
       ELSIF CurCh () = '[' THEN
       ELSIF CurCh () = '[' THEN
          DropCh (GetCh ()) ;
          DropCh (GetCh ()) ;
-         IF symtab [r.idx].cls = TString THEN
+         IF r.cls = TString THEN
             (* s[i]: a STRING is a length byte at s[0] followed by the
             (* s[i]: a STRING is a length byte at s[0] followed by the
                characters, so index i selects the one byte at off+i.  A second
                characters, so index i selects the one byte at off+i.  A second
                subscript on a character is not a type. *)
                subscript on a character is not a type. *)
@@ -2816,7 +2829,7 @@ BEGIN
                r.sz := 1
                r.sz := 1
             END ;
             END ;
             ExpectDelim (']', ENoSemi)
             ExpectDelim (']', ENoSemi)
-         ELSIF symtab [r.idx].cls = TArray THEN
+         ELSIF r.arrIdx > 0 THEN
             (* An array subscript LIST: one expression per dimension, comma
             (* An array subscript LIST: one expression per dimension, comma
                separated.  The byte offset is
                separated.  The byte offset is
                   sum over k of (i[k] - lo[k]) * stride[k]
                   sum over k of (i[k] - lo[k]) * stride[k]
@@ -2825,12 +2838,8 @@ BEGIN
                A constant index folds into coff; a run-time one is scaled as
                A constant index folds into coff; a run-time one is scaled as
                soon as it is parsed and summed on the STACK, because parsing
                soon as it is parsed and summed on the STACK, because parsing
                the next index may clobber AX and DI. *)
                the next index may clobber AX and DI. *)
-            IF symtab [r.idx].arrIdx = 0 THEN
-               Err (ESimpType) ;
-               RETURN
-            END ;
             baseDI := (r.kind = 5) ;
             baseDI := (r.kind = 5) ;
-            ai := symtab [r.idx].arrIdx - 1 ;
+            ai := r.arrIdx - 1 ;
             nd := 0 ;
             nd := 0 ;
             coff := 0 ;
             coff := 0 ;
             accOnStack := FALSE ;
             accOnStack := FALSE ;
@@ -2928,7 +2937,10 @@ BEGIN
                r.boff := W16 (VAL (LONGINT, r.boff) + coff)
                r.boff := W16 (VAL (LONGINT, r.boff) + coff)
             END ;
             END ;
             r.sz := arrs [ai].elem ;
             r.sz := arrs [ai].elem ;
-            r.cls := arrs [ai].ecls
+            r.cls := arrs [ai].ecls ;
+            r.fldFirst := arrs [ai].fldFirst ;
+            r.fldLast := arrs [ai].fldLast ;
+            r.arrIdx := 0
          ELSE
          ELSE
             ParseExpr (t) ;
             ParseExpr (t) ;
             IF OK () THEN
             IF OK () THEN
@@ -2969,6 +2981,9 @@ BEGIN
          r.boff := 0 ;
          r.boff := 0 ;
          r.cls := symtab [p].cls ;
          r.cls := symtab [p].cls ;
          r.sz := symtab [p].size ;
          r.sz := symtab [p].size ;
+         r.fldFirst := symtab [p].fldFirst ;
+         r.fldLast := symtab [p].fldLast ;
+         r.arrIdx := symtab [p].arrIdx ;
          ParseSub (r) ;
          ParseSub (r) ;
          RETURN
          RETURN
       END
       END
@@ -2994,6 +3009,9 @@ BEGIN
       r.setsize := symtab [r.idx].size
       r.setsize := symtab [r.idx].size
    END ;
    END ;
    r.sz := symtab [r.idx].size ;
    r.sz := symtab [r.idx].size ;
+   r.fldFirst := symtab [r.idx].fldFirst ;
+   r.fldLast := symtab [r.idx].fldLast ;
+   r.arrIdx := symtab [r.idx].arrIdx ;
    ParseSub (r)
    ParseSub (r)
 END ParseVar ;
 END ParseVar ;
 
 
@@ -3034,7 +3052,9 @@ PROCEDURE BinOpEmit (op : CARDINAL ; left, right : ERes ; VAR res : ERes) ;
 VAR f : LONGINT ;
 VAR f : LONGINT ;
     okc : BOOLEAN ;
     okc : BOOLEAN ;
 BEGIN
 BEGIN
-   IF (left.cls = TReal) OR (right.cls = TReal) THEN
+   IF (left.cls = TReal) OR (right.cls = TReal) OR (op = OpDiv) THEN
+      (* `/` is real division even when both operands are integers; Pascal's
+         `div` is the integer operator and has its own token. *)
       RealBin (op, left, right, res) ;
       RealBin (op, left, right, res) ;
       RETURN
       RETURN
    END ;
    END ;
@@ -4371,6 +4391,9 @@ BEGIN
          r.idx := withIdx [w] ;
          r.idx := withIdx [w] ;
          r.cls := fields [fi].cls ;
          r.cls := fields [fi].cls ;
          r.sz := fields [fi].size ;
          r.sz := fields [fi].size ;
+         r.fldFirst := fields [fi].fldFirst ;
+         r.fldLast := fields [fi].fldLast ;
+         r.arrIdx := fields [fi].arrIdx ;
          IF r.cls = TSet THEN
          IF r.cls = TSet THEN
             r.setbase := fields [fi].elem ;
             r.setbase := fields [fi].elem ;
             r.setsize := fields [fi].size
             r.setsize := fields [fi].size
@@ -4674,7 +4697,7 @@ BEGIN
       IF NOT OK () THEN
       IF NOT OK () THEN
          RETURN
          RETURN
       END ;
       END ;
-      IF (r.cls # TChar) AND (r.cls # TScalar) THEN
+      IF (r.cls # TChar) AND (r.cls # TScalar) AND (r.cls # TEnum) THEN
          Err (ETypeErr) ;
          Err (ETypeErr) ;
          RETURN
          RETURN
       END ;
       END ;
@@ -4804,6 +4827,26 @@ BEGIN
          Err (ETypeErr)
          Err (ETypeErr)
       END ;
       END ;
       RETURN
       RETURN
+   ELSIF (symtab [idx].tag = KBuiltin) AND (symtab [idx].cls = BI_Ord) THEN
+      IF NOT MatchDelim ('(') THEN
+         Err (ENoSemi) ;
+         RETURN
+      END ;
+      ParseExpr (r) ;
+      ExpectDelim (')', ENoSemi) ;
+      IF NOT OK () THEN
+         RETURN
+      END ;
+      IF (r.cls # TChar) AND (r.cls # TEnum) AND (r.cls # TScalar)
+         AND (r.cls # TBool) THEN
+         Err (ETypeErr) ;
+         RETURN
+      END ;
+      LoadAtom (r) ;
+      r.kind := 2 ;
+      r.cls := TScalar ;
+      r.chr := FALSE ;
+      RETURN
    ELSIF (symtab [idx].tag = KBuiltin) AND (symtab [idx].cls = BI_Length) THEN
    ELSIF (symtab [idx].tag = KBuiltin) AND (symtab [idx].cls = BI_Length) THEN
       (* LENGTH (s): the string's length byte, loaded inline.  No runtime
       (* LENGTH (s): the string's length byte, loaded inline.  No runtime
          entry, and no address is pushed - a string's value already carries its
          entry, and no address is pushed - a string's value already carries its
@@ -5204,6 +5247,32 @@ BEGIN
    END
    END
 END IntToMbf ;
 END IntToMbf ;
 
 
+PROCEDURE RealOperand (r : ERes ; dstOff : CARDINAL) ;
+(* Copy a REAL operand into a 6-byte scratch, converting an ordinal value to
+   REAL first.  RealBin and RealUnary used to call EmCopyRealTo directly; an
+   INTEGER variable there was refused, so `E := A/B' could not compile. *)
+VAR tmp : CARDINAL ;
+BEGIN
+   IF r.cls = TReal THEN
+      EmCopyRealTo (dstOff, r)
+   ELSIF (r.cls = TSet) OR (r.cls = TString) THEN
+      Err (ETypeErr)
+   ELSE
+      IF r.kind = 5 THEN
+         EmPushDi ()
+      ELSIF r.kind = 1 THEN
+         EmPushSymAddr (r.idx, (symtab [r.idx].off + r.boff) MOD 10000H)
+      ELSE
+         LoadAtom (r) ;
+         EmStoreVar (FALSE, symtab [tmpA].off, 2) ;
+         EmPushVarAddr (FALSE, symtab [tmpA].off)
+      END ;
+      EmPushVarAddr (FALSE, dstOff) ;
+      DropC (EmCall (TU_RealFromInt)) ;
+      EmAddSp (4)
+   END
+END RealOperand ;
+
 PROCEDURE RealBin (op : CARDINAL ; left, right : ERes ; VAR res : ERes) ;
 PROCEDURE RealBin (op : CARDINAL ; left, right : ERes ; VAR res : ERes) ;
 (* REAL + - * / using the runtime's 12-byte block entries.  SaveLeft has
 (* REAL + - * / using the runtime's 12-byte block entries.  SaveLeft has
    already copied a REAL left operand to a stable scratch; a kind-5 right is
    already copied a REAL left operand to a stable scratch; a kind-5 right is
@@ -5212,11 +5281,11 @@ VAR block, ent : CARDINAL ;
 BEGIN
 BEGIN
    block := NewScratchRealBlock () ;
    block := NewScratchRealBlock () ;
    IF right.kind = 5 THEN
    IF right.kind = 5 THEN
-      EmCopyRealTo (symtab [block].off + 6, right) ;
-      EmCopyRealTo (symtab [block].off, left)
+      RealOperand (right, symtab [block].off + 6) ;
+      RealOperand (left, symtab [block].off)
    ELSE
    ELSE
-      EmCopyRealTo (symtab [block].off, left) ;
-      EmCopyRealTo (symtab [block].off + 6, right)
+      RealOperand (left, symtab [block].off) ;
+      RealOperand (right, symtab [block].off + 6)
    END ;
    END ;
    CASE op OF
    CASE op OF
       OpAdd : ent := TU_RealAdd
       OpAdd : ent := TU_RealAdd
@@ -5265,6 +5334,46 @@ BEGIN
       Err (EUnknown) ;
       Err (EUnknown) ;
       RETURN
       RETURN
    END ;
    END ;
+   IF ((r.kind = 1) OR (r.kind = 5)) AND (VarSize (r) > 2)
+      AND ((r.cls = TRecord) OR (r.cls = TArray)) THEN
+      (* Whole record/array assignment: copy VarSize(r) bytes.  This is what
+         BIGREC/DYNREC's `Mother := Self', `Father^ := Mother^' and
+         `Friend[Index] := Mother' need.  A DI-addressed destination has to
+         be parked before the source expression can reuse DI. *)
+      IF r.kind = 5 THEN
+         EmPushDi ()
+      END ;
+      ParseExpr (src) ;
+      IF NOT OK () THEN
+         RETURN
+      END ;
+      IF (src.kind # 1) AND (src.kind # 5) THEN
+         Err (ETypeErr) ;
+         RETURN
+      END ;
+      IF VarSize (src) # VarSize (r) THEN
+         Err (ETypeErr) ;
+         RETURN
+      END ;
+      IF src.kind = 5 THEN
+         EmPushDi ()
+      ELSE
+         EmPushSymAddr (src.idx, (symtab [src.idx].off + src.boff) MOD 10000H)
+      END ;
+      IF r.kind = 5 THEN
+         (* stack now holds dest, source; make it source, dest for the ABI *)
+         EmPopAx () ;
+         EmPopBx () ;
+         EmPushAx () ;
+         EmPushBx ()
+      ELSE
+         EmPushSymAddr (r.idx, (symtab [r.idx].off + r.boff) MOD 10000H)
+      END ;
+      EmMovCxi (VarSize (r)) ;
+      DropC (EmCall (TU_MemCopy)) ;
+      EmAddSp (4) ;
+      RETURN
+   END ;
    IF r.cls = TReal THEN
    IF r.cls = TReal THEN
       (* A 6-byte REAL.  A compile-time ordinal constant is converted to the
       (* A 6-byte REAL.  A compile-time ordinal constant is converted to the
          measured MBF layout; another REAL field is copied byte for byte.
          measured MBF layout; another REAL field is copied byte for byte.
@@ -7043,7 +7152,7 @@ BEGIN
             Err (ECompOvf) ;
             Err (ECompOvf) ;
             RETURN
             RETURN
          END ;
          END ;
-         IF symtab [t.idx].fldFirst >= symtab [t.idx].fldLast THEN
+         IF t.fldFirst >= t.fldLast THEN
             Err (ETypeErr) ;
             Err (ETypeErr) ;
             RETURN
             RETURN
          END ;
          END ;
@@ -7064,8 +7173,8 @@ BEGIN
             RETURN
             RETURN
          END ;
          END ;
          withIdx [withN] := t.idx ;
          withIdx [withN] := t.idx ;
-         withFF [withN] := symtab [t.idx].fldFirst ;
-         withFL [withN] := symtab [t.idx].fldLast ;
+         withFF [withN] := t.fldFirst ;
+         withFL [withN] := t.fldLast ;
          INC (withN) ;
          INC (withN) ;
          IF MatchDelim (',') THEN
          IF MatchDelim (',') THEN
             (* another base; the last one is innermost *)
             (* another base; the last one is innermost *)
@@ -7082,6 +7191,8 @@ BEGIN
    ELSE
    ELSE
       (* identifier statement: assignment or call *)
       (* identifier statement: assignment or call *)
       IF WithLookup (wrd, t) THEN
       IF WithLookup (wrd, t) THEN
+         (* A field inside WITH may be followed by `.subfield' or `[index]'. *)
+         ParseSub (t) ;
          IF MatchAssign () THEN
          IF MatchAssign () THEN
             Assignment (t) ;
             Assignment (t) ;
             RETURN
             RETURN
@@ -7115,6 +7226,9 @@ BEGIN
             t.cls := symtab [t.idx].cls
             t.cls := symtab [t.idx].cls
          END ;
          END ;
          t.sz := symtab [t.idx].size ;      (* ParseSub may narrow it further *)
          t.sz := symtab [t.idx].size ;      (* ParseSub may narrow it further *)
+         t.fldFirst := symtab [t.idx].fldFirst ;
+         t.fldLast := symtab [t.idx].fldLast ;
+         t.arrIdx := symtab [t.idx].arrIdx ;
          IF t.cls = TSet THEN
          IF t.cls = TSet THEN
             (* The identifier-statement path does not go through ParseVar, so
             (* The identifier-statement path does not go through ParseVar, so
                it must carry the same set metadata: Assignment's set branch
                it must carry the same set metadata: Assignment's set branch
@@ -7150,6 +7264,7 @@ VAR tok : CARDINAL ;
     consumed : BOOLEAN ;
     consumed : BOOLEAN ;
     running, groupFirst, i, an, recStart : CARDINAL ;
     running, groupFirst, i, an, recStart : CARDINAL ;
     innerIdx, innerN, leafElem, leafCls, j : CARDINAL ;
     innerIdx, innerN, leafElem, leafCls, j : CARDINAL ;
+    elemFldFirst, elemFldLast, fieldFldFirst, fieldFldLast, fieldArrIdx : CARDINAL ;
     alo, ahi : ARRAY [0..3] OF LONGINT ;
     alo, ahi : ARRAY [0..3] OF LONGINT ;
     ecls2 : CARDINAL ;
     ecls2 : CARDINAL ;
 BEGIN
 BEGIN
@@ -7278,6 +7393,8 @@ BEGIN
       ParseType (cls, s2, e2) ;
       ParseType (cls, s2, e2) ;
       ecls2 := cls ;
       ecls2 := cls ;
       innerIdx := newArrIdx ;
       innerIdx := newArrIdx ;
+      elemFldFirst := recFldFirst ;
+      elemFldLast := recFldLast ;
       IF (cls = TArray) AND (innerIdx > 0) THEN
       IF (cls = TArray) AND (innerIdx > 0) THEN
          (* Flatten array-of-array: the outer symbol must index the same
          (* Flatten array-of-array: the outer symbol must index the same
             number of dimensions as `array[a,b]'.  A named element array
             number of dimensions as `array[a,b]'.  A named element array
@@ -7285,6 +7402,8 @@ BEGIN
             inner ArrRec; keep only the leaf element size and class here. *)
             inner ArrRec; keep only the leaf element size and class here. *)
          leafElem := arrs [innerIdx - 1].elem ;
          leafElem := arrs [innerIdx - 1].elem ;
          leafCls := arrs [innerIdx - 1].ecls ;
          leafCls := arrs [innerIdx - 1].ecls ;
+         elemFldFirst := arrs [innerIdx - 1].fldFirst ;
+         elemFldLast := arrs [innerIdx - 1].fldLast ;
          innerN := arrs [innerIdx - 1].n
          innerN := arrs [innerIdx - 1].n
       ELSE
       ELSE
          leafElem := s2 ;
          leafElem := s2 ;
@@ -7306,6 +7425,8 @@ BEGIN
          arrs [arrTop].n := an + innerN ;
          arrs [arrTop].n := an + innerN ;
          arrs [arrTop].elem := leafElem ;
          arrs [arrTop].elem := leafElem ;
          arrs [arrTop].ecls := leafCls ;
          arrs [arrTop].ecls := leafCls ;
+         arrs [arrTop].fldFirst := elemFldFirst ;
+         arrs [arrTop].fldLast := elemFldLast ;
          i := 0 ;
          i := 0 ;
          WHILE i < an DO
          WHILE i < an DO
             arrs [arrTop].lo [i] := alo [i] ;
             arrs [arrTop].lo [i] := alo [i] ;
@@ -7396,6 +7517,9 @@ BEGIN
          IF NOT OK () THEN
          IF NOT OK () THEN
             RETURN
             RETURN
          END ;
          END ;
+         fieldFldFirst := recFldFirst ;
+         fieldFldLast := recFldLast ;
+         fieldArrIdx := newArrIdx ;
          (* every name in the group gets the type and its own slot *)
          (* every name in the group gets the type and its own slot *)
          i := groupFirst ;
          i := groupFirst ;
          WHILE i < fldTop DO
          WHILE i < fldTop DO
@@ -7405,6 +7529,9 @@ BEGIN
             fields [i].off := running ;
             fields [i].off := running ;
             fields [i].lo := enumLo ;
             fields [i].lo := enumLo ;
             fields [i].hi := enumHi ;
             fields [i].hi := enumHi ;
+            fields [i].fldFirst := fieldFldFirst ;
+            fields [i].fldLast := fieldFldLast ;
+            fields [i].arrIdx := fieldArrIdx ;
             running := running + s2 ;
             running := running + s2 ;
             INC (i)
             INC (i)
          END ;
          END ;
@@ -7459,7 +7586,30 @@ BEGIN
       END
       END
    ELSE
    ELSE
       IF consumed THEN
       IF consumed THEN
-         IF Search (wrd, idx) AND (symtab [idx].tag = KType) THEN
+         IF Search (wrd, idx) AND (symtab [idx].tag = KConst) THEN
+            (* An enum/ordinal subrange written with named constants:
+               `Work = Mon..Fri`.  Without this, `Mon' was mistaken for a
+               named TYPE and failed at the first dot. *)
+            lo := symtab [idx].lval ;
+            subcls := symtab [idx].cls ;
+            IF NOT MatchRange () THEN
+               Err (ESimpType) ;
+               RETURN
+            END ;
+            RdConst (hi, subcls, strf) ;
+            IF NOT OK () THEN
+               RETURN
+            END ;
+            cls := TEnum ;
+            IF (lo >= 0) AND (hi <= 255) AND (lo <= hi) THEN
+               size := 1
+            ELSE
+               size := 2
+            END ;
+            elem := size ;
+            enumLo := lo ;
+            enumHi := hi
+         ELSIF Search (wrd, idx) AND (symtab [idx].tag = KType) THEN
             cls := symtab [idx].cls ;
             cls := symtab [idx].cls ;
             size := symtab [idx].size ;
             size := symtab [idx].size ;
             (* The type's ELEMENT size, not its total size.  They are equal for
             (* The type's ELEMENT size, not its total size.  They are equal for
@@ -8381,6 +8531,8 @@ BEGIN
    i := NewSym ("KEYPRESSED", KBuiltin, BI_KeyPressed, 0, 0, 0, 0, FALSE) ;
    i := NewSym ("KEYPRESSED", KBuiltin, BI_KeyPressed, 0, 0, 0, 0, FALSE) ;
    symtab [i].defnd := TRUE ;
    symtab [i].defnd := TRUE ;
    i := NewSym ("UPCASE", KBuiltin, BI_UpCase, 0, 0, 0, 0, FALSE) ;
    i := NewSym ("UPCASE", KBuiltin, BI_UpCase, 0, 0, 0, 0, FALSE) ;
+   i := NewSym ("ORD", KBuiltin, BI_Ord, 0, 0, 0, 0, FALSE) ;
+   symtab [i].defnd := TRUE ;
    i := NewSym ("SUCC", KBuiltin, BI_Succ, 0, 0, 0, 0, FALSE) ;
    i := NewSym ("SUCC", KBuiltin, BI_Succ, 0, 0, 0, 0, FALSE) ;
    i := NewSym ("PRED", KBuiltin, BI_Pred, 0, 0, 0, 0, FALSE) ;
    i := NewSym ("PRED", KBuiltin, BI_Pred, 0, 0, 0, 0, FALSE) ;
    symtab [i].defnd := TRUE ;
    symtab [i].defnd := TRUE ;
@@ -8583,12 +8735,14 @@ BEGIN
    TU_WrCharW  := RT_Entry (56) ;
    TU_WrCharW  := RT_Entry (56) ;
    TU_WrBoolW  := RT_Entry (57) ;
    TU_WrBoolW  := RT_Entry (57) ;
    TU_WrStrW   := RT_Entry (58) ;
    TU_WrStrW   := RT_Entry (58) ;
+   TU_MemCopy  := RT_Entry (59) ;
    TU_WrReal   := RT_Entry (6) ;
    TU_WrReal   := RT_Entry (6) ;
    TU_RealWrite := RT_Entry (30) ;     (* fmtreal + padding + output *)
    TU_RealWrite := RT_Entry (30) ;     (* fmtreal + padding + output *)
    TU_RealFmt   := RT_Entry (28) ;     (* fmtreal into a string variable *)
    TU_RealFmt   := RT_Entry (28) ;     (* fmtreal into a string variable *)
    TU_RealVal   := RT_Entry (29) ;     (* ascreal: ASCIIZ -> REAL *)
    TU_RealVal   := RT_Entry (29) ;     (* ascreal: ASCIIZ -> REAL *)
    TU_RealCmp   := RT_Entry (26) ;     (* recmp: AX = -1/0/+1 *)
    TU_RealCmp   := RT_Entry (26) ;     (* recmp: AX = -1/0/+1 *)
    TU_RealAdd   := RT_Entry (22) ;
    TU_RealAdd   := RT_Entry (22) ;
+   TU_RealFromInt := RT_Entry (21) ;
    TU_RealSub   := RT_Entry (23) ;
    TU_RealSub   := RT_Entry (23) ;
    TU_RealMul   := RT_Entry (24) ;
    TU_RealMul   := RT_Entry (24) ;
    TU_RealDiv   := RT_Entry (25) ;
    TU_RealDiv   := RT_Entry (25) ;

+ 25 - 2
shell/Runtime.mod

@@ -1323,6 +1323,28 @@ BEGIN
    RetR
    RetR
 END EmitWrStr ;
 END EmitWrStr ;
 
 
+PROCEDURE EmitMemCopy ;
+(* Copy CX bytes from [BP+6] to [BP+4].  Used for whole record/array
+   assignment.  ES is not reloaded: in this flat .COM ES = DS. *)
+BEGIN
+   M ("memcopy") ;
+   PushBp ; MovBpSp ;
+   PushSi ; PushDi ;
+   MovSiBp6 ;
+   MovDiBp4 ;
+   Jcxz8 ("memcopy_done") ;
+   M ("memcopy_loop") ;
+   MovAlSi ; StDiAl ;                  (* byte loop: MOVS/STOS use ES, and the
+                                          shell's interpreter only guarantees
+                                          DS=ES in a .COM *)
+   IncSi ; IncDi ;
+   Loop8 ("memcopy_loop") ;
+   M ("memcopy_done") ;
+   PopDi ; PopSi ;
+   MovSpBp ; PopBp ;
+   RetR
+END EmitMemCopy ;
+
 PROCEDURE EmitWrCharW ;
 PROCEDURE EmitWrCharW ;
 (* CHAR with a field width: value at [BP+6], width at [BP+4]. *)
 (* CHAR with a field width: value at [BP+6], width at [BP+4]. *)
 BEGIN
 BEGIN
@@ -4224,7 +4246,7 @@ BEGIN
    EmitRealFromInt ; EmitRealAddSub ; EmitRealMul ;
    EmitRealFromInt ; EmitRealAddSub ; EmitRealMul ;
    EmitRealCmp ; EmitRealInt ; EmitRealMath ; EmitRealConv ; EmitSetOps ;
    EmitRealCmp ; EmitRealInt ; EmitRealMath ; EmitRealConv ; EmitSetOps ;
    EmitStrDelete ; EmitStrInsert ; EmitFileOps ; EmitValReal ;
    EmitStrDelete ; EmitStrInsert ; EmitFileOps ; EmitValReal ;
-   EmitWrCharW ; EmitWrBoolW ; EmitWrStrW ;
+   EmitWrCharW ; EmitWrBoolW ; EmitWrStrW ; EmitMemCopy ;
    EmitData ;
    EmitData ;
    FixUp ;
    FixUp ;
    SetStr (entNm [0], "initmem") ;
    SetStr (entNm [0], "initmem") ;
@@ -4286,6 +4308,7 @@ BEGIN
    SetStr (entNm [56], "wrcharw") ;
    SetStr (entNm [56], "wrcharw") ;
    SetStr (entNm [57], "wrboolw") ;
    SetStr (entNm [57], "wrboolw") ;
    SetStr (entNm [58], "wrstrw") ;
    SetStr (entNm [58], "wrstrw") ;
+   SetStr (entNm [59], "memcopy") ;
    built := TRUE
    built := TRUE
 END RT_Build ;
 END RT_Build ;
 
 
@@ -4313,7 +4336,7 @@ BEGIN
    IF NOT built THEN
    IF NOT built THEN
       RT_Build (0)
       RT_Build (0)
    END ;
    END ;
-   IF i > 58 THEN
+   IF i > 59 THEN
       RETURN 0
       RETURN 0
    END ;
    END ;
    RETURN rtBase + LblOff (entNm [i])
    RETURN rtBase + LblOff (entNm [i])

+ 4 - 3
shell/tests/RtProbe.mod

@@ -108,7 +108,7 @@ END PENT ;
 
 
 PROCEDURE Main ;
 PROCEDURE Main ;
 VAR i, n, base : CARDINAL ;
 VAR i, n, base : CARDINAL ;
-    names : ARRAY [0..58] OF ARRAY [0..15] OF CHAR ;
+    names : ARRAY [0..59] OF ARRAY [0..15] OF CHAR ;
 BEGIN
 BEGIN
    base := GetBase () ;
    base := GetBase () ;
    RT_Build (base) ;
    RT_Build (base) ;
@@ -121,7 +121,7 @@ BEGIN
    PCARD (RT_Size ()) ; PS (" bytes") ; NL ;
    PCARD (RT_Size ()) ; PS (" bytes") ; NL ;
    PS ("code ends at ") ; PCARD (RT_CodeEnd ()) ; NL ;
    PS ("code ends at ") ; PCARD (RT_CodeEnd ()) ; NL ;
    i := 0 ;
    i := 0 ;
-   WHILE i <= 58 DO
+   WHILE i <= 59 DO
       names [i] := "" ;
       names [i] := "" ;
       INC (i)
       INC (i)
    END ;
    END ;
@@ -171,8 +171,9 @@ BEGIN
    names [56] := "wrcharw" ;
    names [56] := "wrcharw" ;
    names [57] := "wrboolw" ;
    names [57] := "wrboolw" ;
    names [58] := "wrstrw" ;
    names [58] := "wrstrw" ;
+   names [59] := "memcopy" ;
    i := 0 ;
    i := 0 ;
-   WHILE i <= 58 DO
+   WHILE i <= 59 DO
       PENT (i, names [i]) ;
       PENT (i, names [i]) ;
       INC (i)
       INC (i)
    END ;
    END ;

+ 3 - 2
shell/tests/audit_helpers.py

@@ -157,6 +157,7 @@ NON_ONE_LINE = {
         # golden; the name grammar cannot read a whole routine.
         # golden; the name grammar cannot read a whole routine.
         "EmitFileOps": COMP,
         "EmitFileOps": COMP,
         "EmitWrCharW": COMP, "EmitWrBoolW": COMP, "EmitWrStrW": COMP,
         "EmitWrCharW": COMP, "EmitWrBoolW": COMP, "EmitWrStrW": COMP,
+        "EmitMemCopy": COMP,
         "AddDiImm": COMP, "AddBxImm": COMP,
         "AddDiImm": COMP, "AddBxImm": COMP,
     },
     },
     "Compiler.mod": {
     "Compiler.mod": {
@@ -772,7 +773,7 @@ def sweep(src, mod, verbose):
     # Its D_ offsets are asserted against this block by check_runtime.py's
     # Its D_ offsets are asserted against this block by check_runtime.py's
     # golden, so "excluded from the name audit" is not "unchecked": the bytes
     # golden, so "excluded from the name audit" is not "unchecked": the bytes
     # are pinned, only the name-to-opcode correspondence does not apply.
     # are pinned, only the name-to-opcode correspondence does not apply.
-    excluded = {"EmitData", "EmitFileOps"}
+    excluded = {"EmitData", "EmitFileOps", "EmitMemCopy"}
     if verbose:
     if verbose:
         print("-- %s" % mod)
         print("-- %s" % mod)
         print("name                 bytes            FCML decode")
         print("name                 bytes            FCML decode")
@@ -981,7 +982,7 @@ def sweep(src, mod, verbose):
     # Its D_ offsets are asserted against this block by check_runtime.py's
     # Its D_ offsets are asserted against this block by check_runtime.py's
     # golden, so "excluded from the name audit" is not "unchecked": the bytes
     # golden, so "excluded from the name audit" is not "unchecked": the bytes
     # are pinned, only the name-to-opcode correspondence does not apply.
     # are pinned, only the name-to-opcode correspondence does not apply.
-    excluded = {"EmitData", "EmitFileOps"}
+    excluded = {"EmitData", "EmitFileOps", "EmitMemCopy"}
     strip = (lambda s: s[2:]) if mod != "Runtime.mod" else (lambda s: s)
     strip = (lambda s: s[2:]) if mod != "Runtime.mod" else (lambda s: s)
     # The table is written in the names the audit uses (Runtime.mod's own
     # The table is written in the names the audit uses (Runtime.mod's own
     # names, Compiler.mod's with the Em prefix already removed), so it is NOT
     # names, Compiler.mod's with the Em prefix already removed), so it is NOT

+ 1 - 1
shell/tests/fixtures/calc_ladder.tsv

@@ -32,4 +32,4 @@
 # data base.  UpCase, set membership, strings, pointers, files, REAL
 # data base.  UpCase, set membership, strings, pointers, files, REAL
 # expressions/functions, recursion, forward definitions, Copy/Val/Delete/
 # expressions/functions, recursion, forward definitions, Copy/Val/Delete/
 # Insert, local sets and the compact math runtime are all landed.
 # Insert, local sets and the compact math runtime are all landed.
-CALC.PAS	OK	27498	19694
+CALC.PAS	OK	27484	19694

+ 8 - 0
shell/tests/fixtures/expected.tsv

@@ -756,3 +756,11 @@ t122_addr	OK	91	9
 t123_nestedarray	OK	79	19
 t123_nestedarray	OK	79	19
 
 
 t124_forwardptr	OK	94	7
 t124_forwardptr	OK	94	7
+
+t125_nestedrecord	OK	53	7
+
+t126_recordassign	OK	111	21
+
+t127_enumsub	OK	55	6
+
+t128_intreal	OK	147	27

+ 1 - 0
shell/tests/fixtures/t125_nestedrecord.out

@@ -0,0 +1 @@
+42

+ 8 - 0
shell/tests/fixtures/t125_nestedrecord.pas

@@ -0,0 +1,8 @@
+program nrec;
+type Inner = record A : integer; end;
+     Outer = record N : Inner; end;
+var O : Outer;
+begin
+  O.N.A := 42;
+  writeln(O.N.A)
+end.

+ 1 - 0
shell/tests/fixtures/t126_recordassign.out

@@ -0,0 +1 @@
+42 hi

+ 12 - 0
shell/tests/fixtures/t126_recordassign.pas

@@ -0,0 +1,12 @@
+program record_assign;
+type R = record
+       A : integer;
+       S : string[5];
+     end;
+var X, Y : R;
+begin
+  X.A := 42;
+  X.S := 'hi';
+  Y := X;
+  writeln(Y.A, ' ', Y.S)
+end.

+ 1 - 0
shell/tests/fixtures/t127_enumsub.out

@@ -0,0 +1 @@
+0

+ 8 - 0
shell/tests/fixtures/t127_enumsub.pas

@@ -0,0 +1,8 @@
+program enumsub;
+type Days = (Mon,Tue,Wed,Thu,Fri,Sat,Sun);
+     Work = Mon..Fri;
+var W : Work;
+begin
+  W := Mon;
+  writeln(Ord(W))
+end.

+ 1 - 0
shell/tests/fixtures/t128_intreal.out

@@ -0,0 +1 @@
+4.50

+ 9 - 0
shell/tests/fixtures/t128_intreal.pas

@@ -0,0 +1,9 @@
+program intreal;
+var A, B : integer;
+    E : real;
+begin
+  A := 9;
+  B := 2;
+  E := A / B;
+  writeln(E:0:2)
+end.

+ 1 - 1
shell/tests/rt_exec.py

@@ -118,7 +118,7 @@ ENTS = ["initmem", "progend", "stackchk", "wrint", "wrchar", "wrbool",
         "int_fmt", "val_int", "str_copy", "val_real", "real_abs",
         "int_fmt", "val_int", "str_copy", "val_real", "real_abs",
         "real_sqrt", "real_ln", "real_exp", "real_sin", "real_cos",
         "real_sqrt", "real_ln", "real_exp", "real_sin", "real_cos",
         "real_arctan", "str_delete", "str_insert",
         "real_arctan", "str_delete", "str_insert",
-        "wrcharw", "wrboolw", "wrstrw"]
+        "wrcharw", "wrboolw", "wrstrw", "memcopy"]
 
 
 # A call slot's flags, as rtdrv.s reads them.
 # A call slot's flags, as rtdrv.s reads them.
 F_STACK = 1     # the argument goes on the stack, popped by the caller
 F_STACK = 1     # the argument goes on the stack, popped by the caller

File diff suppressed because it is too large
+ 395 - 395
shell/tests/runtime.golden


Some files were not shown because too many files changed in this diff