-- VolumeInitCommandImpl    modified November 10, 1982 5:51 pm by Taft
-- This file is the command Processor/"Higher level" stuff.
-- It is machine- and device-independent.

DIRECTORY
  File USING [Error, ErrorType, Unknown],
  Inline USING [BITAND, BITOR, LowHalf],
  OthelloDevice USING [CallCommandProc],
  OthelloDefs,
  PhysicalVolume USING [CanNotScavenge, Error, ErrorType],
  Runtime USING [GetBuildTime],
  RuntimeInternal USING [SendMsgSignal],
  Scavenger USING [Error, ErrorType],
  Storage USING [FreeString],
  String USING [AppendLongNumber, StringBoundsFault],
  System USING [LocalTimeParameters, LocalTimeParametersUnknown,
    SetLocalTimeParameters],
  TTY USING [Create, SetEcho, GetChar, Handle, PutChar],
  Time USING [Append, Unpack],
  UserTerminal USING [BlinkDisplay, SetCursorPattern],
  Volume USING [InsufficientSpace, NeedsScavenging, NotOpen, Unknown];

VolumeInitCommandImpl: PROGRAM
IMPORTS
  File, Inline, OthelloDevice, PhysicalVolume, Runtime, RuntimeInternal, Scavenger,
  Storage, String, System, Time, TTY, UserTerminal, Volume
EXPORTS OthelloDefs =
BEGIN

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;

ttyHandle: TTY.Handle = TTY.Create["Othello"];

Cannon: PROC [c: CHARACTER] RETURNS [CHARACTER] = 
  {RETURN[IF (c ← UnMakeUserChar[c]) IN ['A..'Z] THEN c+('a-'A) ELSE c]};

CollectCommand: PROC [
    p: DESCRIPTOR FOR ARRAY OF OthelloDefs.CommandTableRecord]
    RETURNS [CARDINAL] = 
  BEGIN
  userString: STRING ← [100];
  ans: FindAnswer;
  c: CHARACTER;
  WriteString["> "L];
  userString.length ← 0;
  DO SELECT (c ← ReadChar[]) FROM
    DEL => {WriteLine[" XXX"L]; ERROR TryAgain};
    BS, ControlA => EraseBack[userString];
    '? => ExplainOptions[userString, p];
    CR, SP =>
      BEGIN
      IF c=CR AND userString.length=0 THEN {WriteString["\r> "L]; LOOP};
      ans ← FindPossibles[userString, p];
      WITH ans SELECT FROM
        none, many => IF userString[userString.length-1]#SP THEN 
          {AbortCommandFileIfAny[]; UserTerminal.BlinkDisplay[]};
        one => RETURN[index];
        ENDCASE => ERROR;
      END;
    ENDCASE => {WriteChar[c]; StuffChar[userString, MakeUserChar[c]]};
    ENDLOOP;
  END;

Confirm: PUBLIC PROC [how: OthelloDefs.ConfirmType←once] =
  BEGIN
  Nap: PROC = {FOR i: CARDINAL IN [0..LAST[CARDINAL]) DO ENDLOOP};
  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 => {WriteLine["Yes"L]; EXIT};
      'n, 'N, DEL => {WriteLine["No"L]; ERROR TryAgain}; 
      ENDCASE => UserTerminal.BlinkDisplay[];
    ENDLOOP;
  IF how=twice THEN {Nap[]; Confirm[thrice]};
  END;

EraseBack: PROC [userString: STRING] =
  BEGIN
  c: CHARACTER;
  WHILE userString.length#0 DO
    userString.length ← userString.length - 1;
    c ← userString[userString.length];
    EraseTTYChar[UnMakeUserChar[c]];
    IF IsUserChar[c] THEN EXIT;
    ENDLOOP;
  END;

