-- Copyright (C) 1982, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- CommandsAndSuch.mesa, HGM, 20-Apr-85 16:35:08
-- From Othello's VolumeInitCommandImpl of 30-Sep-82 13:48:35.

DIRECTORY
  Ascii USING [CR],
  Boot USING [mdsiGerm],
  Environment USING [bytesPerWord, Long],
  Format USING [HostNumber, StringProc],
  Frame USING [Free, ReadLocalWord],
  Heap USING [systemZone],
  Inline USING [LowHalf],
  MStoreImpl USING [countFree],
  OthelloDefs USING [
    BlinkDisplay, CommandProcessor, ConfirmType, EchoNoEcho,
    NewLine, ReadChar,
    WriteChar, WriteLine, WriteLongNumber, WriteOctal, WriteString],
  OthelloOps USING [
    BootFileType, GetTimeFromTimeServer, IsTimeValid,
    SetProcessorTime, TimeServerError],
  OthelloForgot USING [
    EraseTTYChar, ResetAbort, SmashPassword, WaitForConnection, WriteLongOctal],
  PilotMP USING [cTimeNotAvailable],
  PrincOps USING [frameSizeMap, LocalFrameHandle, LocalOverhead],
  ProcessorFace USING [SetMP, mp],
  Process USING [Detach, Pause, SecondsToTicks],
  Runtime USING [GetBuildTime],
  SDDefs USING [SD, sSystemDate],
  SpecialRuntime USING [GetCurrentSignal],
  SpecialSpace USING [realMemorySize],
  String USING [
    AppendChar, AppendCharAndGrow, AppendDecimal, AppendLongDecimal,
    AppendLongNumber, AppendString, EquivalentSubString, InvalidNumber,
    StringBoundsFault, StringToNumber, SubStringDescriptor, UpperCase],
  System USING [
    AdjustGreenwichMeanTime, GetGreenwichMeanTime, GetLocalTimeParameters, GreenwichMeanTime,
    gmtEpoch, HostNumber, localHostNumber, LocalTimeParameters, SetLocalTimeParameters],
  Time USING [Append, defaultTime, Invalid, Pack, Unpack, Unpacked],
  Version USING [Append],
  
  CpuIdle USING [GetSmoothedCpuUtilization],
  BootFileInfoNoDisk USING [],
  MicrocodeVersion USING [VERSION, VersionResult];

