-- SMIntImpl.mesa
-- last edit by Schmidt, May 27, 1983 7:12 pm
-- last edit by Satterthwaite, August 12, 1983 9:54 am

DIRECTORY
  Buttons: TYPE USING [Button, ButtonProc],
  Containers: TYPE USING [ChildXBound, ChildYBound, Container, Create],
  CS: TYPE USING [CardFromRope, EndsIn, RopeFromCard, SetPFCodes],
  Directory: TYPE USING [DeleteFile, Error, Rename],
  FileIO: TYPE USING [Open, OpenFailed],
  IO: TYPE USING [
    card, Close, PutF, PutFR, ResetUserAbort, RIS, rope, SetUserAbort,
    STREAM, time, UserAborted],
  Labels: TYPE USING [Create, Label, Set, SetDisplayStyle], 
  List: TYPE USING [DRemove],
  MBQueue: TYPE USING [Create, CreateMenuEntry, CreateButton, Flush, Queue],
  Menus: TYPE USING [CreateEntry, CreateMenu, InsertMenuEntry, Menu, MenuProc], 
  Rope: TYPE USING [Cat, Equal, Flatten, IsEmpty, ROPE, Text],
  Rules: TYPE USING [Create, Rule],
  SMBcd: TYPE USING[WriteModelBcd],
  SMComp: TYPE USING [CompileAll, LoadCompiler],
  SMDF: TYPE USING [WriteDFFile],
  SMEval: TYPE USING [Eval, UnitToRope],
  SMFI: TYPE USING [SrcFileInfo],
  SMFIOps: TYPE USING [Ambiguous, --Flush,-- NewestSource],
  SMLDriver: TYPE USING [LoadAndBind, Loaded, StartAll, Started, Unload],
  SMOps: TYPE USING [MS, NewModel],
  SMUtil: TYPE USING [ParseStream, PrettyPrint, PrintTree],
  SMTree: TYPE Tree USING [Handle, Link, null],
  SMTreeOps: TYPE --TreeOps-- USING [
    Initialize, Finalize, NthSon, OpName, PutExt, PutNthSon, Scan, ScanSons],
  TypeScript: TYPE USING [TS, Create],
  ViewerClasses: TYPE USING [Viewer], 
  ViewerEvents: TYPE USING [
    EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
  ViewerIO: TYPE USING [CreateViewerStreams],
  ViewerOps: TYPE USING [
    AddProp, EstablishViewerPosition, FetchProp, PaintViewer, SetMenu, SetOpenHeight], 
  ViewerTools: TYPE USING [
    GetContents, GetSelectionContents, MakeNewTextViewer, SetSelection];

SMIntImpl: CEDAR PROGRAM 
    IMPORTS
      Containers, CS, Directory, FileIO, MBQueue, IO, Labels, List, Menus, Rope, Rules,
      SMBcd, SMComp, SMDF, SMEval, SMFIOps, SMLDriver, SMOps, SMUtil,
      SMTreeOps, TypeScript, ViewerEvents, ViewerOps, ViewerIO, ViewerTools ~ {
  OPEN Tree~~SMTree, TreeOps~~SMTreeOps;

 -- modeller state
 
  ModelState: TYPE~{	-- ordered
    idle, unparsed, parsed, evaluated, compiled, loaded, run};
    
 -- global data
  Global: TYPE ~ REF GlobalRecord;
  GlobalRecord: TYPE ~ RECORD[
   -- viewers data
    container: Containers.Container←NIL, 
    ttyin: IO.STREAM←NIL,
    ttyout: IO.STREAM←NIL,
    msgout: IO.STREAM←NIL,
   -- fields
    startModellingFileNameButton: Buttons.Button←NIL,
    startModellingFileNameViewer: ViewerClasses.Viewer←NIL,
    confirmButton: Buttons.Button←NIL,
    confirmViewer: ViewerClasses.Viewer←NIL,
    attachEditorButton: Buttons.Button←NIL,
    attachEditorLabel: ViewerClasses.Viewer←NIL,
   -- modelling state
    state: ModelState←$idle,
    stateLabel: Labels.Label←NIL,
   -- other objects
    q: MBQueue.Queue←NIL,
    noticeList: LIST OF Rope.Text←NIL,	-- files that have been noticed
    confirmCompiles: REF BOOL,
    attachEditor: BOOL←TRUE,
    attachEditorRef: REF ANY←NIL,
    model: SMOps.MS←NIL,
    modelFileName: Rope.Text←NIL,
    modelUpdated: BOOL←FALSE,
    debugLevel: NAT←NAT.LAST  -- >= 1: parse tree, >= 2: value tree, >= 3: pp value
    ];
	
 -- MDS usage
  globalList: LIST OF Global ← NIL;	-- not properly monitored
  destroyEventRegistration: ViewerEvents.EventRegistration;
 -- end of MDS usage

 -- these are commands for the viewers world

  entryHeight: CARDINAL ~ 15; 
  entryVSpace: CARDINAL ~ 7; 
  entryHSpace: CARDINAL ~ 10; 
  
  Create: PROC RETURNS[g: Global] ~ {
    ttyTypeScript, msgTypeScript: TypeScript.TS;
    vName: Rope.ROPE ~ IO.PutFR["Cedar Modeller, started on %t", IO.time[]];
    menu: Menus.Menu ~ Menus.CreateMenu[lines~3];

    MenuItem: PROC[name: Rope.ROPE, proc: Menus.MenuProc, line: NAT] ~ {
      menu.InsertMenuEntry[(g.q).CreateMenuEntry[name, proc, g], line]};
      
    g ← NEW[GlobalRecord ← [
    	confirmCompiles~NEW[BOOL←FALSE],
    	container~Containers.Create[info~[name~vName, iconic~FALSE, scrollable~FALSE]],
        q~MBQueue.Create[]]];
    ViewerOps.AddProp[g.container, $SMGlobalRef, g];
   -- first row of menu items
    MenuItem["StopModel", StopModel, 0]; 
    MenuItem["Continue", Continue, 0]; 
    MenuItem["Begin", Begin, 0]; 
    MenuItem["NoticeAll", NoticeAll, 0]; 
    MenuItem["StartModel", StartModel, 0]; 
   -- second row of menu items
    MenuItem["NewModeller", NewModeller, 1]; 
    MenuItem["Bind", Bind, 1]; 
    MenuItem["MakeDFFile", MakeDFFile, 1]; 
    MenuItem["MakeModelBcd", MakeModelBcd, 1]; 
   -- third row of menu items
    MenuItem["Debug", Debug, 2]; 
    menu.InsertMenuEntry[Menus.CreateEntry["Abort", Abort, g], 2]; 
    MenuItem["Unload", Unload, 2]; 
    MenuItem["Start", Start, 2]; 
    MenuItem["Load", Load, 2]; 
    MenuItem["Compile", Compile, 2]; 
    MenuItem["Check", Check, 2];
   --
    ViewerOps.SetMenu[g.container, menu, FALSE];
    [ttyTypeScript, msgTypeScript] ← BuildUserInput[g];
   -- kludge required for multiple rows in menus
    ViewerOps.EstablishViewerPosition[
      g.container, g.container.wx, g.container.wy, g.container.ww, g.container.wh];
    ViewerOps.PaintViewer[g.container, $all];
    [in~g.ttyin, out~g.ttyout] ← ViewerIO.CreateViewerStreams[viewer~ttyTypeScript, name~NIL];
    g.msgout ← ViewerIO.CreateViewerStreams[viewer~msgTypeScript, name~NIL].out;
    CS.SetPFCodes[g.ttyout];  CS.SetPFCodes[g.msgout];
    IF g.attachEditor THEN AttachSymbiote[g];
    globalList ← CONS[g, globalList]};

  BuildUserInput: PROC[g: Global] RETURNS[ttyTypeScript, msgTypeScript: TypeScript.TS] ~ {
    heightSoFar: CARDINAL ← 0; 
    l: ViewerClasses.Viewer ← NIL;
    rule: Rules.Rule;
   
    CreateButton: PROC[bname, lname: Rope.Text, newLine: BOOL, drawRule: BOOL←FALSE] 
	RETURNS[button: Buttons.Button, label: Labels.Label] ~ {
      x: CARDINAL;
      IF newLine THEN {
	heightSoFar ← heightSoFar + entryVSpace/2;
	IF drawRule THEN {
	  rule ← Rules.Create[
	      info~[parent~g.container, wx~0, wy~heightSoFar, ww~0, wh~1]];
	  Containers.ChildXBound[g.container, rule];
	  heightSoFar ← heightSoFar + entryVSpace};
	x ← 0}
      ELSE x ← l.wx + l.ww + entryHSpace;
      l ← button ← MBQueue.CreateButton[
		q~g.q,
		info~[name~bname, parent~g.container, border~FALSE, wx~x, wy~heightSoFar],
		proc~PushButton,
		clientData~g];
      IF lname ~= NIL THEN
	l ← label ← Labels.Create[info~[
		name~lname, parent~g.container,
		wx~button.wx+button.ww+entryHSpace, wy~heightSoFar, border~TRUE]];
      };

   -- first line
    [g.startModellingFileNameButton, ] ← CreateButton["ModelName:", NIL, TRUE];
    l ← g.startModellingFileNameViewer ← ViewerTools.MakeNewTextViewer[
	info~[
	    parent~g.container,
	    wx~l.wx+l.ww+entryHSpace, wy~heightSoFar, ww~100, wh~entryHeight, 
	    data~NIL, scrollable~FALSE, border~FALSE],
	paint~FALSE];
    Containers.ChildXBound[g.container, g.startModellingFileNameViewer];
    heightSoFar ← heightSoFar + l.wh + entryVSpace/2;
   -- second line
    heightSoFar ← heightSoFar + entryVSpace/2;
    l ← Labels.Create[info~[
	name~"State: ", parent~g.container, wx~0, wy~heightSoFar, border~FALSE]];
    l ← g.stateLabel ← Labels.Create[info~[
	name~"wwwwww",
	parent~g.container, wx~l.wx+l.ww+entryHSpace, wy~heightSoFar, border~FALSE]];
    SetState[g, $idle];
    heightSoFar ← heightSoFar + l.wh + entryVSpace/2;
   -- third line
    [g.confirmButton, g.confirmViewer] ← CreateButton["ConfirmCompiles:", "FALSE", TRUE];
    IF g.confirmCompiles↑ THEN Labels.Set[g.confirmViewer, "TRUE"];
    [g.attachEditorButton, g.attachEditorLabel] ← CreateButton["AttachEditor:", "FALSE", FALSE];
    IF g.attachEditor THEN Labels.Set[g.attachEditorLabel, "TRUE"];
    heightSoFar ← heightSoFar + entryVSpace/2+l.wh;
   --
   -- first the msg window
   --  now the line above the typescript
    rule ← Rules.Create[info: [parent~g.container, wx~0, wy~heightSoFar, ww~0, wh~1]];
    Containers.ChildXBound[g.container, rule];
    heightSoFar ← heightSoFar + entryVSpace/2;
   -- now the typescript
    msgTypeScript ← TypeScript.Create[
	info~[parent~g.container, wx~0, wy~heightSoFar, ww~0, wh~25, border~FALSE]];
    Containers.ChildXBound[g.container, msgTypeScript];
    heightSoFar ← heightSoFar + entryVSpace + 20;
   --  now the line above the typescript
    rule ← Rules.Create[info~[parent~g.container, wx~0, wy~heightSoFar, ww~0, wh~1]];
    Containers.ChildXBound[g.container, rule];
    heightSoFar ← heightSoFar + entryVSpace/2;
   -- now the typescript
    ttyTypeScript ← TypeScript.Create[
	info~[parent~g.container, wx~0, wy~heightSoFar, ww~0, wh~80, border~FALSE]];
    heightSoFar ← heightSoFar + entryVSpace + 80;
    Containers.ChildXBound[g.container, ttyTypeScript];
    Containers.ChildYBound[g.container, ttyTypeScript];
    ViewerOps.SetOpenHeight[g.container, heightSoFar + 200]};

  SetState: PROC[g: Global, state: ModelState] ~ {
    Labels.SetDisplayStyle[g.stateLabel, $BlackOnWhite];
    Labels.Set[g. stateLabel, SELECT state FROM
      $idle => "idle",
      $unparsed => "unparsed",
      $parsed => "parsed",
      $evaluated => "checked",
      $compiled => "compiled",
      $loaded => "loaded",
      $run => "started",
      ENDCASE => "ERROR"];
    g.state ← state};
    
  PushButton: Buttons.ButtonProc ~ {
    g: Global ~ NARROW[clientData];
    SELECT NARROW[parent, ViewerClasses.Viewer] FROM
      g.startModellingFileNameButton =>
	ViewerTools.SetSelection[g.startModellingFileNameViewer, NIL];
      g.confirmButton => {
	g.confirmCompiles↑ ← ~g.confirmCompiles↑;
	Labels.Set[g.confirmViewer, IF g.confirmCompiles↑ THEN "TRUE" ELSE "FALSE"]};
      g.attachEditorButton => {
	g.attachEditor ← ~g.attachEditor;
	Labels.Set[g.attachEditorLabel, IF g.attachEditor THEN "TRUE" ELSE "FALSE"];
	IF g.attachEditor THEN AttachSymbiote[g] ELSE DetachSymbiote[g, TRUE]};
      ENDCASE => ERROR;
    };
	
 -- BUTTON PROCS

 -- not on the queue
  Abort: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
    MBQueue.Flush[g.q];
    g.ttyin.SetUserAbort[]};
	
  Begin: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      InternalUnload[g, TRUE];
      IF g.state = $idle THEN InternalStartModel[g, TRUE];	-- auto StartModel
      ClearExtensions[g.model.tree];
      InternalCheck[g];
      InternalCompile[g, FALSE];
      InternalLoad[g, FALSE];
      InternalStart[g];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Begin aborted\n"]};
      };
    SetState[g, g.state]};

  Bind: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
    g.ttyout.PutF["Bind not implemented yet.\n"];
    g.ttyout.PutF["-------------\n"]};

  Check: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      IF g.state = $idle THEN InternalStartModel[g, TRUE];
      InternalCheck[g];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Check aborted\n"]};
      };
    SetState[g, g.state]};

  Compile: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      IF g.state = $idle THEN InternalStartModel[g, TRUE];
      InternalCheck[g];
      InternalCompile[g, FALSE];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Compilation aborted\n"]};
      };
    SetState[g, g.state]};

  Continue: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      IF g.state = $idle THEN InternalStartModel[g, TRUE];	-- auto StartModel
      InternalCheck[g];
      InternalCompile[g, TRUE];
      InternalLoad[g, TRUE];
      InternalStart[g];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Continue aborted\n"]};
      };
    SetState[g, g.state]};

  Debug: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
    g.ttyout.PutF["-------------\n"];
    IF g.state = $idle THEN {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      g.model ← SMOps.NewModel[g.ttyin, g.ttyout, g.ttyout];
      (g.model.tm).Initialize;
      g.model.tree ← SMUtil.ParseStream[g.model, IO.RIS[ViewerTools.GetSelectionContents[]]];
      IF g.model.tree # Tree.null THEN {
        IF g.debugLevel <= 1 THEN SMUtil.PrintTree[g.model, g.model.tree];
        SMUtil.PrettyPrint[g.model.out, g.model.tree, g.model.comments]};
      IF g.model.tree # Tree.null THEN {
        g.model.val ← SMEval.Eval[g.model, g.model.tree, NIL];
        g.model.out.PutF["\n\n"];
        SMUtil.PrintTree[g.model, g.model.val];
        SMUtil.PrettyPrint[g.model.out, g.model.val, NIL];
        g.model.val ← NIL};
      g.model ← NIL;
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Debug aborted\n"]};
      };
    g.ttyout.PutF["-------------\n"]};

  Load: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      InternalLoad[g, FALSE];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Load aborted\n"]};
      };
    SetState[g, g.state]};

  MakeDFFile: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      IF g.state >= $evaluated THEN {
        InternalTemporary[g];
        SMDF.WriteDFFile[g.model, g.model.val, g.modelFileName, g.modelFileName]};
      EXITS
        out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["MakeDFFile aborted\n"]};
      };
    };

  MakeModelBcd: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
    {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      IF g.state >= $evaluated THEN {
	InternalTemporary[g];
	SMBcd.WriteModelBcd[g.model, g.model.val, g.modelFileName, g.modelFileName]};
      EXITS
        out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["MakeModelBcd aborted\n"]};
      };
    };

  NewModeller: Menus.MenuProc ~ {
    [] ← Create[]};
	
  NoticeAll: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      InternalNoticeAll[g];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["NoticeAll aborted\n"]};
      };
    };

  StartModel: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      InternalStartModel[g];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["StartModelling aborted\n"]};
      };
    SetState[g, g.state]};

  Start: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      InternalStart[g];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Start aborted\n"]};
      };
    SetState[g, g.state]};

  StopModel: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      InternalStopModel[g];
      EXITS
	out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["StopModelling aborted\n"]};
      };
    SetState[g, $idle]};

  Unload: Menus.MenuProc ~ {
    g: Global ~ NARROW[clientData];
      {
      ENABLE ABORTED, IO.UserAborted => {GOTO out};
      InternalUnload[g, TRUE];
      EXITS
        out => {g.ttyin.ResetUserAbort[]; g.ttyout.PutF["Unload aborted\n"]};
      };
    SetState[g, g.state]};

 
 -- SUPPORT ROUTINES

  AttachSymbiote: PROC[g: Global] ~ {
    IF g.attachEditorRef = NIL THEN 
      g.attachEditorRef ← ViewerEvents.RegisterEventProc[SaveEvent, $save];
    g.msgout.PutF["Editor set to call this modeller.\n"]};

  DetachSymbiote: PROC[g: Global, print: BOOL] ~ {
    IF g.attachEditorRef ~= NIL THEN
      ViewerEvents.UnRegisterEventProc[g.attachEditorRef, $save];
    g.attachEditorRef ← NIL;
    IF print THEN g.msgout.PutF["Editor detached from this modeller.\n"]};

