-- File: ScriptScanImpl.mesa - last edit by
-- Karlton:	 2-Sep-82 13:43:52

DIRECTORY
  Ascii USING [NUL, SP],
  Inline USING [HighByte, LowByte],
  Real USING [StringToReal],
  ScriptHash USING [Enter, Handle, Hash, nullHash, nullVal, Val],
  ScriptParse USING [Error, Terminal, TerminalType],
  Stream USING [EndOfStream, GetChar, Handle],
  String USING [AppendChar, AppendString, StringBoundsFault, StringToLongNumber];
  
ScriptScanImpl: PROGRAM
  IMPORTS Inline, Real, ScriptHash, ScriptParse, Stream, String
  EXPORTS ScriptParse = {
  
  nullVal: ScriptHash.Val = ScriptHash.nullVal;
  
  ScanState: TYPE = {start, version, token, stop};
  ScanHandle: TYPE = LONG POINTER TO ScanObject;
  ScanObject: PUBLIC TYPE = RECORD [
    index: LONG CARDINAL ← 0,
    char: CHARACTER ← Ascii.NUL,
    eof: BOOLEAN ← FALSE,
    state: ScanState ← start,
    endScript, links, true, false: ScriptHash.Hash,
    token: LONG STRING,
    univ, id: ScriptHash.Handle,
    z: UNCOUNTED ZONE,
    stream: Stream.Handle];
  
  NextChar: PROCEDURE [s: ScanHandle] = {
    IF s.eof OR s.state = stop THEN s.char ← Ascii.NUL
    ELSE {
      DO
        s.index ← s.index + 1;
	s.char ← s.stream.GetChar[ !
          Stream.EndOfStream => {
	    s.index ← s.index - 1;
	    s.eof ← TRUE;
	    s.char ← Ascii.NUL;
	    EXIT}];
         IF s.char IN [40C..176C] THEN EXIT;
	 ENDLOOP}};
    
  AppendToToken: PROCEDURE [scan: ScanHandle] = {
    String.AppendChar[scan.token, scan.char ! String.StringBoundsFault => {
      t: LONG STRING ← scan.z.NEW[StringBody[scan.token.length + 40]];
      String.AppendString[t, scan.token];
      scan.z.FREE[@scan.token];
      scan.token ← t;
      RESUME[t]}]};
      
  NextSymbol: PUBLIC PROCEDURE [scan: ScanHandle]
    RETURNS [t: ScriptParse.Terminal] = {
    IntSequence: PROCEDURE = {
      number: CARDINAL;
      set, char: CHARACTER;
      DO
        NextChar[scan];
	SELECT scan.char FROM
	  '=, '> => EXIT;
	  IN ['0..'9] => {
	    number ← (scan.char - '0);
	    DO
	      NextChar[scan];
	      SELECT scan.char FROM
	        IN ['0..'9] => number ← (number * 10) + (scan.char - '0);
		Ascii.SP => EXIT;
		ENDCASE => ERROR ScriptParse.Error[scan, scan.index];
	      ENDLOOP};
	  IN ['A..'P] => {
	    left, right: CHARACTER;
	    left ← scan.char;
	    NextChar[scan];
	    right ← scan.char;
	    IF right ~IN ['A..'P] THEN ERROR ScriptParse.Error[scan, scan.index];
	    number ← (left - 'A)*16 + (right - 'A)};
	  ENDCASE => ERROR ScriptParse.Error[scan, scan.index];
	IF (char ← Inline.LowByte[number]) = 377C OR 
	   (set ← Inline.HighByte[number]) = 377C THEN
	  ERROR ScriptParse.Error[scan, scan.index];
	IF set # curSet THEN {
	  onlySet0 ← simpleChars ← FALSE;
	  curSet ← set;
	  {scan.char ← 377C; AppendToToken[scan]};
	  {scan.char ← set; AppendToToken[scan]}};
	scan.char ← char;
	SELECT scan.char FROM
	  '=, '> => simpleChars ← FALSE;
	  IN [40C..176C] => NULL;
	  ENDCASE => simpleChars ← FALSE;
	AppendToToken[scan];
	ENDLOOP};
    Number: PROCEDURE [neg: BOOLEAN] = {
      haveDot, haveE, okMinus: BOOLEAN ← FALSE;
      scan.token.length ← 0;
      IF neg THEN String.AppendChar[scan.token, '-];
      DO
        SELECT scan.char FROM
          IN ['0..'9] => {okMinus ← FALSE; AppendToToken[scan]};
          '. => {
	    IF haveDot THEN EXIT;
	    haveDot ← TRUE;
	    okMinus ← FALSE;
	    AppendToToken[scan]};
          'E => {
	    IF ~haveDot OR haveE THEN EXIT;
	    haveE ← TRUE;
	    okMinus ← TRUE;
	    AppendToToken[scan]};
          '- => {IF ~okMinus THEN EXIT; okMinus ← FALSE; AppendToToken[scan]};
	  ENDCASE => EXIT;
	NextChar[scan];
	ENDLOOP;
      SELECT TRUE FROM
        haveDot => t.body ← real[Real.StringToReal[scan.token]];
	~haveDot => t.body ← integer[String.StringToLongNumber[scan.token]];
	ENDCASE => ERROR};
    onlySet0, simpleChars: BOOLEAN;
    curSet: CHARACTER;
	
    SELECT scan.state FROM
      start => {scan.state ← version; RETURN[[body: start[]]]};
      version => {NextChar[scan]; scan.state ← token; RETURN[VersionID[scan]]};
      stop => RETURN[[pos: scan.index, body: stop[]]];
      ENDCASE => NULL;  -- fall through and do normal stuff
    DO
      BEGIN
      t.pos ← scan.index;
      SELECT scan.char FROM
        Ascii.NUL => {scan.state ← stop; RETURN[[pos: scan.index, body: stop[]]]};
	
        Ascii.SP, ', => GOTO tryAgain;
	
        '$ => {t.body ← dollar[]; GOTO almostDone};
        '' => {t.body ← quote[]; GOTO almostDone};
        '← => {t.body ← leftArrow[]; GOTO almostDone};
        '% => {t.body ← percent[]; GOTO almostDone};
        '| => {t.body ← bar[]; GOTO almostDone};
        '* => {t.body ← times[]; GOTO almostDone};
        '/ => {t.body ← divide[]; GOTO almostDone};
        '+ => {t.body ← plus[]; GOTO almostDone};
        '↑ => {t.body ← upArrow[]; GOTO almostDone};
        '[ => {t.body ← leftBracket[]; GOTO almostDone};
        '] => {t.body ← rightBracket[]; GOTO almostDone};
        '{ => {t.body ← leftBrace[]; GOTO almostDone};
        '} => {t.body ← rightBrace[]; GOTO almostDone};
        '( => {t.body ← leftParen[]; GOTO almostDone};
        ') => {t.body ← rightParen[]; GOTO almostDone};
        '. => {t.body ← dot[]; GOTO almostDone};
        '? => {t.body ← question[]; GOTO almostDone};
	
        ': => {  -- colonEqual
	  NextChar[scan];
	  IF scan.char # '= THEN t.body ← colon[]
	  ELSE t.body ← colonEqual[];
	  GOTO almostDone};
	
        '- => {  -- minus or comment or negative integer
	  NextChar[scan];
	  SELECT scan.char FROM
	    '- => { -- eat the comment
	      DO
	        NextChar[scan];
	        IF scan.char = '- THEN {
		  NextChar[scan]; IF scan.char = '- THEN EXIT};
	        ENDLOOP;
	      GOTO tryAgain};
	    IN ['0..'9], '. => {Number[TRUE]; GOTO done};
	    ENDCASE => {t.body ← minus[]; GOTO done}};
	
	'< =>  { -- string
	  temp: LONG STRING;
	  onlySet0 ← simpleChars ← TRUE;
	  curSet ← 0C;
	  scan.token.length ← 0;
	  DO
	    NextChar[scan];
	    SELECT scan.char FROM
	      '> => EXIT;
	      '= => {
		IntSequence[];
		SELECT scan.char FROM
		  '> => EXIT;
		  '= => NULL;
		  ENDCASE => ERROR ScriptParse.Error[scan, scan.index]};
	      ENDCASE => {
	        IF curSet # 0C THEN {
		  save: CHARACTER ← scan.char;
		  {scan.char ← 377C; AppendToToken[scan]};
		  {scan.char ← 0C; AppendToToken[scan]};
		  scan.char ← save};
		IF scan.char ~IN [40C..176C] THEN simpleChars ← FALSE;
		AppendToToken[scan]};
	    ENDLOOP;
	  temp ← scan.z.NEW[StringBody[scan.token.length]];
	  String.AppendString[temp, scan.token];
	  t.body ← string[
	    [allSet0: onlySet0, allSimple: simpleChars, string: temp]];
	  GOTO almostDone};
	    
	'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm,
        'n, 'o, 'p, 'q, 'r, 's, 't, 'u, 'v, 'w, 'x, 'u, 'z,
	'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 =>  { -- id or universal
	  allCaps: BOOLEAN ← scan.char IN ['A..'Z];
	  scan.token.length ← 0;
	  AppendToToken[scan];
	  DO
	    NextChar[scan];
	    SELECT scan.char FROM
	      IN ['a..'z] => {AppendToToken[scan]; allCaps ← FALSE};
	      IN ['0..'9], IN ['A..'Z] => AppendToToken[scan];
	      ENDCASE => EXIT;
	    ENDLOOP;
	  IF allCaps THEN {
	    hash: ScriptHash.Hash = scan.univ.Enter[scan.token, nullVal];
	    SELECT hash FROM
	      scan.endScript => t.body ← stop[];
	      scan.true => t.body ← boolean[TRUE];
	      scan.false => t.body ← boolean[FALSE];
	      scan.links => t.body ← links[];
	      ENDCASE => t.body ← universal[hash]}
	  ELSE t.body ← id[scan.id.Enter[scan.token, nullVal]];
	  GOTO done};
	    
	'0, '1, '2, '3, '4, '5, '6, '7, '8, '9  => {  -- number
	  Number[FALSE];
	  GO TO done};
	  
	 ENDCASE => ERROR ScriptParse.Error[scan, scan.index];
	
      EXITS
        almostDone => {NextChar[scan]; EXIT};
        done => EXIT;
	tryAgain => {NextChar[scan]};
      END
      ENDLOOP};
  
  VersionID: PROCEDURE [scan: ScanHandle] RETURNS [t: ScriptParse.Terminal] = {
    match: STRING = "Interscript/Reference/83 "L;
    FOR i: CARDINAL IN [0..match.length) DO
      IF scan.char # match[i] THEN ERROR ScriptParse.Error[scan, i];
      NextChar[scan];
      ENDLOOP;
    scan.index ← match.length;
    RETURN[[body: versionId[]]]};
  
  InitScan: PUBLIC PROCEDURE [
    stream: Stream.Handle, univ, id: ScriptHash.Handle, z: UNCOUNTED ZONE]
    RETURNS [scan: ScanHandle] = {
    scan ← z.NEW[ScanObject ← [
      z: z, univ: univ, id: id, stream: stream,
      endScript: univ.Enter["ENDSCRIPT"L, nullVal],
      links: univ.Enter["LINKS"L, nullVal],
      true: univ.Enter["T"L, nullVal],
      false: univ.Enter["F"L, nullVal],
      token: z.NEW[StringBody[40]] ]]};
  
  FinishScan: PUBLIC PROCEDURE [scan: ScanHandle] = {
    z: UNCOUNTED ZONE = scan.z;
    z.FREE[@scan.token];
    z.FREE[@scan]};
  
  }. -- of ScriptScanImpl