Browse Source

calc: Insert string builtin (ladder 51322)

Add str_insert (runtime entry 55).  ABI: [BP+4]=max, [BP+6]=index,
[BP+8]=destination, [BP+10]=source.  The tail moves backwards, the source is
copied in at the clamped 1-based index, and an over-capacity result leaves via
TP3 error 16.  A CHAR source is materialised as a one-character counted string
by the compiler; string sources are passed directly.

t110_insert covers char/string sources, middle/end/front and index clamping and
runs under qemu and Exec86.

CALC now stops at ERR 1 51322: the `end;` closing the CASE/ELSE in GetLine.
The runtime is 9,373 bytes.

Verified: compile matrix 111/111, ladder 1/1, 97 .COM executions under both
oracles, rt_exec 276/276, nonvacuity 121/121, run_all ALL PASS.
Eric Streit 5 days ago
parent
commit
b1b945cadf

+ 10 - 5
CALC-COMPILE.md

@@ -97,7 +97,7 @@ Current rung (measured today, normalised position):
 
 
 | position | blocker |
 | position | blocker |
 |---|---|
 |---|---|
-| **50744** | `insert(Ch,S,X)` in `GetLine` — the `Insert` string builtin (`ERR 41`) |
+| **51322** | the `end;` closing `CASE ... ELSE ... END` in `GetLine` (`ERR 1`) |
 
 
 Rungs behind it (now passed):
 Rungs behind it (now passed):
 
 
@@ -160,7 +160,8 @@ Rungs ahead:
 | 47788 | code ceiling reached; raised to 0x8000 - DONE |
 | 47788 | code ceiling reached; raised to 0x8000 - DONE |
 | 48461 | local `set of Char` variable - DONE |
 | 48461 | local `set of Char` variable - DONE |
 | 48574 | code-image ceiling - DONE (runtime shrunk) |
 | 48574 | code-image ceiling - DONE (runtime shrunk) |
-| 50744 | `Insert` string builtin (`ERR 41`) |
+| 50744 | `Insert` string builtin - DONE |
+| 51322 | CASE/ELSE closing `end;` in `GetLine` (`ERR 1`) |
 
 
 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:
@@ -599,6 +600,9 @@ Exec86):
 - **`Abs`, `Sqr` and `Sqrt` are landed (`t104_mathbasic`).**  `Abs` clears the
 - **`Abs`, `Sqr` and `Sqrt` are landed (`t104_mathbasic`).**  `Abs` clears the
   MBF sign, `Sqr` lowers to the real multiply entry, and `Sqrt` is Newton over
   MBF sign, `Sqr` lowers to the real multiply entry, and `Sqrt` is Newton over
   the existing real division/addition entries.
   the existing real division/addition entries.
+- **`Insert(Source,Dest,Index)` is landed (`t110_insert`).**  A CHAR source is
+  materialised as a one-character counted string; string sources and index
+  clamping are covered.  The runtime shifts the tail and copies the source.
 - **The math runtime was compacted.**  Ln/Exp/Sin/Cos/Arctan now evaluate
 - **The math runtime was compacted.**  Ln/Exp/Sin/Cos/Arctan now evaluate
   their polynomials with guest Horner loops and Exp's eight squarings with one
   their polynomials with guest Horner loops and Exp's eight squarings with one
   guest loop, instead of fully unrolled copies.  The runtime fell from 14,063
   guest loop, instead of fully unrolled copies.  The runtime fell from 14,063
@@ -1246,9 +1250,10 @@ and TEXT file output is done (`t91_textout`), both compile-only because the
 harness has no filesystem.  `Str` real formatting is done (`t92_strreal`),
 harness has no filesystem.  `Str` real formatting is done (`t92_strreal`),
 pointer types with `New`/`Dispose`/dereference are done (`t93_pointer`), and
 pointer types with `New`/`Dispose`/dereference are done (`t93_pointer`), and
 text `Eof`/`Readln` plus integer `Str` are done (`t94_textread`/`t95_strint`).
 text `Eof`/`Readln` plus integer `Str` are done (`t94_textread`/`t95_strint`).
-The next blocker is the `Insert` string builtin in `GetLine`.  The runtime is
-now 9,203 bytes; `rt_exec` is at HDR=0x4000 / data 0x5000, and the compiler's
-code ceiling is 0x8000.  The image-capacity failure is gone for this rung.
+The next blocker is the `end;` closing the `CASE ... ELSE ... END` in
+`GetLine` (error 1), not another string runtime entry.  The runtime is 9,373
+bytes; `rt_exec` is at HDR=0x4000 / data 0x5000, and the compiler's code ceiling
+is 0x8000.
 
 
 **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),

