Просмотр исходного кода

calc: text-file output (ladder 26066)

A leading TText argument to Write/Writeln is now lowered to DOS AH=40h:
Writeln(F) writes the CRLF pair, Write(F,' ') writes a one-character
code-resident literal, and writeln(F,S) writes the string's bytes (length
byte + characters) followed by CRLF.  String literals use EmStrBlob, string
variables use their address, and the result byte count comes from the length
byte.  Each write feeds the io_store runtime entry, so IOResult still sees the
last status.

t91_textout covers all three forms; like typed-file I/O it is compile-only
because neither boot harness has a filesystem.

The CALC ladder moves 25674 -> 26066; the next blocker is Str, real-to-string
formatting into a string variable.

Verified: compile matrix 92/92, ladder 1/1, 83 .COM linked, 79 executed under
both qemu and Exec86, rt_exec 276/276, nonvacuity 121/121, run_all ALL PASS.
Eric Streit 2 дней назад
Родитель
Сommit
93d2ed4183

+ 18 - 12
CALC-COMPILE.md

@@ -97,7 +97,7 @@ Current rung (measured today, normalised position):
 
 
 | position | blocker |
 | position | blocker |
 |---|---|
 |---|---|
-| **25674** | `Writeln(MCFile)` in `Print` — text-file output (`ERR 102`) |
+| **26066** | `Str(Value:FW:DEC,P)` in `Print` — real-to-string formatting (`ERR 41`) |
 
 
 Rungs behind it (now passed):
 Rungs behind it (now passed):
 
 
@@ -136,7 +136,8 @@ Rungs ahead:
 | 22280 | file operations (`Assign`, `Reset`, `Rewrite`, `Close`, `IoResult`) - done by the file control-block rung |
 | 22280 | file operations (`Assign`, `Reset`, `Rewrite`, `Close`, `IoResult`) - done by the file control-block rung |
 | 22706 | `var Line : AnyString` - a by-reference string parameter |
 | 22706 | `var Line : AnyString` - a by-reference string parameter |
 | 23700 | typed-file data write (`write(MCfile, Sheet[I,J])`) - DONE |
 | 23700 | typed-file data write (`write(MCfile, Sheet[I,J])`) - DONE |
-| 25674 | text-file output (`Writeln(MCFile)`) |
+| 25674 | text-file output (`Writeln(MCFile)`) - DONE |
+| 26066 | `Str(Value:FW:DEC,P)` - real-to-string formatting |
 
 
 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:
@@ -457,10 +458,10 @@ subscripts, write field widths, WITH with n-dimensional subscripts, set-field
 assignment, string fields through DI, REAL constants, UpCase, run-time set
 assignment, string fields through DI, REAL constants, UpCase, run-time set
 membership, string fields in WRITE, real output, run-time set algebra,
 membership, string fields in WRITE, real output, run-time set algebra,
 `Succ`/`Pred`, the original memory layout, the untyped file type, the
 `Succ`/`Pred`, the original memory layout, the untyped file type, the
