Sfoglia il codice sorgente

topspeed corpus: conditional compilation ((*%T/%F/%E*)) in the sidecar scanner

- tools/topspeed-grammar/scanner.frm: a sidecar copy of the scanner
  frame with a CondFilter that evaluates (*%T cond*)/(*%F cond*)/(*%E*)
  and blanks the inactive regions of the source buffer (CR/LF kept) so
  exactly one branch is selected.  V3's compiler/src/scanner.frm is
  untouched.
- build.sh uses the sidecar frame; README documents it.
- Define set {_fdata,_mthread,_fptr,_fcall} (InitDefs), tuned against
  the corpus.
- Result: 631 -> 644 parse-OK / 18 syntax-bad (662 files).
  Coverage overall 590 -> 644.
Eric Streit 1 settimana fa
parent
commit
1f8a728cc7

+ 50 - 44
docs/summary_topspeed-corpus.md

@@ -6,80 +6,86 @@ Tag `v3-topspeed-corpus`. V3 suite **151/151**, fixpoint OK
 ## Why
 
 After self-hosting, the next "normal" step was to exercise the
-front-end on a **real corpus**. The historical TopSpeed/JPI V3 corpus
-is the natural target: `$HOME/bin/Dos/M2/TS-V3` — **662 files**
-(352 `.DEF` + 310 `.MOD`).
+front-end on a **real corpus**: the historical TopSpeed/JPI V3 tree at
+`$HOME/bin/Dos/M2/TS-V3` — **662 files** (352 `.DEF` + 310 `.MOD`).
 
 V3's own `M2.atg` is the classic/Redux subset and would not take this
 dialect (pragmas `(*#...)`, conditional compilation `(*%...)`, `H`/`C`/
 `B` literals, `::=`, address constructors `[seg:ofs]^`, `CLASS`/`IS`,
-manifest constants, ...).  The project already carries a
-**sidecar** grammar for it, `compiler/src/TopSpeed-V3-M2.atg`, whose
-deliverable is *parsing* the dialect (unlowered constructs end in the
-standard `230`, parse-now / lower-later).
-
-That sidecar had never been compiled and run over the full corpus.
+manifest constants, ...).  The project already carries a **sidecar**
+grammar for it, `compiler/src/TopSpeed-V3-M2.atg`, whose deliverable is
+*parsing* the dialect (unlowered constructs end in the standard `230`,
+parse-now / lower-later).  It had never been compiled and run over the
+full corpus.
 
 ## What was done
 
-1. **Built the sidecar** (`tools/topspeed-grammar/build.sh`) — copies
-   the grammar + the V3 frames + `SymTab`/`QbeGen`/`FileIO`, runs
+1. **Built the sidecar** — `tools/topspeed-grammar/build.sh` copies the
+   grammar + the frames + the V3 `SymTab`/`QbeGen`/`FileIO`, runs
    `CR -m -C`, `gm2 -fiso`, links `build/TSM2`.