+ 5 - 4
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
-`ERR 41` at pos 50744, `Insert(Ch,S,X)`), the `{$I}` include,
+`ERR 1` at pos 51322, the CASE/ELSE closing `end;`), 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`/
@@ -2069,9 +2069,10 @@ standard functions (`Abs`, `Sqr`, `Sqrt`, `Ln`, `Exp`, `Sin`, `Cos`,
 `Arctan`), `Int(REAL)` and forward definitions that repeat only the name are
 `Arctan`), `Int(REAL)` and forward definitions that repeat only the name are
 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.  The ladder now stops at `Insert(Ch,S,X)` (`ERR 41 50744`).  The runtime
-is 9,203 bytes / 55 entries; the rt_exec scaffold is at HDR=0x4000 / data
-0x5000.  Items 1-4 below are independent of both tracks.
+bytes.  `Insert` is also landed; the ladder now stops at the `end;` closing
+`CASE ... ELSE ... END` in `GetLine` (`ERR 1 51322`).  The runtime is 9,373
+bytes / 56 entries; the rt_exec scaffold is at HDR=0x4000 / data 0x5000.
+Items 1-4 below are independent of both tracks.
 
 
 1. The rest of the string language. `v-TP3-STRVAR`/`v-TP3-STRREAD-ASSIGN` cover
 1. The rest of the string language. `v-TP3-STRVAR`/`v-TP3-STRREAD-ASSIGN` cover
    string variables, `s := 'literal'`, `writeln (s)`, `readln (s)`/`read (s)`
    string variables, `s := 'literal'`, `writeln (s)`, `readln (s)`/`read (s)`

+ 53 - 2
shell/Compiler.mod

@@ -144,7 +144,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_Int = 36 ; BI_Delete = 37 ; BI_Insert = 38 ;
    (* 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 ;
@@ -424,6 +424,7 @@ VAR
    TU_RealLn      : CARDINAL ;  TU_RealExp : CARDINAL ;
    TU_RealLn      : CARDINAL ;  TU_RealExp : CARDINAL ;
    TU_RealSin     : CARDINAL ;  TU_RealCos : CARDINAL ;  TU_RealArctan : CARDINAL ;
    TU_RealSin     : CARDINAL ;  TU_RealCos : CARDINAL ;  TU_RealArctan : CARDINAL ;
    TU_RealIntFunc : CARDINAL ;  TU_StrDelete : CARDINAL ;
    TU_RealIntFunc : CARDINAL ;  TU_StrDelete : CARDINAL ;
+   TU_StrInsert : CARDINAL ;
    TU_CmpStr   : CARDINAL ;  (* string comparison; two addresses on the stack,
    TU_CmpStr   : CARDINAL ;  (* string comparison; two addresses on the stack,
                                 signed -1/0/+1 in AX *)
                                 signed -1/0/+1 in AX *)
    TU_StrCat   : CARDINAL ;  (* string concatenation; dest and source addresses
    TU_StrCat   : CARDINAL ;  (* string concatenation; dest and source addresses
@@ -5513,7 +5514,7 @@ VAR arg : ERes ;
     reading : BOOLEAN ;
     reading : BOOLEAN ;
     pushed : BOOLEAN ;
     pushed : BOOLEAN ;
     skipArg : BOOLEAN ;
     skipArg : BOOLEAN ;
-    n, p : CARDINAL ;
+    n, p, scratch, cap : CARDINAL ;
     dummy, errV : ERes ;
     dummy, errV : ERes ;
 BEGIN
 BEGIN
    which := symtab [idx].cls ;               (* BI_* *)
    which := symtab [idx].cls ;               (* BI_* *)
@@ -5624,6 +5625,53 @@ BEGIN
       END ;
       END ;
       RETURN
       RETURN
    END ;
    END ;
+   IF which = BI_Insert THEN
+      (* Insert(Source, Dest, Index).  A CHAR source is materialised as a
+         one-character counted string in a scratch; Dest must be a modifiable
+         string variable/field.  Stack order is source, dest, index, max, so
+         the runtime sees max at [BP+4] and source at [BP+10]. *)
+      IF NOT MatchDelim ('(') THEN
+         Err (ENoSemi) ;
+         RETURN
+      END ;
+      ParseExpr (arg) ;
+      IF OK () AND (arg.cls = TString) AND
+         ((arg.kind = 1) OR (arg.kind = 3) OR (arg.kind = 5)) THEN
+         EmPushStrAddr (arg)
+      ELSIF OK () AND ((arg.cls = TChar) OR
+                       ((arg.cls = TScalar) AND arg.chr)) THEN
+         scratch := NewScratchStr () ;
+         LoadAtom (arg) ;
+         EmStoreVar (FALSE, (symtab [scratch].off + 1) MOD 10000H, 1) ;
+         EmMovAxi (1) ;
+         EmStoreVar (FALSE, symtab [scratch].off, 1) ;
+         EmPushVarAddr (FALSE, symtab [scratch].off)
+      ELSIF OK () THEN
+         Err (ETypeErr) ;
+         RETURN
+      END ;
+      ExpectDelim (',', ENoSemi) ;
+      ParseExpr (dummy) ;
+      IF OK () AND (dummy.cls = TString) AND
+         ((dummy.kind = 1) OR (dummy.kind = 5)) THEN
+         EmPushStrAddr (dummy) ;
+         cap := VarSize (dummy) - 1 ;
+         ExpectDelim (',', ENoSemi) ;
+         ParseExpr (errV) ;
+         IF OK () AND ((errV.cls = TScalar) OR (errV.cls = TChar)) THEN
+            LoadAtom (errV) ; EmPushAx () ;
+            EmMovAxi (cap) ; EmPushAx () ;
+            DropC (EmCall (TU_StrInsert)) ;
+            EmAddSp (8)
+         ELSIF OK () THEN
+            Err (ETypeErr)
+         END
+      ELSIF OK () THEN
+         Err (ETypeErr)
+      END ;
+      ExpectDelim (')', ENoSemi) ;
+      RETURN
+   END ;
    IF which = BI_Delete THEN
    IF which = BI_Delete THEN
       (* Delete(S, pos, len): S must be a variable (or a DI-addressed field);
       (* Delete(S, pos, len): S must be a variable (or a DI-addressed field);
          the runtime mutates it in place.  S's address is parked while the
          the runtime mutates it in place.  S's address is parked while the
@@ -7987,6 +8035,8 @@ BEGIN
    symtab [i].defnd := TRUE ;
    symtab [i].defnd := TRUE ;
    i := NewSym ("DELETE", KBuiltin, BI_Delete, 0, 0, 0, 0, FALSE) ;
    i := NewSym ("DELETE", KBuiltin, BI_Delete, 0, 0, 0, 0, FALSE) ;
    symtab [i].defnd := TRUE ;
    symtab [i].defnd := TRUE ;
+   i := NewSym ("INSERT", KBuiltin, BI_Insert, 0, 0, 0, 0, FALSE) ;
+   symtab [i].defnd := TRUE ;
    (* KBD: the console keyboard as a TEXT file.  This runtime's read entries
    (* KBD: the console keyboard as a TEXT file.  This runtime's read entries
       already read the console, so `Read(Kbd, Ch)' drops the file argument and
       already read the console, so `Read(Kbd, Ch)' drops the file argument and
       reads a character exactly as `Read(Ch)' does. *)
       reads a character exactly as `Read(Ch)' does. *)
@@ -8175,6 +8225,7 @@ BEGIN
    TU_RealArctan  := RT_Entry (53) ;
    TU_RealArctan  := RT_Entry (53) ;
    TU_RealIntFunc := RT_Entry (27) ;
    TU_RealIntFunc := RT_Entry (27) ;
    TU_StrDelete   := RT_Entry (54) ;
    TU_StrDelete   := RT_Entry (54) ;
+   TU_StrInsert   := RT_Entry (55) ;
    TU_WrStr    := RT_Entry (15) ;      (* a string variable is written through
    TU_WrStr    := RT_Entry (15) ;      (* a string variable is written through
                                           the address on the stack *)
                                           the address on the stack *)
    TU_RdStr    := RT_Entry (16) ;      (* readln(s)/read(s): TPSRC8 prdstr,
    TU_RdStr    := RT_Entry (16) ;      (* readln(s)/read(s): TPSRC8 prdstr,

+ 2 - 0
shell/Runtime.def

@@ -94,6 +94,8 @@ CONST
    E_RealCos = 52 ;         (* [BP+4]=12-byte block; Cos of the first REAL *)
    E_RealCos = 52 ;         (* [BP+4]=12-byte block; Cos of the first REAL *)
    E_RealArctan = 53 ;      (* [BP+4]=12-byte block; Arctan of the first REAL *)
    E_RealArctan = 53 ;      (* [BP+4]=12-byte block; Arctan of the first REAL *)
    E_StrDelete = 54 ;       (* [BP+4]=counted string, [BP+6]=pos, [BP+8]=len *)
    E_StrDelete = 54 ;       (* [BP+4]=counted string, [BP+6]=pos, [BP+8]=len *)
+   E_StrInsert = 55 ;       (* [BP+4]=max, [BP+6]=index, [BP+8]=dest,
+                               [BP+10]=source *)
 
 
    (* File control block, mirrored by Compiler.FileVarSize. *)
    (* File control block, mirrored by Compiler.FileVarSize. *)
    F_NameOff = 4 ;           (* handle word, mode word, then the name *)
    F_NameOff = 4 ;           (* handle word, mode word, then the name *)

+ 64 - 3
shell/Runtime.mod

@@ -136,7 +136,9 @@ CONST
    D_HT    = 825 ;    (* 2 bytes: Horner table pointer *)
    D_HT    = 825 ;    (* 2 bytes: Horner table pointer *)
    D_HC    = 827 ;    (* 1 byte: Horner/square iteration count *)
    D_HC    = 827 ;    (* 1 byte: Horner/square iteration count *)
    D_HF    = 828 ;    (* 1 byte: Horner final +1 flag *)
    D_HF    = 828 ;    (* 1 byte: Horner final +1 flag *)
-   D_END   = 829 ;    (* the block is padded to here *)    (* the block is padded to here *)
+   D_ISTMP = 829 ;    (* 2 bytes: Insert index *)
+   D_ITMP  = 831 ;    (* 2 bytes: Insert new length *)
+   D_END   = 833 ;    (* the block is padded to here *)    (* the block is padded to here *)
 
 
    (* THE LOAD BIAS.  Everything above is an offset into the image as this
    (* THE LOAD BIAS.  Everything above is an offset into the image as this
       compiler numbers it: the entry JMP is at image 0.  But a DOS .COM is
       compiler numbers it: the entry JMP is at image 0.  But a DOS .COM is
@@ -802,6 +804,10 @@ PROCEDURE XorBhBh ; BEGIN B (30H) ; B (0FFH) END XorBhBh ;       (* BH:=0 *)
 PROCEDURE AddAlAh ; BEGIN B (00H) ; B (0E0H) END AddAlAh ;       (* AL:=AL+AH *)
 PROCEDURE AddAlAh ; BEGIN B (00H) ; B (0E0H) END AddAlAh ;       (* AL:=AL+AH *)
 PROCEDURE AddAxCx ; BEGIN B (03H) ; B (0C1H) END AddAxCx ;       (* AX:=AX+CX *)
 PROCEDURE AddAxCx ; BEGIN B (03H) ; B (0C1H) END AddAxCx ;       (* AX:=AX+CX *)
 PROCEDURE AddAxBx ; BEGIN B (03H) ; B (0C3H) END AddAxBx ;       (* AX:=AX+BX *)
 PROCEDURE AddAxBx ; BEGIN B (03H) ; B (0C3H) END AddAxBx ;       (* AX:=AX+BX *)
+PROCEDURE AddSiAx ; BEGIN B (01H) ; B (0C6H) END AddSiAx ;       (* SI:=SI+AX *)
+PROCEDURE CmpAxBx ; BEGIN B (39H) ; B (0D8H) END CmpAxBx ;       (* CMP AX,BX *)
+PROCEDURE SubAxSi ; BEGIN B (2BH) ; B (0C6H) END SubAxSi ;       (* AX:=AX-SI *)
+PROCEDURE MovSiDx ; BEGIN B (89H) ; B (0D6H) END MovSiDx ;       (* SI:=DX *)
 PROCEDURE SubAxCx ; BEGIN B (2BH) ; B (0C1H) END SubAxCx ;       (* AX:=AX-CX *)
 PROCEDURE SubAxCx ; BEGIN B (2BH) ; B (0C1H) END SubAxCx ;       (* AX:=AX-CX *)
 PROCEDURE AdcBxCx ; BEGIN B (13H) ; B (0D9H) END AdcBxCx ;       (* BX:=BX+CX+CF *)
 PROCEDURE AdcBxCx ; BEGIN B (13H) ; B (0D9H) END AdcBxCx ;       (* BX:=BX+CX+CF *)
 PROCEDURE AdcDxCx ; BEGIN B (13H) ; B (0D1H) END AdcDxCx ;       (* DX:=DX+CX+CF *)
 PROCEDURE AdcDxCx ; BEGIN B (13H) ; B (0D1H) END AdcDxCx ;       (* DX:=DX+CX+CF *)
@@ -868,6 +874,7 @@ PROCEDURE PushBp6 ; BEGIN B (0FFH) ; B (76H) ; B (6) END PushBp6 ;
 PROCEDURE MovCxBp6 ; BEGIN B (8BH) ; B (4EH) ; B (6) END MovCxBp6 ;
 PROCEDURE MovCxBp6 ; BEGIN B (8BH) ; B (4EH) ; B (6) END MovCxBp6 ;
 PROCEDURE MovCxBp4 ; BEGIN B (8BH) ; B (4EH) ; B (4) END MovCxBp4 ;
 PROCEDURE MovCxBp4 ; BEGIN B (8BH) ; B (4EH) ; B (4) END MovCxBp4 ;
 PROCEDURE MovBxBp8 ; BEGIN B (8BH) ; B (5EH) ; B (8) END MovBxBp8 ;
 PROCEDURE MovBxBp8 ; BEGIN B (8BH) ; B (5EH) ; B (8) END MovBxBp8 ;
+PROCEDURE MovBxBp10 ; BEGIN B (8BH) ; B (5EH) ; B (0AH) END MovBxBp10 ;
 PROCEDURE MovSiBp8 ; BEGIN B (8BH) ; B (76H) ; B (8) END MovSiBp8 ;
 PROCEDURE MovSiBp8 ; BEGIN B (8BH) ; B (76H) ; B (8) END MovSiBp8 ;
 PROCEDURE MovSiBp10 ; BEGIN B (8BH) ; B (76H) ; B (0AH) END MovSiBp10 ;
 PROCEDURE MovSiBp10 ; BEGIN B (8BH) ; B (76H) ; B (0AH) END MovSiBp10 ;
 PROCEDURE LdDlDi ; BEGIN B (8AH) ; B (15H) END LdDlDi ;          (* DL:=[DI] *)
 PROCEDURE LdDlDi ; BEGIN B (8AH) ; B (15H) END LdDlDi ;          (* DL:=[DI] *)
@@ -3410,6 +3417,59 @@ BEGIN
    PopDi ; PopSi ; PopDx ; PopCx ; PopBx ; MovSpBp ; PopBp ; RetR
    PopDi ; PopSi ; PopDx ; PopCx ; PopBx ; MovSpBp ; PopBp ; RetR
 END EmitStrDelete ;
 END EmitStrDelete ;
 
 
+PROCEDURE EmitStrInsert ;
+(* str_insert: Insert(Source, Dest, Index, Max).  The compiler pushes Max,
+   Index, Dest, Source in that order, so Source is at [BP+10], Dest at
+   [BP+8], Index at [BP+6], Max at [BP+4].  Both strings are counted; the
+   operation is in place.  Index is clamped to [1, |Dest|+1], and an
+   overflowing result leaves through TP3 error 16 ("String too long"). *)
+BEGIN
+   M ("str_insert") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushCx ; PushDx ; PushSi ; PushDi ;
+   MovBxBp10 ;                       (* BX := source *)
+   LdAlBx ; XorAhAh ; MovCxAx ;      (* CX := source length *)
+   CmpAx0 ; JccNear (74H, "ins_done") ;
+   MovDiBp8 ;                        (* DI := destination *)
+   LdAlDi ; XorAhAh ; MovDxAx ;      (* DX := destination length *)
+   MovAxBp6 ;                        (* AX := index *)
+   CmpAxImm (1) ; Jae8 ("ins_idx") ;
+   MovAxImm (1) ;
+   M ("ins_idx") ;
+   MovSiDx ; IncSi ;                 (* |Dest| + 1 *)
+   CmpAxSi ; Jbe8 ("ins_idx2") ;
+   MovAxSi ;
+   M ("ins_idx2") ;
+   StDataAx (D_ISTMP) ;
+   MovAxCx ; AddAxDx ;               (* total length *)
+   StDataAx (D_ITMP) ;
+   MovBxBp4 ;                        (* BX := max *)
+   CmpAxBx ; Jbe8 ("ins_len") ;
+   MovAlD (10H) ; MovAh (4CH) ; Int21 ;
+   M ("ins_len") ;
+   (* Move the tail backwards: tail = |Dest| - Index + 1. *)
+   MovAxData (D_ISTMP) ; MovSiAx ;  (* SI := index *)
+   MovAxDx ; SubAxSi ; AddAxImm (1) ;
+   MovCxAx ;                        (* tail count *)
+   MovAxDx ; AddAxDi ; MovSiAx ;    (* SI := dest + destlen *)
+   MovBxBp10 ; LdAlBx ; XorAhAh ;   (* source length *)
+   MovDiSi ; AddDiAx ;              (* DI := SI + source length *)
+   Jcxz8 ("ins_tail_done") ;
+   M ("ins_tail") ;
+   LdAlSi ; StDiAl ; DecSi ; DecDi ; Loop8 ("ins_tail") ;
+   M ("ins_tail_done") ;
+   (* Copy the source forward at dest+index. *)
+   MovDiBp8 ; MovAxData (D_ISTMP) ; AddDiAx ;
+   MovBxBp10 ; LdAlBx ; XorAhAh ; MovCxAx ; IncBx ;
+   Jcxz8 ("ins_copy_done") ;
+   M ("ins_copy") ;
+   LdAlBx ; StDiAl ; IncBx ; IncDi ; Loop8 ("ins_copy") ;
+   M ("ins_copy_done") ;
+   MovAxData (D_ITMP) ; MovDiBp8 ; StDiAl ;
+   M ("ins_done") ;
+   PopDi ; PopSi ; PopDx ; PopCx ; PopBx ; MovSpBp ; PopBp ; RetR
+END EmitStrInsert ;
+
 PROCEDURE EmitFileOps ;
 PROCEDURE EmitFileOps ;
 (* File control-block operations.  The block is Compiler.FileVarSize:
 (* File control-block operations.  The block is Compiler.FileVarSize:
       +0  DOS handle (FFFFh when closed)
       +0  DOS handle (FFFFh when closed)
@@ -4056,7 +4116,7 @@ BEGIN
    EmitRdStr ; EmitStrStore ; EmitCmpStr ; EmitStrCat ; EmitStrCopy ;
    EmitRdStr ; EmitStrStore ; EmitCmpStr ; EmitStrCat ; EmitStrCopy ;
    EmitRealFromInt ; EmitRealAddSub ; EmitRealMul ;
    EmitRealFromInt ; EmitRealAddSub ; EmitRealMul ;
    EmitRealCmp ; EmitRealInt ; EmitRealMath ; EmitRealConv ; EmitSetOps ;
    EmitRealCmp ; EmitRealInt ; EmitRealMath ; EmitRealConv ; EmitSetOps ;
-   EmitStrDelete ; EmitFileOps ; EmitValReal ;
+   EmitStrDelete ; EmitStrInsert ; EmitFileOps ; EmitValReal ;
    EmitData ;
    EmitData ;
    FixUp ;
    FixUp ;
    SetStr (entNm [0], "initmem") ;
    SetStr (entNm [0], "initmem") ;
@@ -4114,6 +4174,7 @@ BEGIN
    SetStr (entNm [52], "real_cos") ;
    SetStr (entNm [52], "real_cos") ;
    SetStr (entNm [53], "real_arctan") ;
    SetStr (entNm [53], "real_arctan") ;
    SetStr (entNm [54], "str_delete") ;
    SetStr (entNm [54], "str_delete") ;
+   SetStr (entNm [55], "str_insert") ;
    built := TRUE
    built := TRUE
 END RT_Build ;
 END RT_Build ;
 
 
@@ -4141,7 +4202,7 @@ BEGIN
    IF NOT built THEN
    IF NOT built THEN
       RT_Build (0)
       RT_Build (0)
    END ;
    END ;
-   IF i > 54 THEN
+   IF i > 55 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..54] OF ARRAY [0..15] OF CHAR ;
+    names : ARRAY [0..55] 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 <= 54 DO
+   WHILE i <= 55 DO
       names [i] := "" ;
       names [i] := "" ;
       INC (i)
       INC (i)
    END ;
    END ;
@@ -167,8 +167,9 @@ BEGIN
    names [52] := "real_cos" ;
    names [52] := "real_cos" ;
    names [53] := "real_arctan" ;
    names [53] := "real_arctan" ;
    names [54] := "str_delete" ;
    names [54] := "str_delete" ;
+   names [55] := "str_insert" ;
    i := 0 ;
    i := 0 ;
-   WHILE i <= 54 DO
+   WHILE i <= 55 DO
       PENT (i, names [i]) ;
       PENT (i, names [i]) ;
       INC (i)
       INC (i)
    END ;
    END ;

+ 6 - 5
shell/tests/fixtures/calc_ladder.tsv

@@ -25,7 +25,7 @@
 # positions shift — which is exactly why this must be a measured number, not a
 # positions shift — which is exactly why this must be a measured number, not a
 # remembered one.
 # remembered one.
 #
 #
-# Current rung (2026-10-23): `insert(Ch,S,X)' in GetLine.
+# Current rung (2026-10-23): the `end;` closing the CASE/ELSE in GetLine.
 # UpCase, run-time set membership, string fields through DI, the original memory
 # UpCase, run-time set membership, string fields through DI, the original memory
 # layout, untyped-file declarations, file control-block operations, VAR string
 # layout, untyped-file declarations, file control-block operations, VAR string
 # parameters, Chr, typed-file block I/O, text-file output, Str,
 # parameters, Chr, typed-file block I/O, text-file output, Str,
