-- VolumeInitCommandImpl, last edited by
--   Johnsson  12-Sep-83 22:47:20
--   Saaranzin 14-Dec-83 15:20:28
--   elliott   17-Feb-84 15:12:02
--   Conde       18-Jul-84 13:58:37
-- This file is the command Processor.

DIRECTORY
  BitBlt USING [AlignedBBTable, BITBLT, BBptr, BBTableSpace],
  File  USING [Error, ErrorType, Unknown],
  Environment USING [bitsPerWord],
  ESCAlpha USING [aBITBLT],
  Format USING [HostNumber, StringProc],
  Frame USING [Free, GetReturnFrame, ReadLocalWord, ReadPC, WritePC],
  Heap USING [systemZone],
  KeyStations USING [D1, D2, KeyBits],
  Inline USING [BITNOT, HighHalf, LowHalf],
  OthelloDefs,
  OthelloOps USING [
    GetTimeFromTimeServer, IsTimeValid,
    SetProcessorTime, TimeServerError],
  PhysicalVolume USING [Error, ErrorType, NeedsScavenging],
  PilotClient USING [],
  PrincOps USING [
    ControlLink, ESCTrapTable, frameSizeMap, LocalFrameHandle,
    LocalOverhead, OpTrapTable],
  Process USING [Pause, SecondsToTicks],
  Runtime USING [GetBuildTime, IsBound],
  SpecialRuntime USING [GetCurrentSignal],
  SpecialSpace USING [realMemorySize],
  SpecialSystem USING [GetProcessorID],
  Scavenger USING [Error, ErrorType],
  String USING [
    AppendChar, AppendCharAndGrow, AppendDecimal, AppendLongNumber,
    EquivalentSubString, InvalidNumber, StringBoundsFault, StringToNumber,
    SubStringDescriptor, UpperCase],
  System USING [
    GetGreenwichMeanTime, GreenwichMeanTime, gmtEpoch,
    LocalTimeParameters, GetLocalTimeParameters, SetLocalTimeParameters],
  TTY USING [
    BlinkDisplay, CharsAvailable, Create, CreateTTYInstance, GetChar, Handle,
    nullHandle, PutChar, PutString, ResetUserAbort, UserAbort],
  Time USING [
    Append, defaultTime, Invalid, Pack, Unpack, Unpacked, useGMT, useSystem],
  UserTerminal USING [
    CursorArray, GetCursorPattern, keyboard, SetCursorPattern],
  Volume USING [
    InsufficientSpace, NeedsScavenging, NotOpen, ReadOnly, Unknown],
  VolumeConversion USING [Error, ErrorType];

