-- 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]}; }.