EraseTTYChar: PROC [c: CHARACTER] =
  BEGIN
  SELECT c FROM IN [' ..'~] => NULL; CR => RETURN; ENDCASE => EraseTTYChar[' ];
  TTY.PutChar[ttyHandle, BS];
  TTY.PutChar[ttyHandle, ' ];
  TTY.PutChar[ttyHandle, BS];
  END;

ExplainOptions: PROC [
    userString: STRING,
    commandTable: DESCRIPTOR FOR ARRAY OF OthelloDefs.CommandTableRecord] = 
  BEGIN
  first: BOOLEAN ← TRUE;
  i: CARDINAL;
  WriteChar['?];
  IF userString.length#0 THEN FOR i IN [0..LENGTH[commandTable]) DO
      IF HeadMatch[userString, commandTable[i].name, userString.length] THEN
        BEGIN
        WriteString[IF first THEN "\rCurrent Options Are: "L ELSE ", "L];
        first ← FALSE;
        WriteString[commandTable[i].name];
        END;
      ENDLOOP;
  IF first THEN	-- Didn't match... tell all
    BEGIN
    WriteString["\rValid Commands Are: "L];
    FOR i IN [0..LENGTH[commandTable]) DO
      WriteString[commandTable[i].name];
      IF i+1#LENGTH[commandTable] THEN WriteString[", "L];
      ENDLOOP;
    END;
  WriteString["\r> "L];
  WriteString[userString];
  END;

FindAnswer: TYPE = RECORD[SELECT how:* FROM
  none=> NULL, many =>  NULL, one => [index: CARDINAL], ENDCASE];

FindPossibles: PROC [
    userString: STRING,
    commandTable: DESCRIPTOR FOR ARRAY OF OthelloDefs.CommandTableRecord]
    RETURNS [ans: FindAnswer] = 
  BEGIN
  cmd: CARDINAL;
  head: CARDINAL ← userString.length;
  matchString: STRING;
  StickTail: PROC = 
    BEGIN
    WHILE userString.length#commandTable[cmd].name.length DO
      StuffChar[userString, matchString[userString.length]];
      ENDLOOP;
    END;
  ReduceTail: PROC = 
    BEGIN
    userString.length ← head;
    WHILE Cannon[userString[userString.length]]=Cannon[matchString[userString.length]]
      DO userString.length ← userString.length +1 ENDLOOP;
    END;
  ans ← [none[]];
  IF head=0 THEN RETURN;
  FOR cmd IN [0..LENGTH[commandTable]) DO
    matchString ← commandTable[cmd].name;
    IF HeadMatch[userString, matchString, head] THEN 
      WITH ans SELECT FROM
        none => {ans ← [one[cmd]]; StickTail[]};
        ENDCASE => {ans ← [many[]]; ReduceTail[]};
    ENDLOOP;
  WHILE head#userString.length DO WriteChar[userString[head]]; head ← head+1 ENDLOOP;
  END;

GetName: PUBLIC PROC [
    prompt, dest: STRING, how: OthelloDefs.EchoNoEcho←echo,
    signalQuestion: BOOLEAN ← FALSE, escString: STRING ← NIL] =
  BEGIN
  c: CHARACTER;
  first: BOOLEAN ← TRUE;
  EraseChar: PROC =
    BEGIN
    IF dest.length=0 THEN RETURN;
    dest.length ← dest.length - 1;
    IF how=echo THEN  EraseTTYChar[dest[dest.length]];
    END;
  CWriteC: PROC [c: CHARACTER] = {IF how=echo THEN WriteChar[c]};
  CWriteString: PROC[s: STRING] = {IF how=echo THEN WriteString[s]};

  WriteString[prompt];
  CWriteString[dest];
  DO
    c ← ReadChar[];
    SELECT TRUE FROM
      c=BS, c=ControlA => EraseChar[]; 
      c=SP, c=CR => {NewLine[]; RETURN};
      c=DEL => {WriteLine[" XXX"L]; ERROR TryAgain};
      c=ControlW => WHILE dest.length#0 DO EraseChar[]; ENDLOOP;
      c=ESC AND escString#NIL AND dest.length=0 =>
        BEGIN
        i: CARDINAL;
        dest.length ← escString.length;
        FOR i IN [0..escString.length) DO
          dest[i] ← escString[i]; CWriteC[dest[i]]; ENDLOOP;
        END;
      c='? AND signalQuestion  =>
        {SIGNAL Question;  WriteString[prompt]; CWriteString[dest]};
      dest.length >= dest.maxlength =>
        BEGIN
        WriteLine[" ... String Too Long"L];
        WriteString[prompt];
        dest.length ← 0;
        END;
      c >= SP =>
        BEGIN
        IF first THEN WHILE dest.length#0 DO EraseChar[]; ENDLOOP;
        CWriteC[c];
        dest[dest.length] ← c;
        dest.length ← dest.length + 1;
        END;
      ENDCASE => UserTerminal.BlinkDisplay[];
    first ← FALSE;
    ENDLOOP;
  END;

HeadMatch: PROC [userString, matchString: STRING, head: CARDINAL] RETURNS [BOOLEAN] =
  BEGIN
  i: CARDINAL;
  IF head>matchString.length THEN RETURN[FALSE];
  FOR i IN [0..head) DO
    IF Cannon[userString[i]]#Cannon[matchString[i]] THEN RETURN[FALSE];
    ENDLOOP;
  RETURN[TRUE];
  END;

IsUserChar: PROC [c: UNSPECIFIED] RETURNS [BOOLEAN] = {RETURN[Inline.BITAND[0200B, c]#0]};

MakeUserChar: PROC [c: UNSPECIFIED] RETURNS [UNSPECIFIED] = 
  {RETURN[Inline.BITOR[0200B, c]]};

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

PrintBcdTime: PUBLIC PROC = 
  BEGIN
  calParams: System.LocalTimeParameters = [west, 8, 0, 121, 305];
    -- California time
  string: STRING ← [40];
  string.length ← 0;
    BEGIN
    Time.Append[string, Time.Unpack[Runtime.GetBuildTime[] !
      System.LocalTimeParametersUnknown => GOTO TimeParamsUnknown]];
    EXITS TimeParamsUnknown =>
      BEGIN
      System.SetLocalTimeParameters[calParams];
      Time.Append[string, Time.Unpack[Runtime.GetBuildTime[]]];
      END;
    END;
  WriteString[" of "L];
  WriteLine[string];
  END;

command: STRING ← NIL;
index: CARDINAL ← 0;

SetCommandString: PUBLIC PROC [s: STRING, i: CARDINAL ← 0] = {
  IF command # NIL THEN Storage.FreeString[command];
  command ← s; index ← i};

GetCommandString: PUBLIC PROC RETURNS [s: STRING, i: CARDINAL] = {
  s ← command; i ← index;
  command ← NIL; index ← 0};

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

AbortCommandFileIfAny: PUBLIC PROC =
  {IF CommandFileActive[] THEN
    {WriteLine["[Command file aborted]"L]; SetCommandString[NIL]}};

ReadChar: PUBLIC PROC RETURNS [c: CHARACTER] = {
  IF command # NIL THEN
    BEGIN
    IF index < command.length THEN
      {c ← command[index]; index ← index + 1; RETURN};
    SetCommandString[NIL];
    WriteLine["[End of command file]"L];
    END;
  RETURN[TTY.GetChar[ttyHandle]]};

ReadNumber: PUBLIC PROC [prompt: STRING, min, max, default: LONG CARDINAL]
    RETURNS [ans: LONG CARDINAL] = 
  BEGIN
  i: CARDINAL;
  s: STRING ← [30];
  DO
    s.length ← 0;
    IF default#LAST[LONG CARDINAL] THEN String.AppendLongNumber[s, default, 10];
    WriteString[prompt];
    WriteChar['[];
    WriteLongNumber[min];
    WriteString[".."L];
    WriteLongNumber[max];
    WriteString["]: "L];
    GetName[""L, s];
    ans ← 0;
    FOR i IN [0..s.length) DO
      IF s[i] NOT IN ['0..'9] THEN EXIT;
      ans ← 10*ans + s[i]-'0;
      REPEAT FINISHED => IF ans IN [min..max] THEN RETURN;
      ENDLOOP;
    ReportError["Bad Number !"L];
    ENDLOOP;
  END;

-- Should be more careful about truncation
ReadShortNumber: PUBLIC PROC [prompt: STRING, min, max, default: LONG CARDINAL]
  RETURNS [CARDINAL] =
  {RETURN[Inline.LowHalf[ReadNumber[prompt, min, max, default]]]};
  
DebugAsk: PUBLIC PROC = 
  BEGIN
  AbortCommandFileIfAny[];
  WriteString["\rType ControlP to muddle on........"L];
  WHILE ReadChar[]#ControlP DO ENDLOOP;
  NewLine[];
  END;

ReportError: PUBLIC PROC [message: STRING] = {WriteLine[message]; AbortCommandFileIfAny[]};

RunCommand: PUBLIC PROC [p: DESCRIPTOR FOR ARRAY OF OthelloDefs.CommandTableRecord] =
  BEGIN
  i: CARDINAL;
  SetCursor: PROC =
    BEGIN
    UserTerminal.SetCursorPattern[
      [100000B, 140000B, 160000B, 170000B, 174000B, 176000B,
       177000B, 170000B, 154000B, 114000B, 6000B, 6000B, 3000B,
       3000B, 1400B, 1400B]];
    END;
  []  ← TTY.SetEcho[ttyHandle, FALSE];
  SetCursor[];
  i ← CollectCommand[p ! TryAgain => RETRY];
  NewLine[];
  OthelloDevice.CallCommandProc[p[i].proc
    -- CallCommandProc catches machine- and device-dependent SIGNALs.
    -- Only device-independent SIGNALs should be mentioned here.
    ! PhysicalVolume.Error => 
        BEGIN
        PrintNames: PROC [x: PhysicalVolume.ErrorType] =
          BEGIN
          e: ARRAY PhysicalVolume.ErrorType OF STRING = [
            "badDisk"L, "badSpotTableFull"L, "containsOpenVolumes"L, "diskReadError"L,
            "hardwareError"L, "hasPilotVolume"L, "alreadyAsserted"L,
            "insufficientSpace"L, "invalidHandle"L, "nameRequired"L, "notReady"L,
            "noSuchDrive"L, "noSuchLogicalVolume"L, "pageCountTooSmallForVolume"L,
            "physicalVolumeUnknown"L, "subvolumeHasTooManyBadPages"L,
            "tooManySubvolumes"L, "writeProtected"L, "wrongFormat"L];
          WriteString[e[x]];
          END;
        WriteString["\rPhysicalVolume.Error["L];
        PrintNames[error];
        WriteChar[']];
        DebugAsk[];
        CONTINUE;
        END;
      Scavenger.Error => 
        BEGIN
        PrintNames: PROC [x: Scavenger.ErrorType] =
          BEGIN
          e: ARRAY Scavenger.ErrorType OF STRING = [
            "cannotWriteLog"L, "noSuchPage"L, "orphanNotFound"L, "volumeOpen"L,
            "diskHardwareError"L, "diskNotReady"L];
          WriteString[e[x]];
          END;
        WriteString["\rScavenger.Error["L];
        PrintNames[error];
        WriteChar[']];
        DebugAsk[];
        CONTINUE;
        END;
      File.Error => 
        BEGIN
        PrintNames: PROC [x: File.ErrorType] =
          BEGIN
          e: ARRAY File.ErrorType OF STRING = [
            "insufficientPermissions"L, "immutable"L, "nonuniqueID"L,
            "notImmutable"L, "reservedType"L];
          WriteString[e[x]];
          END;
        WriteString["\rFile.Error["L];
        PrintNames[type];
        WriteChar[']];
        DebugAsk[];
        CONTINUE;
        END;
      PhysicalVolume.CanNotScavenge =>
        {WriteString["\rPhysicalVolume.CanNotScavenge"L]; DebugAsk[]; CONTINUE};
      Volume.InsufficientSpace =>
        {WriteString["\rVolume.InsufficientSpace"L]; DebugAsk[]; CONTINUE};
      Volume.NotOpen => {WriteString["\rVolume.NotOpen"L]; DebugAsk[]; CONTINUE};
      Volume.NeedsScavenging =>
        {WriteLine["\rPlease Scavenge the volume first"L]; AbortCommandFileIfAny[]; CONTINUE};
      Volume.Unknown => {WriteString["\rVolume.Unknown"L]; DebugAsk[]; CONTINUE};
      File.Unknown => {WriteString["\rFile.Unknown"L]; DebugAsk[]; CONTINUE};
      String.StringBoundsFault => 
        {WriteString["\rString.StringBoundsFault"L]; DebugAsk[]; CONTINUE};
      TryAgain => {AbortCommandFileIfAny[]; CONTINUE};
      ANY =>
        BEGIN
        a, b: UNSPECIFIED;
        WriteString["\rUncaught Signal =  "L];
        [b, a]  ← SIGNAL RuntimeInternal.SendMsgSignal;
        WriteOctal[a];
        WriteString[", msg = "L];
        WriteOctal[b];
        DebugAsk[];
        CONTINUE;
        END];
  END;

StuffChar: PROC [userString: STRING, char: CHARACTER] =
  BEGIN
  userString.length ← userString.length + 1;
  IF userString.length=userString.maxlength THEN 
    {WriteLine[" Command too long!"L]; ERROR TryAgain};
  userString[userString.length-1] ← char;
  END;

UnMakeUserChar: PROC [c: CHARACTER] RETURNS [CHARACTER] = 
  {RETURN[Inline.BITAND[177B, c]]};

UpperCase: PUBLIC PROC [c: CHARACTER] RETURNS [CHARACTER] =
  {IF c IN ['a..'z] THEN c ← c + ('A-'a); RETURN[c]};

WriteChar: PUBLIC PROC [c: CHARACTER] = {TTY.PutChar[ttyHandle, UnMakeUserChar[c]]};

WriteFixedWidthNumber: PUBLIC PROC [
  x: LONG CARDINAL, count: CARDINAL, base: CARDINAL ← 10] =
  BEGIN
  WFD: PROC [x: LONG CARDINAL, c: CARDINAL] =
    BEGIN
    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 ' ];
    END;
  WFD[x, 0];
  END;

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

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

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

WriteString: PUBLIC PROC [s: STRING] =
  BEGIN
  i: CARDINAL;
  IF s#NIL THEN FOR i IN [0..s.length) DO WriteChar[s[i]]; ENDLOOP;
  END;

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


END..


LOG
Time: June 1, 1980  2:31 AM		By: Forrest
	Action: upgraded for new formatSA4000

Time: August 29, 1980  10:21 AM		By: BLyon
	Action: added EquivalentSubString, StringToNumber, and UpperCase.

Time: September 3, 1980  10:58 PM	By: Forrest
	Action: Added new physical volume error types.

Time: October 2, 1980  3:39 PM		By: Jose
	Action: Added catch phrases for Floppy formatter signals.

Time: October 2, 1980  3:39 PM		By: Forrest
	Action: Kill Storage&String impls, import String.

Time: February 6, 1981  7:45 PM		By: Luniewski
	Action: New Scavenger.ErrorType's.

Time: February 11, 1981  12:10 PM	By: Gobbel
	Action: Catch System.LocalTimeParametersUnknown.

Time: April 14, 1981  11:33 AM		By: Bruce
	Action: add SetCommandString.

11-Jun-81 10:14:29  Taft  catch SIGNALs from FormatTrident.
19-Feb-82 16:53:04  Taft  remove WriteString \r interpretation since compiler does this now.
 4-Jun-82  8:39:32  Taft  Add GetCommandString
September 9, 1982 5:18 pm  Taft  Mods for new Cedar Login
September 12, 1982 1:08 pm  Taft  Add CommandFileActive, AbortCommandFileIfAny; Confirm just returns if command file is active; misc command file cleanup