-- file CtoSScannerImpl.mesa
-- derived from Compiler>Scanner.mesa
-- last modified by Satterthwaite, July 29, 1983 9:32 am
DIRECTORY
Environment: TYPE USING [charsPerPage, Word],
IO: TYPE USING [
BS, CR, FF, LF, NUL, TAB,
STREAM, EndOf, GetBlock, GetChar, GetIndex, PutChar, Put, SetIndex,
card, rope, string],
CtoSP1: TYPE --P1-- USING [Token, nullTValue],
CtoSParseTable: TYPE ParseTable USING [
HashIndex, HashTableRef, IndexTableRef, ScanTableRef, TableRef,
TSymbol, VocabularyRef,
endMarker,
tokenARROW, tokenATOM, tokenBRACKET, tokenCHAR, tokenDOT,
tokenEQUAL, tokenGE, tokenGREATER, tokenID, tokenLE, tokenLESS,
tokenFLNUM, tokenLNUM, tokenLSTR, tokenMINUS, tokenNE, tokenNUM,
tokenSTR, tokenTILDE],
Rope: TYPE USING [ROPE, FromProc],
SMOps: TYPE USING [MS];
CtoSScannerImpl: CEDAR PROGRAM
IMPORTS IO, Rope
EXPORTS CtoSP1 = {
OPEN CtoSParseTable, P1~~CtoSP1;
-- table installation
tablePtr: TableRef;
hashTab: HashTableRef;
scanTab: ScanTableRef;
vocab: VocabularyRef;
vocabIndex: IndexTableRef;
InstallScanTable: PUBLIC PROC [base: TableRef] = TRUSTED {
tablePtr ← base;
hashTab ← @tablePtr[tablePtr.scanTable.hashTab];
scanTab ← @tablePtr[tablePtr.scanTable.scanTab];
vocab ← LOOPHOLE[@tablePtr[tablePtr.scanTable.vocabBody]];
vocabIndex ← @tablePtr[tablePtr.scanTable.vocabIndex]};
CharClass: PROC [c: CHAR] RETURNS [TSymbol] = TRUSTED INLINE {
RETURN [scanTab[c]]};
-- scanner state
cm: SMOps.MS;
out: IO.STREAM;
zone: ZONE ← NIL;
StreamIndex: TYPE ~ INT;
stream: IO.STREAM ← NIL; -- the input stream
textPages: NAT ~ 6;
textChars: NAT ~ textPages*Environment.charsPerPage;
tB: REF TEXT;
tI, tMax: [0..textChars];
tOrigin, tLimit: CARDINAL;
tEnded: BOOL;
FillBuffer: PROC ~ {
tOrigin ← tLimit;
IF tEnded THEN tMax ← 0
ELSE {
tMax ← stream.GetBlock[tB, 0, textChars].nBytesRead;
IF tMax < textChars THEN tEnded ← TRUE;
tLimit ← tOrigin + tMax};
IF tMax = 0 THEN {tB[0] ← IO.NUL; tMax ← 1};
tI ← 0};
buffer: REF TEXT ← NIL; -- token assembly area
iMax: CARDINAL; -- iMax = buffer.maxLength
desc: RECORD [offset, length: NAT]; -- initial buffer segment
nTokens: NAT; -- token count
nErrors: NAT; -- lexical errors
BufferOverflow: ERROR ~ CODE;
ExpandBuffer: PROC ~ TRUSTED {
oldBuffer: REF TEXT ← buffer;
IF oldBuffer.length > 2000 THEN ERROR BufferOverflow;
buffer ← zone.NEW[TEXT[2*oldBuffer.length]];
FOR i: NAT IN [0..oldBuffer.length) DO buffer[i] ← oldBuffer[i] ENDLOOP;
iMax ← buffer.length ← buffer.maxLength;
zone.FREE[@oldBuffer]};
char: CHAR; -- current (most recently scanned) character
nesting: NAT; -- counts depth of nesting with [/( ... )/]
NextChar: PROC ~ { -- also expanded inline within Atom
IF (tI←tI+1) = tMax THEN FillBuffer[]; char ← tB[tI]};
Substr: PUBLIC PROC[start: StreamIndex, length: INT] RETURNS[r: Rope.ROPE] ~ {
saveIndex: StreamIndex ~ stream.GetIndex[];
GetChar: PROC RETURNS[CHAR] ~ {RETURN [stream.GetChar[]]};
stream.SetIndex[start];
r ← Rope.FromProc[length, GetChar];
stream.SetIndex[saveIndex];
RETURN};
Atom: PUBLIC PROC RETURNS [token: P1.Token] ~ {
OPEN token;
DO {
WHILE char IN [IO.NUL..' ] DO
SELECT char FROM
IO.NUL => { -- ↑@↑@ is Tioga escape seq
IF (tI←tI+1) = tMax THEN {IF tEnded THEN GO TO EndFile; FillBuffer[]};
char ← tB[tI];
IF char = IO.NUL THEN GO TO EndFile};
ENDCASE => {
IF (tI←tI+1) = tMax THEN {IF tEnded THEN GO TO EndFile; FillBuffer[]};
char ← tB[tI]};
ENDLOOP;
IF nesting = 0 THEN {index ← tOrigin + tI; value ← P1.nullTValue};
SELECT char FROM
'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm,
'n, 'o, 'p, 'q, 'r, 's, 't, 'u, 'v, 'w, 'x, 'y, 'z => {
i: CARDINAL ← 0;
DO
buffer[i] ← char;
IF (tI←tI+1) = tMax THEN FillBuffer[];
char ← tB[tI];
SELECT char FROM
IN ['a..'z], IN ['A..'Z], IN ['0..'9] =>
IF (i ← i+1) >= iMax THEN ExpandBuffer[];
ENDCASE => EXIT;
ENDLOOP;
desc.length ← i+1;
class ← tokenID;
GO TO GotNext};
'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'I, 'J, 'K, 'L, 'M,
'N, 'O, 'P, 'Q, 'R, 'S, 'T, 'U, 'V, 'W, 'X, 'Y, 'Z => {
i: CARDINAL ← 0;
uId: BOOL ← TRUE;
first, last: NAT ← char.ORD;
DO
buffer[i] ← char;
IF (tI←tI+1) = tMax THEN FillBuffer[];
char ← tB[tI];
SELECT char FROM
IN ['A..'Z] => {
last ← char.ORD; IF (i ← i+1) >= iMax THEN ExpandBuffer[]};
IN ['a..'z], IN ['0..'9] => {
uId ← FALSE; IF (i ← i+1) >= iMax THEN ExpandBuffer[]};
ENDCASE => EXIT;
ENDLOOP;
i ← i+1;
IF uId THEN TRUSTED {
h: HashIndex ← ((first*128-first) + last) MOD HashIndex.LAST + 1;
j, s1, s2: CARDINAL;
WHILE (j ← hashTab[h].symbol) # 0 DO
IF vocabIndex[j]-(s2←vocabIndex[j-1]) = i THEN
FOR s1 IN [0 .. i) DO
IF buffer[s1] # vocab.text[s2] THEN EXIT;
s2 ← s2+1;
REPEAT
FINISHED => {class ← j; GO TO GotNext};
ENDLOOP;
IF (h ← hashTab[h].link) = 0 THEN EXIT;
ENDLOOP};
desc.length ← i;
class ← tokenID;
GO TO GotNext};
'0, '1, '2, '3, '4, '5, '6, '7, '8, '9 => {
valid: BOOL;
[class, valid] ← CollectNumber[i~0];
IF ~valid THEN ScanError[$number, index];
GO TO GotNext};
',, ';, ':, '←, '#, '+, '*, '/, '↑, '@, '!,
'{, '} => {
class ← CharClass[char]; GO TO GetNext};
'' => {
c: CHAR;
valid, advance: BOOL;
NextChar[];
[c, valid, advance] ← Escape[];
IF ~valid THEN ScanError[$escape, index + 1];
class ← tokenCHAR;
IF advance THEN GO TO GetNext ELSE GO TO GotNext};
'" => {
i: CARDINAL ← 0;
valid: BOOL;
advance: BOOL ← TRUE;
DO
IF advance THEN {
IF (tI←tI+1) = tMax THEN {IF tEnded THEN GO TO EOFEnd; FillBuffer[]};
char ← tB[tI]};
SELECT char FROM
'" => {
IF (tI←tI+1) = tMax THEN FillBuffer[];
char ← tB[tI];
IF char # '" THEN GO TO QuoteEnd};
ENDCASE;
IF i >= iMax THEN ExpandBuffer[
! BufferOverflow => {ScanError[string, index]; i ← 0; CONTINUE}];
[buffer[i], valid, advance] ← Escape[]; i ← i+1;
IF ~valid THEN ScanError[$escape, tOrigin + tI];
REPEAT
QuoteEnd => NULL;
EOFEnd => {ScanError[$string, index]; FillBuffer[]; char ← tB[tI]};
ENDLOOP;
desc.length ← i;
IF char = 'l OR char = 'L THEN {class ← tokenLSTR; GO TO GetNext}
ELSE {class ← tokenSTR; GO TO GotNext}};
'$ => {
i: CARDINAL;
i ← 0; NextChar[];
SELECT char FROM
IN ['a..'z], IN ['A..'Z] => NULL;
ENDCASE => ScanError[$atom, index];
DO
SELECT char FROM
IN ['a..'z], IN ['A..'Z], IN ['0..'9] => {
IF i >= iMax THEN ExpandBuffer[]; buffer[i] ← char; i ← i+1};
ENDCASE => EXIT;
NextChar[];
ENDLOOP;
desc.length ← i;
class ← tokenATOM;
GO TO GotNext};
'- => {
NextChar[];
IF char # '- THEN {class ← tokenMINUS; GO TO GotNext};
char ← IO.NUL;
DO
pChar: CHAR ~ char;
IF (tI←tI+1) = tMax THEN {IF tEnded THEN GO TO EndFile; FillBuffer[]};
char ← tB[tI];
SELECT char FROM
'- => IF pChar = '- THEN EXIT;
IO.CR => EXIT;
ENDCASE;
ENDLOOP;
NextChar[]};
'. => {
NextChar[];
SELECT char FROM
IN ['0..'9] => {
valid: BOOL;
buffer[0] ← '.;
[class, valid] ← CollectNumber[i~1, float~TRUE];
IF ~valid THEN ScanError[$number, index];
GO TO GotNext};
ENDCASE => {class ← tokenDOT; GO TO GotNext}};
'= => {
NextChar[];
IF char = '> THEN {class ← tokenARROW; GO TO GetNext}
ELSE {class ← tokenEQUAL; GO TO GotNext}};
'< => {
NextChar[];
IF char = '= THEN {class ← tokenLE; GO TO GetNext}
ELSE {class ← tokenLESS; GO TO GotNext}};
'> => {
NextChar[];
IF char = '= THEN {class ← tokenGE; GO TO GetNext}
ELSE {class ← tokenGREATER; GO TO GotNext}};
'~ => {
NextChar[];
SELECT char FROM
'= => {class ← tokenNE; GO TO GetNext};
'< => {class ← tokenGE; GO TO GetNext};
'> => {class ← tokenLE; GO TO GetNext}
ENDCASE => {class ← tokenTILDE; GO TO GotNext}};
'[, '( => {
nesting ← nesting + 1; GO TO GetNext};
'], ') => {
nesting ← nesting - 1;
IF nesting <= 0 THEN {nesting ← 0; class ← tokenBRACKET};
GO TO GetNext};
ENDCASE => {
class ← CharClass[char];
IF class # 0 THEN GO TO GetNext;
NextChar[];
--ScanError[$char, index];--
GO TO GotNext};
EXITS
GetNext => {
IF (tI←tI+1) = tMax THEN FillBuffer[]; char ← tB[tI];
IF nesting = 0 THEN EXIT};
GotNext => IF nesting = 0 THEN EXIT};
REPEAT
EndFile => {
class ← endMarker; index ← tOrigin + (tI-1);
UNTIL tEnded DO FillBuffer[] ENDLOOP; -- flush stream
FillBuffer[]; char ← tB[tI]};
ENDLOOP;
value ← (tOrigin + tI) - index; nTokens ← nTokens + 1;
RETURN};
-- numerical conversion
endMark: CHAR ~ IO.NUL;
CollectNumber: PROC [i: CARDINAL, float: BOOL←FALSE]
RETURNS [class: TSymbol, valid: BOOL] ~ {
maxWord: LONG CARDINAL ~ Environment.Word.LAST;
hexCount: NAT ← 0;
hexSig: PACKED ARRAY CHAR['a..'h] OF {F, T} ← ALL[F];
v: LONG CARDINAL;
Accept: PROC ~ INLINE {
buffer[i] ← char;
IF (i ← i+1) >= iMax THEN ExpandBuffer[];
NextChar[]};
class ← tokenLNUM;
DO
SELECT char FROM
IN ['0..'9] => Accept[];
'e, 'E => {
hexSig['e] ← T; hexCount ← hexCount + 1; Accept[];
IF hexCount = 1 AND char = '+ OR char = '- THEN {
float ← TRUE; Accept[]}};
IN ['a..'f] => {hexSig[char] ← T; hexCount ← hexCount+1; Accept[]};
IN ['A..'F] => {
hexSig[char+('a-'A)] ← T; hexCount ← hexCount+1; Accept[]};
'h, 'H => {hexSig['h] ← T; hexCount ← hexCount+1; Accept[]};
'. => {
IF hexCount # 0 OR float THEN EXIT;
NextChar[];
float ← TRUE;
buffer[i] ← '.;
IF (i ← i+1) >= iMax THEN ExpandBuffer[]};
ENDCASE => EXIT;
ENDLOOP;
buffer[i] ← endMark;
SELECT TRUE FROM
float => {class ← tokenFLNUM; valid ← ScanFloating[buffer]};
(hexSig['h] = T) => valid ← ScanHex[buffer];
ENDCASE =>
SELECT hexCount FROM
0 => valid ← ScanDecimal[buffer];
1 =>
SELECT hexSig FROM
[F,T,F,F,F,F,F,F] => valid ← ScanOctal[buffer];
[F,F,T,F,F,F,F,F] => {
class ← tokenCHAR;
valid ← ScanOctalChar[buffer]};
[F,F,F,T,F,F,F,F] => valid ← ScanDecimal[buffer];
[F,F,F,F,T,F,F,F] => {
class ← tokenFLNUM;
valid ← ScanFloating[buffer]};
ENDCASE => valid ← ScanHex[buffer];
ENDCASE => valid ← ScanHex[buffer];
SELECT class FROM
tokenCHAR => NULL;
tokenFLNUM => NULL;
ENDCASE =>
IF LOOPHOLE[v, LONG CARDINAL] <= maxWord THEN
class ← tokenNUM;
RETURN};
Digit: ARRAY CHAR ['0..'9] OF [0..9] ~ [0,1,2,3,4,5,6,7,8,9];
HexDigit: ARRAY CHAR ['A..'F] OF [0..15] ~ [10,11,12,13,14,15];
AppendDecimal: PROC [v: LONG CARDINAL, digit: CHAR ['0..'9]]
RETURNS [newV: LONG CARDINAL, valid: BOOL] ~ {
maxV: LONG CARDINAL ~ 429496729; -- (2**32-1)/10
maxD: NAT ~ 5; -- (2**32-1) MOD 10
d: [0..9] ~ Digit[digit];
valid ← v < maxV OR (v = maxV AND d <= maxD);
newV ← 10*v + d;
RETURN};
AppendOctal: PROC [v: LONG CARDINAL, digit: CHAR ['0..'7]]
RETURNS [newV: LONG CARDINAL, valid: BOOL] ~ {
maxV: LONG CARDINAL ~ 3777777777b; -- (2**32-1)/8
d: [0..7] ~ Digit[digit];
valid ← (v <= maxV);
newV ← 8*v + d;
RETURN};
AppendHex: PROC [v: LONG CARDINAL, digit: CHAR ['0..'F]]
RETURNS [newV: LONG CARDINAL, valid: BOOL] ~ {
maxV: LONG CARDINAL ~ 0FFFFFFFh; -- (2**32-1)/16
d: [0..15] ~ IF digit IN ['0..'9] THEN Digit[digit] ELSE HexDigit[digit];
valid ← (v <= maxV);
newV ← 16*v + d;
RETURN};
AppendToScale: PROC [v: CARDINAL, digit: CHAR ['0..'9]]
RETURNS [newV: CARDINAL, valid: BOOL] ~ {
maxV: NAT ~ 6553; -- (2**16-1)/10
maxD: NAT ~ 5; -- (2**16-1) MOD 10
d: [0..9] ~ Digit[digit];
valid ← v < maxV OR (v = maxV AND d <= maxD);
newV ← 10*v + d;
RETURN};
ValidFraction: PROC [v: LONG CARDINAL, digit: CHAR ['0..'9]] RETURNS [BOOL] ~ {
maxV: LONG CARDINAL ~ 214748364; -- (2**31-1)/10
maxD: NAT ~ 7; -- (2**31-1) MOD 10
RETURN [v < maxV OR (v = maxV AND Digit[digit] <= maxD)]};
ScanDecimal: PROC [s: REF TEXT] RETURNS [valid: BOOL←TRUE] ~ {
c: CHAR;
i: CARDINAL ← 0;
v: LONG CARDINAL ← 0;
IF s[i] NOT IN ['0..'9] THEN valid ← FALSE;
WHILE (c ← s[i]) IN ['0..'9] DO
IF valid THEN [v, valid] ← AppendDecimal[v, c];
i ← i+1;
ENDLOOP;
IF c = 'd OR c = 'D THEN {
scale: CARDINAL ← 0;
WHILE (c ← s[i←i+1]) IN ['0..'9] DO
IF valid THEN [scale, valid] ← AppendToScale[scale, c];
ENDLOOP;
THROUGH [1 .. scale] WHILE valid DO
[v, valid] ← AppendDecimal[v, '0] ENDLOOP};
IF c # endMark THEN valid ← FALSE;
RETURN};
ScanOctal: PROC [s: REF TEXT] RETURNS [valid: BOOL←TRUE] ~ {
c: CHAR;
i: CARDINAL ← 0;
v: LONG CARDINAL ← 0;
IF s[i] NOT IN ['0..'7] THEN valid ← FALSE;
WHILE (c ← s[i]) IN ['0..'7] DO
IF valid THEN [v, valid] ← AppendOctal[v, c];
i ← i+1;
ENDLOOP;
IF c = 'b OR c = 'B THEN {
scale: CARDINAL ← 0;
WHILE (c ← s[i←i+1]) IN ['0..'9] DO
IF valid THEN [scale, valid] ← AppendToScale[scale, c];
ENDLOOP;
THROUGH [1 .. scale] WHILE valid DO
[v, valid] ← AppendOctal[v, '0] ENDLOOP};
IF c # endMark THEN valid ← FALSE;
RETURN};
ScanOctalChar: PROC [s: REF TEXT] RETURNS [valid: BOOL←TRUE] ~ {
c: CHAR;
maxChar: NAT ~ 377b;
i: CARDINAL ← 0;
v: LONG CARDINAL ← 0;
IF s[i] NOT IN ['0..'7] THEN valid ← FALSE;
WHILE (c ← s[i]) IN ['0..'7] DO
IF valid THEN [v, valid] ← AppendOctal[v, c];
i ← i+1;
ENDLOOP;
IF c = 'c OR c = 'C THEN c ← s[i←i+1] ELSE valid ← FALSE;
IF c # endMark OR v NOT IN [0 .. maxChar] THEN valid ← FALSE;
RETURN};
ScanHex: PROC [s: REF TEXT] RETURNS [valid: BOOL←TRUE] ~ {
c: CHAR;
i: CARDINAL ← 0;
v: LONG CARDINAL ← 0;
IF s[i] NOT IN ['0..'9] THEN valid ← FALSE;
DO
SELECT (c ← s[i]) FROM
IN ['0..'9], IN ['A..'F] =>
IF valid THEN [v, valid] ← AppendHex[v, c];
IN ['a..'f] =>
IF valid THEN [v, valid] ← AppendHex[v, VAL[(c.ORD-'a.ORD)+'A.ORD]];
ENDCASE => EXIT;
i ← i + 1;
ENDLOOP;
IF c = 'h OR c = 'H THEN {
scale: CARDINAL ← 0;
WHILE (c ← s[i←i+1]) IN ['0..'9] DO
IF valid THEN [scale, valid] ← AppendToScale[scale, c];
ENDLOOP;
THROUGH [1 .. scale] WHILE valid DO
[v, valid] ← AppendHex[v, '0] ENDLOOP};
IF c # endMark THEN valid ← FALSE;
RETURN};
ScanFloating: PROC [s: REF TEXT] RETURNS [valid: BOOL←TRUE] ~ {
c: CHAR;
i: CARDINAL ← 0;
v: LONG CARDINAL ← 0;
exp: INTEGER ← 0;
WHILE (c ← s[i]) IN ['0..'9] DO
valid ← valid AND ValidFraction[v, c];
IF valid THEN v ← AppendDecimal[v, c].newV
ELSE exp ← exp + 1; -- should round
i ← i+1;
ENDLOOP;
IF c = '. THEN {
i ← i+1;
IF s[i] NOT IN ['0..'9] THEN valid ← FALSE;
WHILE (c ← s[i]) IN ['0..'9] DO
valid ← valid AND ValidFraction[v, c];
IF valid THEN {[v, valid] ← AppendDecimal[v, c]; exp ← exp-1}
ELSE NULL; -- should round
i ← i+1;
ENDLOOP};
valid ← TRUE;
IF c = 'e OR c = 'E THEN {
scale: INTEGER ← 0;
op: {plus, minus} ← $plus;
i ← i + 1;
SELECT s[i] FROM
'+ => i ← i+1;
'- => {op ← $minus; i ← i+1};
ENDCASE;
IF s[i] NOT IN ['0..'9] THEN valid ← FALSE;
WHILE (c ← s[i]) IN ['0..'9] DO
IF valid THEN [scale, valid] ← AppendToScale[scale, c];
i ← i+1;
ENDLOOP;
exp ← IF op = $plus THEN exp + scale ELSE exp - scale}; -- need overflow check
IF c # endMark THEN valid ← FALSE;
RETURN};
-- character and string constants
escapeMark: CHAR ~ '\\;
Escape: PROC RETURNS [c: CHAR, valid, advance: BOOL←TRUE] ~ {
c ← char;
IF c = escapeMark THEN {
NextChar[];
SELECT char FROM
'n, 'N => c ← IO.CR;
'r, 'R => c ← IO.CR;
'l, 'L => c ← IO.LF;
't, 'T => c ← IO.TAB;
'b, 'B => c ← IO.BS;
'f, 'F => c ← IO.FF;
'', '", escapeMark => c ← char;
IN ['0 .. '7] => {
nc, v: CARDINAL ← 0;
DO
IF ~(char IN ['0..'7]) THEN {valid ← advance ← FALSE; EXIT};
v ← 8*v + Digit[char];
IF (nc ← nc+1) = 3 THEN EXIT;
NextChar[];
ENDLOOP;
IF v > 377b THEN {valid ← FALSE; v ← 0};
c ← v + 0c};
ENDCASE => valid ← advance ← FALSE};
RETURN};
-- initialization/finalization
ScanInit: PUBLIC PROC [model: SMOps.MS, source: IO.STREAM] ~ {
cm ← model; out ← model.out; zone ← model.z;
stream ← source;
IF buffer = NIL THEN buffer ← zone.NEW[TEXT[256]];
iMax ← buffer.length ← buffer.maxLength;
desc.offset ← 0;
tB ← zone.NEW[TEXT[textChars]];
tOrigin ← tLimit ← 0; tMax ← 0; tEnded ← FALSE;
FillBuffer[]; char ← tB[tI]; nesting ← 0;
nTokens ← nErrors ← 0};
ScanReset: PUBLIC PROC RETURNS [NAT, NAT] ~ TRUSTED {
stream ← out ← NIL;
zone.FREE[@tB];
IF buffer # NIL THEN zone.FREE[@buffer];
zone ← NIL;
RETURN [nTokens, nErrors]};
-- error handling
ResetScanIndex: PUBLIC PROC [index: StreamIndex] RETURNS [success: BOOL] ~ {
IF ~(index IN [tOrigin .. tLimit)) THEN {
page: CARDINAL ~ index/Environment.charsPerPage;
tOrigin ← tLimit ← page*Environment.charsPerPage;
tMax ← 0; tEnded ← FALSE;
stream.SetIndex[tOrigin];
FillBuffer[]};
tI ← index - tOrigin;
IF tI >= tMax THEN FillBuffer[]; char ← tB[tI]; RETURN [TRUE]};
ErrorCode: TYPE ~ {number, string, char, atom, escape};
ScanError: PROC [code: ErrorCode, tokenIndex: StreamIndex] ~ {
nErrors ← nErrors + 1;
ErrorContext[SELECT code FROM
$number => "invalid number",
$string => "string unterminated or too long",
$char => "invalid character",
$atom => "invalid atom",
$escape => "invalid escape sequence",
ENDCASE => NIL,
tokenIndex];
out.PutChar['\n]};
ErrorContext: PUBLIC PROC [message: Rope.ROPE, tokenIndex: StreamIndex] ~ {
saveIndex: StreamIndex ~ stream.GetIndex;
start, lineIndex: StreamIndex ← tokenIndex;
n: [1..100];
FOR n IN [1..100] UNTIL lineIndex = 0 DO
lineIndex ← lineIndex - 1;
stream.SetIndex[lineIndex];
IF stream.GetChar[] = IO.CR THEN EXIT;
start ← lineIndex;
ENDLOOP;
stream.SetIndex[start];
FOR n IN [1..100] UNTIL stream.EndOf DO
char: CHAR ~ stream.GetChar[];
IF char = IO.CR THEN EXIT;
out.PutChar[char];
ENDLOOP;
out.PutChar[IO.CR];
stream.SetIndex[start];
UNTIL stream.GetIndex = tokenIndex OR stream.EndOf DO
char ← stream.GetChar[];
out.PutChar[IF char = IO.TAB THEN '\t ELSE ' ];
ENDLOOP;
out.Put[IO.string["↑ "L]]; out.Put[IO.rope[message]]; out.Put[IO.string[" ["L]];
out.Put[IO.card[tokenIndex]];
out.PutChar[']]; out.PutChar['\n];
stream.SetIndex[saveIndex]};
}.