-- Copyright (C) 1986  by Xerox Corporation. All rights reserved. 
-- TajoCEnvUtil.mesa
-- NFS		29-May-86 11:57:28
-- MEW		14-Apr-86 16:37:19

-- Implementation of CEnvUtil for Tajo.

DIRECTORY
  Ascii USING [BS, CR, LF, NUL, SP, TAB],
  BucketAlloc USING [Destroy],
  CEnvUtil USING [
    AppendCmdProc, ArgSeq, Data, DataHandle, error, normal,
    Outcome, ReadAndRun, StandardStream, StringSeq, ToolType,
    zone],
  CmFile USING [Error, FreeString, UserDotCmLine],
  Context USING [
    Create, Data, Destroy, Find, Type, UniqueType],
  CRuntime USING [arrayZone, log, NoteAbortedProcess, z],
  CString USING [CString, LongStringToCString],
  CWindowLib,
  Emulator USING [
    Create, Destroy, EmuSWType, PutChar, StartLog, StopLog,
    SetRefresh, table],
  Exec USING [
    AddCommand, CheckForAbort, ExecProc, FreeTokenString,
    GetChar, GetToken, GetTTY, Handle, Object, OutputProc,
    RemoveCommand],
  FileSW USING [GetFile],
  Format USING [LongOctal, StringProc],
  FormSW USING [
    AllocateItemDescriptor, BooleanItem, ClientItemsProcType,
    CommandItem, DisplayItem, NotifyProcType, line0, line1,
    ProcType, StringItem],
  Heap USING [Delete],
  Inline USING [BITAND, LowHalf],
  MFile USING [
    Acquire, AppendErrorMessage, EnumerateDirectory, EnumerateProc, Error,
    Handle, maxNameLength, Object, Release, ValidFilename],
  MLoader USING [Handle, Object],
  MStream USING [
    Error, GetLength, Log, PleaseReleaseProc, ReadOnly,
    SetLogReadLength],
  Pipe USING [Create, Delete, Handle],
  Process USING [Abort, GetCurrent, Pause, SecondsToTicks],
  Put USING [Text],
  Runtime USING [GetBcdTime, IsBound],
  StartState USING [
    EnumerateHandles, EnumerateProc, Unload, UnloadError,
    UnloadFromFile, zone],
  Stream USING [
    defaultObject, Delete, EndOfStream, Handle,
    PutByteProcedure, PutChar, PutProcedure, SetSSTProcedure,
    SubSequenceType],
  String USING [
    AppendChar, AppendCharAndGrow, AppendExtensionIfNeeded,
    AppendNumber, AppendString, AppendStringAndGrow, StringBoundsFault,
    CopyToNewString, Empty, Equivalent, FreeString,
    InvalidNumber, StringToLongNumber],
  Time USING [Append, Unpack],
  Token USING [
    Filtered, FilterProcType, FreeStringHandle,
    FreeTokenString, Handle, Item, MaybeQuoted, Skip,
    StringToHandle, Switches, UnterminatedQuote, WhiteSpace],
  Tool USING [
    Create, Destroy, MakeClientSW, MakeFormSW, MakeMsgSW,
    MakeSWsProc, MakeTTYSW, UnusedLogName],
  ToolWindow USING [
    Handle, TransitionProcType, WindowForSubwindow],
  TTY USING [
    CharStatus, Create, Destroy, GetEditedString, Handle,
    LineOverflow, PutChar, PutString, ResetUserAbort,
    Rubout],
  TTYConstants USING [
    blinkDisplay, normal, removeChars, setBackingSize],
  TTYSW USING [GetTTYHandle],
  UserInput USING [
    AttentionProcType, SetAttention, StringProcType],
  UserTerminal USING [BlinkDisplay],
  Version USING [Append],
  Window USING [Handle, Object];