-2. **Fixed API drift** vs the current `SymTab`/`QbeGen` (the sidecar
-   predated several changes):
-   - `SymTab.NewArray(e)` → `SymTab.NewOpenArray(e)`;
-   - `SymTab.FixPending(t)` / `SetProcRes(t)` now return `BOOLEAN`;
-   - `QbeGen.EndModule` now takes the module name.
-3. **Three real grammar gaps** the corpus exposed:
-   - **tagless variant** `CASE : T OF` (anonymous selector) — TopSpeed
-     accepted, the sidecar required a named tag;
-   - **manifest constants without `CONST`** (`NAME = value;` at
-     declaration level);
-   - **trailing `|`** in a variant list (`TRUE: …; | FALSE: …; |`).
-4. **Corpus harness** (`tools/topspeed-grammar/corpus.sh`) — runs
-   `TSM2` over every `.DEF`/`.MOD` in a scratch tree (so listings never
-   touch the corpus) and classifies each file as *parse-OK* (only the
-   deliberate `230`/semantic markers) or *syntax-bad* (a real Coco/R
-   error).
+2. **Fixed API drift** vs the current `SymTab`/`QbeGen`:
+   `SymTab.NewArray` → `NewOpenArray`; `FixPending`/`SetProcRes` now
+   return `BOOLEAN`; `QbeGen.EndModule` now takes the module name.
+3. **Grammar fixes** the corpus exposed:
+   - **tagless variant** `CASE : T OF` (anonymous selector);
+   - **manifest constants without `CONST`** (`NAME = value;`);
+   - **trailing `|`** in a variant list.
+4. **Conditional compilation** `(*%T cond*)` / `(*%F cond*)` /
+   `(*%E*)` — the sidecar now uses its **own scanner frame**
+   (`tools/topspeed-grammar/scanner.frm`; V3's `compiler/src/scanner.frm`
+   is untouched).  A `CondFilter` runs on the source buffer after it is
+   read: it evaluates each directive against a define set and **blanks
+   the inactive regions** (keeping CR/LF so line numbers survive).
+   This matters because the corpus's `%F`/`%T` branches wrap *partial
+   statements* (an `IF … THEN` in one branch, the body/`END` outside),
+   so exactly one branch must be selected.
+   The define set is `{_fdata, _mthread, _fptr, _fcall}` (in `InitDefs`),
+   tuned against the corpus; it makes the prose/legacy `%F` blocks
+   inactive while keeping the plain parseable branches.  (Pragmas
+   `(*#...)` were never a problem — they lex as ordinary comments.)
+5. **Corpus harness** — `tools/topspeed-grammar/corpus.sh` runs `TSM2`
+   over every file in a scratch tree (so listings never touch the
+   corpus) and classifies each as *parse-OK* (only the deliberate
+   `230`/semantic markers) or *syntax-bad* (a real Coco/R error).
 
 ## Result
 
 | | files |
 | --- | --- |
-| corpus | **662** (352 `.DEF` + 310 `.MOD`) |
-| **parse-OK** (230/semantic only) | **631** |
-| syntax-bad | **31** |
+| corpus | **662** |
+| **parse-OK** (230/semantic only) | **644** |
+| syntax-bad | **18** |
 
-Coverage rose **590 → 627 → 631** as the fixes landed.
+Coverage rose **590 → 627 → 631 → 640 → 644** as the fixes landed
+(grammar fixes then conditional compilation).
 
-### Remaining 31 syntax-bad
+### Remaining 18 syntax-bad
 
-- **17 use `(*%…*)` conditional compilation** — a lexer feature the
-  sidecar does not implement; text inside a disabled block is parsed
-  as code.  (Pragmas `(*#…)` are already fine — they lex as ordinary
-  `(* … *)` comments.)
-- **14 assorted**, e.g. a typed set constructor in an argument list
-  (`GetMenu("…", CharSet{'N','P','T'}, ch)`), address constructors,
-  and a few files with malformed/commented-out regions
-  (`END (*QMLB` …).  Full list in the harness output.
+A mix of:
+- a few files whose conditional branch still selects the "wrong" side
+  for a universal define set (`FIO`, `FIOR`, `LOADER`, `SHTHEAP`, …);
+- non-conditional gaps: a typed set constructor in an argument list
+  (`GetMenu("…", CharSet{'N','P','T'}, ch)`), address constructors, and
+  a few files with malformed/commented-out regions (`END (*QMLB` …).
 
 ## Reproduce
 
 ```sh
 cd tools/topspeed-grammar
 ./build.sh          # -> build/TSM2
-./corpus.sh         # -> 631 parse-OK / 31 syntax-bad
+./corpus.sh         # -> 644 parse-OK / 18 syntax-bad
 ```
 
 ## Notes
 
-- V3's own compiler (`M2.atg`) and the self-hosting path are untouched;
-  the sidecar is a separate grammar built on demand.
+- V3's own compiler (`M2.atg`, `compiler/src/scanner.frm`) and the
+  self-hosting path are untouched; the sidecar has its own scanner
+  frame and is built on demand (`build/` is git-ignored).
 - The sidecar now tracks the V3 `SymTab`/`QbeGen` API; if those drift
   again, `build.sh` is where the small patch belongs.
 
 ## Files
 
 `compiler/src/TopSpeed-V3-M2.atg` (three grammar fixes),
-`tools/topspeed-grammar/{build.sh,corpus.sh,README.md}`,
+`tools/topspeed-grammar/{build.sh,corpus.sh,scanner.frm,README.md}`,
 `.gitignore` (build dir), this doc.