-file control-block operations, VAR string parameters, `Chr`, and typed-file
-block I/O). The
-ladder has moved from the first typed constant at 7,791 to **25,674**, where
-`Writeln(MCFile)` asks for text-file output.
+file control-block operations, VAR string parameters, `Chr`, typed-file
+block I/O, and text-file output). The
+ladder has moved from the first typed constant at 7,791 to **26,066**, where
+`Str(Value:FW:DEC,P)` asks for real-to-string formatting.
 
 
 The rung commits and their tags, in order (each is one rung with its own
 The rung commits and their tags, in order (each is one rung with its own
 fixture, red proof, and — where the harness allows — execution under qemu and
 fixture, red proof, and — where the harness allows — execution under qemu and
@@ -499,7 +500,8 @@ Exec86):
 | `ed9e790` | `v-TP3-CALC-UNTYPEDFILE` | `ERR 41 22280` (`assign`) |
 | `ed9e790` | `v-TP3-CALC-UNTYPEDFILE` | `ERR 41 22280` (`assign`) |
 | `ab7cced` | `v-TP3-CALC-FILEOPS` | `ERR 102 22706` (VAR string parameter) |
 | `ab7cced` | `v-TP3-CALC-FILEOPS` | `ERR 102 22706` (VAR string parameter) |
 | `02d5b6e` | `v-TP3-CALC-VARSTR` | `ERR 102 23700` (typed-file data write) |
 | `02d5b6e` | `v-TP3-CALC-VARSTR` | `ERR 102 23700` (typed-file data write) |
-| *(this commit)* | `v-TP3-CALC-FILEIO` | **`ERR 102 25674`** (text-file output) |
+| `0859a3b` | `v-TP3-CALC-FILEIO` | `ERR 102 25674` (text-file output) |
+| *(this commit)* | `v-TP3-CALC-TEXTOUT` | **`ERR 41 26066`** (`Str`) |
 
 
 - **Code HEAD** is the layout rung (`v-TP3-CALC-LAYOUT`).  Nothing is pushed.
 - **Code HEAD** is the layout rung (`v-TP3-CALC-LAYOUT`).  Nothing is pushed.
   The terminal-config work (`v-TP3-TERMCFG`) and the editor key map
   The terminal-config work (`v-TP3-TERMCFG`) and the editor key map
@@ -545,6 +547,10 @@ Exec86):
   parameter slot holds the caller's address; assignment, `Read`, `Length`,
   parameter slot holds the caller's address; assignment, `Read`, `Length`,
   `writeln`, concatenation, comparison and `x[i]` all go through that address.
   `writeln`, concatenation, comparison and `x[i]` all go through that address.
   `Chr(n)` folds or loads an ordinal and yields a CHAR.
   `Chr(n)` folds or loads an ordinal and yields a CHAR.
+- **Text-file output is landed (`t91_textout`).**  `Writeln(F)` writes CRLF,
+  `Write(F,' ')` writes one CHAR, and `writeln(F,Line)` writes the string then
+  CRLF, all as DOS AH=40h blocks from code-resident literals or the string's
+  address.  Compile-only for the same no-filesystem reason as typed I/O.
 - **Typed-file block I/O is landed (`t90_fileio`).**  `write`/`read` with a
 - **Typed-file block I/O is landed (`t90_fileio`).**  `write`/`read` with a
   leading `TFile` lower to DOS INT 21h AH=40h/3Fh; the element size must agree
   leading `TFile` lower to DOS INT 21h AH=40h/3Fh; the element size must agree
   with the file's declared element, and the result goes through the tiny
   with the file's declared element, and the result goes through the tiny
@@ -1123,11 +1129,11 @@ filename, `Reset`/`Rewrite`/`Close` issue DOS `INT 21h` open/create/close, and
 so both answer `AH=3Dh` with `CF=1, AX=2`; the fixture checks that error path
 so both answer `AH=3Dh` with `CF=1, AX=2`; the fixture checks that error path
 under qemu and Exec86, and `rt_exec` pins the name copy.
 under qemu and Exec86, and `rt_exec` pins the name copy.
 
 
-Typed-file block I/O (`write`/`read` with a `TFile`) is done (`t90_fileio`,
-compile-only because the harness has no filesystem).  Still open: TEXT file
-output (`Writeln(MCFile)`, `Write(MCFile,' ')`, `writeln(MCFile,Line)`), then
-`EndOfFile` and text `Readln(H, ...)`.  These need text-mode newline/`^Z`
-semantics (`RESUME-TP3.md` §2).  The next ladder blocker is text output.
+Typed-file block I/O (`write`/`read` with a `TFile`) is done (`t90_fileio`)
+and TEXT file output is done (`t91_textout`), both compile-only because the
+harness has no filesystem.  Still open: `Str`/`Val`, then `EndOfFile` and text
+`Readln(H, ...)`, which need text-mode newline/`^Z` semantics
+(`RESUME-TP3.md` §2).  The next ladder blocker is `Str`.
 
 
 **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),