TajoCEnvUtil: PROGRAM
  IMPORTS
    BucketAlloc, CEnvUtil, CmFile, Context, CRuntime,
    CString, Emulator, Exec, FileSW, Format,
    FormSW, Heap, Inline, MFile, MStream, Pipe, Process, Put,
    Runtime, StartState, Stream, String, Time, Token, Tool,
    ToolWindow, TTY, TTYSW, UserInput, UserTerminal, Version
  EXPORTS CEnvUtil =
  {
  OPEN CEnvUtil;


  Data: PUBLIC TYPE = MACHINE DEPENDENT RECORD [
    stdin(0): LONG STRING ← zone.NEW[
      StringBody [MFile.maxNameLength]],
    stdout(2): LONG STRING ← zone.NEW[
      StringBody [MFile.maxNameLength]],
    stderr(4): LONG STRING ← zone.NEW[
      StringBody [MFile.maxNameLength]],
    debug(6): BOOLEAN ← FALSE,
    stopOnError(7): BOOLEAN ← TRUE,
    endingToolInstance(8): BOOLEAN ← FALSE,
    msgSW(9): Window.Handle ← NIL,  -- remains nil for CExec
    iomode(11): INTEGER ← 0,
    cToolPart(12): SELECT tool(12): ToolType FROM
      ctool => [
        formSW(13): Window.Handle ← NIL,
        ttySW(15): Window.Handle ← NIL,
        p(17): PROCESS,
        emuPart(18): SELECT tty(18): * FROM
          emulation => [
            pH(19): Pipe.Handle,
            toBuffer(21), ttyStream(23): Stream.Handle,
            modeVal(25): LONG CARDINAL ← 0,
            mode(27): Stream.SubSequenceType ←
              TTYConstants.normal,
            logging(28): BOOLEAN ← FALSE],
          regular => [logStream(19): Stream.Handle ← NIL],
          ENDCASE],
      cexec => [],
      temp => [],
      ENDCASE];

  Handle: TYPE = LONG POINTER TO Data;
  CTHandle: TYPE = LONG POINTER TO ctool Data;
  RCTHandle: TYPE = LONG POINTER TO regular ctool Data;
  ECTHandle: TYPE = LONG POINTER TO emulation ctool Data;

  FileHandle: PUBLIC TYPE = MFile.Handle;
  LoadHandle: PUBLIC TYPE = MLoader.Handle;

  FileError: PUBLIC ERROR [message: LONG STRING] = CODE;

  AcquireFile: PUBLIC PROCEDURE [name: LONG STRING]
    RETURNS [fh: FileHandle] = {
    fh ← MFile.Acquire[
      name: name, access: readOnly, release: [] !
      MFile.Error => {
        msg: LONG STRING ← [100];
        MFile.AppendErrorMessage[msg, code, file];
        ERROR FileError[msg];
        }];
    };

  ReleaseFile: PUBLIC PROCEDURE [fh: FileHandle] = {
    MFile.Release[fh ! MFile.Error => CONTINUE]; };

  UniqueFilePrefix: PUBLIC PROCEDURE [fileName: LONG STRING]
    RETURNS [matches: CARDINAL] = {
    Check: PROCEDURE [ext: LONG STRING] RETURNS [BOOLEAN] =
      BEGIN
      NoteMatch: MFile.EnumerateProc =
        BEGIN
        IF matches = 0 THEN {
          String.AppendString[
            to: matchName, from: name];
          matches ← matches.SUCC}
        ELSE
          IF ~String.Equivalent[name, matchName] THEN {
            matchName.length ← 0;
            String.AppendString[to: matchName, from: name];
            matches ← matches.SUCC;
            };
        RETURN[matches > 1];  -- stop if already not unique.
        END;  -- NoteMatch
      extName: LONG STRING ← [MFile.maxNameLength];
      matchName: LONG STRING ← [MFile.maxNameLength];
      matchName.length ← 0;
      IF ext # NIL THEN {
        extName.length ← 0;
        String.AppendString[
          to: extName, from: fileName !
          String.StringBoundsFault => GOTO TooLong];
        String.AppendString[
          to: extName, from: ext !
          String.StringBoundsFault => GOTO TooLong];
        }
      ELSE extName ← fileName;
      MFile.EnumerateDirectory[
        extName, NoteMatch, filesOnly];
      IF matches = 1 THEN {
        fileName.length ← 0;
	  String.AppendString[to: fileName, from: matchName];};
      RETURN[matches # 0];  -- TRUE if we have a match or an ambiguous name
      EXITS TooLong => RETURN[matches # 0];
      END;  -- Check 
    matches ← 0;
    IF ~MFile.ValidFilename[fileName] THEN RETURN;
    IF Check[NIL] OR Check[".archivebcd"L] OR Check[".bcd"L]
      OR Check["*.archivebcd"L] OR Check["*.bcd"L] THEN NULL;
    };

  GetStdStreams: PUBLIC PROCEDURE [
    data: Handle,
    defaultStdin, defaultStdout, defaultStderr:
      Stream.Handle]
    RETURNS [stdin, stdout, stderr: Stream.Handle] = {
    stdin ←
      IF String.Empty[LOOPHOLE[data, Handle].stdin] THEN
      defaultStdin
      ELSE MStream.ReadOnly[
        data.stdin, [] !
        MStream.Error => ERROR StdStreamError[stdin]];
    stdout ←
      IF String.Empty[data.stdout] THEN defaultStdout
      ELSE MStream.Log[
        data.stdout, [SetReadLengthAndRefuse] !
        MStream.Error => {
          Stream.Delete[stdin];
          ERROR StdStreamError[stdout]}];
    stderr ←
      SELECT TRUE FROM
        String.Empty[data.stderr] => defaultStderr,
        String.Equivalent[
          data.stdout, LOOPHOLE[data, Handle].stderr] =>
          stdout
        ENDCASE => MStream.Log[
          data.stderr, [SetReadLengthAndRefuse] !
          MStream.Error => {
            Stream.Delete[stdin];
            Stream.Delete[stdout];
            ERROR StdStreamError[stderr]}];
    };

  SetReadLengthAndRefuse: MStream.PleaseReleaseProc = {
    MStream.SetLogReadLength[
      stream, MStream.GetLength[stream]];
    RETURN[no];  -- no conflict if read access requested
    };

  StdStreamError: PUBLIC ERROR [
    whichStream: StandardStream] = CODE;

  GetFileInputStream: PUBLIC PROCEDURE [buffer: LONG STRING]
    RETURNS [sH: Stream.Handle] = {
    fileName: LONG STRING;
    tH: Token.Handle ← Token.StringToHandle[buffer];
    Token.Skip[tH, NIL, Token.WhiteSpace, TRUE];
    fileName ← Token.Item[tH];
    IF fileName = NIL THEN {
      tH ← Token.FreeStringHandle[tH]; RETURN[NIL]; };
    sH ← MStream.ReadOnly[
      fileName, [] !
      MStream.Error => {sH ← NIL; CONTINUE; }];
    IF sH = NIL THEN {  -- Try adding ".cscript" extension.
      extensionLength: CARDINAL = 8;
      extendedName: LONG STRING ← String.CopyToNewString[
        fileName, zone, extensionLength];
      IF String.AppendExtensionIfNeeded[
        to: @extendedName, extension: ".cscript"L, z: zone]
        THEN
        sH ← MStream.ReadOnly[
          extendedName, [] ! MStream.Error => {CONTINUE; }];
      String.FreeString[zone, extendedName];
      };
    [] ← Token.FreeTokenString[fileName];
    tH ← Token.FreeStringHandle[tH];
    };

  AppendLine: PUBLIC PROCEDURE [
    line: LONG POINTER TO LONG STRING,
    getChar: PROC RETURNS [c: CHAR]] = {
    DO
      c: CHAR ← getChar[
        ! MStream.Error, Stream.EndOfStream => EXIT];
      String.AppendCharAndGrow[to: line, c: c, z: zone];
      IF c = Ascii.CR THEN EXIT;
      ENDLOOP;
    };

  contextType: Context.Type;

  MakeCTool: PROCEDURE [emulation: BOOLEAN] = {
    name: LONG STRING ← zone.NEW[StringBody [60]];
    name.length ← 0;
    String.AppendString[name, "CTool "L];
    Version.Append[name];
    String.AppendString[name, " of "L];
    Time.Append[name, Time.Unpack[Runtime.GetBcdTime[]]];
    [] ← Tool.Create[
      makeSWsProc: IF emulation THEN MakeSWsEmu ELSE MakeSWs,
      initialState: default,
      initialBox: [
      [0, 60], [512, IF emulation THEN 480 ELSE 465]],
      clientTransition: ClientTransition, name: name,
      tinyName1: "CTool"L, cmSection: "CTool"L];
    zone.FREE[@name];
    };

  Another: FormSW.ProcType = {
    h: CTHandle ← Context.Find[
      contextType, ToolWindow.WindowForSubwindow[sw]];
    WITH hh: h SELECT FROM
      emulation => MakeCTool[TRUE];
      regular => MakeCTool[FALSE];
      ENDCASE;
    };

  DestroyInstance: FormSW.ProcType = {
    Tool.Destroy[ToolWindow.WindowForSubwindow[sw]]; };

  messageLines: CARDINAL = 3;

  MakeSWs: Tool.MakeSWsProc = {  -- MakeSWsProc without terminal emulation
    h: RCTHandle;
    logName: LONG STRING ← [12];
    h ← zone.NEW[regular ctool Data];
    Tool.UnusedLogName[unused: logName, root: "CTool.log"L];
    Context.Create[contextType, h, FreeToolData, window];
    h.msgSW ← Tool.MakeMsgSW[
      window: window, lines: messageLines];
    h.formSW ← Tool.MakeFormSW[
      window: window, formProc: MakeForm];
    h.ttySW ← Tool.MakeTTYSW[window: window, name: logName];
    h.logStream ← FileSW.GetFile[h.ttySW].s;
    UserInput.SetAttention[h.ttySW, AbortProgram];
    h.p ← FORK CallReadAndRun[
      LOOPHOLE[h], TTYSW.GetTTYHandle[h.ttySW]];
    };

  MakeSWsEmu: Tool.MakeSWsProc = {  --MakeSWsProc with terminal emulation
    h: ECTHandle;
    tH: TTY.Handle;
    h ← zone.NEW[emulation ctool Data];
    Context.Create[contextType, h, FreeToolData, window];
    h.msgSW ← Tool.MakeMsgSW[
      window: window, lines: messageLines];
    h.formSW ← Tool.MakeFormSW[
      window: window, formProc: MakeFormEmu];
    [h: h.pH, producer: h.toBuffer, consumer: h.ttyStream] ←
      Pipe.Create[200];
    h.ttyStream.put ← WriteToWindow;
    h.ttyStream.putByte ← Stream.defaultObject.putByte;
    -- Pipe.Create set putByte to a proc. that raises Stream.InvalidOperation.
    h.ttyStream.setSST ← SetSST;
    h.ttyStream.clientData ← h;
    h.ttySW ← Tool.MakeClientSW[
      window: window, clientProc: MakeEmulator,
      clientData: NIL, swType: Emulator.EmuSWType];
    UserInput.SetAttention[h.ttySW, AbortProgram];
    tH ← TTY.Create[
      name: NIL, backingStream: NIL, ttyImpl: h.ttyStream];
    h.p ← FORK ReadAndRunThenCleanUp[h, tH];
    };

  CallReadAndRun: PROCEDURE [h: CTHandle, tH: TTY.Handle] = {
    AppendCmd: AppendCmdProc = {
      RETURN[CallGetEditedString[tH, buffer]]; };
    [] ← ReadAndRun[h, tH, AppendCmd];
    };

  ReadAndRunThenCleanUp: PROCEDURE [
    h: ECTHandle, tH: TTY.Handle] = {
    AppendCmd: AppendCmdProc = {
      RETURN[CallGetEditedString[tH, buffer]]; };
    [] ← ReadAndRun[h, tH, AppendCmd];
    TTY.Destroy[tH, TRUE];
    Pipe.Delete[h.pH];
    Emulator.Destroy[h.ttySW];
    };

  AbortProgram: UserInput.AttentionProcType = {
    h: CTHandle = Context.Find[
      contextType, ToolWindow.WindowForSubwindow[window]];
    CRuntime.NoteAbortedProcess[h.p];
    };

  FreeToolData: PROCEDURE [
    d: Context.Data, w: Window.Handle] = {
    h: Handle = LOOPHOLE[d];
    zone.FREE[@h.stdin];
    zone.FREE[@h.stdout];
    zone.FREE[@h.stderr];
    zone.FREE[@d];
    };

  FreeData: PUBLIC PROCEDURE [h: Handle] = {
    IF h = NIL THEN RETURN;
    IF h.stdin # NIL THEN zone.FREE[@h.stdin];
    IF h.stdout # NIL THEN zone.FREE[@h.stdout];
    IF h.stderr # NIL THEN zone.FREE[@h.stderr];
    };


  ClientTransition: ToolWindow.TransitionProcType = {
    IF new = inactive THEN {
      h: CTHandle ← Context.Find[contextType, window];
      h.endingToolInstance ← TRUE;
      zone.FREE[@h.stdin];
      zone.FREE[@h.stdout];
      zone.FREE[@h.stderr];
      Process.Abort[h.p];
      JOIN h.p;
      Context.Destroy[contextType, window];
      };
    };

  FormItems: TYPE = {
    another, destroy, debug, stopOnError, stdin, stdout,
    stderr};

  MakeForm: FormSW.ClientItemsProcType = {
    OPEN FormSW;
    nItems: CARDINAL = FormItems.LAST.ORD + 1;
    h: Handle ← Context.Find[
      contextType, ToolWindow.WindowForSubwindow[sw]];
    items ← AllocateItemDescriptor[nItems];
    items[FormItems.another.ORD] ← CommandItem[
      tag: "Another"L, place: [1, line0], proc: Another];
    items[FormItems.destroy.ORD] ← CommandItem[
      tag: "Destroy"L, place: [150, line0],
      proc: DestroyInstance];
    items[FormItems.debug.ORD] ← BooleanItem[
      tag: "Debug"L, place: [300, line0], switch: @h.debug];
    items[FormItems.stopOnError.ORD] ← BooleanItem[
      tag: "StopScriptOnError"L, place: [365, line0],
      switch: @h.stopOnError];
    items[FormItems.stdin.ORD] ← StringItem[
      tag: "stdin"L, place: [1, line1], string: @h.stdin];
    items[FormItems.stdout.ORD] ← StringItem[
      tag: "stdout"L, place: [150, line1],
      string: @h.stdout];
    items[FormItems.stderr.ORD] ← StringItem[
      tag: "stderr"L, place: [300, line1],
      string: @h.stderr];
    RETURN[items: items, freeDesc: TRUE];
    };

  FormItemsEmu: TYPE = {
    another, destroy, debug, stopOnError, writeLog, stdin,
    stdout, stderr};

  MakeFormEmu: FormSW.ClientItemsProcType = {
    OPEN FormSW;
    nItems: CARDINAL = FormItemsEmu.LAST.ORD + 1;
    h: ECTHandle ← Context.Find[
      contextType, ToolWindow.WindowForSubwindow[sw]];
    items ← AllocateItemDescriptor[nItems];
    items[FormItemsEmu.another.ORD] ← CommandItem[
      tag: "Another"L, place: [1, line0], proc: Another];
    items[FormItemsEmu.destroy.ORD] ← CommandItem[
      tag: "Destroy"L, place: [90, line0],
      proc: DestroyInstance];
    items[FormItemsEmu.debug.ORD] ← BooleanItem[
      tag: "Debug"L, place: [180, line0], switch: @h.debug];
    items[FormItemsEmu.stopOnError.ORD] ← BooleanItem[
      tag: "StopScriptOnError"L, place: [245, line0],
      switch: @h.stopOnError];
    items[FormItemsEmu.writeLog.ORD] ← BooleanItem[
      tag: "WriteLog"L, place: [370, line0],
      proc: SetEmuLogging, switch: @h.logging];
    items[FormItemsEmu.stdin.ORD] ← StringItem[
      tag: "stdin"L, place: [1, line1], string: @h.stdin];
    items[FormItemsEmu.stdout.ORD] ← StringItem[
      tag: "stdout"L, place: [150, line1],
      string: @h.stdout];
    items[FormItemsEmu.stderr.ORD] ← StringItem[
      tag: "stderr"L, place: [300, line1],
      string: @h.stderr];
    RETURN[items: items, freeDesc: TRUE];
    };

  -- Emulation window procs.

  MakeEmulator: PROCEDURE [
    sw: Window.Handle, clientData: LONG POINTER] = {
    h: ECTHandle = Context.Find[
      contextType, ToolWindow.WindowForSubwindow[sw]];
    Emulator.Create[
      sw: sw, data: Emulator.table[vt100],
      typeIn: WriteToBuffer,
      otherData: Emulator.table[xvt52], refresh: never,
      logfile: "EmuCTool.log"L, writeToLog: h.logging];
    Emulator.SetRefresh[sw, always];
    };

  WriteToBuffer: UserInput.StringProcType = {
    h: ECTHandle ← Context.Find[
      contextType, ToolWindow.WindowForSubwindow[window]];
    FOR i: CARDINAL IN [0..string.length) DO
      Stream.PutChar[h.toBuffer, string[i]]; ENDLOOP;
    };

  WriteToWindow: Stream.PutProcedure = {
    h: ECTHandle = sH.clientData;
    crMode: BOOLEAN ← Inline.BITAND[
      h.iomode, CWindowLib.crMode] # 0;
    SELECT h.mode FROM
      TTYConstants.normal => {
        FOR i: CARDINAL IN
          [block.startIndex..block.stopIndexPlusOne) DO
          Emulator.PutChar[
            h.ttySW, LOOPHOLE[block.blockPointer[i]]];
          IF ~crMode AND block.blockPointer[i] = Ascii.CR.ORD
            THEN Emulator.PutChar[h.ttySW, Ascii.LF];
          ENDLOOP;
        };
      TTYConstants.setBackingSize, TTYConstants.removeChars
        => {
        FOR i: CARDINAL IN
          [block.startIndex..block.stopIndexPlusOne) DO
          h.modeVal ←
            h.modeVal * 256 + block.blockPointer[i];
          ENDLOOP;
        };
      ENDCASE;
    };

  SetSST: Stream.SetSSTProcedure = {
    h: ECTHandle = sH.clientData;
    SELECT h.mode FROM
      TTYConstants.removeChars =>
        FOR i: CARDINAL IN [0..Inline.LowHalf[h.modeVal]) DO
          Emulator.PutChar[h.ttySW, Ascii.BS];
          Emulator.PutChar[h.ttySW, Ascii.SP];
          Emulator.PutChar[h.ttySW, Ascii.BS];
          ENDLOOP;
      TTYConstants.setBackingSize => {};
      TTYConstants.blinkDisplay =>
        UserTerminal.BlinkDisplay[];
      ENDCASE;
    h.mode ← sst;
    h.modeVal ← 0;
    };

  SetEmuLogging: FormSW.NotifyProcType = {
    h: ECTHandle = Context.Find[
      contextType, ToolWindow.WindowForSubwindow[sw]];
    IF h.logging THEN Emulator.StartLog[h.ttySW]
    ELSE Emulator.StopLog[h.ttySW];
    };

  CToolCommand: Exec.ExecProc = {
    token, switches: LONG STRING;
    sense: BOOLEAN ← TRUE;
    emulation: BOOLEAN ← FALSE;
    [token: token, switches: switches] ← Exec.GetToken[h];
    IF switches # NIL THEN
      FOR i: CARDINAL IN [0..switches.length) DO
        SELECT switches[i] FROM
          '~, '- => sense ← ~sense;
          'e, 'E => {emulation ← sense; EXIT};
          ENDCASE => sense ← TRUE;
        ENDLOOP;
    [] ← Exec.FreeTokenString[token];
    [] ← Exec.FreeTokenString[switches];
    IF emulation
      AND ~Runtime.IsBound[LOOPHOLE[Emulator.Create]] THEN
      Exec.OutputProc[h]["Emu.bcd not loaded"L]
    ELSE MakeCTool[emulation];
    };

  ErrorCToolCommand: Exec.ExecProc = {
    Exec.OutputProc[h][
      "Can not create CTool in this environment.  Use CExec.~"L];
    };


  CExecCommand: Exec.ExecProc = {
    AbortWatcher: PROCEDURE = {
      ENABLE ABORTED => CONTINUE;
      DO
        Process.Pause[Process.SecondsToTicks[2]];
        IF Exec.CheckForAbort[h] THEN {
          TTY.ResetUserAbort[Exec.GetTTY[h]];
          CRuntime.NoteAbortedProcess[cExecProcess];
          };
        ENDLOOP;
      };
    NormalAppendCmd: AppendCmdProc = {
      RETURN[CallGetEditedString[tH, buffer]]; };
    OneStringAppendCmd: AppendCmdProc = {
      IF pos >= restOfCmdLine.length THEN
        String.AppendString[to: buffer↑, from: "↑\N"L]
      ELSE AppendLine[buffer, NextChar];
      RETURN[FALSE];
      };
    NextChar: PROCEDURE RETURNS [c: CHAR] = {
      IF pos >= restOfCmdLine.length THEN RETURN[Ascii.CR];
      c ← restOfCmdLine[pos];
      pos ← pos.SUCC;
      };
    data: Handle ← zone.NEW[cexec Data];
    tH: TTY.Handle = Exec.GetTTY[h];
    cExecProcess: PROCESS = Process.GetCurrent[];
    abortWatchProcess: PROCESS;
    restOfCmdLine: LONG STRING ← GetRestOfCmdLine[h];
    pos: CARDINAL;
    intOutcome: Outcome;
    appendCmd: AppendCmdProc;
    data↑ ← [cToolPart: cexec[]];
    IF restOfCmdLine = NIL THEN {
      name: LONG STRING ← zone.NEW[StringBody [60]];
      write: Format.StringProc = Exec.OutputProc[h];
      name.length ← 0;
      String.AppendString[name, "CExec "L];
      Version.Append[name];
      String.AppendString[name, " of "L];
      Time.Append[name, Time.Unpack[Runtime.GetBcdTime[]]];
      write[name];
      zone.FREE[@name];
      write["\NCurrent status:\N"L];
      PrintStatus[LOOPHOLE[data], tH];
      appendCmd ← NormalAppendCmd;
      }
    ELSE {pos ← 0; appendCmd ← OneStringAppendCmd; };
    {
    ENABLE {
      ABORTED => {
        Process.Abort[abortWatchProcess];
        JOIN abortWatchProcess;
        CONTINUE;
        };
      UNWIND => {
        Process.Abort[abortWatchProcess];
        JOIN abortWatchProcess;
        };
      };
    abortWatchProcess ← FORK AbortWatcher[];
    intOutcome ← ReadAndRun[LOOPHOLE[data], tH, appendCmd];
    Process.Abort[abortWatchProcess];
    JOIN abortWatchProcess;
    };
    zone.FREE[@data.stdin];
    zone.FREE[@data.stdout];
    zone.FREE[@data.stderr];
    zone.FREE[@data];
    IF restOfCmdLine # NIL THEN {
      zone.FREE[@restOfCmdLine];
      IF intOutcome # normal THEN outcome ← error;};
    };

  GetRestOfCmdLine: PROCEDURE [h: Exec.Handle]
    RETURNS [LONG STRING] = {
    line: LONG STRING;
    lineMarker: CHAR = '%;
    c: CHAR ← Exec.GetChar[h];
    WHILE c = Ascii.SP OR c = Ascii.TAB DO
      c ← Exec.GetChar[h]; ENDLOOP;
    IF c = Ascii.CR OR c = Ascii.NUL THEN RETURN[NIL];
    line ← zone.NEW[StringBody [80]];
    line.length ← 0;
    UNTIL c = Ascii.NUL OR c = Ascii.CR DO
      String.AppendCharAndGrow[
        to: @line, c: IF c = lineMarker THEN Ascii.CR ELSE c,
        z: zone];
      c ← Exec.GetChar[h];
      ENDLOOP;
    String.AppendCharAndGrow[
      to: @line, c: Ascii.CR, z: zone];
    RETURN[line];
    };

  CallGetEditedString: PROCEDURE [
    tH: TTY.Handle, buffer: LONG POINTER TO LONG STRING]
    RETURNS [lineDeleted: BOOLEAN ← FALSE] = {
    CommandLineRead: PROCEDURE [c: CHAR]
      RETURNS [status: TTY.CharStatus] = {
      IF c = '\N THEN {TTY.PutChar[tH, c]; RETURN[stop]; }
      ELSE RETURN[ok];
      };
    [] ← TTY.GetEditedString[
      h: tH, s: buffer↑, t: CommandLineRead !
      TTY.LineOverflow => {
        ns ← String.CopyToNewString[
          s: s, z: zone, longer: s.maxlength / 2];
        String.FreeString[z: zone, s: s];
        buffer↑ ← ns;
        RESUME [ns];
        };
      TTY.Rubout => {
        TTY.PutString[tH, " XXX"L];
        lineDeleted ← TRUE;
        CONTINUE;
        }];
    };

  CEnvironmentCommand: Exec.ExecProc = {
    -- This prevents accidentally loading a new instance. --
    };

  RemoveCommands: Exec.ExecProc = {
    Exec.RemoveCommand[h, "CTool.~"L];
    Exec.RemoveCommand[h, "CExec.~"L];
    Exec.RemoveCommand[h, "CEnvironment.~"L];
    BucketAlloc.Destroy[];
    Heap.Delete[StartState.zone];
    Heap.Delete[CRuntime.z];
    Heap.Delete[CRuntime.arrayZone];
    Heap.Delete[zone];
    IF CRuntime.log # NIL THEN Stream.Delete[CRuntime.log];
    };

  RegisterItems: PUBLIC PROCEDURE = {
    -- Register the normal CTool.~ command only if it is running
    -- in a tajo environment, rather than on an integration machine. 
    IF Runtime.IsBound[LOOPHOLE[Tool.Create]] THEN
      Exec.AddCommand[name: "CTool.~"L, proc: CToolCommand]
    ELSE
      Exec.AddCommand[
        name: "CTool.~"L, proc: ErrorCToolCommand];
    Exec.AddCommand[name: "CExec.~"L, proc: CExecCommand];
    Exec.AddCommand[
      name: "CEnvironment.~"L, proc: CEnvironmentCommand,
      unload: RemoveCommands];
    };

  GetPrompt: PUBLIC PROCEDURE RETURNS [prompt: LONG STRING] =
    {
    cmPrompt: LONG STRING ← NIL;
    cmPrompt ← CmFile.UserDotCmLine[
      "CTool"L, "Prompt"L !
      CmFile.Error => {cmPrompt ← NIL; CONTINUE}];
    IF cmPrompt = NIL THEN {
      prompt ← zone.NEW[StringBody [4]];
      prompt.length ← 0;
      String.AppendString[to: prompt, from: "\N>>>"L];
      }
    ELSE {
      prompt ← zone.NEW[StringBody [cmPrompt.length + 1]];
      prompt.length ← 0;
      String.AppendChar[s: prompt, c: Ascii.CR];
      String.AppendString[to: prompt, from: cmPrompt];
      [] ← CmFile.FreeString[cmPrompt];
      };
    };

  Unload: PUBLIC PROCEDURE [
    cmdLine: LONG STRING, tty: TTY.Handle]
    RETURNS [outcome: Outcome ← normal] = {
    th: Token.Handle ← NIL;
    item: LONG STRING ← NIL;
    fileName: LONG STRING ← zone.NEW[
      StringBody [MFile.maxNameLength]];
    {
    ENABLE
      UNWIND => {
        IF item # NIL THEN [] ← Token.FreeTokenString[item];
        IF th # NIL THEN [] ← Token.FreeStringHandle[th];
        IF fileName # NIL THEN zone.FREE[@fileName];
        };
    th ← Token.StringToHandle[cmdLine];
    item ← Token.Item[th];  -- Get past "Unload.~"
    item ← Token.FreeTokenString[item];
    DO
      item ← Token.Item[th];
      IF item = NIL THEN EXIT;
      TTY.PutString[tty, "\NUnloading "L];
      IF item[0] IN ['0..'9] THEN {
        ENABLE {
          String.InvalidNumber => {
            Blink[];
            TTY.PutString[tty, "Invalid parameter"L];
            CONTINUE;
            };
          StartState.UnloadError => {
            outcome ← error;
            Blink[];
            TTY.PutString[tty, message];
            CONTINUE;
            };
          };
        h: MLoader.Handle = String.StringToLongNumber[item];
        TTY.PutString[tty, item];
        TTY.PutString[tty, "..."L];
        StartState.Unload[h];
        TTY.PutString[tty, "done."L];
        }
      ELSE {
        ENABLE
          MFile.Error => {
            msg: LONG STRING ← [75];
            MFile.AppendErrorMessage[msg, code, file];
            TTY.PutString[tty, msg];
            outcome ← error;
            CONTINUE;
            };
        num: LONG STRING ← [8];
        file: MFile.Handle;
	found: CARDINAL;
        instancesUnloaded: CARDINAL;
        fileName.length ← 0;
        String.AppendString[to: fileName, from: item];
	found ← UniqueFilePrefix[fileName];
        IF found # 1 THEN {
          Blink[];
          TTY.PutString[tty, item];
          TTY.PutString[
            tty, IF found = 0 THEN ": not found"L ELSE ": ambiguous"L];
          zone.FREE[@fileName];
          th ← Token.FreeStringHandle[th];
          RETURN[error];
          };
        TTY.PutString[tty, fileName];
        TTY.PutString[tty, "..."L];
        file ← MFile.Acquire[fileName, anchor, []];
        instancesUnloaded ← StartState.UnloadFromFile[
          file !
          StartState.UnloadError => {
            outcome ← error;
            Blink[];
            TTY.PutString[tty, message];
            instancesUnloaded ← instancesAlreadyUnloaded;
            CONTINUE;
            }; UNWIND => MFile.Release[file]];
        MFile.Release[file];
        String.AppendChar[s: num, c: Ascii.CR];
        String.AppendNumber[num, instancesUnloaded];
        TTY.PutString[tty, num];
        TTY.PutString[tty, " instance(s) unloaded."L];
        };
      item ← Token.FreeTokenString[item];
      ENDLOOP;
    th ← Token.FreeStringHandle[th];
    zone.FREE[@fileName];
    };
    };

  ShowHandles: PUBLIC PROCEDURE [
    cmdLine: LONG STRING, tty: TTY.Handle]
    RETURNS [outcome: Outcome ← normal] = {
    PrintLoadHandle: StartState.EnumerateProc = {
      Write: Format.StringProc = {TTY.PutString[tty, s]; };
      Format.LongOctal[Write, mh];
      TTY.PutChar[tty, ' ];
      };
    th: Token.Handle ← NIL;
    item: LONG STRING ← NIL;
    fileName: LONG STRING ← zone.NEW[
      StringBody [MFile.maxNameLength]];
    {
    ENABLE
      UNWIND => {
        IF item # NIL THEN [] ← Token.FreeTokenString[item];
        IF th # NIL THEN [] ← Token.FreeStringHandle[th];
        IF fileName # NIL THEN zone.FREE[@fileName];
        };
    th ← Token.StringToHandle[cmdLine];
    item ← Token.Item[th];  -- Get past "ShowHandles.~"
    item ← Token.FreeTokenString[item];
    DO
      ENABLE
        MFile.Error => {
          msg: LONG STRING ← [75];
          MFile.AppendErrorMessage[msg, code, file];
          TTY.PutString[tty, msg];
          outcome ← error;
          CONTINUE;
          };
      file: MFile.Handle;
      found: CARDINAL;
      item ← Token.Item[th];
      IF item = NIL THEN EXIT;
      TTY.PutChar[tty, '\N];
      fileName.length ← 0;
      String.AppendString[from: item, to: fileName];
      found ← UniqueFilePrefix[fileName];
      IF found # 1 THEN {
        Blink[];
        TTY.PutString[tty, item];
        TTY.PutString[
          tty, IF found = 0 THEN ": not found"L ELSE ": ambiguous"L];
        zone.FREE[@fileName];
        th ← Token.FreeStringHandle[th];
        RETURN[error];
        };
      TTY.PutString[tty, fileName];
      TTY.PutString[tty, ": "L];
      {ENABLE MFile.Error => {
        TTY.PutString[tty, "Unable to acquire file."L];
	CONTINUE;};
      file ← MFile.Acquire[fileName, anchor, []];
      StartState.EnumerateHandles[file, PrintLoadHandle];
      MFile.Release[file];
      };
      item ← Token.FreeTokenString[item];
      ENDLOOP;
    zone.FREE[@fileName];
    th ← Token.FreeStringHandle[th];
    };
    };

  ChangeStandardStreams: PUBLIC PROCEDURE [
    buffer: LONG STRING, data: Handle] = {
    tH: Token.Handle = Token.StringToHandle[buffer];
    tokenStdin, tokenStdout, tokenStderr: LONG STRING;
    Token.Skip[tH, NIL, Token.WhiteSpace, TRUE];
    tokenStdin ← Token.Filtered[tH, NIL, FilterFileName];
    -- stdin
    data.stdin.length ← 0;
    IF tokenStdin # NIL THEN {
      String.AppendStringAndGrow[
        to: @(data.stdin), from: tokenStdin, z: zone];
      [] ← Token.FreeTokenString[tokenStdin];
      };
    WITH hh: data SELECT FROM
      ctool =>
        FormSW.DisplayItem[
          sw: hh.formSW,
          index:
          WITH hh SELECT FROM
            regular => FormItems.stdin.ORD,
            ENDCASE => FormItemsEmu.stdin.ORD];
      ENDCASE;
    -- stdout
    tokenStdout ← Token.Filtered[tH, NIL, FilterFileName];
    data.stdout.length ← 0;
    IF tokenStdout # NIL THEN {
      String.AppendStringAndGrow[
        to: @(data.stdout), from: tokenStdout, z: zone];
      [] ← Token.FreeTokenString[tokenStdout];
      };
    WITH hh: data SELECT FROM
      ctool =>
        FormSW.DisplayItem[
          sw: hh.formSW,
          index:
          WITH hh SELECT FROM
            regular => FormItems.stdout.ORD,
            ENDCASE => FormItemsEmu.stdout.ORD];
      ENDCASE;
    -- stderr
    tokenStderr ← Token.Filtered[tH, NIL, FilterFileName];
    LOOPHOLE[data, Handle].stderr.length ← 0;
    IF tokenStderr # NIL THEN {
      String.AppendStringAndGrow[
        to: @(LOOPHOLE[data, Handle].stderr),
        from: tokenStderr, z: zone];
      [] ← Token.FreeTokenString[tokenStderr];
      };
    WITH hh: data SELECT FROM
      ctool =>
        FormSW.DisplayItem[
          sw: hh.formSW,
          index:
          WITH hh SELECT FROM
            regular => FormItems.stderr.ORD,
            ENDCASE => FormItemsEmu.stderr.ORD];
      ENDCASE;
    [] ← Token.FreeStringHandle[tH];
    };

  FilterFileName: Token.FilterProcType = {
    inClass ← c # Ascii.SP AND c # ', AND c # Ascii.CR
      AND c # Ascii.TAB;
    };

  ChangeSwitches: PUBLIC PROCEDURE [
    buffer: LONG STRING, data: Handle] = {
    tH: Token.Handle = Token.StringToHandle[buffer];
    switches: LONG STRING;
    sense: BOOLEAN ← TRUE;
    Token.Skip[tH, NIL, Token.WhiteSpace, TRUE];
    switches ← Token.Filtered[tH, NIL, Token.Switches];
    IF switches # NIL THEN {
      FOR i: CARDINAL IN [0..switches.length) DO
        SELECT switches[i] FROM
          '~, '- => sense ← ~sense;
          'd => {
            data.debug ← sense;
            WITH hh: data SELECT FROM
              ctool =>
                FormSW.DisplayItem[
                  sw: hh.formSW,
                  index:
                  WITH hh SELECT FROM
                    regular => FormItems.debug.ORD,
                    ENDCASE => FormItemsEmu.debug.ORD];
              ENDCASE;
            sense ← TRUE;
            };
          's => {
            data.stopOnError ← sense;
            WITH hh: data SELECT FROM
              ctool =>
                FormSW.DisplayItem[
                  sw: hh.formSW,
                  index:
                  WITH hh SELECT FROM
                    regular => FormItems.stopOnError.ORD,
                    ENDCASE => FormItemsEmu.stopOnError.ORD];
              ENDCASE;
            sense ← TRUE;
            };
          ENDCASE;
        ENDLOOP;
      [] ← Token.FreeTokenString[switches];
      };
    [] ← Token.FreeStringHandle[tH];
    };

  PrintStatus: PUBLIC PROCEDURE [
    data: Handle, tty: TTY.Handle] = {
    TTY.PutString[tty, "stdin: "L];
    TTY.PutString[
      tty,
      IF String.Empty[data.stdin] THEN "window"L
      ELSE data.stdin];
    TTY.PutString[tty, "\Nstdout: "L];
    TTY.PutString[
      tty,
      IF String.Empty[data.stdout] THEN "window"L
      ELSE data.stdout];
    TTY.PutString[tty, "\Nstderr: "L];
    TTY.PutString[
      tty,
      IF String.Empty[data.stderr] THEN "window"L
      ELSE data.stderr];
    TTY.PutString[tty, "\NDebug: "];
    TTY.PutString[
      tty, IF data.debug THEN "TRUE"L ELSE "FALSE"L];
    TTY.PutString[tty, "\NStopScriptOnError: "L];
    TTY.PutString[
      tty, IF data.stopOnError THEN "TRUE"L ELSE "FALSE"L];
    };


  ParseLine: PUBLIC PROCEDURE [buffer: LONG STRING]
    RETURNS [
      name: LONG STRING ← NIL, argC: CARDINAL ← 1,
      argV: ArgSeq ← NIL] = {
    OPEN CString;
    initArgSeqLength: CARDINAL = 8;
    tH: Token.Handle ← NIL;
    tempName: LONG STRING ← NIL;
    {
    ENABLE
      UNWIND => {
        IF tH # NIL THEN [] ← Token.FreeStringHandle[tH];
        IF tempName # NIL THEN
          [] ← Token.FreeTokenString[tempName];
        };
    IF buffer.length = 0 THEN RETURN;
    tH ← Token.StringToHandle[buffer];
    tempName ← Token.Item[tH];
    IF tempName # NIL THEN {
      name ← String.CopyToNewString[
        tempName, zone,
        MFile.maxNameLength - tempName.length];
      tempName ← Token.FreeTokenString[tempName];
      argV ← zone.NEW[StringSeq [initArgSeqLength]];
      argV.length ← 1;
      argV[0] ← LongStringToCString[name, zone];
      };
    DO
      nextArg: LONG STRING ← Token.MaybeQuoted[
        tH, NIL ! Token.UnterminatedQuote => RESUME ];
      {
      ENABLE
        UNWIND =>
          IF nextArg # NIL THEN
            [] ← Token.FreeTokenString[nextArg];
      IF String.Empty[nextArg] THEN EXIT;
      argC ← argC + 1;
      IF argV.length < argV.maxlength THEN {
        argV[argV.length] ← LongStringToCString[
          nextArg, zone];
        argV.length ← argV.length + 1;
        }
      ELSE {
        oldArgV: ArgSeq ← argV;
        {
        ENABLE
          UNWIND => {
            IF argV # NIL THEN zone.FREE[@argV];
            IF oldArgV # NIL THEN zone.FREE[@oldArgV];
            };
        argV ← zone.NEW[StringSeq [oldArgV.maxlength * 2]];
        FOR i: CARDINAL IN [0..oldArgV.length) DO
          argV[i] ← oldArgV[i]; ENDLOOP;
        argV.length ← oldArgV.length + 1;
        argV[oldArgV.length] ← LongStringToCString[
          nextArg, zone];
        zone.FREE[@oldArgV];
        };
        };
      nextArg ← Token.FreeTokenString[nextArg];
      };
      ENDLOOP;
    tH ← Token.FreeStringHandle[tH];
    };
    };

  GetFirstWord: PUBLIC PROCEDURE [buffer: LONG STRING]
    RETURNS [firstWord: LONG STRING] = {
    th: Token.Handle ← NIL;
    ts: LONG STRING ← NIL;
    {
    ENABLE
      UNWIND => {
        IF ts # NIL THEN [] ← Token.FreeTokenString[ts];
        IF th # NIL THEN [] ← Token.FreeStringHandle[th];
        };
    th ← Token.StringToHandle[buffer];
    ts ← Token.Item[th];
    IF ts = NIL THEN {
      th ← Token.FreeStringHandle[th]; RETURN[NIL]; };
    firstWord ← String.CopyToNewString[ts, zone];
    ts ← Token.FreeTokenString[ts];
    th ← Token.FreeStringHandle[th];
    };
    };

  Blink: PUBLIC PROCEDURE = {UserTerminal.BlinkDisplay[]; };

  Print: PUBLIC PROCEDURE [msg: LONG STRING, data: Handle] =
    {Put.Text[data.msgSW, msg]; };

  Debugging: PUBLIC PROCEDURE [data: Handle]
    RETURNS [BOOLEAN] = {RETURN[data.debug]; };

  StopOnError: PUBLIC PROCEDURE [data: Handle]
    RETURNS [BOOLEAN] = {RETURN[data.stopOnError]; };

  EndingToolInstance: PUBLIC PROCEDURE [data: Handle]
    RETURNS [BOOLEAN] = {RETURN[data.endingToolInstance]; };

  StopIfCExec: PUBLIC PROCEDURE [data: Handle] = {
    IF data.tool = cexec THEN {
      data.endingToolInstance ← TRUE;
      ERROR ABORTED;  -- quit
      };
    };

  UpdateLogReadLength: PUBLIC PROCEDURE [data: Handle] = {
    WITH hh: data SELECT FROM
      ctool =>
        WITH hhh: hh SELECT FROM
          regular =>
            MStream.SetLogReadLength[
              stream: hhh.logStream,
              position: MStream.GetLength[hhh.logStream]];
          ENDCASE;
      ENDCASE;
    };

  MakeDataForSystemCall: PUBLIC PROCEDURE
    RETURNS [newData: Handle] = {
    newData ← zone.NEW[temp Data];
    newData.cToolPart ← temp[]};

  SetOutputMode: PUBLIC PROCEDURE [
    h: Handle, iomode: INTEGER] = {h.iomode ← iomode; };

  IF Runtime.IsBound[LOOPHOLE[Tool.Create]] THEN
    contextType ← Context.UniqueType[];

  }.