@@ -33,7 +33,8 @@
 # value REAL and REAL function results, REAL literals/comparisons/arithmetic,
 # value REAL and REAL function results, REAL literals/comparisons/arithmetic,
 # self-recursion, enum-indexed arrays, string-array typed constants, local REAL
 # self-recursion, enum-indexed arrays, string-array typed constants, local REAL
 # variables, Copy, real Val, Abs/Sqr/Sqrt, Ln/Exp/Sin/Cos/Arctan, Int(REAL),
 # variables, Copy, real Val, Abs/Sqr/Sqrt, Ln/Exp/Sin/Cos/Arctan, Int(REAL),
-# forward definitions, Delete, local set of Char variables and the compact
-# Horner/square runtime moved the ladder 15911 -> ... -> 48574 -> 50744.  The
-# next blocker is the Insert string builtin (TP3 error 41).
-CALC.PAS	ERR	41	50744
+# forward definitions, Delete, local set of Char variables, the compact
+# Horner/square runtime and Insert moved the ladder 15911 -> ... -> 50744 ->
+# 51322.  The next blocker is the CASE/ELSE closing `end;` in GetLine (TP3
+# error 1).
+CALC.PAS	ERR	1	51322

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

@@ -725,3 +725,6 @@ t108_delete	OK	417	26
 
 
 #   t109_localset       local `set of Char` declaration, literal assignment and IN.
 #   t109_localset       local `set of Char` declaration, literal assignment and IN.
 t109_localset	OK	322	5
 t109_localset	OK	322	5