+ 4 - 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
-`ERR 102` at pos 25674, `Writeln(MCFile)` in `Print`), the `{$I}` include,
+`ERR 41` at pos 26066, `Str(Value:FW:DEC,P)` in `Print`), 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`/
@@ -2060,8 +2060,9 @@ real output (`t84_realwrite`), run-time set algebra and `Succ`/`Pred`
 (`t85`/`t86`), the original code-only `.COM` layout (§3.2), the untyped `file`
 (`t85`/`t86`), the original code-only `.COM` layout (§3.2), the untyped `file`
 type (`t87`), file control-block operations (`t88`: `Assign`/`Reset`/
 type (`t87`), file control-block operations (`t88`: `Assign`/`Reset`/
 `Rewrite`/`Close`/`IoResult`; data I/O is still refused), and VAR string
 `Rewrite`/`Close`/`IoResult`; data I/O is still refused), and VAR string
-parameters plus `Chr` (`t89`), and typed-file block I/O (`t90`, compile-only:
-the harness has no filesystem). The next rung is text-file output. The runtime is 3,907 bytes / 39 entries and its probe
+parameters plus `Chr` (`t89`), typed-file block I/O (`t90`, compile-only), and
+text-file output (`t91`, compile-only). The next rung is `Str` real-to-string
+formatting. The runtime is 3,907 bytes / 39 entries and its probe
 scaffold is nearly full, so the next runtime addition must move HDR. Items 1-4
 scaffold is nearly full, so the next runtime addition must move HDR. Items 1-4
 below are independent of both tracks.
 below are independent of both tracks.
 
 

+ 90 - 11
shell/Compiler.mod

@@ -1497,7 +1497,7 @@ END EmStoreInd ;
 
 
 PROCEDURE EmFileIo (reading : BOOLEAN ; fileLocal : BOOLEAN ; fileOff : CARDINAL ;
 PROCEDURE EmFileIo (reading : BOOLEAN ; fileLocal : BOOLEAN ; fileOff : CARDINAL ;
                     addrKind : CARDINAL ; addrLocal : BOOLEAN ;
                     addrKind : CARDINAL ; addrLocal : BOOLEAN ;
-                    addrOff, count : CARDINAL) ;
+                    addrOff, count : CARDINAL ; stringArg : BOOLEAN) ;
 (* Typed-file block I/O: read/write `count' bytes at the data argument to the
 (* Typed-file block I/O: read/write `count' bytes at the data argument to the
    open file in the file variable.
    open file in the file variable.
 
 
@@ -1512,23 +1512,54 @@ PROCEDURE EmFileIo (reading : BOOLEAN ; fileLocal : BOOLEAN ; fileOff : CARDINAL
    per call site and the compiler's code budget is the loose one. *)
    per call site and the compiler's code budget is the loose one. *)
 BEGIN
 BEGIN
    IF addrKind = 5 THEN
    IF addrKind = 5 THEN
-      Ebyte (89H) ; Ebyte (0FAH)         (* MOV DX,DI *)
+      (* DI already holds a kind-5 element's address. *)
+   ELSIF addrKind = 4 THEN
+      EmLeaDi (FALSE, addrOff)           (* a code-resident blob *)
    ELSIF addrKind = 1 THEN
    ELSIF addrKind = 1 THEN
-      EmLeaDi (addrLocal, addrOff) ;
-      Ebyte (89H) ; Ebyte (0FAH)         (* MOV DX,DI *)
+      EmLeaDi (addrLocal, addrOff)
    ELSE
    ELSE
       Err (ETypeErr) ;
       Err (ETypeErr) ;
       RETURN
       RETURN
    END ;
    END ;
+   Ebyte (89H) ; Ebyte (0FAH) ;          (* MOV DX,DI *)
+   IF stringArg THEN
+      (* DX points at the characters, CX = the length byte at [DI]. *)
+      Ebyte (42H) ;                      (* INC DX *)
+      EmLdAlDi () ;
+      EmMovClAl () ;
+      Ebyte (30H) ; Ebyte (0EDH)         (* XOR CH,CH *)
+   ELSE
+      EmMovCxi (count)
+   END ;
    EmLoadVar (fileLocal, fileOff, 2) ;   (* AX := the DOS handle *)
    EmLoadVar (fileLocal, fileOff, 2) ;   (* AX := the DOS handle *)
    Ebyte (89H) ; Ebyte (0C3H) ;          (* MOV BX,AX *)
    Ebyte (89H) ; Ebyte (0C3H) ;          (* MOV BX,AX *)
-   EmMovCxi (count) ;
    Ebyte (0B4H) ;                        (* MOV AH,3Fh/40h *)
    Ebyte (0B4H) ;                        (* MOV AH,3Fh/40h *)
    IF reading THEN Ebyte (3FH) ELSE Ebyte (40H) END ;
    IF reading THEN Ebyte (3FH) ELSE Ebyte (40H) END ;
    Ebyte (0CDH) ; Ebyte (21H) ;          (* INT 21h *)
    Ebyte (0CDH) ; Ebyte (21H) ;          (* INT 21h *)
    DropC (EmCall (TU_IoStore))
    DropC (EmCall (TU_IoStore))
 END EmFileIo ;
 END EmFileIo ;
 
 
+PROCEDURE EmInlineCh (ch : CARDINAL) : CARDINAL ;
+(* Emit a one-character string literal behind a JMP and return the image
+   offset of its length byte.  Used by text-file Write(File, ' '). *)
+VAR here : CARDINAL ;
+BEGIN
+   Ebyte (0EBH) ; Ebyte (2) ;
+   here := pc ;
+   Ebyte (1) ; Ebyte (VAL (BYTE, ch)) ;
+   RETURN here
+END EmInlineCh ;
+
+PROCEDURE EmInlineCrLf () : CARDINAL ;
+(* The CR LF pair every text-file Writeln emits. *)
+VAR here : CARDINAL ;
+BEGIN
+   Ebyte (0EBH) ; Ebyte (3) ;
+   here := pc ;
+   Ebyte (2) ; Ebyte (13) ; Ebyte (10) ;
+   RETURN here
+END EmInlineCrLf ;
+
 PROCEDURE EmSubSp (n : CARDINAL) ;
 PROCEDURE EmSubSp (n : CARDINAL) ;
 BEGIN
 BEGIN
    Ebyte (81H) ; Ebyte (0ECH) ; Eword (n MOD 10000H)
    Ebyte (81H) ; Ebyte (0ECH) ; Eword (n MOD 10000H)
@@ -5066,18 +5097,65 @@ BEGIN
                (* A leading TEXT file argument.  KBD is the console keyboard
                (* A leading TEXT file argument.  KBD is the console keyboard
                   and this runtime's read entries already read the console, so
                   and this runtime's read entries already read the console, so
                   the file is dropped and the rest of the argument list is read
                   the file is dropped and the rest of the argument list is read
-                  exactly as without it.  Other text-file I/O is the next file
-                  increment and stays loud rather than reading the file
-                  variable as a data argument. *)
+                  exactly as without it. *)
                IF reading AND (arg.kind = 1) AND symtab [arg.idx].isKbd THEN
                IF reading AND (arg.kind = 1) AND symtab [arg.idx].isKbd THEN
                   IF NOT MatchDelim (',') THEN
                   IF NOT MatchDelim (',') THEN
                      Err (ENoSemi) ;
                      Err (ENoSemi) ;
                      RETURN
                      RETURN
                   END ;
                   END ;
                   skipArg := TRUE
                   skipArg := TRUE
-               ELSE
+               ELSIF reading THEN
+                  (* Text-file Read/Readln is the next increment. *)
                   Err (ENoLib) ;
                   Err (ENoLib) ;
                   RETURN
                   RETURN
+               ELSE
+                  (* Text-file output: Writeln(File) is CRLF, Write(File, C)
+                     is one CHAR, and writeln(File, S) is S followed by CRLF.
+                     Write each byte range with DOS AH=40h; a string argument
+                     carries its own length byte, a one-CHAR literal becomes a
+                     tiny code-resident <1><char>. *)
+                  IF (arg.kind # 1) OR (symtab [arg.idx].cls # TText) THEN
+                     Err (ETypeErr) ;
+                     RETURN
+                  END ;
+                  IF MatchDelim (',') THEN
+                     ParseExpr (dummy) ;
+                     IF NOT OK () THEN
+                        RETURN
+                     END ;
+                     IF dummy.cls = TString THEN
+                        IF dummy.kind = 1 THEN
+                           EmFileIo (FALSE, symtab [arg.idx].local,
+                                     symtab [arg.idx].off, 1,
+                                     symtab [dummy.idx].local,
+                                     (symtab [dummy.idx].off + dummy.boff)
+                                        MOD 10000H,
+                                     0, TRUE)
+                        ELSIF dummy.kind = 3 THEN
+                           EmFileIo (FALSE, symtab [arg.idx].local,
+                                     symtab [arg.idx].off, 4, FALSE,
+                                     EmStrBlob (dummy), 0, TRUE)
+                        ELSE
+                           Err (ENoLib) ;
+                           RETURN
+                        END
+                     ELSIF (dummy.cls = TScalar) AND dummy.chr THEN
+                        EmFileIo (FALSE, symtab [arg.idx].local,
+                                  symtab [arg.idx].off, 4, FALSE,
+                                  EmInlineCh (VAL (CARDINAL, dummy.imm)),
+                                  0, TRUE)
+                     ELSE
+                        Err (ENoLib) ;
+                        RETURN
+                     END
+                  END ;
+                  IF which = BI_WriteLn THEN
+                     EmFileIo (FALSE, symtab [arg.idx].local,
+                               symtab [arg.idx].off, 4, FALSE,
+                               EmInlineCrLf (), 0, TRUE)
+                  END ;
+                  ExpectDelim (')', ENoSemi) ;
+                  RETURN
                END
                END
             ELSIF (nArgs = 0) AND (arg.cls = TFile) THEN
             ELSIF (nArgs = 0) AND (arg.cls = TFile) THEN
                (* Typed-file block I/O: `write(MCfile, Sheet[I,J])' and the
                (* Typed-file block I/O: `write(MCfile, Sheet[I,J])' and the
@@ -5102,12 +5180,13 @@ BEGIN
                END ;
                END ;
                IF dummy.kind = 5 THEN
                IF dummy.kind = 5 THEN
                   EmFileIo (reading, symtab [arg.idx].local,
                   EmFileIo (reading, symtab [arg.idx].local,
-                            symtab [arg.idx].off, 5, FALSE, 0, VarSize (dummy))
+                            symtab [arg.idx].off, 5, FALSE, 0, VarSize (dummy),
+                            FALSE)
                ELSE
                ELSE
                   EmFileIo (reading, symtab [arg.idx].local,
                   EmFileIo (reading, symtab [arg.idx].local,
                             symtab [arg.idx].off, 1, symtab [dummy.idx].local,
                             symtab [arg.idx].off, 1, symtab [dummy.idx].local,
                             (symtab [dummy.idx].off + dummy.boff) MOD 10000H,
                             (symtab [dummy.idx].off + dummy.boff) MOD 10000H,
-                            VarSize (dummy))
+                            VarSize (dummy), FALSE)
                END ;
                END ;
                ExpectDelim (')', ENoSemi) ;
                ExpectDelim (')', ENoSemi) ;
                RETURN
                RETURN

+ 2 - 0
shell/tests/audit_helpers.py

@@ -159,6 +159,8 @@ NON_ONE_LINE = {
     },
     },
     "Compiler.mod": {
     "Compiler.mod": {
         "FileIo": COMP,        # typed-file INT 21h sequence; see EmFileIo
         "FileIo": COMP,        # typed-file INT 21h sequence; see EmFileIo
+        "InlineCh": COMP,      # emits a string blob, not one instruction
+        "InlineCrLf": COMP,    # emits a string blob, not one instruction
         "AddSp": PARAM, "SubSp": PARAM, "Setcc": PARAM,
         "AddSp": PARAM, "SubSp": PARAM, "Setcc": PARAM,
         "Call": COMP, "Jcc": COMP, "JmpNear": COMP,
         "Call": COMP, "Jcc": COMP, "JmpNear": COMP,
         "BpDisp": COMP, "LoadVar": COMP, "StoreVar": COMP,
         "BpDisp": COMP, "LoadVar": COMP, "StoreVar": COMP,

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

@@ -25,11 +25,11 @@
 # 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-22): `Writeln(MCFile)' in Print.  UpCase, run-time set
-# membership, string fields through DI, the original memory layout, untyped-file
-# declarations, file control-block operations, VAR string parameters, Chr and
-# typed-file block I/O moved the ladder 15911 -> 18267 -> 19847 -> 20912 ->
-# 22253 -> 22280 -> 22706 -> 22859 -> 23700 -> 25674.  The next blocker is TEXT
-# file output (`Writeln(MCFile)`, `Write(MCFile,' ')`, `writeln(MCFile,Line)`),
-# still refused with ENoLib (TP3 error 102).
-CALC.PAS	ERR	102	25674
+# Current rung (2026-10-22): `Str(Value:FW:DEC,P)' in Print.  UpCase,
+# run-time set membership, string fields through DI, the original memory layout,
+# untyped-file declarations, file control-block operations, VAR string
+# parameters, Chr, typed-file block I/O and text-file output moved the ladder
+# 15911 -> 18267 -> 19847 -> 20912 -> 22253 -> 22280 -> 22706 -> 22859 ->
+# 23700 -> 25674 -> 26066.  The next blocker is Str: real-to-string formatting
+# into a string variable (TP3 error 41).
+CALC.PAS	ERR	41	26066

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

@@ -651,3 +651,8 @@ t89_varstr	OK	390	276
 #                   sequence and the runtime's io_store status path are the
 #                   sequence and the runtime's io_store status path are the
 #                   checked parts.
 #                   checked parts.
 t90_fileio	OK	212	149
 t90_fileio	OK	212	149
+
+#   t91_textout          text-file output: bare Writeln(F) writes CRLF,
+#                   Write(F,' ') one char, writeln(F,'hello') the string then
+#                   CRLF.  Compile-only: the harnesses have no filesystem.
+t91_textout	OK	231	137

+ 24 - 0
shell/tests/fixtures/t91_textout.pas

@@ -0,0 +1,24 @@
+program t91_textout;
+
+{ t91 -- text-file output.  CALC.INC's Print does
+
+      Writeln(MCFile);
+      Write(MCFile, ' ');
+      writeln(MCFile, Line);
+
+  These are lowered to DOS INT 21h AH=40h writes: CRLF for the bare
+  Writeln, one CHAR for the space, and a length-prefixed STRING followed by
+  CRLF for the line.  Compile-only: neither boot harness has a filesystem, so
+  there is no execution oracle for a successful text-file write. }
+
+var
+  F : text;
+begin
+  assign (F, 'T91.TXT');
+  rewrite (F);
+  writeln (F);
+  write (F, ' ');
+  writeln (F, 'hello');
+  close (F);
+  writeln ('text output compiled')
+end.