UtilityPilotClientImpl: PROGRAM
  IMPORTS
    BitBlt, File, Format, Frame, Heap, Inline, OthelloDefs, OthelloOps,
    PhysicalVolume, Process, Runtime, SpecialRuntime, SpecialSpace, SpecialSystem,
    Scavenger, String, System, Time, TTY, AdmTTY: TTY, UserTerminal, Volume,
    VolumeConversion
  EXPORTS OthelloDefs, PilotClient =
  BEGIN

  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;

  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: PROC [index: CARDINAL], index: CARDINAL] = CODE;

  ForAllCommandProcs: PROC [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: PROC = {
    WidthProc: PROC [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 => {
	    WriteString[myNameIs];
	    THROUGH [myNameIs.length..tabWidth) DO WriteChar[' ] ENDLOOP;
	    WriteLine[myHelpIs];
	    CONTINUE};
	  IndexTooLarge => EXIT];
	ENDLOOP ENDLOOP;
    WriteLine[
      "In General, Del will abort current command, ? will explain options"L]};

  TimeUser: PROC [index: CARDINAL] = {
    SELECT index FROM
      0 => {
        SIGNAL MyNameIs[myNameIs: "Time"L, myHelpIs: "Time of day"L];
	WriteString["Current time"L]; WriteTime[Time.defaultTime, TRUE]};
      1 =>
        Help[];
      ENDCASE =>
        ERROR IndexTooLarge};
    
  RegisterCommandProc: PUBLIC PROC [
    commandProc: POINTER TO CommandProcessor] = {
    commandProc.next ← commands; commands ← commandProc};
    
  commands: POINTER TO CommandProcessor ← @helpCommandProcessor;
  helpCommandProcessor: CommandProcessor ← [TimeUser, NIL];

  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Basic command processing
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  CollectCommand: PROC RETURNS [
    p: PROC [index: CARDINAL], index: CARDINAL] = {
    ExplainOptions: PROC = {
      first: BOOLEAN ← TRUE;
      WriteChar['?];
      IF userString.length # 0 THEN {
        P: PROC [s: STRING] = {
          IF HeadMatch[s, userString.length] THEN {
            WriteString[IF first THEN "\rCurrent Options Are: "L ELSE ", "L];
	    WriteString[s]; first ← FALSE}};
        ForAllCommandProcs[P]};
      IF first THEN {  -- Didn't match... tell all
        P: PROC [s: STRING] = {
	  IF ~first THEN WriteString[", "L]; WriteString[s]; first ← FALSE};
        WriteString["\rValid Commands Are: "L];
	ForAllCommandProcs[P]};
      WriteString["\r> "L]; WriteString[userString]};
    FindAnswer: TYPE = RECORD [
      SELECT how: * FROM  none => NULL, many => NULL,
      one => [proc: PROC [index: CARDINAL], index: CARDINAL],
      ENDCASE];
    FindPossibles: PROC RETURNS [ans: FindAnswer ←  [none[]]] = {
      P: PROC [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 {
                  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
        WriteChar[userString[head]]; head ← head + 1 ENDLOOP};
    HeadMatch: PROC [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: PROC [c: CHARACTER] RETURNS [CHARACTER] = {
      RETURN[IF c IN ['A..'Z] THEN c + ('a - 'A) ELSE c]};

    userString: STRING = [100];
 
    userString.length ← 0;
    WriteString["> "L];
    DO
      c: CHARACTER = ReadChar[];
      SELECT c FROM
        DEL => {WriteLine[" XXX"L]; ERROR TryAgain};
        BS, ControlA => IF userString.length # 0 THEN
	  EraseTTYChar[userString[userString.length ← userString.length - 1]];
        ControlW =>
	  IF userString.length # 0 THEN DO
	    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 => {
	      IF Runtime.IsBound[LOOPHOLE[OthelloDefs.AlternateGetCMFile]]
	         AND userString.length > 1 AND userString[0] = '@ THEN {
		 NewLine[];
		 OthelloDefs.AlternateGetCMFile[userString
		 ! OthelloDefs.MyNameIs => RESUME];
		 ERROR TryAgain};
	      IF prometheusBound THEN AbortingCommand["Script Error"L]
	      ELSE BlinkDisplay[]};
	    many => NULL;
            one  => RETURN[theAns.proc, theAns.index];
            ENDCASE => ERROR};
        ENDCASE => 
	  IF (userString.length ← userString.length + 1) = userString.maxlength THEN {
            WriteLine[" Command too long!"L]; ERROR TryAgain}
          ELSE WriteChar[userString[userString.length - 1] ← c];
      ENDLOOP};

  -- ~~~~~~~~~~~~~~~~~~~~~~~~
  -- Utility-Type Functions
  -- ~~~~~~~~~~~~~~~~~~~~~~~~
  Confirm: PUBLIC PROC [how: OthelloDefs.ConfirmType ← once] = {
    IF CommandFileActive[] THEN RETURN;
    WriteString["Are you "L];
    IF how = thrice THEN WriteString["still "L];
    WriteString["sure? [y or n]: "L];
    DO
      c: CHARACTER = ReadChar[];
      SELECT c FROM
        'y, 'Y, CR  => {WriteLine["Yes"L]; EXIT};
        'n, 'N, DEL => {WriteLine["No"L]; ERROR TryAgain};
        ENDCASE =>     BlinkDisplay[];
      ENDLOOP;
    IF how = twice THEN {
      Process.Pause[Process.SecondsToTicks[3]]; FlushInput[]; Confirm[thrice]}};

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

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

    IF dest↑ = NIL THEN dest↑ ← Heap.systemZone.NEW[StringBody[10]];
    WriteString[prompt]; CWriteString[];
    DO
      c: CHARACTER = ReadChar[];
      SELECT TRUE FROM
        c = BS, c = ControlA => EraseChar[];
        (c = SP AND ~spacesInStringOK), c = CR => {NewLine[]; RETURN};
        c = DEL => {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; WriteString[prompt]; CWriteString[]; LOOP};
        c >= SP => {
          IF first THEN WHILE dest.length#0 DO EraseChar[] ENDLOOP;
          String.AppendCharAndGrow[dest, c, Heap.systemZone]; CWriteC[dest[dest.length-1]]};
        ENDCASE => BlinkDisplay[];
      first ← FALSE;
      ENDLOOP;
    END;

  numberString: LONG STRING ← NIL;
  ReadNumber: PUBLIC PROC [
    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 ← Heap.systemZone.NEW[StringBody[15]];
	numberString.length ← 0; String.AppendLongNumber[numberString, default, 10]};
      WriteString[prompt]; WriteChar['[]; WriteLongNumber[min];
      WriteString[".."L]; WriteLongNumber[max]; 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 {
	  Heap.systemZone.FREE[@numberString]; RETURN};
        ENDLOOP;
      WriteLine["Bad Number !"L];
      ENDLOOP};

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

  WriteFixedWidthNumber: PUBLIC PROC [
    x: LONG CARDINAL, count: CARDINAL, base: CARDINAL ← 10] = {
    WFD: PROC [x: LONG CARDINAL, c: CARDINAL] = {
      IF c = count THEN RETURN;
      WFD[x/base, c + 1];
      WriteChar[IF c = 0 OR x # 0 THEN Inline.LowHalf[x MOD base] + '0 ELSE ' ]};
    WFD[x, 0]};

  WriteLongNumber: PUBLIC PROC [num: LONG CARDINAL] = {
    s: STRING ← [40];
    s.length ← 0;
    String.AppendLongNumber[s, num, 10];
    WriteString[s]};

  WriteOctal: PUBLIC PROC [num: CARDINAL] = {
    IF num # 0 THEN WriteOctal[num/8]; WriteChar[(num MOD 8) + '0]};

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

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

  --  string format must be: bDD-MMM-YYbbHH:MM:SSbbZZTb
  PackedTimeFromString: PUBLIC PROC [
    s: LONG STRING, justDate: BOOLEAN]
    RETURNS [t: System.GreenwichMeanTime] = {
    Empty: PROC [s: LONG STRING] RETURNS [BOOLEAN] = {
      RETURN[s = NIL OR s.length = 0]};
    EquivalentChar: PUBLIC PROC [c1, c2: CHARACTER] RETURNS [BOOLEAN] = {
      RETURN[String.UpperCase[c1] = String.UpperCase[c2]]};
    GetToken: PROC [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: PROC [s: LONG STRING] RETURNS [t: System.GreenwichMeanTime] = {
      Get: PROC RETURNS [CARDINAL] = {
        s1.length ← 0; nextChar ← GetToken[s1, s, nextChar];
	RETURN[s1.length]};
      GetNumber: PROC 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: PROC [
    t: System.GreenwichMeanTime, showDay: BOOLEAN ← TRUE,
    type: {system, gmt, pacific} ← system] = {
    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, SELECT type FROM
	pacific => [useThese[[west, 8, 0, 121, 305]]],
	gmt => Time.useGMT,
	ENDCASE => Time.useSystem]];
    IF showDay THEN {
      WriteChar[' ]; WriteString[days[Time.Unpack[t].unpacked.weekday]]};
    IF temps[0] # ' THEN WriteChar[' ];
    WriteLine[temps]};

  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- The Big Loop
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  prometheusBound: BOOLEAN =
    Runtime.IsBound[LOOPHOLE[OthelloDefs.GetCannedScript]];
  
  Run: PUBLIC PROC =
    BEGIN
    DO
      TellError: PROC [s: LONG STRING] = {
        IF prometheusBound THEN OthelloDefs.ThereIsAnError[];
	commandIndex ← LAST[CARDINAL]; NewLine[]; WriteString[s]};
      p: PROC [index: CARDINAL]; i: CARDINAL;
      IF (~CommandFileActive[]) AND prometheusBound THEN {
        ResetAbort[]; OthelloDefs.GetCannedScript[]};
      IF CommandFileActive[] THEN
        CheckUserAbort[
	! ABORTED => {TellError["Command File Aborted\r"L]; LOOP}]
      ELSE ResetAbort[];
      [p, i]  ← CollectCommand[
      ! TryAgain => RETRY;
        AbortingCommand => {TellError[reason]; WriteLine[reasonOne]; LOOP}];
      NewLine[];
      p[i !
        MyNameIs => RESUME;
	ABORTED => {TellError["ABORTED\r"L]; CONTINUE};
	AbortingCommand => {
	  TellError[reason]; WriteLine[reasonOne]; CONTINUE};
        File.Unknown => {
	  TellError["File.Unknown"L]; DebugAsk[]; CONTINUE};
        File.Error => {
          PrintNames: PROC [x: File.ErrorType] = {
            e: ARRAY File.ErrorType OF STRING = [
              invalidParameters: "invalidParameters"L,
              reservedType: "reservedType"L];
            WriteString[e[x]]};
          TellError["File.Error["L];
          PrintNames[type];
          WriteChar[']];
          DebugAsk[];
          CONTINUE};
	PhysicalVolume.Error => {
          PrintNames: PROC [x: PhysicalVolume.ErrorType] = {
            e: ARRAY PhysicalVolume.ErrorType OF STRING = [
              badDisk: "badDisk"L,
              badSpotTableFull: "badSpotTableFull"L,
              containsOpenVolumes: "containsOpenVolumes"L,
              diskReadError: "diskReadError"L,
              hardwareError: "hardwareError"L,
              hasPilotVolume: "hasPilotVolume"L,
              alreadyAsserted: "alreadyAsserted"L,
              insufficientSpace: "insufficientSpace"L,
              invalidHandle: "invalidHandle"L,
              nameRequired: "nameRequired"L,
	      needsConversion: "needsConversion"L,
              notReady: "notReady"L,
              noSuchDrive: "noSuchDrive"L,
              noSuchLogicalVolume: "noSuchLogicalVolume"L,
              physicalVolumeUnknown: "physicalVolumeUnknown"L,
              writeProtected: "writeProtected"L,
              wrongFormat: "wrongFormat"L];
            WriteString[e[x]]};
          TellError["PhysicalVolume.Error["L];  PrintNames[error];
	  WriteChar[']];
          DebugAsk[];
          CONTINUE};
        PhysicalVolume.NeedsScavenging => {
          TellError["PhysicalVolume.NeedsScavenging"L];
	  DebugAsk[]; CONTINUE};
        Scavenger.Error =>{
          PrintNames: PROC [x: Scavenger.ErrorType] = {
            e: ARRAY Scavenger.ErrorType OF STRING = [
              cannotWriteLog: "cannotWriteLog"L,
              noSuchPage: "noSuchPage"L,
              orphanNotFound: "orphanNotFound"L,
              volumeOpen: "volumeOpen"L,
              diskHardwareError: "diskHardwareError"L,
              diskNotReady: "diskNotReady"L,
	      needsConversion: "needsConversion"L,
              needsRiskyRepair: "needsRiskyRepair"L];
            WriteString[e[x]]};
          TellError["Scavenger.Error["L]; PrintNames[error]; WriteChar[']];
          DebugAsk[];
          CONTINUE};
        VolumeConversion.Error =>{
          PrintNames: PROC [x: VolumeConversion.ErrorType] = {
            e: ARRAY VolumeConversion.ErrorType OF STRING = [
              hardwareBroken: "hardwareBroken"L,
              lostLog: "lostLog"L,
              runPreviousScavenger: "runPreviousScavenger"L,
              volumeVersionTooNew: "volumeVersionTooNew"L,
              volumeVersionTooOld: "volumeVersionTooOld"L];
            WriteString[e[x]]};
          TellError["VolumeConversion.Error["L]; PrintNames[error]; WriteChar[']];
          DebugAsk[];
          CONTINUE};
        Volume.InsufficientSpace => {
          TellError["Volume.InsufficientSpace"L];
	  DebugAsk[]; CONTINUE};
        Volume.NotOpen => {
          TellError["Volume.NotOpen"L]; DebugAsk[]; CONTINUE};
        Volume.NeedsScavenging => {
          TellError["Please Scavenge the volume first"L]; CONTINUE};
        Volume.Unknown => {
          TellError["Volume.Unknown"L]; DebugAsk[]; CONTINUE};
        Volume.ReadOnly => {
	  TellError["Volume.ReadOnly"L]; DebugAsk[]; CONTINUE};
	String.StringBoundsFault => {
          TellError["String.StringBoundsFault"L]; DebugAsk[]; CONTINUE};
        TryAgain => CONTINUE;
        ANY => {
          signal: SIGNAL;
	  args:  PrincOps.LocalFrameHandle;
	  TellError["Uncaught Signal = ["L];
          [signal: signal, signalArgs: args] ←
	     SIGNAL SpecialRuntime.GetCurrentSignal;
          WriteOctal[Inline.LowHalf[LOOPHOLE[signal]]];
	  WriteChar[',];
          WriteOctal[Inline.HighHalf[LOOPHOLE[signal]]];
	  WriteChar[']];
	  IF args # NIL THEN {
	    size: CARDINAL ← PrincOps.frameSizeMap[Frame.ReadLocalWord[args].fsi]
	      - SIZE[PrincOps.LocalOverhead];
	    WriteString[", msg = ["L];
	    FOR i: CARDINAL IN [0..size-1) DO
	      WriteOctal[args[i]]; WriteString[", "L] ENDLOOP;
	    WriteOctal[args[size-1]]; WriteChar[']];
	    Frame.Free[args]};
	  DebugAsk[]; CONTINUE}];
      ENDLOOP;
    END;

  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- TTY Interface Stuff
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- These two BitBlt procedures adapted from ProcessorHeadDLion
  
  HasBitBlt: PROC RETURNS [hasBitBlt: BOOLEAN ← FALSE] = {
    -- Execute a dummy BitBlt to find out if the microcode implements it.
    dummySrc: BOOLEAN ← TRUE;
    bba: BitBlt.BBTableSpace;
    bbt: BitBlt.BBptr = BitBlt.AlignedBBTable[@bba];
    escTrap: PrincOps.OpTrapTable ← PrincOps.ESCTrapTable;
    oldTrapValue: PrincOps.ControlLink = escTrap[ESCAlpha.aBITBLT];
    bbt↑ ← [
      dst: [word: @hasBitBlt, bit: 0], dstBpl: Environment.bitsPerWord,
      src: [word: @dummySrc, bit: 0],
      srcDesc: [srcBpl[Environment.bitsPerWord]],
      width: Environment.bitsPerWord, height: 1,
      flags: [
        direction: forward, disjoint: TRUE, disjointItems: TRUE, gray: FALSE,
        srcFunc: null, dstFunc: null]];
    escTrap[ESCAlpha.aBITBLT] ← LOOPHOLE[BITBLTUnimplemented];
    BitBlt.BITBLT[bbt]; -- microcode will set hasBitBlt=TRUE; software will not.
    escTrap[ESCAlpha.aBITBLT] ← oldTrapValue;
    RETURN};

  BITBLTUnimplemented: PROC [BitBlt.BBptr] = {
    -- If this procedure is invoked, that means that the microcode does not
    -- implement BitBlt (e.g. RavenMesa microcode).
    lf: PrincOps.LocalFrameHandle ← Frame.GetReturnFrame[];
    Frame.WritePC[pc: [Frame.ReadPC[lf]+2], lf: lf];
    -- increment PC past BITBLT instruction--};

  UseADM: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    -- check for ADM code loaded
    IF ~Runtime.IsBound[LOOPHOLE[AdmTTY.CreateTTYInstance]] THEN RETURN[FALSE];
    -- check for presence of LF keyboard
    IF(LOOPHOLE[UserTerminal.keyboard, LONG POINTER TO KeyStations.KeyBits]
        [KeyStations.D1]=up 
      OR LOOPHOLE[UserTerminal.keyboard, LONG POINTER TO KeyStations.KeyBits]
         [KeyStations.D2]=up) THEN RETURN[TRUE];
    -- if we get here we have an LF keyboard and we have ADM code.  See if BitBlt is implemented.
    RETURN[~HasBitBlt[]]
    END;
  
  useADM: BOOLEAN = UseADM[];
  
  ttyHandle: TTY.Handle = TTYMuxCreate[];
  
  TTYMuxCreate: PROC RETURNS [TTY.Handle] = {
    IF ~useADM THEN RETURN[TTY.Create["Hello"L]];
    RETURN[TTY.Create[
      name: "Hello"L,
      ttyImpl: AdmTTY.CreateTTYInstance[
        "Hello"L, NIL, TTY.nullHandle].ttyImpl]]};
  
  BlinkDisplay: PUBLIC PROC = {TTY.BlinkDisplay[ttyHandle]};
    
  CheckUserAbort: PUBLIC PROC = {
    IF TTY.UserAbort[ttyHandle] THEN {ResetAbort[]; ERROR ABORTED}};
    
  EraseTTYChar: PROC [c: CHARACTER] = {
    SELECT c FROM IN [' ..'~] => NULL; CR => RETURN; ENDCASE => EraseTTYChar[' ];
    WriteChar[BS]; WriteChar[' ]; WriteChar[BS]};

  ReadChar: PUBLIC PROC RETURNS [c: CHARACTER] = {
    gotIt: BOOLEAN;
    [gotIt, c] ← GetCommandFileCharacter[];
    IF gotIt THEN RETURN;
    RETURN[TTY.GetChar[ttyHandle]]};

  SetCursor: PUBLIC PROC [c: OthelloDefs.Cursor] = {
    cursor: ARRAY OthelloDefs.Cursor OF UserTerminal.CursorArray = [
      pointer: [
        100000B, 140000B, 160000B, 170000B, 174000B, 176000B, 177000B, 170000B,
	154000B, 114000B, 006000B, 006000B, 003000B, 003000B, 001400B, 001400B],
      ftp: [
        000177B, 076077B, 040037B, 040017B, 070007B, 043703B, 040401B, 040400B,
        000400B, 100436B, 140421B, 160421B, 170036B, 174020B, 176020B, 177020B]];
      IF ~useADM THEN UserTerminal.SetCursorPattern[cursor[c]];
      cursorFlipped ← FALSE};

  cursorFlipped: BOOLEAN;

  FlipCursor: PUBLIC PROC = {
    IF ~useADM THEN  {
      c: UserTerminal.CursorArray ← UserTerminal.GetCursorPattern[];
      FOR i: CARDINAL IN [0..LENGTH[c]) DO c[i] ← Inline.BITNOT[c[i]] ENDLOOP;
      UserTerminal.SetCursorPattern[c]}
    ELSE {
      IF cursorFlipped THEN WriteChar[BS] ELSE WriteChar[SP];
      cursorFlipped ← ~cursorFlipped}};

  FlushInput: PROC = {
    UNTIL TTY.CharsAvailable[ttyHandle] = 0 DO
      [] ← TTY.GetChar[ttyHandle] ENDLOOP};

  NewLine: PUBLIC PROC = {WriteChar[CR]};

  ResetAbort: PROC = {TTY.ResetUserAbort[ttyHandle]};
  
  WriteChar: PUBLIC PROC [c: CHARACTER] = {
    IF prometheusBound AND OthelloDefs.SuppressOutput[] THEN RETURN;
    TTY.PutChar[ttyHandle, c]};

  WriteLine: PUBLIC PROC [s: LONG STRING] = {WriteString[s]; NewLine[]};

  WriteString: PUBLIC PROC [s: LONG STRING] = {
    IF prometheusBound AND OthelloDefs.SuppressOutput[] THEN RETURN;
    IF s # NIL THEN TTY.PutString[ttyHandle, s]};

  command:      LONG STRING ← NIL;
  commandIndex: CARDINAL ← 0;

  CommandFileActive: PROC RETURNS [BOOLEAN] = INLINE {RETURN[command#NIL]};

  GetCommandFileCharacter: PROC RETURNS [
    isThere: BOOLEAN, c: CHARACTER] = INLINE {
    IF command # NIL THEN {
      IF commandIndex >= command.length THEN  {
        Heap.systemZone.FREE[@command]; command ← NIL}
      ELSE {
       commandIndex ← commandIndex + 1;
       RETURN[TRUE, command[commandIndex-1]]}};
    RETURN[FALSE, 0C]};

  SetCommandString: PUBLIC PROC [s: LONG STRING] = {
    IF command # NIL THEN Heap.systemZone.FREE[@command];
    command ← s; commandIndex ← 0};

  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Initialization Stuff
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  GetTime: PROC = {
    timeTrys:       CARDINAL ← 3;
    time:           System.GreenwichMeanTime;
    LTPs:           System.LocalTimeParameters;
    timeFromServer: BOOLEAN ← TRUE;
    getTimeString:  LONG STRING ← NIL;
    [time, LTPs] ← OthelloOps.GetTimeFromTimeServer[
      ! OthelloOps.TimeServerError => IF error=noResponse THEN {
          IF (timeTrys ← timeTrys-1)=0 THEN {timeFromServer ← FALSE; CONTINUE}
	  ELSE {IF timeTrys=2 THEN WriteString["Locating Time Server..."L]; RETRY}}
        ELSE IF error=noCommunicationFacilities THEN {
	  WriteLine["not Communication Facilities to find time"L];
	  timeFromServer ← FALSE; CONTINUE}
	ELSE ERROR];
    IF timeFromServer THEN {
      IF timeTrys#3 THEN WriteLine["success"L];
      System.SetLocalTimeParameters[LTPs];
      OthelloOps.SetProcessorTime[time]; 
      RETURN};
    WriteLine["failed.\rPlease enter time information (type ? for help)"L];
    getTimeString ← Heap.systemZone.NEW[StringBody[10]];
    LTPs ← GetTimeZoneFromUser[@getTimeString ! TryAgain => RETRY];
    System.SetLocalTimeParameters[LTPs];
    spacesInStringOK ← TRUE;
    GetTimeFromUser[@getTimeString ! TryAgain => RETRY];
    spacesInStringOK ← FALSE;
    Heap.systemZone.FREE[@getTimeString]};
    
  GetTimeFromUser: PROC [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 {
      WriteString["Current time"L]; WriteTime[System.GetGreenwichMeanTime[]];
      IF ~Yes["Do you wish to change the time?: "L] THEN RETURN};
    IF p#NIL THEN p.length ← 0;
    DO
      time: System.GreenwichMeanTime;
      GetName[timePrompt, p];
      time ← PackedTimeFromString[p↑, FALSE];
      IF time=System.gmtEpoch THEN {
        WriteLine["Invalid date/time -- please try again."L]; LOOP};
      WriteString["Set time to"L]; WriteTime[time];
      IF Yes["Okay?: "L] THEN {
        OthelloOps.SetProcessorTime[time]; EXIT}
      ELSE LOOP
      ENDLOOP};
    
  GetTimeZoneFromUser: PROC [string: POINTER TO LONG STRING] 
    RETURNS [ltp: System.LocalTimeParameters] = {
    GetNum: PROC [
      prompt: STRING, min, max, default: INTEGER]
      RETURNS [ans: INTEGER] = {
      string.length ← 0;
      String.AppendDecimal[string↑, default];
      DO
        isNeg: BOOLEAN ← FALSE;
        WriteString[prompt];
        WriteChar['[]; IF ans<0 THEN WriteChar['-]; WriteLongNumber[ABS[min]];
        WriteString[".."L]; WriteLongNumber[max]; 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;
        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 => {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 => {WriteLine["\rAlmost always zero"L]; RETRY}];
    ltp.beginDST ← GetNum[prompt: "First day of DST "L, min: 0, max: 366, default: 121
    ! Question => {WriteLine[dstSpiel]; RETRY}];
    ltp.endDST ← GetNum[prompt: "Last day of DST "L,  min: 0, max: 366, default: 305
    ! Question => {WriteLine[dstSpiel]; RETRY}]};
    
  PrintHerald: PROC = {
    string: STRING  = "Hello 11.0 of "L;
    IF useADM THEN WriteChar['\032]; -- clear screen
    WriteString[
      "Copyright (C) Xerox Corporation 1983, 1984. All rights reserved.\n\n"L]; 
    WriteString[string]; WriteTime[Runtime.GetBuildTime[], FALSE, pacific]};

  PrintPIDs: PROC = {
    w: Format.StringProc = {WriteString[s]};
    WriteString["Processor = "L];
    Format.HostNumber[proc: w,
      hostNumber: LOOPHOLE[SpecialSystem.GetProcessorID[]], format: hex];
    WriteString[" = "L];
    Format.HostNumber[proc: w,
      hostNumber: LOOPHOLE[SpecialSystem.GetProcessorID[]], format: octal];
    WriteString["B = "L];
    Format.HostNumber[proc: w, hostNumber:
      LOOPHOLE[SpecialSystem.GetProcessorID[]], format: productSoftware];
    NewLine[];
    };
    
  PrintMemorySize: PROC = {
  size: LONG CARDINAL ← ((SpecialSpace.realMemorySize+255)/256)*64;
    WriteString["Memory size = "L];
    WriteLongNumber[size*2];
    WriteString["K bytes"L];
    NewLine[];
    };
    
  ResetAbort[];
  SetCursor[pointer];
  PrintHerald[];
  PrintPIDs[];
  PrintMemorySize[];
  GetTime[];

  END..


LOG
Time:  1-Oct-81 18:44:29  By: Forrest   Action: Re-do module,
                                        add Time Stuff & Proc ID 
Time:  13-Nov-81 16:27:44  By: Forrest  Action: 8.0e build 
Time:  19-Nov-81  9:26:07  By: Forrest  Make PackedTimeFromString public for
                                        implementing SetBootFileExpirationDate 
Time:  17-Dec-81 17:52:16  By: Fay      Action: 8.0f build -- changed herald. 
Time:  29-Dec-81 14:29:14  By: Fay      Action: 8.0g build -- changed herald. 
Time:  29-Dec-81 14:29:14  By: Forrest  Action: 8.0h build -- changed herald. 
Time:   1-Feb-82 16:11:37  By: Fay      Action: 8.0i build -- changed herald. 
Time:   3-Feb-82 15:02:19  By: Jose     Action: Print processor ID all 3
				        ways using Format. 
Time:   8-Feb-82 17:20:50  By: Fay      Action: 8.0j build -- changed herald.
Time:   1-Mar-82 13:55:31  By: Jose     Action: final 8.0 build -- changed herald.
Time:   20-Aug-82 16:49:26 By: Fasnacht Action: Change to 9.0b.
Time:   16-Sep-82 11:47:54 By: Fasnacht Action: Change to 9.0c.
Time:   24-Sep-82 17:24:53 By: Fasnacht Action: Change to 9.0d.
Time:   30-Sep-82 13:48:48 By: Fasnacht Action: Change to 9.0.
Time:   12-Dec-82 12:50:23 By: Johnsson Action: 10.0c; remove Storage.