+
+#   t110_insert           Insert(Source,Dest,Index): char/string, clamping.
+t110_insert	OK	405	1051

+ 5 - 0
shell/tests/fixtures/t110_insert.out

@@ -0,0 +1,5 @@
+abce
+abce
+aceXY
+aceZ
+Zace

+ 26 - 0
shell/tests/fixtures/t110_insert.pas

@@ -0,0 +1,26 @@
+program t110_insert;
+
+{ t110 -- Insert(Source, Dest, Index): char and string sources, middle/end/
+  front, and index clamping. }
+
+var
+  S : string[20];
+  C : char;
+begin
+  S := 'ace';
+  C := 'b';
+  Insert (C, S, 2);
+  writeln (S);
+  S := 'ace';
+  Insert ('b', S, 2);
+  writeln (S);
+  S := 'ace';
+  Insert ('XY', S, 4);
+  writeln (S);
+  S := 'ace';
+  Insert ('Z', S, 99);
+  writeln (S);
+  S := 'ace';
+  Insert ('Z', S, 0);
+  writeln (S)
+end.

+ 1 - 1
shell/tests/rt_exec.py

@@ -117,7 +117,7 @@ ENTS = ["initmem", "progend", "stackchk", "wrint", "wrchar", "wrbool",
         "ioresult", "io_store", "heap_new", "file_eof", "file_readln",
         "ioresult", "io_store", "heap_new", "file_eof", "file_readln",
         "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"]
+        "real_arctan", "str_delete", "str_insert"]
 
 
 # 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