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

  }.