CommandsAndSuch: PROGRAM
  IMPORTS
    Format, Frame, Heap, Inline, OthelloDefs, OthelloOps, OthelloForgot,
    Process, ProcessorFace, Runtime, SpecialRuntime, SpecialSpace,
    String, System, Time, Version,
    CpuIdle, MicrocodeVersion
  EXPORTS OthelloDefs, OthelloForgot, BootFileInfoNoDisk
  SHARES MStoreImpl =
  BEGIN
  
  z: UNCOUNTED ZONE = Heap.systemZone;
  
  startTime: System.GreenwichMeanTime ← System.gmtEpoch;

  MyNameIs: PUBLIC SIGNAL [myNameIs: STRING, myHelpIs: STRING] = CODE;
  AbortingCommand: PUBLIC ERROR [reason: LONG STRING, reasonOne: LONG STRING ← NIL] = CODE;
  IndexTooLarge:  PUBLIC ERROR  = CODE;
  Question: PUBLIC SIGNAL = CODE;
  TryAgain: PUBLIC SIGNAL = CODE;
  Quit: SIGNAL = CODE;

  BS: CHARACTER = 10C;
  ControlA: CHARACTER = 'A - 100B;
  ControlP: CHARACTER = 'P - 100B;
  ControlW: CHARACTER = 'W - 100B;
  CR: CHARACTER = 15C;
  DEL: CHARACTER = 177C;
  ESC: CHARACTER = 33C;
  SP: CHARACTER = ' ;
  NUL: CHARACTER = 0C;

  CommandProcessor: TYPE = OthelloDefs.CommandProcessor;
  
  -- ~~~~~~~~~~~~~~~~~~~~~~~~
  -- Commands
  -- ~~~~~~~~~~~~~~~~~~~~~~~~
  CurrentComand: SIGNAL RETURNS [
    proc: PROCEDURE [index: CARDINAL], index: CARDINAL] = CODE;

  ForAllCommandProcs: PROCEDURE [P: PROC[STRING]] = {
    FOR c: POINTER TO CommandProcessor ← commands, c.next WHILE c # NIL DO
      FOR i: CARDINAL IN [0..LAST[CARDINAL]) DO 
        ENABLE CurrentComand => RESUME[c.proc, i];
	c.proc[i
	! MyNameIs => {P[myNameIs]; CONTINUE};
	  IndexTooLarge => EXIT];
	ENDLOOP ENDLOOP};
    
  Help: PROCEDURE = {
    WidthProc: PROCEDURE [s: STRING] = {tabWidth ← MAX[tabWidth, s.length]};
    tabWidth: CARDINAL ← 0;
    SIGNAL MyNameIs[myNameIs: "Help"L, myHelpIs: "Type this table"L];
    ForAllCommandProcs[WidthProc];
    tabWidth ← tabWidth + 4;
    FOR c: POINTER TO CommandProcessor ← commands, c.next WHILE c # NIL DO
      FOR i: CARDINAL IN [0..LAST[CARDINAL]) DO 
	c.proc[i
	! MyNameIs => {
	    OthelloDefs.WriteString[myNameIs];
	    THROUGH [myNameIs.length..tabWidth) DO OthelloDefs.WriteChar[' ] ENDLOOP;
	    OthelloDefs.WriteLine[myHelpIs];
	    CONTINUE};
	  IndexTooLarge => EXIT];
	ENDLOOP ENDLOOP;
    OthelloDefs.WriteLine[
      "In General, Del will abort current command, ? will explain options"L]};

  BuiltinCommands: PROCEDURE [index: CARDINAL] = {
    SELECT index FROM
      0 => {
        SIGNAL MyNameIs[myNameIs: "Info"L, myHelpIs: "Retype startup Info"L];
	PrintHerald[];
	PrintMemorySize[];
	PrintPIDs[];
	PrintBootFileDate[];
	PrintGermDate[];
	PrintMicrocodeInfo[];
	OthelloDefs.WriteString["Current time"L];
	WriteTime[Time.defaultTime, TRUE];
	PrintTimeInfo[];
	OthelloDefs.NewLine[]; };
      1 => {
        SIGNAL MyNameIs[myNameIs: "Quit"L, myHelpIs: "Break the connection"L];
	Confirm[];
	SIGNAL Quit; };
      2 => {
        SIGNAL MyNameIs[myNameIs: "Time"L, myHelpIs: "Time of day"L];
	OthelloDefs.WriteString["Current time"L];
	WriteTime[Time.defaultTime, TRUE];
	PrintTimeInfo[];
	OthelloDefs.NewLine[]; };
      3 => Help[];
      ENDCASE =>
        ERROR IndexTooLarge};
    

  RegisterCommandProc: PUBLIC PROCEDURE [
    commandProc: POINTER TO CommandProcessor] = {
    commandProc.next ← commands; commands ← commandProc};
    
  commands: POINTER TO CommandProcessor ← @helpCommandProcessor;
  helpCommandProcessor: CommandProcessor ← [BuiltinCommands, NIL];

  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Basic command processing
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  CollectCommand: PROCEDURE RETURNS [
    p: PROCEDURE [index: CARDINAL], index: CARDINAL] = {
    ExplainOptions: PROCEDURE = {
      first: BOOLEAN ← TRUE;
      OthelloDefs.WriteChar['?];
      IF userString.length # 0 THEN {
        P: PROCEDURE [s: STRING] = {
          IF HeadMatch[s, userString.length] THEN {
            OthelloDefs.WriteString[IF first THEN "\rCurrent Options Are: "L ELSE ", "L];
	    OthelloDefs.WriteString[s]; first ← FALSE}};
        ForAllCommandProcs[P]};
      IF first THEN {  -- Didn't match... tell all
        P: PROCEDURE [s: STRING] = {
	  IF ~first THEN OthelloDefs.WriteString[", "L]; OthelloDefs.WriteString[s]; first ← FALSE};
        OthelloDefs.WriteString["\rValid Commands Are: "L];
	ForAllCommandProcs[P]};
      OthelloDefs.WriteString["\r> "L]; OthelloDefs.WriteString[userString]};
    FindAnswer: TYPE = RECORD [
      SELECT how: * FROM  none => NULL, many => NULL,
      one => [proc: PROCEDURE [index: CARDINAL], index: CARDINAL],
      ENDCASE];
    FindPossibles: PROCEDURE RETURNS [ans: FindAnswer ←  [none[]]] = {
      P: PROCEDURE [matchString: STRING] = {
        IF HeadMatch[matchString, head] THEN
          WITH ans SELECT FROM
            none => {
	      ans ← [one[CurrentComand[].proc, CurrentComand[].index]];
	      UNTIL userString.length = matchString.length DO
                userString[userString.length] ← matchString[userString.length];
		IF (userString.length ← userString.length + 1) = userString.maxlength THEN {
                  OthelloDefs.WriteLine[" Command too long!"L]; ERROR TryAgain}
	        ENDLOOP};
            ENDCASE => {
	      --ASSERT[head#0]
              FOR i : CARDINAL IN [head - 1..LAST[CARDINAL]) DO
		IF LowerCase[userString[i]] # LowerCase[matchString[i]] THEN {
		  userString.length ← i; EXIT};
	        ENDLOOP;
	      ans ← [many[]]}};
      head: CARDINAL ← userString.length;
      IF head = 0 THEN RETURN;
      ForAllCommandProcs[P];
      WHILE head # userString.length DO
        OthelloDefs.WriteChar[userString[head]]; head ← head + 1 ENDLOOP};
    HeadMatch: PROCEDURE [matchString: STRING, head: CARDINAL]
      RETURNS [BOOLEAN] = {
      IF head > matchString.length THEN RETURN[FALSE];
      FOR i: CARDINAL IN [0..head) DO
        IF LowerCase[userString[i]] # LowerCase[matchString[i]] THEN
	  RETURN[FALSE]
	ENDLOOP;
      RETURN[TRUE]};
    LowerCase: PROCEDURE [c: CHARACTER] RETURNS [CHARACTER] = {
      RETURN[IF c IN ['A..'Z] THEN c + ('a - 'A) ELSE c]};

    userString: STRING = [100];
 
    userString.length ← 0;
    OthelloDefs.WriteString["> "L];
    DO
      c: CHARACTER = OthelloDefs.ReadChar[];
      SELECT c FROM
        DEL => {OthelloDefs.WriteLine[" XXX"L]; ERROR TryAgain};
        BS, ControlA => IF userString.length # 0 THEN
	  OthelloForgot.EraseTTYChar[userString[userString.length ← userString.length - 1]];
        ControlW =>
	  IF userString.length # 0 THEN DO
	    OthelloForgot.EraseTTYChar[userString[userString.length ← userString.length - 1]];
	    IF userString.length=0 OR userString[userString.length - 1] = SP THEN EXIT
	    ENDLOOP;
	'? => ExplainOptions[];
        CR, SP => {
          ans: FindAnswer = FindPossibles[];
          WITH theAns: ans SELECT FROM
            none => OthelloDefs.BlinkDisplay[];
	    many => NULL;
            one  => RETURN[theAns.proc, theAns.index];
            ENDCASE => ERROR};
        ENDCASE => 
	  IF (userString.length ← userString.length + 1) = userString.maxlength THEN {
            OthelloDefs.WriteLine[" Command too long!"L]; ERROR TryAgain}
          ELSE OthelloDefs.WriteChar[userString[userString.length - 1] ← c];
      ENDLOOP};

  -- ~~~~~~~~~~~~~~~~~~~~~~~~
  -- Utility-Type Functions
  -- ~~~~~~~~~~~~~~~~~~~~~~~~
  Confirm: PUBLIC PROCEDURE [how: OthelloDefs.ConfirmType ← once] = {
    Nap: PROCEDURE = {FOR i: CARDINAL IN [0..LAST[CARDINAL]) DO ENDLOOP};
    OthelloDefs.WriteString["Are you "L];
    IF how = thrice THEN OthelloDefs.WriteString["still "L];
    OthelloDefs.WriteString["sure? [y or n]: "L];
    DO
      c: CHARACTER = OthelloDefs.ReadChar[];
      SELECT c FROM
        'y, 'Y, CR  => {OthelloDefs.WriteLine["Yes"L]; EXIT};
        'n, 'N, DEL => {OthelloDefs.WriteLine["No"L]; ERROR TryAgain};
        ENDCASE =>     OthelloDefs.BlinkDisplay[];
      ENDLOOP;
    IF how = twice THEN {Nap[]; Confirm[thrice]}};

  DebugAsk: PUBLIC PROCEDURE = {
    OthelloDefs.WriteString["\rType ControlP to muddle on........"L];
    WHILE OthelloDefs.ReadChar[] # ControlP DO ENDLOOP; OthelloDefs.NewLine[]};

  GetNameWithSpaces: PUBLIC PROCEDURE [prompt: STRING ← NIL, dest: POINTER TO LONG STRING] =
    BEGIN ENABLE UNWIND => spacesInStringOK ← FALSE;
    spacesInStringOK ← TRUE;
    GetName[prompt, dest];
    spacesInStringOK ← FALSE;
    END;

  spacesInStringOK: BOOLEAN ← FALSE;
  GetName: PUBLIC PROCEDURE [
    prompt: STRING ← NIL, dest: POINTER TO LONG STRING,
    how: OthelloDefs.EchoNoEcho ← echo, signalQuestion: BOOLEAN ← FALSE] =
    BEGIN
    first: BOOLEAN ← TRUE;
    EraseChar: PROCEDURE = {
      IF dest.length = 0 THEN RETURN;
      dest.length ← dest.length - 1;
      OthelloForgot.EraseTTYChar[IF how = echo THEN dest[dest.length] ELSE '*];
      IF dest.length = 0 AND dest.maxlength > 20 THEN {
	z.FREE[dest]; dest↑ ← z.NEW[StringBody[10]]; }};
    CWriteC: PROCEDURE [c: CHARACTER] = {OthelloDefs.WriteChar[IF how = echo THEN c ELSE '*]};
    CWriteString: PROCEDURE = {
      FOR i: CARDINAL IN [0..dest.length) DO CWriteC[dest[i]] ENDLOOP};

    IF dest↑ = NIL THEN dest↑ ← z.NEW[StringBody[10]];
    OthelloDefs.WriteString[prompt]; CWriteString[];
    DO
      c: CHARACTER = OthelloDefs.ReadChar[];
      SELECT TRUE FROM
        c = BS, c = ControlA => EraseChar[];
        (c = SP AND ~spacesInStringOK), c = CR => {OthelloDefs.NewLine[]; RETURN};
        c = DEL => {OthelloDefs.WriteLine[" XXX"L]; ERROR TryAgain};
        c = ControlW =>
	  DO
	    EraseChar[];
	    IF dest.length=0 THEN EXIT;
	    SELECT dest[dest.length-1] FROM
	      IN ['a..'z], IN ['A..'Z], IN ['0..'9] => LOOP;
	      ENDCASE => EXIT;
	    ENDLOOP;
	c = '? AND signalQuestion => {
          SIGNAL Question; OthelloDefs.WriteString[prompt]; CWriteString[]; LOOP};
        c >= SP => {
          IF first THEN WHILE dest.length#0 DO EraseChar[] ENDLOOP;
          String.AppendCharAndGrow[dest, c, z]; CWriteC[dest[dest.length-1]]};
        ENDCASE => OthelloDefs.BlinkDisplay[];
      first ← FALSE;
      ENDLOOP;
    END;

  numberString: LONG STRING ← NIL;
  ReadNumber: PUBLIC PROCEDURE [
    prompt: STRING, min, max, default: LONG CARDINAL ← LAST[LONG CARDINAL]]
    RETURNS [ans: LONG CARDINAL] = {
    DO
      IF default # LAST[LONG CARDINAL] THEN {
        IF numberString=NIL THEN numberString ← z.NEW[StringBody[15]];
	numberString.length ← 0; String.AppendLongNumber[numberString, default, 10]};
      OthelloDefs.WriteString[prompt];
      OthelloDefs.WriteChar['[];
      OthelloDefs.WriteLongNumber[min];
      OthelloDefs.WriteString[".."L];
      OthelloDefs.WriteLongNumber[max];
      OthelloDefs.WriteString["]: "L];
      GetName[dest: @numberString];
      ans ← 0;
      FOR i: CARDINAL IN [0..numberString.length) DO
        IF numberString[i] NOT IN ['0..'9] THEN EXIT;
        ans ← 10*ans + numberString[i] - '0;
        REPEAT FINISHED => IF ans IN [min..max] THEN {
	  z.FREE[@numberString]; RETURN};
        ENDLOOP;
      OthelloDefs.WriteLine["Bad Number !"L];
      ENDLOOP};

  ReadShortNumber: PUBLIC PROCEDURE [
    prompt: STRING, min, max, default: LONG CARDINAL]
    RETURNS [CARDINAL] = {
    RETURN[Inline.LowHalf[
      ReadNumber[prompt, min, MIN[max, LONG[LAST[CARDINAL]]], default]]]};

  Yes: PUBLIC PROCEDURE [s: STRING] RETURNS [BOOLEAN] = {
    OthelloDefs.WriteString[s];
    DO
      SELECT OthelloDefs.ReadChar[] FROM
        'Y, 'y, CR  => {OthelloDefs.WriteLine["yes"L]; RETURN[TRUE]};
        'N, 'n, DEL => {OthelloDefs.WriteLine["no"L];  RETURN[FALSE]};
	ENDCASE    => OthelloDefs.WriteChar['?];
      ENDLOOP};

  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Time munging
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  --  string format must be: bDD-MMM-YYbbHH:MM:SSbbZZTb
  PackedTimeFromString: PUBLIC PROCEDURE [
    s: LONG STRING, justDate: BOOLEAN]
    RETURNS [t: System.GreenwichMeanTime] = {
    Empty: PROCEDURE [s: LONG STRING] RETURNS [BOOLEAN] = {
      RETURN[s = NIL OR s.length = 0]};
    EquivalentChar: PUBLIC PROCEDURE [c1, c2: CHARACTER] RETURNS [BOOLEAN] = {
      RETURN[String.UpperCase[c1] = String.UpperCase[c2]]};
    GetToken: PROCEDURE [storage: LONG STRING, s: LONG STRING, c: CARDINAL]
      RETURNS [is: CARDINAL] = {
      FOR is ← c, is + 1 UNTIL is >= s.length DO
        ch: CHARACTER = s[is];
	SELECT ch FROM
	  IN ['a..'z], IN ['A..'Z], IN ['0..'9] =>
	    String.AppendChar[storage, ch];
	  ':, '- => EXIT; -- terminator
	  '      => IF ~Empty[storage] THEN EXIT; --terminating blank
	  ENDCASE;
        ENDLOOP;
      RETURN[is + 1]};

    DoIt: PROCEDURE [s: LONG STRING] RETURNS [t: System.GreenwichMeanTime] = {
      Get: PROCEDURE RETURNS [CARDINAL] = {
        s1.length ← 0; nextChar ← GetToken[s1, s, nextChar];
	RETURN[s1.length]};
      GetNumber: PROCEDURE RETURNS [CARDINAL] = {
        [] ← Get[]; RETURN[String.StringToNumber[s1, 10]]};
      m: String.SubStringDescriptor ← [
        base:   "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC"L,
	offset: NULL, length: 3];
      s1: STRING = [3];
      month: String.SubStringDescriptor ← [
        base: s1, offset: 0, length: NULL];
      time: Time.Unpacked ← [
        0, 0, 0, 0, 0, 0, 0, FALSE, System.GetLocalTimeParameters[]];
      nextChar: CARDINAL ← 0;
      packIt: BOOLEAN ← TRUE;
      IF Empty[s] THEN RETURN[System.gmtEpoch];
      time.day ← GetNumber[];
      month.length ← Get[];
      FOR i: CARDINAL IN [0..12) DO
        m.offset ← i*3;
        IF String.EquivalentSubString[@month, @m] THEN {
	  time.month ← i; EXIT};
        ENDLOOP;
      time.year ← GetNumber[];
      time.year ← time.year + (IF time.year>68 THEN 1900 ELSE 2000);
      IF justDate THEN {
        time.hour ← 23; time.minute ← 59; time.second ← 59}
      ELSE {
        time.hour   ← GetNumber[];
	time.minute ← GetNumber[];
        time.second ← GetNumber[];
	IF Get[] # 0 THEN {
          zones: PACKED ARRAY [5..8] OF CHARACTER = ['E, 'C, 'M, 'P];
          FOR i: CARDINAL IN [5..8] DO
	    IF EquivalentChar[s1[0], zones[i]] THEN {time.zone.zone ← i; EXIT};
	    REPEAT FINISHED => time.zone.zone ← 0; -- GMT
	    ENDLOOP;
          time.dst ← EquivalentChar[s1[1], 'D];
          packIt ← FALSE}};
      t ← Time.Pack[time, packIt]};
    t ← DoIt[s
    ! String.InvalidNumber, String.StringBoundsFault, Time.Invalid => {
      t ← System.gmtEpoch; CONTINUE}]};

  WriteTime: PROCEDURE [t: System.GreenwichMeanTime, showDay: BOOLEAN ← TRUE] = {
    days: ARRAY [0..7) OF STRING = [
      "Monday"L, "Tuesday"L, "Wednesday"L, "Thursday"L,
      "Friday"L, "Saturday"L, "Sunday"L];
    temps: STRING = [40];
    Time.Append[temps, Time.Unpack[t], TRUE];
    IF showDay THEN {
      OthelloDefs.WriteChar[' ]; OthelloDefs.WriteString[days[Time.Unpack[t].unpacked.weekday]]};
    IF temps[0] # ' THEN OthelloDefs.WriteChar[' ];
    OthelloDefs.WriteString[temps]};

  PrintTimeInfo: PROCEDURE =
    BEGIN
    OthelloDefs.WriteString[", UP: "L];
    PrintUpTime[System.GetGreenwichMeanTime[] -startTime];
    OthelloDefs.WriteString[", CPU: "L];
    OthelloDefs.WriteLongNumber[CpuIdle.GetSmoothedCpuUtilization[]];
    OthelloDefs.WriteString["%"L];
    OthelloDefs.WriteString["."L];
    END;
    
 PrintUpTime: PROCEDURE [sec: LONG CARDINAL] =
    BEGIN
    min: LONG INTEGER;
    hours: LONG INTEGER;
    hours ← sec/3600;
    sec ← sec - hours*3600;
    min ← sec/60;
    sec ← sec - min*60;
    OthelloDefs.WriteLongNumber[hours];
    OthelloDefs.WriteChar[':];
    OthelloDefs.WriteLongNumber[min];
    OthelloDefs.WriteChar[':];
    OthelloDefs.WriteLongNumber[sec];
    END;

  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- The Big Loop
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Commander: PROCEDURE =
    BEGIN
    -- Krock.  Give Login command time to connect up
    Process.Pause[Process.SecondsToTicks[10]];
    IF startTime = System.gmtEpoch THEN startTime ← System.GetGreenwichMeanTime[];
    DO ENABLE ABORTED, Quit => RETRY;
      OthelloForgot.SmashPassword[];
      OthelloForgot.WaitForConnection[];
      PrintHerald[];
      IF ~OthelloOps.IsTimeValid[] THEN GetTime[]
      ELSE
        BEGIN
	IF startTime = System.gmtEpoch THEN startTime ← System.GetGreenwichMeanTime[];
	END;
      DO
        TellError: PROCEDURE [s: LONG STRING] =
	  BEGIN
	  OthelloDefs.NewLine[];
	  OthelloDefs.WriteString[s];
	  END;
        p: PROCEDURE [index: CARDINAL];
        i: CARDINAL;
        OthelloForgot.ResetAbort[];
        [p, i]  ← CollectCommand[ !
          TryAgain => RETRY;
          AbortingCommand => {TellError[reason]; OthelloDefs.WriteLine[reasonOne]; LOOP}];
        OthelloDefs.NewLine[];
	BEGIN
        p[i !
          MyNameIs => RESUME;
	  ABORTED => GOTO Aborted; -- Beware of ML hangups
	  AbortingCommand => {
	    TellError[reason]; OthelloDefs.WriteLine[reasonOne]; CONTINUE};
	  String.StringBoundsFault => {
            TellError["String.StringBoundsFault"L]; DebugAsk[]; CONTINUE};
          TryAgain => CONTINUE;
          Quit => REJECT;
          UNWIND => NULL;
          ANY => {
            signal: SIGNAL;
	    args: PrincOps.LocalFrameHandle;
            size: CARDINAL;
	    TellError["Uncaught Signal =  "L];
            [signal: signal, signalArgs: args] ←
	       SIGNAL SpecialRuntime.GetCurrentSignal;
            OthelloForgot.WriteLongOctal[LOOPHOLE[signal]];
	    size ← PrincOps.frameSizeMap[Frame.ReadLocalWord[args].fsi]
	      - SIZE[PrincOps.LocalOverhead];
	    OthelloDefs.WriteString[", msg = ["L];
	    FOR i: CARDINAL IN [0..size-1) DO
	      OthelloDefs.WriteOctal[args[i]];
	      OthelloDefs.WriteString[", "L]
	      ENDLOOP;
	    OthelloDefs.WriteOctal[args[size-1]];
	    OthelloDefs.WriteChar[']];
	    Frame.Free[args];
	    DebugAsk[];
	    CONTINUE}];
	  EXITS Aborted => TellError["ABORTED\r"L];
	  END;
        ENDLOOP;
      ENDLOOP;
    END;


  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Initialization Stuff
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  GetTime: PROCEDURE =
    BEGIN
    timeTrys:       CARDINAL ← 10;
    time:           System.GreenwichMeanTime;
    LTPs:           System.LocalTimeParameters;
    timeFromServer: BOOLEAN ← TRUE;
    getTimeString:  LONG STRING ← NIL;
    teMP: CARDINAL = ProcessorFace.mp;
    ProcessorFace.SetMP[PilotMP.cTimeNotAvailable+3000];
    [time, LTPs] ← OthelloOps.GetTimeFromTimeServer[
      ! OthelloOps.TimeServerError => IF error=noResponse THEN {
          IF (timeTrys ← timeTrys-1)=0 THEN {timeFromServer ← FALSE; CONTINUE}
	  ELSE {IF timeTrys=2 THEN OthelloDefs.WriteString["Locating Time Server..."L]; RETRY}}
        ELSE IF error=noCommunicationFacilities THEN {
	  OthelloDefs.WriteLine["not Communication Facilities to find time"L];
	  timeFromServer ← FALSE; CONTINUE}
	ELSE ERROR];
    ProcessorFace.SetMP[teMP];
    IF timeFromServer THEN
      BEGIN
      IF timeTrys # 10 THEN OthelloDefs.WriteLine["success"L];
      System.SetLocalTimeParameters[LTPs];
      OthelloOps.SetProcessorTime[time]; 
      startTime ← time;
      RETURN;
      END;
    OthelloDefs.WriteLine["failed.\rPlease enter time information (type ? for help)"L];
    getTimeString ← z.NEW[StringBody[10]];
    LTPs ← GetTimeZoneFromUser[@getTimeString ! TryAgain => RETRY];
    System.SetLocalTimeParameters[LTPs];
    spacesInStringOK ← TRUE;
    GetTimeFromUser[@getTimeString ! TryAgain => RETRY];
    spacesInStringOK ← FALSE;
    z.FREE[@getTimeString];
    ProcessorFace.SetMP[teMP];
    END;
    
  GetTimeFromUser: PROCEDURE [p: POINTER TO LONG STRING] = {
    timePrompt: STRING = "Please Enter the date and 24 hour time in form
      DD-MMM-YY HH:MM:SS
Time: "L;
    IF OthelloOps.IsTimeValid[] THEN
      BEGIN
      OthelloDefs.WriteString["Current time"L];
      WriteTime[System.GetGreenwichMeanTime[]];
      OthelloDefs.NewLine[];
      IF ~Yes["Do you wish to change the time?: "L] THEN RETURN;
      END;
    IF p#NIL THEN p.length ← 0;
    DO
      time: System.GreenwichMeanTime;
      GetName[timePrompt, p];
      time ← PackedTimeFromString[p↑, FALSE];
      IF time=System.gmtEpoch THEN {
        OthelloDefs.WriteLine["Invalid date/time -- please try again."L]; LOOP};
      OthelloDefs.WriteString["Set time to"L];
      WriteTime[time, TRUE];
      OthelloDefs.NewLine[];
      IF Yes["Okay?: "L] THEN
        BEGIN
        OthelloOps.SetProcessorTime[time];
	startTime ← time;
	EXIT;
	END;
      ENDLOOP};
    
  GetTimeZoneFromUser: PROCEDURE [string: POINTER TO LONG STRING] 
    RETURNS [ltp: System.LocalTimeParameters] = {
    GetNum: PROCEDURE [
      prompt: STRING, min, max, default: INTEGER]
      RETURNS [ans: INTEGER] = {
      string.length ← 0;
      String.AppendDecimal[string↑, default];
      DO
        isNeg: BOOLEAN ← FALSE;
        OthelloDefs.WriteString[prompt];
        OthelloDefs.WriteChar['[];
	IF ans < 0 THEN OthelloDefs.WriteChar['-];
	OthelloDefs.WriteLongNumber[ABS[min]];
        OthelloDefs.WriteString[".."L];
	OthelloDefs.WriteLongNumber[max];
	OthelloDefs.WriteString["]: "L];
        GetName[dest: string, signalQuestion: TRUE];
        ans ← 0;
        FOR i: CARDINAL IN [0..string.length) DO
          IF i=0 AND string[i]='- THEN {isNeg ← TRUE; LOOP};
	  IF string[i] NOT IN ['0..'9] THEN EXIT;
          ans ← 10*ans + string[i] - '0;
          REPEAT FINISHED => {
	    IF isNeg THEN ans ← -ans; IF ans IN [min..max] THEN RETURN};
          ENDLOOP;
        OthelloDefs.WriteLine["Bad Number !"L];
        ENDLOOP};
    dstSpiel: STRING = "
The ""First day of DST"" is the day of the year on or before which
Daylight Savings Time takes effect, where:
        1 =>  January  1
      366 => December 31.
(The correspondence  between numbers and days is based on a leap
year.  Similarly, ""Last day of DST"" is the day of the year on or
before which Daylight Savings Time ends.  Note that in any given
year,  Daylight Savings Time actually begins and ends at 2 AM on
the last Sunday not  following  the  specified date.  The system
makes this adjustment  for you automatically.  The normal values
are
      121   (April 30) for the first day of DST
      305 (October 31) for the last  day of DST.
If Daylight  Savings  Time is not  observed locally, both values
should be set to zero."L;
    ZoneSpiel: STRING = "
Number  of  hours  between  Greenwich  and local time.  For time
zones west of  Greenwich, the offset is negative; for time zones
east of Greenwich, the offset is positive. Examples:
      San Francisco  -8 hours    (Pacific  time zone)
      Denver         -7 hours    (Mountain time zone)
      Chicago        -6 hours    (Central  time zone)
      Boston         -5 hours    (Eastern  time zone)"L;
    n: INTEGER;

    n ← GetNum[prompt: "Time zone offset from Greenwich "L, min: -12, max: 12, default: -8
    ! Question => {OthelloDefs.WriteLine[ZoneSpiel]; RETRY}];
    ltp.direction ← IF n<0 THEN west ELSE east; ltp.zone ← ABS[n];
    ltp.zoneMinutes ← GetNum[prompt: "Minute offset "L, min: 0, max: 59, default: 0
    ! Question => {OthelloDefs.WriteLine["\rAlmost always zero"L]; RETRY}];
    ltp.beginDST ← GetNum[prompt: "First day of DST "L, min: 0, max: 366, default: 121
    ! Question => {OthelloDefs.WriteLine[dstSpiel]; RETRY}];
    ltp.endDST ← GetNum[prompt: "Last day of DST "L,  min: 0, max: 366, default: 305
    ! Question => {OthelloDefs.WriteLine[dstSpiel]; RETRY}]};
    
  PrintHerald: PROCEDURE =
    BEGIN
    herald: STRING  = [100];
    String.AppendString[herald, "Oscar "L];
    Version.Append[herald];
    String.AppendString[herald, " of"L];
    OthelloDefs.WriteString[herald];
    WriteTime[Runtime.GetBuildTime[], FALSE];
    OthelloDefs.NewLine[];
    END;

  PrintBootFileDate: PROCEDURE =
    BEGIN
    when: System.GreenwichMeanTime ←  LOOPHOLE[SDDefs.SD[SDDefs.sSystemDate]];
    OthelloDefs.WriteString["This Boot File was built on"L];
    WriteTime[when, FALSE];
    OthelloDefs.WriteString["."L];
    OthelloDefs.NewLine[];
    END;

  PrintGermDate: PROCEDURE =
    BEGIN
    germsSD: LONG POINTER TO ARRAY [0..0) OF LONG UNSPECIFIED =
      LOOPHOLE[Environment.Long[any[low: SDDefs.SD, high: Boot.mdsiGerm]]];
    when: System.GreenwichMeanTime ←  LOOPHOLE[germsSD[SDDefs.sSystemDate]];
    OthelloDefs.WriteString["This Germ was built on"L];
    WriteTime[when, FALSE];
    OthelloDefs.WriteString["."L];
    OthelloDefs.NewLine[];
    END;

  PrintMicrocodeInfo: PROCEDURE =
    BEGIN
    temp: STRING = [40];
    version: MicrocodeVersion.VersionResult ← MicrocodeVersion.VERSION[];
    when: System.GreenwichMeanTime ←  -- 0 => Jan 1, 1901
      System.AdjustGreenwichMeanTime[[0], version.releaseDate*86400];
    unpacked: Time.Unpacked;
    OthelloDefs.WriteString["This microcode was built on "L];
    unpacked ← Time.Unpack[when, [useThese[[west, 0, 0, 366, 366]]]];
    Time.Append[temp, unpacked, FALSE];
    temp.length ← temp.length - 9;  -- Discard time = " xx:xx:xx"
    OthelloDefs.WriteString[temp];
    OthelloDefs.WriteString["."L];
    OthelloDefs.NewLine[];
    OthelloDefs.WriteString["This machine is a "L];
    OthelloDefs.WriteString[
      SELECT version.machineType FROM
        dolphin => "Dolphin"L,
        dorado => "Dorado"L,
        dandelion => "Dandelion"L,
        dicentra => "Dicentra"L,
	ENDCASE => "??"L];
    OthelloDefs.WriteString[".  FloatintPoint = "L];
    OthelloDefs.WriteString[IF version.floatingPoint THEN "TRUE"L ELSE "FALSE"L];
    OthelloDefs.WriteString[", Cedar = "L];
    OthelloDefs.WriteString[IF version.cedar THEN "TRUE"L ELSE "FALSE"L];
    OthelloDefs.WriteString["."L];
    OthelloDefs.NewLine[];
    END;

  maxFreePages: LONG CARDINAL ← 0;
  PrintMemorySize: PROCEDURE =
    BEGIN
    total: LONG CARDINAL ← SpecialSpace.realMemorySize + 16*pagesPerK;
    pagesPerK: CARDINAL = 4;
    pagesPerBank: CARDINAL = 256;
    mStoreImpl: POINTER TO FRAME [MStoreImpl] = LOOPHOLE[1FF4H];  -- In Oscar
    temp: STRING = [200];
    String.AppendString[temp, "There are "L];
    String.AppendLongDecimal[temp, SpecialSpace.realMemorySize];
    String.AppendString[temp, " pages of real memory.  (With a 16K map, that's "L];
    String.AppendLongDecimal[temp, Environment.bytesPerWord*total/pagesPerK];
    String.AppendString[temp, "K Bytes.)"L];
    String.AppendChar[temp, Ascii.CR];
    IF total MOD pagesPerBank # 0 THEN
      BEGIN
      String.AppendString[temp, "It looks like some pages have been lost."L];
      String.AppendChar[temp, Ascii.CR];
      END;
    maxFreePages ← MAX[maxFreePages, mStoreImpl.countFree];
    String.AppendString[temp, "There are "L];
    String.AppendLongDecimal[temp, mStoreImpl.countFree];
    String.AppendString[temp, " pages of free memory."L];
    IF maxFreePages # mStoreImpl.countFree THEN
      BEGIN
      String.AppendString[temp, "  (Once there were "L];
      String.AppendLongDecimal[temp, maxFreePages];
      String.AppendString[temp, ".)"L];
      END;
    OthelloDefs.WriteLine[temp];
    END;
    
  PrintPIDs: PROCEDURE =
    BEGIN
    me: System.HostNumber = System.localHostNumber;
    Push: Format.StringProc = BEGIN OthelloDefs.WriteString[s]; END;
    OthelloDefs.WriteString["Processor = "L];
    Format.HostNumber[Push, me, hex];
    OthelloDefs.WriteString[" = "L];
    Format.HostNumber[Push, me, octal];
    OthelloDefs.WriteString[" = "L];
    Format.HostNumber[Push, me, productSoftware];
    OthelloDefs.WriteLine["."L];
    END;
    
  GetBootInfo: PUBLIC PROCEDURE[type: OthelloOps.BootFileType, info: LONG STRING]
    RETURNS[exists: BOOLEAN] =
    BEGIN
    info.length ← 0;
    SELECT type FROM
      hardMicrocode =>
       BEGIN
        version: MicrocodeVersion.VersionResult ← MicrocodeVersion.VERSION[];
        when: System.GreenwichMeanTime ←  -- 0 => Jan 1, 1901
        System.AdjustGreenwichMeanTime[[0], version.releaseDate*86400];
        unpacked: Time.Unpacked;
        unpacked ← Time.Unpack[when, [useThese[[west, 0, 0, 366, 366]]]];
        Time.Append[info, unpacked, FALSE];
        info.length ← info.length - 9;  -- Discard time = " xx:xx:xx"
        String.AppendString[info,
          SELECT version.machineType FROM
            dolphin => ", Dolphin"L,
            dorado => ", Dorado"L,
            dandelion => ", Dandelion"L,
            dicentra => ", Dicentra"L,
	    ENDCASE => "??"L];
        String.AppendString[info, ", Float = "L];
        String.AppendString[info, IF version.floatingPoint THEN "T"L ELSE "F"L];
        String.AppendString[info, ", Cedar = "L];
        String.AppendString[info, IF version.cedar THEN "T"L ELSE "F"L];
        END;
      softMicrocode => String.AppendString[info, "None"L];
      germ =>
        BEGIN
        germsSD: LONG POINTER TO ARRAY [0..0) OF LONG UNSPECIFIED =
          LOOPHOLE[Environment.Long[any[low: SDDefs.SD, high: Boot.mdsiGerm]]];
        when: System.GreenwichMeanTime ←  LOOPHOLE[germsSD[SDDefs.sSystemDate]];
        Time.Append[info, Time.Unpack[when]];
        END;
      pilot =>
        BEGIN
        when: System.GreenwichMeanTime ←  LOOPHOLE[SDDefs.SD[SDDefs.sSystemDate]];
        Time.Append[info, Time.Unpack[when]];
        END;
      ENDCASE => String.AppendString[info, "ENDCASE"L];
    END;
    
  Process.Detach[FORK Commander[]];
  END..