+ 12 - 5
tools/topspeed-grammar/README.md

@@ -17,11 +17,18 @@ cannot lower yet still **parse** and end with the standard
 ./build.sh        # -> build/TSM2   (build/ is git-ignored)
 ```
 
-It copies the grammar, the V3 frames (`scanner/parser/compiler.frm`)
-and the V3 `SymTab`/`QbeGen`/`FileIO` sources into `build/`, runs
-`CR -m -C`, compiles with `gm2 -fiso` and links `TSM2`. Needs the
-project `CR` (`$HOME/Projets/Projets-Modula2/MyWork/CocoGm2/CR`) and
-GNU Modula-2.
+It copies the grammar, the frames and the V3
+`SymTab`/`QbeGen`/`FileIO` sources into `build/`, runs `CR -m -C`,
+compiles with `gm2 -fiso` and links `TSM2`. Needs the project `CR`
+(`$HOME/Projets/Projets-Modula2/MyWork/CocoGm2/CR`) and GNU Modula-2.
+
+The sidecar uses **its own scanner frame** — `scanner.frm` in this
+directory — which adds TopSpeed **conditional compilation**
+`(*%T cond*)` / `(*%F cond*)` / `(*%E*)`: a `CondFilter` blanks the
+inactive regions of the source buffer (CR/LF kept) before scanning.
+The define set is `{_fdata, _mthread, _fptr, _fcall}` (see `InitDefs`);
+tune it there.  V3's own `compiler/src/scanner.frm` is left untouched,
+so the sidecar never affects the self-hosting compiler.
 
 ## Corpus check
 

+ 5 - 1
tools/topspeed-grammar/build.sh

@@ -20,7 +20,11 @@ B=build
 
 rm -rf "$B"; mkdir -p "$B"
 cp "$SRC/TopSpeed-V3-M2.atg" "$B/M2.atg"
-cp "$SRC/scanner.frm" "$SRC/parser.frm" "$SRC/compiler.frm" "$B/"
+# The sidecar uses its OWN scanner frame: it adds TopSpeed conditional
+# compilation ((*%T cond*) / (*%F cond*) / (*%E*)).  V3's own scanner
+# (compiler/src/scanner.frm) is left untouched.
+cp "$(pwd)/scanner.frm" "$B/scanner.frm"
+cp "$SRC/parser.frm" "$SRC/compiler.frm" "$B/"
 cp "$SRC/SymTab.def" "$SRC/SymTab.mod" "$SRC/QbeGen.def" "$SRC/QbeGen.mod" \
    "$SRC/FileIO.def" "$SRC/FileIO.mod" "$B/"
 

+ 331 - 0
tools/topspeed-grammar/scanner.frm

@@ -0,0 +1,331 @@
+IMPLEMENTATION MODULE -->modulename;
+
+(* Scanner generated by Coco/R - using the FileIO library supplied with this project. *)
+
+IMPORT FileIO, Storage;
+FROM Storage IMPORT ALLOCATE;  (* gm2 needs this for NEW substitution *)
+
+CONST
+  noSYMB  = -->unknownsym; (*error token code*)
+  (* not only for errors but also for not finished states of scanner analysis *)
+  eof     = CHR(26) (* MS-DOS Keyboard eof char *);
+  EOF     = CHR(0);
+  EOL     = CHR(13);
+  CR      = CHR(13);
+  LF      = CHR(10);
+  Long0   = 0;
+  Long1   = 1;
+  BlkSize = 16384;
+TYPE
+  BufBlock   = ARRAY [0 .. BlkSize-1] OF CHAR;
+  Buffer     = ARRAY [0 .. 31] OF POINTER TO BufBlock;
+  StartTable = ARRAY [0 .. 255] OF INTEGER;
+  GetCH      = PROCEDURE (INT32): CHAR;
+VAR
+  lastCh,
+  ch:        CHAR;       (*current input character*)
+  curLine:   INTEGER;    (*current input line (may be higher than line)*)
+  lineStart: INT32;      (*start position of current line*)
+  apx:       INT32;      (*length of appendix (CONTEXT phrase)*)
+  oldEols:   INTEGER;    (*number of EOLs in a comment*)
+  bp, bp0:   INT32;      (*current position in buf
+                           (bp0: position of current token)*)
+  inputLen:  INT32;      (*source file size*)
+  buf:       Buffer;     (*source buffer for low-level access*)
+  start:     StartTable; (*start state for every character*)
+  CurrentCh: GetCH;
+
+  (* ---- TopSpeed conditional compilation ---- *)
+  condStk: ARRAY [0 .. 31] OF BOOLEAN;
+  nCond: CARDINAL;
+  defs: ARRAY [0 .. 31] OF ARRAY [0 .. 31] OF CHAR;
+  nDefs: CARDINAL;
+  defsInit: BOOLEAN;
+
+PROCEDURE Err (nr, line, col: INTEGER; pos: INT32);
+  BEGIN
+    INC(errors)
+  END Err;
+
+PROCEDURE NextCh;
+(* Return global variable ch *)
+  BEGIN
+    lastCh := ch; INC(bp); ch := CurrentCh(bp);
+    IF (ch = EOL) OR (ch = LF) AND (lastCh # EOL) THEN
+      INC(curLine); lineStart := bp
+    END
+  END NextCh;
+
+PROCEDURE Comment (): BOOLEAN;
+  VAR
+    level, startLine: INTEGER;
+    oldLineStart: INT32;
+  BEGIN
+    level := 1; startLine := curLine; oldLineStart := lineStart;
+    -->commentRETURN FALSE;
+  END Comment;
+
+
+(* ---- TopSpeed conditional compilation: (*%T cond*) / (*%F cond*) /
+   (*%E*) ----  Blank the inactive regions of the source buffer (CR/LF
+   kept, so line numbers survive) before scanning.  Conditions are
+   evaluated against a small define set (see InitDefs); an unknown
+   symbol is 'undefined'. *)
+PROCEDURE CopyStr (s: ARRAY OF CHAR; VAR t: ARRAY OF CHAR);
+  VAR i: CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE (i <= HIGH(t)) AND (i <= HIGH(s)) AND (s[i] # CHR(0)) DO
+      t[i] := s[i]; INC(i)
+    END;
+    IF i <= HIGH(t) THEN t[i] := CHR(0) END
+  END CopyStr;
+
+PROCEDURE Same (a, b: ARRAY OF CHAR): BOOLEAN;
+  VAR i: CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE (a[i] # CHR(0)) AND (b[i] # CHR(0)) AND (a[i] = b[i]) DO INC(i) END;
+    RETURN a[i] = b[i]
+  END Same;
+
+PROCEDURE AddDef (s: ARRAY OF CHAR);
+  BEGIN
+    IF nDefs <= HIGH(defs) THEN CopyStr(s, defs[nDefs]); INC(nDefs) END
+  END AddDef;
+
+PROCEDURE InitDefs;
+(* Define set used to select conditional branches.  Tuned for the
+   TS-V3 corpus: the "_fdata"/"_mthread" variants are the plain,
+   parseable ones; "_OS2"/"_fcall"/... are left undefined so the
+   non-OS2 branches are taken. *)
+  BEGIN
+    nDefs := 0;
+    (* DEFINES:BEGIN *)
+    AddDef("_fdata");
+    AddDef("_mthread");
+    AddDef("_fptr");
+    AddDef("_fcall");
+    (* DEFINES:END *)
+  END InitDefs;
+
+PROCEDURE IsDefined (id: ARRAY OF CHAR): BOOLEAN;
+  VAR k: CARDINAL;
+  BEGIN
+    k := 0;
+    WHILE k < nDefs DO
+      IF Same(id, defs[k]) THEN RETURN TRUE END;
+      INC(k)
+    END;
+    RETURN FALSE
+  END IsDefined;
+
+PROCEDURE CondIdent (c: CHAR): BOOLEAN;
+  BEGIN
+    RETURN ((c >= "A") AND (c <= "Z")) OR ((c >= "a") AND (c <= "z"))
+        OR ((c >= "0") AND (c <= "9")) OR (c = "_")
+  END CondIdent;
+
+PROCEDURE CondBlank (p: INT32);
+  VAR c: CHAR;
+  BEGIN
+    IF (p < 0) OR (p >= inputLen) THEN RETURN END;
+    c := buf[ORD(p DIV BlkSize)]^[ORD(p MOD BlkSize)];
+    IF (c # CR) AND (c # LF) THEN
+      buf[ORD(p DIV BlkSize)]^[ORD(p MOD BlkSize)] := " "
+    END
+  END CondBlank;
+
+PROCEDURE CondFilter;
+  VAR p, q, j, b: INT32;
+    c, letter: CHAR;
+    id: ARRAY [0 .. 31] OF CHAR;
+    k: CARDINAL;
+    parentOk, cond, active: BOOLEAN;
+  BEGIN
+    IF NOT defsInit THEN InitDefs; defsInit := TRUE END;
+    nCond := 0;
+    p := 0;
+    WHILE p < inputLen DO
+      c := buf[ORD(p DIV BlkSize)]^[ORD(p MOD BlkSize)];
+      IF (c = "(") AND (p + 3 < inputLen)
+         AND (buf[ORD((p+1) DIV BlkSize)]^[ORD((p+1) MOD BlkSize)] = "*")
+         AND (buf[ORD((p+2) DIV BlkSize)]^[ORD((p+2) MOD BlkSize)] = "%") THEN
+        letter := buf[ORD((p+3) DIV BlkSize)]^[ORD((p+3) MOD BlkSize)];
+        q := p + 4;
+        WHILE (q + 1 < inputLen)
+          AND NOT ((buf[ORD(q DIV BlkSize)]^[ORD(q MOD BlkSize)] = "*")
+                   AND (buf[ORD((q+1) DIV BlkSize)]^[ORD((q+1) MOD BlkSize)] = ")")) DO
+          INC(q)
+        END;
+        IF letter = "E" THEN
+          IF nCond > 0 THEN DEC(nCond) END
+        ELSE
+          j := p + 4; k := 0;
+          WHILE (j < q) AND (k <= HIGH(id)) DO
+            c := buf[ORD(j DIV BlkSize)]^[ORD(j MOD BlkSize)];
+            IF CondIdent(c) THEN id[k] := c; INC(k)
+            ELSIF k > 0 THEN j := q   (* end of the name *)
+            END;
+            INC(j)
+          END;
+          id[k] := CHR(0);
+          cond := IsDefined(id);
+          IF letter = "F" THEN cond := NOT cond END;
+          parentOk := (nCond = 0) OR condStk[nCond - 1];
+          IF NOT parentOk THEN cond := FALSE END;
+          IF nCond <= HIGH(condStk) THEN condStk[nCond] := cond; INC(nCond) END
+        END;
+        b := p;
+        WHILE b <= q + 1 DO CondBlank(b); INC(b) END;
+        p := q + 2
+      ELSE
+        active := (nCond = 0) OR condStk[nCond - 1];
+        IF active THEN INC(p) ELSE CondBlank(p); INC(p) END
+      END
+    END
+  END CondFilter;
+
+PROCEDURE Get (VAR sym: CARDINAL);
+  VAR
+    state: CARDINAL;
+
+  PROCEDURE Equal (s: ARRAY OF CHAR): BOOLEAN;
+    VAR
+      i: CARDINAL;
+      q: INT32;
+    BEGIN
+      IF nextLen # LENGTH(s) THEN RETURN FALSE END;
+      i := 1; q := bp0; INC(q);
+      WHILE i < nextLen DO
+        IF CurrentCh(q) # s[i] THEN RETURN FALSE END;
+        INC(i); INC(q)
+      END;
+      RETURN TRUE
+    END Equal;
+
+  PROCEDURE CheckLiteral;
+    BEGIN
+      -->literals
+    END CheckLiteral;
+
+  BEGIN (*Get*)
+    -->GetSy1
+    pos := nextPos;   nextPos := bp;
+    col := nextCol;   nextCol := VAL(INTEGER, bp - lineStart);
+    line := nextLine; nextLine := curLine;
+    len := nextLen;   nextLen := 0;
+    apx := 0; state := start[ORD(ch)]; bp0 := bp;
+    LOOP
+      NextCh; INC(nextLen);
+      CASE state OF
+      -->GetSy2
+      ELSE sym := noSYMB; RETURN (*NextCh already done*)
+      END
+    END
+  END Get;
+
+PROCEDURE GetString (pos: INT32; len: CARDINAL; VAR s: ARRAY OF CHAR);
+  VAR
+    i: CARDINAL;
+    p: INT32;
+  BEGIN
+    IF len > HIGH(s) THEN len := HIGH(s) END;
+    p := pos; i := 0;
+    WHILE i < len DO
+      s[i] := CharAt(p); INC(i); INC(p)
+    END;
+    s[len] := CHR(0);
+  END GetString;
+
+PROCEDURE GetName (pos: INT32; len: CARDINAL; VAR s: ARRAY OF CHAR);
+  VAR
+    i: CARDINAL;
+    p: INT32;
+  BEGIN
+    IF len > HIGH(s) THEN len := HIGH(s) END;
+    p := pos; i := 0;
+    WHILE i < len DO
+      s[i] := CurrentCh(p); INC(i); INC(p)
+    END;
+    s[len] := CHR(0);
+  END GetName;
+
+PROCEDURE CharAt (pos: INT32): CHAR;
+  VAR
+    ch: CHAR;
+  BEGIN
+    IF pos >= inputLen THEN RETURN EOF END;
+    ch := buf[ORD(pos DIV BlkSize)]^[ORD(pos MOD BlkSize)];
+    IF ch # eof THEN RETURN ch ELSE RETURN EOF END
+  END CharAt;
+
+PROCEDURE CapChAt (pos: INT32): CHAR;
+  VAR
+    ch: CHAR;
+  BEGIN
+    IF pos >= inputLen THEN RETURN EOF END;
+    ch := CAP(buf[ORD(pos DIV BlkSize)]^[ORD(pos MOD BlkSize)]);
+    IF ch # eof THEN RETURN ch ELSE RETURN EOF END
+  END CapChAt;
+
+PROCEDURE Reset;
+  VAR
+    i, read: CARDINAL;
+  BEGIN (*assert: src has been opened*)
+    i := 0; inputLen := 0;
+    REPEAT
+      NEW(buf[i]);   (* typed alloc: sets the block descriptor's count *)
+      read := BlkSize; FileIO.ReadBytes(src, buf[i]^, read);
+      INC(i); INC(inputLen, VAL(INT32, read))
+    UNTIL read # BlkSize;
+    buf[i-1]^[read] := EOF;
+    CondFilter;
+    curLine := 1; lineStart := -2; bp := -1;
+    oldEols := 0; apx := 0; errors := 0;
+    NextCh;
+  END Reset;
+
+BEGIN
+  -->initializations
+  Error := Err; lastCh := EOF;
+END -->modulename.
+-->definitionDEFINITION MODULE -->modulename;
+
+(* Scanner generated by Coco/R - using the FileIO library supplied with this project. *)
+
+IMPORT FileIO;
+
+TYPE
+  INT32 = FileIO.INT32 (* need 32 bit integers *);
+
+VAR
+  src, lst:    FileIO.File;(*source/list files. To be opened by the main pgm*)
+  directory:   ARRAY [0 .. 255] OF CHAR (*of source file*);
+  line, col:   INTEGER;      (*line and column of current symbol*)
+  len:         CARDINAL;     (*length of current symbol*)
+  pos:         INT32;        (*file position of current symbol*)
+  nextLine:    INTEGER;      (*line of lookahead symbol*)
+  nextCol:     INTEGER;      (*column of lookahead symbol*)
+  nextLen:     CARDINAL;     (*length of lookahead symbol*)
+  nextPos:     INT32;        (*file position of lookahead symbol*)
+  errors:      INTEGER;      (*number of detected errors*)
+  Error:       PROCEDURE ((*nr*)INTEGER, (*line*)INTEGER, (*col*)INTEGER,
+                          (*pos*)INT32);
+
+PROCEDURE Get (VAR sym: CARDINAL);
+(* Gets next symbol from source file *)
+
+PROCEDURE GetString (pos: INT32; len: CARDINAL; VAR name: ARRAY OF CHAR);
+(* Retrieves exact string of max length len from position pos in source file *)
+
+PROCEDURE GetName (pos: INT32; len: CARDINAL; VAR name: ARRAY OF CHAR);
+(* Retrieves name of symbol of length len at position pos in source file *)
+
+PROCEDURE CharAt (pos: INT32): CHAR;
+(* Returns exact character at position pos in source file *)
+
+PROCEDURE Reset;
+(* Reads and stores source file internally *)
+
+END -->modulename.