-- MDParseImpl.mesa
-- last edit by Schmidt, January 6, 1983 1:59 pm
-- last edit by Satterthwaite, February 9, 1983 10:37 am
-- Parser for the system modeller

DIRECTORY
  CWF: TYPE USING [WF0, WF1, WF2, WFC],
  Dir: TYPE USING [AddToDep, ADepRecord, DepSeq],
  FileStream: TYPE USING [EndOf],
  LongString: TYPE USING [EqualString],
  MDModel: TYPE USING [
    LISTSymbol, MODELSymbol, ParseLoc, Sym, Symbol, SymbolSeq, TraverseTree],
  ModelParseData: TYPE,
  P1: FROM "ModelParseDefs" USING [GuaranteeScannerCleanedUp, StreamId, TableId],
  Runtime: TYPE USING [GetTableBase],
  STPSubr: TYPE USING [StopSTP],
  Stream: TYPE USING [Delete, GetChar, Handle],
  Subr: TYPE USING [
    AllocateString, FreeString, GetChar, LongZone, NewStream, Read, strcpy,
    TTYProcs, Write],
  TypeScript: TYPE USING [TS],
  UnsafeSTP: TYPE USING [Error];


MDParseImpl: PROGRAM 
  IMPORTS
    CWF, Dir, FileStream, LongString, MDModel, ModelParseData, P1, Runtime,
    STP: UnsafeSTP, STPSubr, Stream, Subr
  EXPORTS MDModel, P1 = {

  -- be sure to update array TokString if you change this
  Token: TYPE = {
	tokBAD, tokEOF, tokLB, tokRB, tokDOT, tokCOLON, tokCOMMA,
	tokEQ, tokTWIDDLE, tokSEMI, tokID, tokNUM, tokTYPE, tokRETURNS,
	tokSTRLIT, tokFROM, tokDIR, tokIMPORTS, tokEXPORTS, tokPROGRAM,
	tokBEGIN, tokDEFINITIONS, tokCONFIG, tokEND, tokUSING, tokSHARES,
	tokMONITOR, tokCEDAR};


 -- MDS usage!!!
  tablesegptr: LONG POINTER;			-- ??
  logsh: Stream.Handle ← NIL;
  streamstack: ARRAY[0 .. 15) OF Stream.Handle ← ALL[NIL];
  streaminx: CARDINAL ← 0;
  parseRoot: PUBLIC MDModel.LISTSymbol ← NIL;	-- exported to MDModel
  -- for config scanner
  peektok: Token;
  peekvalue: LONG STRING;
  nextchar: CHAR;
  init: BOOL ← FALSE;
  savestr: LONG STRING ← NIL;
  toksave: LONG STRING ← NIL;

  -- the parser will close sh for you!!!
  ModelParse: PUBLIC PROC[symbolseq: MDModel.SymbolSeq, typeScript: TypeScript.TS,
	ttywindow: Subr.TTYProcs] = {

    Cleanup: PROC = {
      IF logsh ~= NIL THEN Stream.Delete[logsh];
      logsh ← NIL;
      streaminx ← 0;
      P1.GuaranteeScannerCleanedUp[]};
	
    {  
    ENABLE {
      UNWIND => Cleanup[];
      STP.Error => {
	CWF.WF0["FTP Error. "L];
	IF error ~= NIL THEN CWF.WF1["message: %s\n"L,error];
	Cleanup[];
	GOTO leave};
	};

    nerrors: CARDINAL;

    [symmodel: symbolseq.toploc.nestedmodel, nerrors: nerrors] ← 
	(symbolseq.toploc).ParseLoc[typeScript, ttywindow];
    -- close connections in case any files were brought over
    STPSubr.StopSTP[];
    P1.GuaranteeScannerCleanedUp[];
    IF logsh ~= NIL THEN {
	sh: Stream.Handle;
	Stream.Delete[logsh];  logsh ← NIL;
	-- since there were parsing errors, 
	-- get rid of the internal data structures
	symbolseq.toploc.nestedmodel ← NIL;
	CWF.WF0["Parser error log stored on 'ModelParser.ErrLog'\n"L];
	sh ← Subr.NewStream["ModelParser.ErrLog"L, Subr.Read];
	UNTIL FileStream.EndOf[sh] DO
	  CWF.WFC[Stream.GetChar[sh]];
	  ENDLOOP;
	Stream.Delete[sh]};
    IF symbolseq.toploc.nestedmodel ~= NIL THEN CheckDefined[symbolseq];
    streaminx ← 0;
    EXITS
    leave => NULL;
    }};


  PushInputStream: PUBLIC PROC[sh: Stream.Handle] = {
    IF streaminx >= streamstack.LENGTH THEN ERROR;
    streamstack[streaminx] ← sh;
    streaminx ← streaminx + 1};

  StreamPop: PROC RETURNS[sh: Stream.Handle] = {
    IF streaminx = 0 THEN ERROR;
    streaminx ← streaminx - 1;
    RETURN[streamstack[streaminx]]};


 -- exported to P1, called by P1.Parse[]

  AcquireStream: PUBLIC PROC [id: P1.StreamId] RETURNS [Stream.Handle] = {
    SELECT id FROM
      $source => RETURN[streamstack[streaminx-1]];
      $log => {
	IF logsh = NIL THEN logsh ← Subr.NewStream["ModelParser.ErrLog"L, Subr.Write];
	RETURN[logsh]};
      ENDCASE => ERROR};

  ReleaseStream: PUBLIC PROC [id: P1.StreamId] = {
    SELECT id FROM
      $source => {
	-- this is currently not used because the
	-- scanner frees these in ResetScanIndex in ModelScannerImpl
	sh: Stream.Handle ← StreamPop[];
	Stream.Delete[sh]};
      $log => NULL
      ENDCASE => ERROR};


  AcquireTable: PUBLIC PROC [id: P1.TableId] RETURNS [LONG POINTER] = {
    RETURN[IF id = $parse THEN tablesegptr ELSE ERROR]};
    
  ReleaseTable: PUBLIC PROC [id: P1.TableId] = {
    IF id = $parse THEN NULL ELSE ERROR};


 --
 
  CheckDefined: PROC[symbolseq: MDModel.SymbolSeq] = {
	
    Proc: PROC[sp: MDModel.Symbol, spmodel: MDModel.MODELSymbol] 
		RETURNS[proceed: BOOL ← TRUE] = {
      SELECT sp.stype FROM
	$typeTYPE, $typePROC, $typeAPPL =>
	  IF ~sp.defn THEN 
	    CWF.WF2["In %s: %s not defined.\n"L, spmodel.modelfilename, MDModel.Sym[sp]];
	$typeLET => RETURN[FALSE];
	ENDCASE => NULL};

    (symbolseq.toploc).TraverseTree[symbolseq, Proc]};

-- produces depseq where bcdfilename[i] does not end in "bcd"
-- parse the stream handle, 
-- it turns out we ignore IMPORTS and EXPORTS since those entries must appear
-- in the DIRECTORY clause
  ParseUnit: PUBLIC PROC[sh: Stream.Handle, depseq: Dir.DepSeq, sfn: LONG STRING] = {
    tok: Token ← $tokBAD;
    tokvalue: LONG STRING;
    str: LONG STRING;
    stemp: STRING ← [100];
    imp, isconfig, isdefns: BOOL ← FALSE;
    savename: STRING ← [100];
    formal: STRING ← [100];

    ScanInit[sh];
    WHILE tok ~= $tokBEGIN AND tok ~= $tokEOF DO
      SELECT tok FROM 
	$tokDIR => [tok, tokvalue] ← Direct[sh, depseq, sfn];
	$tokCONFIG => {
	  IF savename.length # 0 THEN 
	    depseq.moduleName ← depseq.CopyString[savename];
	  isconfig ← TRUE;
	  [tok,tokvalue] ← NextTok[sh]};
	$tokPROGRAM, $tokMONITOR => {
	  IF savename.length # 0 THEN 
	    depseq.moduleName ← depseq.CopyString[savename];
	  isconfig ← FALSE;
	  [tok,tokvalue] ← NextTok[sh]};
	$tokDEFINITIONS => {
	  IF savename.length # 0 THEN 
	    depseq.moduleName ← depseq.CopyString[savename];
	  isdefns ← TRUE;
	  [tok,tokvalue] ← NextTok[sh]};
	$tokIMPORTS, $tokEXPORTS => {
	  imp ← tok = $tokIMPORTS;
	  [tok, tokvalue] ← NextTok[sh];
	  WHILE tok = $tokID OR tok = $tokCOMMA DO
	    IF tok = $tokID THEN {
	      adeprecord: Dir.ADepRecord ← [relation: IF imp THEN $imports ELSE $exports];
	      str ← tokvalue;
	      Subr.strcpy[formal, str];
	      -- str: str
	      -- formal: str
	      IF peektok = $tokCOLON THEN {
		[tok, tokvalue] ← NextTok[sh];
		[tok, tokvalue] ← NextTok[sh];
		str ← tokvalue};
	      -- bcdFileName is the formal
	      -- moduleName  is the type name
	      adeprecord.bcdFileName ← depseq.CopyString[formal];
	      adeprecord.moduleName ← depseq.CopyString[str];
	      Dir.AddToDep[depseq, @adeprecord]};
	    [tok, tokvalue] ← NextTok[sh];
	    ENDLOOP};
	$tokID => {	-- this may be the module name
	  Subr.strcpy[savename, tokvalue];
	  [tok,tokvalue] ← NextTok[sh]};
	ENDCASE => [tok,tokvalue] ← NextTok[sh];
      ENDLOOP;
    depseq.isconfig ← isconfig;
    depseq.isdefns ← isdefns;
    -- currently do not need to parse the body of a config
    -- IF isconfig THEN tok ← ConfigBody[sh, sfn, depseq];
    RETURN};

-- Interface: FROM "FileName": TYPE USING [a,b,c],;
-- Interface: TYPE USING [a,b,c],;
-- Interface ,;
-- FileName: TYPE Interface
-- FileName: TYPE
  Direct: PROC [sh: Stream.Handle, depseq: Dir.DepSeq, sfn: LONG STRING]
	RETURNS[tok: Token, tokvalue: LONG STRING] = {

    CheckTok: PROC[tokshouldbe: Token] = {
      IF tok ~= tokshouldbe THEN {
	CWF.WF0["Token is "L]; PrintTok[tok];
	CWF.WF0[", should be "L]; PrintTok[tokshouldbe];
	CWF.WF1[", in file %s\n"L, sfn]}};

    [tok,tokvalue] ← NextTok[sh];
    WHILE tok ~= $tokPROGRAM AND tok ~= $tokEOF AND tok ~= $tokDEFINITIONS
    AND tok ~= $tokSEMI AND tok~= $tokCONFIG DO
      IF tok = $tokID THEN {
	adeprecord: Dir.ADepRecord  ← [relation: $directory];
	filename: LONG STRING ← tokvalue;
	interface: STRING ← [100];
	Subr.strcpy[interface, filename];
	[tok, tokvalue] ← NextTok[sh];
	IF tok = $tokCOLON THEN {
	  [tok, tokvalue] ← NextTok[sh];
	  SELECT tok FROM
	    $tokTYPE => {
	      IF peektok = $tokID THEN {
		stemp: STRING ← [40];
		[tok, tokvalue] ← NextTok[sh];
		filename ← tokvalue;
		Subr.strcpy[stemp, filename];
		Subr.strcpy[filename, interface];
		Subr.strcpy[interface, stemp]}};
	    $tokFROM => {
	      IF peektok = $tokSTRLIT THEN [tok, tokvalue] ← NextTok[sh];
	      Subr.strcpy[filename, interface]};
	    ENDCASE => CheckTok[$tokTYPE]};
	-- filename will not have ".bcd" at end
	adeprecord.bcdFileName ← depseq.CopyString[filename];
	adeprecord.moduleName ← depseq.CopyString[interface];
	Dir.AddToDep[depseq, @adeprecord];
	WHILE tok ~= $tokCOMMA AND tok ~= $tokSEMI AND tok ~= $tokEOF
	AND tok ~= $tokPROGRAM AND tok ~= $tokDEFINITIONS AND
	tok ~= $tokCONFIG DO
	  IF tok = $tokLB THEN {
	    WHILE tok ~= $tokEOF AND tok ~= $tokRB DO
	      [tok,tokvalue] ← NextTok[sh];
	      ENDLOOP;
	    CheckTok[$tokRB]};
	  [tok,tokvalue] ← NextTok[sh];
	  ENDLOOP}
      ELSE [tok,tokvalue] ← NextTok[sh];
      ENDLOOP;
    RETURN};

 -- initiallizes the various data structures
  ScanInit: PROC [st: Stream.Handle]  = {
    IF ~init THEN Init[];
    peektok ← $tokBAD;
    peekvalue ← NIL;
    nextchar ← '\n;
    [] ← NextTok[st]};

 -- to free this memory, simply call StopScanner
  Init: PROC = {
    longzone: UNCOUNTED ZONE = Subr.LongZone[];
    init ← TRUE;
    savestr ← Subr.AllocateString[200];
    toksave ← Subr.AllocateString[200]};

-- frees memory as needed, call only once
  StopScanner: PUBLIC PROC = {
    longzone: UNCOUNTED ZONE = Subr.LongZone[];
    IF ~init THEN RETURN;	-- Init never called
    Subr.FreeString[toksave];
    Subr.FreeString[savestr];
    savestr ← toksave ← NIL;
    init ← FALSE};


 -- NOTE: this checks the type of Token in case the string literal must be saved
  NextTok: PROC [st: Stream.Handle] RETURNS[tok: Token, tokvalue: LONG STRING] = {
    tok ← peektok;
    IF tok = $tokID OR tok = $tokSTRLIT THEN {
      Subr.strcpy[toksave,peekvalue];
      tokvalue ← toksave}
    ELSE tokvalue ← peekvalue;
    [peektok,peekvalue] ← ReadTok[st];
    RETURN};

  ReadTok: PROC [st: Stream.Handle] RETURNS[toktype: Token, tokval: LONG STRING] = {
    i: CARDINAL;
    lastchar: CHAR;
    DO
      WHILE nextchar = '  OR nextchar = '\n OR nextchar = '\t OR nextchar = '\f DO
	nextchar ← Subr.GetChar[st]
	ENDLOOP;
      IF IsAlpha[nextchar] THEN {
	i ← 0;
	WHILE (IsAlpha[nextchar]  OR IsDigit[nextchar]) AND i <= savestr.maxlength DO
	  savestr[i] ← nextchar;
	  i ← i + 1;
	  nextchar ← Subr.GetChar[st];
	  ENDLOOP;
	savestr.length ← i;
	toktype ← KeywordLookup[savestr];
	RETURN (IF toktype ~= $tokBAD THEN [toktype,NIL] ELSE [$tokID,savestr])}
      ELSE IF IsDigit[nextchar] THEN {
	i ← 0;
	WHILE IsDigit[nextchar] AND i <= savestr.maxlength DO
	  savestr[i] ← nextchar;
	  i ← i+1;
	  nextchar ← Subr.GetChar[st];
	  ENDLOOP;
	savestr.length ← i;
	RETURN[$tokNUM,savestr]}
      ELSE {
	lastchar ← nextchar;
	nextchar ← Subr.GetChar[st];
	SELECT lastchar FROM
	  '[ => toktype ← $tokLB;
	  '] => toktype ← $tokRB;
	  '{ => toktype ← $tokBEGIN;
	  '} => toktype ← $tokEND;
	  '. => toktype ← $tokDOT;
	  ': => toktype ← $tokCOLON;
	  ', => toktype ← $tokCOMMA;
	  '; => toktype ← $tokSEMI;
	  '~ => toktype ← $tokTWIDDLE;
	  '\000 => toktype ← $tokEOF;	-- Tioga
	  '= => toktype ← $tokEQ;
	  '- => {
	    IF nextchar ~= '- THEN CWF.WF0["bad comment\n"L];
	    nextchar ← Subr.GetChar[st];
	    WHILE nextchar ~= '\n AND nextchar ~= '\000 DO
	      IF nextchar = '- THEN {
	        nextchar ← Subr.GetChar[st];
	        IF nextchar = '- THEN EXIT};
	      nextchar ← Subr.GetChar[st];
	      ENDLOOP;
	    nextchar ← Subr.GetChar[st];
	    LOOP};
	  '" => {
	    i ← 0;
	    WHILE i < savestr.maxlength DO
	      savestr[i] ← nextchar;
	      nextchar ← Subr.GetChar[st];
	      i ← i + 1;
	      IF nextchar = '" THEN {
		nextchar ← Subr.GetChar[st];
		IF nextchar = '" THEN LOOP;
		EXIT};
	      REPEAT
		FINISHED => CWF.WF0["String literal too long\n"L];
	      ENDLOOP;
	    savestr.length ← i;
	    RETURN[$tokSTRLIT, savestr]};
	  ENDCASE => {
	    i: INTEGER ← lastchar.ORD;
	    CWF.WF1["unknown char %c\n"L,@i];
	    toktype ← $tokEOF};
        RETURN[toktype,NIL]};
      ENDLOOP};

  KeywordLookup: PROC[str: LONG STRING] RETURNS[tok: Token] = {
    -- return the tok if found, return $tokBAD if error
    OPEN LongString;
    SELECT str.length FROM
      3 =>
	IF EqualString[str,"END"L] THEN RETURN[$tokEND];
      4 => {
	IF EqualString[str,"FROM"L] THEN RETURN[$tokFROM];
	IF EqualString[str,"TYPE"L] THEN RETURN[$tokTYPE]};
      5 => {
	IF EqualString[str,"BEGIN"L] THEN RETURN[$tokBEGIN];
	IF EqualString[str,"CEDAR"L] THEN RETURN[$tokCEDAR];
	IF EqualString[str,"USING"L] THEN RETURN[$tokUSING]};
      6 =>
	IF EqualString[str,"SHARES"L] THEN RETURN[$tokSHARES];
      7 => {
	IF EqualString[str,"EXPORTS"L] THEN RETURN[$tokEXPORTS];
	IF EqualString[str,"IMPORTS"L] THEN RETURN[$tokIMPORTS];
	IF EqualString[str,"MONITOR"L] THEN RETURN[$tokMONITOR];
	IF EqualString[str,"PROGRAM"L] THEN RETURN[$tokPROGRAM];
	IF EqualString[str,"RETURNS"L] THEN RETURN[$tokRETURNS]};
      9 => 
	IF EqualString[str,"DIRECTORY"L] THEN RETURN[$tokDIR];
      11 => 
	IF EqualString[str,"DEFINITIONS"L] THEN RETURN[$tokDEFINITIONS];
      13 => 
	IF EqualString[str,"CONFIGURATION"L] THEN RETURN[$tokCONFIG];
      ENDCASE => NULL;
    RETURN[$tokBAD]};


  PrintTok: PROC [tok: Token] = {
    TokString: ARRAY Token OF STRING = [
      "ErrorToken"L, "EndOfFile"L, "["L, "]"L, "."L, ":"L, ","L,
      "="L, "~"L, ";"L, "Identifier"L, "Number"L, "TYPE"L, "RETURNS"L,
      "StringLiteral"L, "FROM"L, "DIRECTORY"L,"IMPORTS"L, "EXPORTS"L, "PROGRAM"L,
      "{"L, "DEFINITIONS"L, "CONFIGURATION", "END"L, "USING"L, "SHARES"L,
      "MONITOR"L, "CEDAR"L];
    CWF.WF0[TokString[tok]]};

  IsDigit: PROC[c: CHAR] RETURNS[BOOL] = INLINE {
    RETURN[c IN ['0 .. '9]]};

  IsAlpha: PROC[c: CHAR] RETURNS[BOOL] = INLINE {
    RETURN[c IN ['a .. 'z] OR c IN ['A .. 'Z]]};

 -- START code
  tablesegptr ← Runtime.GetTableBase[LOOPHOLE[ModelParseData]];
  }.