-- this is the procedure called by the editor
-- can't print anything in this procedure
  SaveEvent: ViewerEvents.EventProc ~ {
    ENABLE ANY => {GOTO out};
    IF viewer.file # NIL THEN {
      flat: Rope.Text ~ viewer.file.Flatten[];
      IF CS.EndsIn[flat, ".mesa"] THEN	-- only source now
	FOR l: LIST OF Global ← globalList, l.rest UNTIL l = NIL DO
	  l.first.noticeList ← CONS[flat, l.first.noticeList];
	  ENDLOOP;
      }
    EXITS
      out => NULL;
    };

  DestroyEvent: ViewerEvents.EventProc ~ {
    IF event = $destroy THEN {
      g: Global ~ NARROW[ViewerOps.FetchProp[viewer, $SMGlobalRef]];
      IF g ~= NIL THEN DetachSymbiote[g, FALSE];
      IF globalList = NIL THEN {
	ViewerEvents.UnRegisterEventProc[destroyEventRegistration, $destroy];
	destroyEventRegistration ← NIL;
	RETURN};
      FOR l: LIST OF Global ← globalList, l.rest UNTIL l = NIL DO
	IF l.first.container = viewer THEN TRUSTED {
	  globalList ← LOOPHOLE[List.DRemove[ref~l.first, list~LOOPHOLE[globalList]]];
	  RETURN};
	ENDLOOP;
      };
    };


  InternalStartModel: PROC[g: Global, autoNotice: BOOL←FALSE] ~ {
    modelFileName: Rope.Text;
    input: IO.STREAM ← NIL;
    IF g.state ~= $idle THEN InternalStopModel[g];
    -- now set the contents
    modelFileName ← ViewerTools.GetContents[g.startModellingFileNameViewer].Flatten[];
    IF modelFileName.IsEmpty THEN {
      g.ttyout.PutF["Error - no model source input file\n"];
      GOTO failed};
    IF ~CS.EndsIn[modelFileName, ".model"]
      THEN modelFileName ← modelFileName.Cat[".model"].Flatten[];
    input ← FileIO.Open[modelFileName
      ! FileIO.OpenFailed => {
        g.ttyout.PutF["Error - file %s could not be opened\n", IO.rope[modelFileName]];
        GOTO failed}];
    g.model ← SMOps.NewModel[g.ttyin, g.ttyout, g.msgout];
    (g.model.tm).Initialize;
    g.modelFileName ← modelFileName;
    g.modelUpdated ← FALSE;  SetState[g, $unparsed];
    Labels.SetDisplayStyle[g.stateLabel, $BlackOnGrey];
    g.model.tree ← SMUtil.ParseStream[m~g.model, source~input];
    Labels.SetDisplayStyle[g.stateLabel, $BlackOnWhite];
    IF g.model.tree # Tree.null THEN  {
      SetState[g, $parsed];
      IF autoNotice THEN InternalNoticeAll[g];
      IF g.debugLevel <= 1 THEN {
	SMUtil.PrintTree[g.model, g.model.tree];
	SMUtil.PrettyPrint[g.model.out, g.model.tree, g.model.comments]};
      };
    EXITS
      failed => NULL
    };		-- file remains open
	
  InternalNoticeAll: PROC[g: Global] ~ {
    nChanged: CARDINAL ← 0;
	
      LookForSource: TreeOps.Scan ~ {
	WITH t SELECT FROM
	  node: Tree.Handle =>
	    IF TreeOps.OpName[node] = $unitId THEN {
	      fileName: Rope.Text ~ LocalName[node];
	      IF CS.EndsIn[fileName, ".mesa"] AND NoticeSource[g, node, fileName, FALSE] THEN
	        nChanged ← nChanged + 1
	      ELSE IF CS.EndsIn[fileName, ".model"]
	       AND NoticeSource[g, node, fileName, FALSE] THEN {
	        nChanged ← nChanged + 1;
	        TreeOps.PutExt[node, Tree.null]}	-- force reparsing of embedded model
	      }
	    ELSE TreeOps.ScanSons[node, LookForSource];
	  ENDCASE => NULL;
	};

    IF g.state >= $parsed THEN {
      LookForSource[g.model.tree];  g.noticeList ← NIL};
    g.ttyout.PutF["%d files noticed.\n\n", IO.card[nChanged]];
    IF nChanged > 0 THEN {
      g.modelUpdated ← TRUE;
      SetState[g, MIN[g.state, $parsed]];  g.model.val ← NIL};	-- force reevaluation
    };

  InternalCheck: PROC[g: Global] ~ {
    [] ← RecordNoticedFiles[g];
    IF g.state = $parsed THEN {	-- must (re)evaluate
      g.model.errors ← FALSE;	-- set by evaluation errors
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnGrey];
      g.model.val ← SMEval.Eval[g.model, g.model.tree, NIL];
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnWhite];
      IF g.debugLevel <= 2 THEN {
        SMUtil.PrintTree[g.model, g.model.val]; (g.model.out).PutF["\n"]};
      IF g.debugLevel <= 3 THEN SMUtil.PrettyPrint[g.model.out, g.model.val, NIL];
      IF ~g.model.errors THEN SetState[g, $evaluated]};
    };
    
  InternalCompile: PROC[g: Global, replacement: BOOL] ~ {
    IF g.state = $evaluated THEN {
      InternalTemporary[g];
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnGrey];
      IF SMComp.CompileAll[g.model, g.model.val, g.confirmCompiles, replacement].complete THEN
        SetState[g, $compiled];
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnWhite]};
    };
	
  InternalLoad: PROC[g: Global, replacement: BOOL] ~ {
    IF g.state = $compiled THEN {
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnGrey];
      IF ~(g.model.ls).LoadAndBind[g.model.val, replacement].errors THEN SetState[g, $loaded];
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnWhite]};
    };
	
  InternalStart: PROC[g: Global] ~ {
    IF g.state = $loaded THEN {
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnGrey];
      IF ~(g.model.ls).Started THEN (g.model.ls).StartAll[g.model.val];
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnWhite];
      SetState[g, $run]};
    };
	
  InternalTemporary: PROC[g: Global] ~ {
    IF g.modelUpdated THEN TRUSTED {
      sh: IO.STREAM;
      oldName: Rope.Text ~ g.modelFileName.Cat["$"].Flatten[];
      g.ttyout.PutF["Old model on %s, ", IO.rope[oldName]];
      Directory.DeleteFile[fileName: LOOPHOLE[oldName]
			! Directory.Error => {CONTINUE}];
      Directory.Rename[
	oldName~LOOPHOLE[g.modelFileName], newName~LOOPHOLE[oldName]];
      sh ← FileIO.Open[g.modelFileName, $overwrite];
      SMUtil.PrettyPrint[sh, g.model.tree, g.model.comments];
      sh.Close[];
      g.ttyout.PutF["new model on %s\n\n", IO.rope[g.modelFileName]];
      g.modelUpdated ← FALSE};
    };
	
  InternalStopModel: PROC[g: Global] ~ {
    IF g.state # $idle THEN {
      InternalTemporary[g];
      InternalUnload[g, FALSE];
      (g.model.tm).Finalize;
      g.model.val ← NIL;  g.model ← NIL};
    --SMFIOps.Flush[];  SMProj.Flush[];--
    SetState[g, $idle]};

  InternalUnload: PROC[g: Global, unloadBcd: BOOL] ~ {
    IF g.model # NIL AND (g.model.ls).Loaded THEN {
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnGrey];
      (g.model.ls).Unload[g.model.val, unloadBcd];
      Labels.SetDisplayStyle[g.stateLabel, $BlackOnWhite];
      SetState[g, MIN[g.state, $compiled]]};
    };
	

 -- only does this for the parse tree
  ClearExtensions: PROC[parseTree: Tree.Link] ~ {

    ANode: TreeOps.Scan ~ TRUSTED {
      WITH t SELECT FROM
	node: Tree.Handle => {
	  IF TreeOps.OpName[node] ~= $none THEN TreeOps.PutExt[node, NIL];
	  TreeOps.ScanSons[node, ANode]};
	ENDCASE => NULL
      };
		
    ANode[parseTree]};


  LocalName: PROC [uid: Tree.Link] RETURNS[Rope.Text] ~ {
    RETURN [SMEval.UnitToRope[TreeOps.NthSon[uid, 3]].Flatten[]]};
    
  NoticeSource: PROC[g: Global, unitId: Tree.Link, fileName: Rope.Text, new: BOOL]
      RETURNS[changed: BOOL] ~ {
    fiSrc: SMFI.SrcFileInfo ~ SMFIOps.NewestSource[fileName];
    version: Rope.Text ~ NARROW[TreeOps.NthSon[unitId, 4]];
    create: LONG CARDINAL ~
	(IF SMFIOps.Ambiguous[version] THEN 0 ELSE CS.CardFromRope[version]);
    changed ← (fiSrc.create # 0 AND fiSrc.create # create);
    IF changed THEN {
      g.ttyout.PutF["Notice %s\n", IO.rope[fileName]];
      IF new THEN fiSrc.new ← TRUE;
      TreeOps.PutNthSon[unitId, 4, CS.RopeFromCard[fiSrc.create].Flatten[]]};
    RETURN};

  RecordNoticedFiles: PROC[g: Global] RETURNS[noticedFile: BOOL ← FALSE] ~ {
	
    LookForSource: TreeOps.Scan ~ {
      WITH t SELECT FROM
	node: Tree.Handle =>
	  IF TreeOps.OpName[node] = $unitId THEN {
	    fileName: Rope.Text ~ LocalName[node];
	    FOR l: LIST OF Rope.Text ← g.noticeList, l.rest UNTIL l = NIL DO
	      IF fileName.Equal[l.first, FALSE]
	       AND NoticeSource[g, node, fileName, TRUE] THEN {
	        noticedFile ← TRUE; EXIT}	-- new file
	      ENDLOOP;
	    }
	  ELSE TreeOps.ScanSons[node, LookForSource];
	ENDCASE => NULL;
      };

    IF g.noticeList # NIL AND g.state >= $parsed THEN {
      LookForSource[g.model.tree];  g.noticeList ← NIL};
    IF noticedFile THEN {
      g.modelUpdated ← TRUE;
      SetState[g, MIN[g.state, $parsed]];  g.model.val ← NIL};	-- force reevaluation
    };
	

  {
  g: Global;
  destroyEventRegistration ← ViewerEvents.RegisterEventProc[DestroyEvent, $destroy];
  g ← Create[];
  [] ← SMComp.LoadCompiler[g.msgout];
  };

  }.