Bläddra i källkod

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 dagar sedan
förälder
incheckning
93d2ed4183

+ 18 - 12
CALC-COMPILE.md

@@ -97,7 +97,7 @@ Current rung (measured today, normalised position):
 
 | 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):
 
@@ -136,7 +136,8 @@ Rungs ahead:
 | 22280 | file operations (`Assign`, `Reset`, `Rewrite`, `Close`, `IoResult`) - done by the file control-block rung |
 | 22706 | `var Line : AnyString` - a by-reference string parameter |
 | 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
 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
 membership, string fields in WRITE, real output, run-time set algebra,
 `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
 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`) |
 | `ab7cced` | `v-TP3-CALC-FILEOPS` | `ERR 102 22706` (VAR string parameter) |
 | `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.
   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`,
   `writeln`, concatenation, comparison and `x[i]` all go through that address.
   `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
   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
@@ -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
 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`
 (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
 layout** are landed: the ladder (`tests/calc_ladder.sh` +
 `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),
 subranges, arrays, element/field access, file types, string value parameters,
 `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`
 type (`t87`), file control-block operations (`t88`: `Assign`/`Reset`/
 `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
 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 ;
                     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
    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. *)
 BEGIN
    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
-      EmLeaDi (addrLocal, addrOff) ;
-      Ebyte (89H) ; Ebyte (0FAH)         (* MOV DX,DI *)
+      EmLeaDi (addrLocal, addrOff)
    ELSE
       Err (ETypeErr) ;
       RETURN
    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 *)
    Ebyte (89H) ; Ebyte (0C3H) ;          (* MOV BX,AX *)
-   EmMovCxi (count) ;
    Ebyte (0B4H) ;                        (* MOV AH,3Fh/40h *)
    IF reading THEN Ebyte (3FH) ELSE Ebyte (40H) END ;
    Ebyte (0CDH) ; Ebyte (21H) ;          (* INT 21h *)
    DropC (EmCall (TU_IoStore))
 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) ;
 BEGIN
    Ebyte (81H) ; Ebyte (0ECH) ; Eword (n MOD 10000H)
@@ -5066,18 +5097,65 @@ BEGIN
                (* A leading TEXT file argument.  KBD is the console keyboard
                   and this runtime's read entries already read the console, so
                   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 NOT MatchDelim (',') THEN
                      Err (ENoSemi) ;
                      RETURN
                   END ;
                   skipArg := TRUE
-               ELSE
+               ELSIF reading THEN
+                  (* Text-file Read/Readln is the next increment. *)
                   Err (ENoLib) ;
                   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
             ELSIF (nArgs = 0) AND (arg.cls = TFile) THEN
                (* Typed-file block I/O: `write(MCfile, Sheet[I,J])' and the
@@ -5102,12 +5180,13 @@ BEGIN
                END ;
                IF dummy.kind = 5 THEN
                   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
                   EmFileIo (reading, symtab [arg.idx].local,
                             symtab [arg.idx].off, 1, symtab [dummy.idx].local,
                             (symtab [dummy.idx].off + dummy.boff) MOD 10000H,
-                            VarSize (dummy))
+                            VarSize (dummy), FALSE)
                END ;
                ExpectDelim (')', ENoSemi) ;
                RETURN

+ 2 - 0
shell/tests/audit_helpers.py

@@ -159,6 +159,8 @@ NON_ONE_LINE = {
     },
     "Compiler.mod": {
         "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,
         "Call": COMP, "Jcc": COMP, "JmpNear": 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
 # 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
 #                   checked parts.
 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.