TextFilter.mod 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. MODULE TextFilter;
  2. (* Pre-scan filter for the Blaise validator: rewrites TEXT_BLOCK literals
  3. * (''' ... ''') into plain string-shaped placeholders so the Coco/R
  4. * scanner (which cannot distinguish '''-open from string-escape in its
  5. * merged DFA) never sees triple quotes.
  6. *
  7. * Length- and newline-preserving: every rewritten block keeps the exact
  8. * byte length and all newline positions, so parser diagnostics keep
  9. * correct line/column positions. Only well-formed blocks (opening '''
  10. * directly followed by a newline, outside strings/comments) are
  11. * rewritten; anything else passes through untouched.
  12. *
  13. * Usage: TextFilter <input.pas> <output.pas> *)
  14. FROM FileIO IMPORT File, NextParameter, Open, Okay, Close, Length,
  15. StdOut, WriteString, WriteLn, Reset, EndOfFile;
  16. IMPORT FileIO;
  17. FROM Storage IMPORT ALLOCATE;
  18. VAR
  19. srcName, dstName: ARRAY [0..255] OF CHAR;
  20. src, dst: File;
  21. buf: POINTER TO ARRAY [0..16777215] OF CHAR;
  22. len: CARDINAL;
  23. i, n: CARDINAL;
  24. PROCEDURE At (p: CARDINAL): CHAR;
  25. BEGIN
  26. RETURN buf^[p]
  27. END At;
  28. PROCEDURE Put (p: CARDINAL; c: CHAR);
  29. BEGIN
  30. buf^[p] := c
  31. END Put;
  32. PROCEDURE IsNl (c: CHAR): BOOLEAN;
  33. BEGIN
  34. RETURN (c = CHR(10)) OR (c = CHR(13))
  35. END IsNl;
  36. PROCEDURE SkipString;
  37. (* buf[i] = opening quote; skip through closing quote (''-aware). *)
  38. BEGIN
  39. INC(i);
  40. WHILE (i < len) & (At(i) # "'") & ~ IsNl(At(i)) DO
  41. IF (At(i) = "'") & (i + 1 < len) & (At(i + 1) = "'") THEN
  42. INC(i, 2)
  43. ELSE
  44. INC(i)
  45. END
  46. END;
  47. IF (i < len) & (At(i) = "'") THEN INC(i) END
  48. END SkipString;
  49. PROCEDURE SkipBrace;
  50. (* buf[i] = "{"; skip through "}". *)
  51. BEGIN
  52. INC(i);
  53. WHILE (i < len) & (At(i) # "}") DO INC(i) END;
  54. IF (i < len) THEN INC(i) END
  55. END SkipBrace;
  56. PROCEDURE SkipLine;
  57. (* buf[i..] starts "//"; skip through newline (exclusive). *)
  58. BEGIN
  59. INC(i, 2);
  60. WHILE (i < len) & ~ IsNl(At(i)) DO INC(i) END
  61. END SkipLine;
  62. PROCEDURE SkipParen;
  63. (* buf[i..] starts "(*"; skip through matching "*)" (nested). *)
  64. VAR depth: INTEGER;
  65. BEGIN
  66. depth := 1; INC(i, 2);
  67. WHILE (i < len) & (depth > 0) DO
  68. IF (At(i) = "(") & (i + 1 < len) & (At(i + 1) = "*") THEN
  69. INC(depth); INC(i, 2)
  70. ELSIF (At(i) = "*") & (i + 1 < len) & (At(i + 1) = ")") THEN
  71. DEC(depth); INC(i, 2)
  72. ELSE
  73. INC(i)
  74. END
  75. END
  76. END SkipParen;
  77. PROCEDURE TryTextBlock (): BOOLEAN;
  78. (* buf[i..i+2] = "'''", not preceded by a quote. If directly followed
  79. by a newline, blank the whole block to a string-shaped placeholder
  80. (same length, newlines kept) and return TRUE. *)
  81. VAR j, k: CARDINAL; nl: BOOLEAN;
  82. BEGIN
  83. IF (i + 2 >= len) THEN RETURN FALSE END;
  84. IF (At(i) # "'") OR (At(i + 1) # "'") OR (At(i + 2) # "'") THEN
  85. RETURN FALSE
  86. END;
  87. IF (i > 0) & (At(i - 1) = "'") THEN RETURN FALSE END;
  88. j := i + 3;
  89. nl := FALSE;
  90. IF (j < len) & (At(j) = CHR(13)) THEN
  91. INC(j);
  92. IF (j < len) & (At(j) = CHR(10)) THEN INC(j) END;
  93. nl := TRUE
  94. ELSIF (j < len) & (At(j) = CHR(10)) THEN
  95. INC(j);
  96. nl := TRUE
  97. END;
  98. IF ~ nl THEN RETURN FALSE END;
  99. k := j;
  100. WHILE (k + 2 < len) & ~ ((At(k) = "'") & (At(k + 1) = "'") & (At(k + 2) = "'")) DO
  101. INC(k)
  102. END;
  103. IF (k + 2 >= len) THEN RETURN FALSE END;
  104. Put(i + 1, " "); Put(i + 2, " ");
  105. WHILE j < k DO
  106. IF ~ IsNl(At(j)) THEN Put(j, " ") END;
  107. INC(j)
  108. END;
  109. Put(k, " "); Put(k + 1, " ");
  110. i := k + 3;
  111. RETURN TRUE
  112. END TryTextBlock;
  113. PROCEDURE Run;
  114. BEGIN
  115. i := 0;
  116. WHILE i < len DO
  117. IF (At(i) = "'") THEN
  118. IF ~ TryTextBlock() THEN SkipString END
  119. ELSIF (At(i) = "{") THEN SkipBrace
  120. ELSIF (At(i) = "/") & (i + 1 < len) & (At(i + 1) = "/") THEN SkipLine
  121. ELSIF (At(i) = "(") & (i + 1 < len) & (At(i + 1) = "*") THEN SkipParen
  122. ELSE INC(i)
  123. END
  124. END
  125. END Run;
  126. PROCEDURE ReadAll;
  127. VAR c: CHAR;
  128. BEGIN
  129. len := VAL(CARDINAL, Length(src));
  130. ALLOCATE(buf, len + 1);
  131. Reset(src);
  132. n := 0;
  133. WHILE ~ EndOfFile(src) & (n < len) DO
  134. FileIO.Read(src, c);
  135. Put(n, c);
  136. INC(n)
  137. END
  138. END ReadAll;
  139. PROCEDURE WriteAll;
  140. VAR m: CARDINAL;
  141. BEGIN
  142. Open(dst, dstName, TRUE);
  143. IF ~ Okay THEN
  144. WriteString(StdOut, "Could not open output file"); WriteLn(StdOut);
  145. HALT
  146. END;
  147. m := 0;
  148. WHILE m < len DO
  149. FileIO.Write(dst, At(m));
  150. INC(m)
  151. END;
  152. Close(dst)
  153. END WriteAll;
  154. BEGIN
  155. NextParameter(srcName);
  156. NextParameter(dstName);
  157. IF (srcName[0] = 0C) OR (dstName[0] = 0C) THEN
  158. WriteString(StdOut, "Usage: TextFilter <input> <output>"); WriteLn(StdOut);
  159. HALT
  160. END;
  161. Open(src, srcName, FALSE);
  162. IF ~ Okay THEN
  163. WriteString(StdOut, "Could not open input file"); WriteLn(StdOut);
  164. HALT
  165. END;
  166. ReadAll;
  167. Close(src);
  168. Run;
  169. WriteAll
  170. END TextFilter.