CompilerLex.mod 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. IMPLEMENTATION MODULE CompilerLex ;
  2. (* Lexical layer: keyword table, whitespace/comment/directive skipping,
  3. lookahead, delimiters, numeric/character constants and the string-literal
  4. pool. The parser owns the grammar; this module owns the characters. *)
  5. FROM CompilerShared IMPORT MaxName, EUnknown, ECompOvf, AposC, srcPos, abortFac, Err,
  6. TString, TReal,
  7. TScalar, TBool, KConst, dirs,
  8. TkNone, TkProgram, TkBegin, TkEnd, TkIf, TkThen,
  9. TkElse, TkWhile, TkDo, TkRepeat, TkUntil, TkFor,
  10. TkTo, TkDownto, TkCase, TkOf, TkGoto, TkExit,
  11. TkWith, TkVar, TkConst, TkType, TkLabel, TkProcedure,
  12. TkFunction, TkNil, TkAnd, TkOr, TkNot, TkDiv,
  13. TkMod, TkIn, TkFile, TkText, TkRecord, TkArray,
  14. TkSet, TkPacked, TkForward, TkExternal, TkAbsolute,
  15. TkOverlay, TkString ;
  16. FROM CompilerSource IMPORT CurCh, GetCh, PeekAhead, Upper, srcLen ;
  17. FROM CompilerUtil IMPORT Digit, Alpha, AlphaNum, IsHexCh, DropCh, NameEq ;
  18. FROM CompilerSymbols IMPORT symtab, wrd, Search ;
  19. VAR
  20. kName : ARRAY [0..42] OF ARRAY [0..15] OF CHAR ;
  21. kTk : ARRAY [0..42] OF CARDINAL ;
  22. PROCEDURE InitKeys () ;
  23. VAR i : CARDINAL ;
  24. BEGIN
  25. FOR i := 0 TO 42 DO
  26. kTk [i] := 0 ;
  27. kName [i] [0] := 0C
  28. END ;
  29. kName [1] := "PROGRAM" ; kTk [1] := TkProgram ;
  30. kName [2] := "BEGIN" ; kTk [2] := TkBegin ;
  31. kName [3] := "END" ; kTk [3] := TkEnd ;
  32. kName [4] := "IF" ; kTk [4] := TkIf ;
  33. kName [5] := "THEN" ; kTk [5] := TkThen ;
  34. kName [6] := "ELSE" ; kTk [6] := TkElse ;
  35. kName [7] := "WHILE" ; kTk [7] := TkWhile ;
  36. kName [8] := "DO" ; kTk [8] := TkDo ;
  37. kName [9] := "REPEAT" ; kTk [9] := TkRepeat ;
  38. kName [10] := "UNTIL" ; kTk [10] := TkUntil ;
  39. kName [11] := "FOR" ; kTk [11] := TkFor ;
  40. kName [12] := "TO" ; kTk [12] := TkTo ;
  41. kName [13] := "DOWNTO" ; kTk [13] := TkDownto ;
  42. kName [14] := "CASE" ; kTk [14] := TkCase ;
  43. kName [15] := "OF" ; kTk [15] := TkOf ;
  44. kName [16] := "GOTO" ; kTk [16] := TkGoto ;
  45. kName [17] := "EXIT" ; kTk [17] := TkExit ;
  46. kName [18] := "WITH" ; kTk [18] := TkWith ;
  47. kName [19] := "VAR" ; kTk [19] := TkVar ;
  48. kName [20] := "CONST" ; kTk [20] := TkConst ;
  49. kName [21] := "TYPE" ; kTk [21] := TkType ;
  50. kName [22] := "LABEL" ; kTk [22] := TkLabel ;
  51. kName [23] := "PROCEDURE" ; kTk [23] := TkProcedure ;
  52. kName [24] := "FUNCTION" ; kTk [24] := TkFunction ;
  53. kName [25] := "NIL" ; kTk [25] := TkNil ;
  54. kName [26] := "AND" ; kTk [26] := TkAnd ;
  55. kName [27] := "OR" ; kTk [27] := TkOr ;
  56. kName [28] := "NOT" ; kTk [28] := TkNot ;
  57. kName [29] := "DIV" ; kTk [29] := TkDiv ;
  58. kName [30] := "MOD" ; kTk [30] := TkMod ;
  59. kName [31] := "IN" ; kTk [31] := TkIn ;
  60. kName [32] := "FILE" ; kTk [32] := TkFile ;
  61. kName [33] := "TEXT" ; kTk [33] := TkText ;
  62. kName [34] := "RECORD" ; kTk [34] := TkRecord ;
  63. kName [35] := "ARRAY" ; kTk [35] := TkArray ;
  64. kName [36] := "SET" ; kTk [36] := TkSet ;
  65. kName [37] := "PACKED" ; kTk [37] := TkPacked ;
  66. kName [38] := "FORWARD" ; kTk [38] := TkForward ;
  67. kName [39] := "EXTERNAL" ; kTk [39] := TkExternal ;
  68. kName [40] := "ABSOLUTE" ; kTk [40] := TkAbsolute ;
  69. kName [41] := "OVERLAY" ; kTk [41] := TkOverlay ;
  70. kName [42] := "STRING" ; kTk [42] := TkString
  71. END InitKeys ;
  72. PROCEDURE IsBlank (ch : CHAR) : BOOLEAN ;
  73. BEGIN
  74. RETURN (ch = ' ') OR (ORD (ch) = 09H) OR (ORD (ch) = 0DH) OR (ORD (ch) = 0AH) OR (ORD (ch) = 0CH)
  75. END IsBlank ;
  76. PROCEDURE Skip () ;
  77. (* blanks, comments { } and (* *), compiler directives {$ } / (*$ *)
  78. letter + sign toggles rng/chk. Include directives are already gone by the
  79. time Skip runs: ExpandIncludes splices them into the buffer first, so the
  80. {$I file} spelling never reaches here. {$I+}/{$I-} still do, and stay the
  81. I/O-check toggle, exactly as TPSRC10 cdirec splits them on the sign. *)
  82. VAR ch : CHAR ;
  83. letter : CHAR ;
  84. BEGIN
  85. WHILE NOT abortFac DO
  86. WHILE IsBlank (CurCh ()) DO
  87. ch := GetCh ()
  88. END ;
  89. IF CurCh () = '{' THEN
  90. ch := GetCh () ;
  91. IF CurCh () = '$' THEN
  92. ch := GetCh () ;
  93. letter := Upper (GetCh ()) ;
  94. IF CurCh () = '+' THEN
  95. ch := GetCh () ;
  96. IF letter = 'I' THEN dirs.chk := TRUE END ;
  97. IF letter = 'R' THEN dirs.rng := TRUE END
  98. ELSIF CurCh () = '-' THEN
  99. ch := GetCh () ;
  100. IF letter = 'I' THEN dirs.chk := FALSE END ;
  101. IF letter = 'R' THEN dirs.rng := FALSE END
  102. END
  103. END ;
  104. WHILE (CurCh () # '}') AND (CurCh () # 0C) DO
  105. ch := GetCh ()
  106. END ;
  107. IF CurCh () = '}' THEN
  108. ch := GetCh ()
  109. END
  110. ELSIF (CurCh () = '(') AND (PeekAhead (1) = '*') THEN
  111. ch := GetCh () ;
  112. ch := GetCh () ;
  113. IF CurCh () = '$' THEN
  114. ch := GetCh () ;
  115. letter := Upper (GetCh ()) ;
  116. IF CurCh () = '+' THEN
  117. ch := GetCh () ;
  118. IF letter = 'I' THEN dirs.chk := TRUE END ;
  119. IF letter = 'R' THEN dirs.rng := TRUE END
  120. ELSIF CurCh () = '-' THEN
  121. ch := GetCh () ;
  122. IF letter = 'I' THEN dirs.chk := FALSE END ;
  123. IF letter = 'R' THEN dirs.rng := FALSE END
  124. END
  125. END ;
  126. LOOP
  127. IF (CurCh () = '*') AND (PeekAhead (1) = ')') THEN
  128. ch := GetCh () ;
  129. ch := GetCh () ;
  130. EXIT
  131. END ;
  132. IF CurCh () = 0C THEN
  133. EXIT
  134. END ;
  135. ch := GetCh ()
  136. END
  137. ELSE
  138. RETURN
  139. END
  140. END
  141. END Skip ;
  142. PROCEDURE GetWord () ;
  143. (* read identifier into wrd (uppercased); next char must be alpha *)
  144. VAR i : CARDINAL ;
  145. ch : CHAR ;
  146. BEGIN
  147. i := 0 ;
  148. ch := GetCh () ;
  149. LOOP
  150. IF i > MaxName THEN
  151. wrd [MaxName] := 0C ;
  152. RETURN
  153. END ;
  154. wrd [i] := Upper (ch) ;
  155. INC (i) ;
  156. ch := CurCh () ;
  157. IF NOT AlphaNum (ch) THEN
  158. wrd [i] := 0C ;
  159. RETURN
  160. END ;
  161. ch := GetCh ()
  162. END
  163. END GetWord ;
  164. PROCEDURE WddTok () : CARDINAL ;
  165. (* map wrd -> keyword token *)
  166. VAR i : CARDINAL ;
  167. BEGIN
  168. i := 1 ;
  169. WHILE i <= 42 DO
  170. IF kName [i] [0] # 0C THEN
  171. IF NameEq (wrd, kName [i]) THEN
  172. RETURN kTk [i]
  173. END
  174. END ;
  175. INC (i)
  176. END ;
  177. RETURN TkNone
  178. END WddTok ;
  179. PROCEDURE DeclaresProc () : BOOLEAN ;
  180. (* Does the REST of the source declare a PROCEDURE or a FUNCTION?
  181. Needed because the declaration part is compiled BEFORE the main statement
  182. part, so a procedure's code lands between the program prologue and the
  183. main body - and nothing jumps over it. A program with a procedure
  184. therefore ran off the end of the prologue, straight into the first
  185. procedure, which read its argument out of an uninitialised frame and
  186. returned to address 0. Every Pascal program containing a procedure was
  187. broken; `t13_proc` compiled and was never executed, so nothing saw it.
  188. The jump that fixes it has to be emitted BEFORE the declaration part, but
  189. whether one is needed is only known AFTER - so the only honest options are
  190. to emit it unconditionally (3 dead bytes in every program, and every code
  191. size in expected.tsv moves) or to know the answer in advance. This is the
  192. second: it scans ahead and puts srcPos back.
  193. That is safe because the whole program is already in `src` and `srcPos` is
  194. a plain index into it - the same trick PeekKw and KwAhead use. The scan
  195. looks for the keywords anywhere in the remainder rather than tracking the
  196. nesting of `begin`s, which is deliberately loose: a program with no
  197. procedures that merely mentions the word in a string literal would get a
  198. 3-byte jump to the next instruction, which is harmless, whereas tracking
  199. the main `begin` against a procedure's `begin` would be a second parser
  200. to get wrong. *)
  201. VAR save : CARDINAL ;
  202. found : BOOLEAN ;
  203. tk : CARDINAL ;
  204. ch : CHAR ;
  205. BEGIN
  206. save := srcPos ;
  207. found := FALSE ;
  208. tk := TkNone ; (* so the answer is defined if src is empty *)
  209. (* Step over delimiters as well as blanks. Stopping at the first
  210. non-letter looked reasonable and was wrong: `var x : integer ;` is full
  211. of ':' and ';', so the scan gave up inside the variable section and
  212. never reached the PROCEDURE. The loop ends at the end of the source,
  213. not at the first punctuation. *)
  214. WHILE (NOT found) AND (srcPos < srcLen) DO
  215. Skip () ;
  216. IF Alpha (CurCh ()) THEN
  217. GetWord () ;
  218. tk := WddTok () ;
  219. IF (tk = TkProcedure) OR (tk = TkFunction) THEN
  220. found := TRUE
  221. END
  222. ELSE
  223. ch := GetCh () (* a ':' or ';' - step over it *)
  224. END
  225. END ;
  226. srcPos := save ;
  227. RETURN (tk = TkProcedure) OR (tk = TkFunction)
  228. END DeclaresProc ;
  229. PROCEDURE KwAhead (word : ARRAY OF CHAR) : BOOLEAN ;
  230. (* does the next token (past blanks/comments) equal the keyword 'word',
  231. without consuming it? srcPos is saved and restored. *)
  232. VAR save : CARDINAL ;
  233. k : BOOLEAN ;
  234. BEGIN
  235. save := srcPos ;
  236. Skip () ;
  237. k := FALSE ;
  238. IF Alpha (CurCh ()) THEN
  239. GetWord () ;
  240. k := NameEq (wrd, word)
  241. END ;
  242. srcPos := save ;
  243. RETURN k
  244. END KwAhead ;
  245. PROCEDURE PeekKw (VAR tok : CARDINAL) ;
  246. (* peek at the next keyword token without consuming it *)
  247. VAR i : CARDINAL ;
  248. BEGIN
  249. tok := TkNone ;
  250. i := 1 ;
  251. WHILE i <= 42 DO
  252. IF kName [i] [0] # 0C THEN
  253. IF KwAhead (kName [i]) THEN
  254. tok := kTk [i] ;
  255. RETURN
  256. END
  257. END ;
  258. INC (i)
  259. END
  260. END PeekKw ;
  261. PROCEDURE MatchKey (VAR tok : CARDINAL) : BOOLEAN ;
  262. (* skip; if next symbol is a word, read it into wrd and set its token.
  263. Returns TRUE when a word was read (tok = TkNone for plain ids). *)
  264. BEGIN
  265. tok := TkNone ;
  266. Skip () ;
  267. IF NOT Alpha (CurCh ()) THEN
  268. RETURN FALSE
  269. END ;
  270. GetWord () ;
  271. tok := WddTok () ;
  272. RETURN TRUE
  273. END MatchKey ;
  274. PROCEDURE MatchDelim (ch : CHAR) : BOOLEAN ;
  275. BEGIN
  276. Skip () ;
  277. IF CurCh () = ch THEN
  278. DropCh (GetCh ()) ;
  279. RETURN TRUE
  280. END ;
  281. RETURN FALSE
  282. END MatchDelim ;
  283. PROCEDURE MatchAssign () : BOOLEAN ;
  284. BEGIN
  285. Skip () ;
  286. IF (CurCh () = ':') AND (PeekAhead (1) = '=') THEN
  287. DropCh (GetCh ()) ;
  288. DropCh (GetCh ()) ;
  289. RETURN TRUE
  290. END ;
  291. RETURN FALSE
  292. END MatchAssign ;
  293. PROCEDURE MatchRange () : BOOLEAN ;
  294. BEGIN
  295. Skip () ;
  296. IF (CurCh () = '.') AND (PeekAhead (1) = '.') THEN
  297. DropCh (GetCh ()) ;
  298. DropCh (GetCh ()) ;
  299. RETURN TRUE
  300. END ;
  301. RETURN FALSE
  302. END MatchRange ;
  303. PROCEDURE ExpectDelim (ch : CHAR ; n : CARDINAL) ;
  304. BEGIN
  305. Skip () ;
  306. IF CurCh () = ch THEN
  307. DropCh (GetCh ())
  308. ELSE
  309. Err (n)
  310. END
  311. END ExpectDelim ;
  312. PROCEDURE HexVal (ch : CHAR) : CARDINAL ;
  313. BEGIN
  314. IF (ch >= '0') AND (ch <= '9') THEN
  315. RETURN ORD (ch) - ORD ('0')
  316. ELSIF (ch >= 'A') AND (ch <= 'F') THEN
  317. RETURN ORD (ch) - ORD ('A') + 10
  318. END ;
  319. RETURN ORD (ch) - ORD ('a') + 10
  320. END HexVal ;
  321. PROCEDURE RdIntConst (VAR v : LONGINT) ;
  322. (* bare integer constant; current char is digit or '$' *)
  323. VAR acc : LONGINT ;
  324. BEGIN
  325. acc := 0 ;
  326. IF CurCh () = '$' THEN
  327. DropCh (GetCh ()) ;
  328. WHILE IsHexCh (CurCh ()) DO
  329. acc := acc * 16 + VAL (LONGINT, HexVal (CurCh ())) ;
  330. DropCh (GetCh ())
  331. END
  332. ELSE
  333. WHILE Digit (CurCh ()) DO
  334. acc := acc * 10 + VAL (LONGINT, ORD (CurCh ()) - ORD ('0')) ;
  335. DropCh (GetCh ())
  336. END
  337. END ;
  338. v := acc
  339. END RdIntConst ;
  340. PROCEDURE StrNew (first : CARDINAL ; hasFirst : BOOLEAN ) : CARDINAL ;
  341. (* Open a pool slot for a string literal, optionally pre-seeded with its
  342. first character.
  343. RdConst has to read the first character before it can tell a one-character
  344. literal from a longer one - the "is the next character another quote?"
  345. test only makes sense once something has been read - so the seeding has to
  346. happen here and has to advance strTop. Leaving strTop alone and letting the
  347. caller write the character by hand is a trap: strTop is the next FREE byte,
  348. so the first StrPut lands on top of the seeded character and overwrites it.
  349. (That bug shipped the literal 'hi' as 69 00 - 'i' then NUL.) *)
  350. VAR x : CARDINAL ;
  351. BEGIN
  352. IF strCnt > HIGH (strOff) THEN
  353. Err (ECompOvf) ; (* too many literals in one unit *)
  354. RETURN 0
  355. END ;
  356. x := strCnt ;
  357. strOff [x] := strTop ;
  358. strLen [x] := 0 ;
  359. INC (strCnt) ;
  360. IF hasFirst THEN
  361. strPool [strTop] := CHR (first) ;
  362. INC (strTop) ;
  363. strLen [x] := 1
  364. END ;
  365. rdStrX := x ;
  366. RETURN x
  367. END StrNew ;
  368. PROCEDURE StrPut (x : CARDINAL ) ;
  369. (* append the current source character to pool slot x *)
  370. BEGIN
  371. IF x > HIGH (strOff) THEN
  372. RETURN
  373. END ;
  374. IF strTop > HIGH (strPool) THEN
  375. Err (ECompOvf) ; (* literal longer than the pool *)
  376. RETURN
  377. END ;
  378. strPool [strTop] := CurCh () ;
  379. INC (strTop) ;
  380. INC (strLen [x])
  381. END StrPut ;
  382. PROCEDURE RdConst (VAR v : LONGINT ; VAR cls : CARDINAL ;
  383. VAR isStr : BOOLEAN) ;
  384. (* scalar or string constant. A string literal's text is collected into the
  385. pool and its slot index left in rdStrX; a single-character literal stays a
  386. TScalar holding its character code, which is what "c := 'a'" wants. *)
  387. CONST q = AposC ;
  388. VAR ch : CHAR ;
  389. idx : CARDINAL ;
  390. BEGIN
  391. isStr := FALSE ;
  392. cls := TScalar ;
  393. v := 0 ;
  394. Skip () ;
  395. IF CurCh () = '-' THEN
  396. (* Signed ordinal constant, needed for array bounds such as
  397. `array[-17..42] of real'. ParseExpr already handles unary minus in
  398. expressions; RdConst is the constant-only path and had no sign. *)
  399. DropCh (GetCh ()) ;
  400. RdConst (v, cls, isStr) ;
  401. IF NOT isStr THEN
  402. v := 0 - v
  403. END ;
  404. RETURN
  405. END ;
  406. IF CurCh () = '$' THEN
  407. RdIntConst (v) ;
  408. cls := TScalar
  409. ELSIF Digit (CurCh ()) THEN
  410. RdIntConst (v) ;
  411. IF (CurCh () = '.') AND (Digit (PeekAhead (1))) THEN
  412. cls := TReal ;
  413. DropCh (GetCh ())
  414. END ;
  415. IF (CurCh () = 'E') OR (CurCh () = 'e') THEN
  416. cls := TReal ;
  417. DropCh (GetCh ())
  418. END ;
  419. IF cls = TReal THEN
  420. WHILE AlphaNum (CurCh ()) OR (CurCh () = '.') OR (CurCh () = '-')
  421. OR (CurCh () = '+') DO
  422. DropCh (GetCh ())
  423. END
  424. END
  425. ELSIF CurCh () = '^' THEN
  426. (* TP3 caret character constant, unquoted: ^M is CR (13), ^[ is ESC
  427. (27), ^@ is 0. The value is the upper-cased character minus 64, so
  428. the +256-MOD-256 keeps ^@ and any control character from wrapping. *)
  429. DropCh (GetCh ()) ;
  430. IF CurCh () = 0C THEN
  431. Err (EUnknown)
  432. ELSE
  433. ch := Upper (GetCh ()) ;
  434. v := VAL (LONGINT, (ORD (ch) + 256 - 64) MOD 256) ;
  435. cls := TScalar
  436. END
  437. ELSIF CurCh () = '#' THEN
  438. (* TP3 decimal character constant: #27, #255. *)
  439. DropCh (GetCh ()) ;
  440. IF Digit (CurCh ()) THEN
  441. RdIntConst (v) ;
  442. cls := TScalar
  443. ELSE
  444. Err (EUnknown)
  445. END
  446. ELSIF Alpha (CurCh ()) THEN
  447. (* TRUE / FALSE, or a NAMED constant - an enumerator (`[Constant,
  448. Formula]'), or any earlier const. A constant expression may name a
  449. constant, and set literals and `case' labels are full of enumerators. *)
  450. GetWord () ;
  451. IF NameEq (wrd, "TRUE") THEN
  452. v := 1 ;
  453. cls := TBool
  454. ELSIF NameEq (wrd, "FALSE") THEN
  455. v := 0 ;
  456. cls := TBool
  457. ELSIF Search (wrd, idx) AND (symtab [idx].tag = KConst) THEN
  458. v := symtab [idx].lval ;
  459. cls := symtab [idx].cls
  460. ELSE
  461. Err (EUnknown)
  462. END
  463. ELSIF ORD (CurCh ()) = q THEN
  464. DropCh (GetCh ()) ;
  465. IF ORD (CurCh ()) = q THEN
  466. (* '' - the empty string. It used to be reported as the scalar 39,
  467. so writeln('') printed a single quote mark. It is a string of
  468. length zero, and an inline zero-length literal is exactly what the
  469. runtime's JCXZ path is for. *)
  470. DropCh (GetCh ()) ;
  471. isStr := TRUE ;
  472. cls := TString ;
  473. v := 0 ;
  474. rdStrX := StrNew (0, FALSE)
  475. ELSIF (CurCh () = 0C) OR ((ORD (CurCh ()) = 0DH)) OR ((ORD (CurCh ()) = 0AH)) THEN
  476. Err (EUnknown)
  477. ELSE
  478. v := VAL (LONGINT, ORD (GetCh ())) ;
  479. cls := TScalar ;
  480. IF ORD (CurCh ()) = q THEN
  481. DropCh (GetCh ())
  482. ELSE
  483. isStr := TRUE ;
  484. cls := TString ;
  485. (* Scan to the closing quote. NB: the loop condition must test
  486. the CURRENT character and only the current character. The
  487. obvious-looking "while CurCh # quote" with a
  488. "if PeekAhead(1) = quote then consume two" body is wrong:
  489. consuming the quote moves the cursor past it, so the next
  490. condition test sees the character AFTER the literal, is
  491. satisfied, and the scan runs on to end-of-buffer - which
  492. silently eats the rest of the program and makes every later
  493. error point at end-of-file. Stop on the quote itself, and
  494. treat a doubled quote as one embedded quote character.
  495. The first character is already gone - it was read into v above -
  496. so the pool slot is pre-seeded with it. *)
  497. rdStrX := StrNew (VAL (CARDINAL, v), TRUE) ;
  498. LOOP
  499. IF ORD (CurCh ()) = q THEN
  500. IF ORD (PeekAhead (1)) = q THEN
  501. StrPut (rdStrX) ; (* '' inside *)
  502. DropCh (GetCh ()) ; DropCh (GetCh ())
  503. ELSE
  504. EXIT (* closing quote *)
  505. END
  506. ELSIF (CurCh () = 0C) OR (ORD (CurCh ()) = 0DH) THEN
  507. Err (EUnknown) ; (* unterminated *)
  508. EXIT
  509. ELSE
  510. StrPut (rdStrX) ;
  511. DropCh (GetCh ())
  512. END
  513. END ;
  514. IF ORD (CurCh ()) = q THEN
  515. DropCh (GetCh ())
  516. END
  517. END
  518. END
  519. ELSE
  520. Err (EUnknown)
  521. END
  522. END RdConst ;
  523. END CompilerLex.