IMPLEMENTATION MODULE Command ;
(*
        Title:          PANOS Command Interpreter.
        Author:         Keith Rautenbach.
        History
          02 Aug 84     Initial Version.
          31 Aug 84     Remove setting of IO$DefaultFS (now not used)
          24 Aug 84     New Error routine.
          11 Oct 84     CLI$Path Implemented, Newcommand.
                        Read from $.!Panos at startup.
          12 Oct 84     Change to using EndOfFile.
          17 Oct 84     CLI$Stop introduced.
          25 Oct 84     Bug in WriteS ('*' at end of line) fixed.
          10 Nov 84     Mark result as 'message out' when we have said so.
*)

IMPORT Program ;
IMPORT DecodeArg ;
IMPORT Command ;
IMPORT Versions ;
IMPORT Handler ;
IMPORT Fields ;

IMPORT Debug ;
IMPORT Store ; (* for debug only *)

IMPORT IO ;

FROM SYSTEM IMPORT ADR ;

FROM GlobalString IMPORT SetGlobalString , GetGlobalString ;

FROM Convert IMPORT StringToInteger,CardinalToString;

FROM IO IMPORT FindInput , FindOutput , XInputStream , XEndOfFile ,
               SelectInput , SelectOutput , SetErrorStream ,
               WriteByte , ReadByte ;

FROM File IMPORT SetWorkingDirectory ;

FROM TimeAndDate IMPORT Time;

FROM Utils IMPORT ExtractElement , Capitalize ;

FROM String IMPORT LengthC , EqualLC , ExtractCC , CopyLC ,EndStringCh;

IMPORT Error ;

FROM Error IMPORT Facility , IOFacilityErrors ,ControlFacilityErrors,
                  ErrorCode ;

VAR
   Echoing                    : BOOLEAN ;
   Interactive                : BOOLEAN ;
   Finished                   : BOOLEAN ;
   Result                     : INTEGER ;
   ArgHandle                  : DecodeArg.DecodedInformation ;

(* ========================================================================= *)

PROCEDURE Version( VAR String : ARRAY OF CHAR ) ;
BEGIN
   CopyLC( "Command         0.02/23  27 Nov 84 19:54:56" , String ) ;
END Version ;

(* ========================================================================= *)

PROCEDURE Wrch( Ch : CHAR ) ;
VAR
   Junk                    : INTEGER ;
BEGIN
   Junk := WriteByte( INTEGER( Ch ) ) ;
   IF Junk < 0 THEN
      Debug.WriteS( "*NWriteByte failed : Result = " ) ;
      Debug.WriteH( Junk ) ;
      Debug.WriteS( "*N" ) ;
      IF Result >= 0 THEN
         Result := Junk ;
      END (* if *) ;
   END (* if *) ;
END Wrch ;

(* ========================================================================= *)

PROCEDURE WriteS( Text : ARRAY OF CHAR ) ;
VAR
   Index                   : CARDINAL ;
   Ch                      : CHAR ;
   Length                  : CARDINAL ;
BEGIN
   Index := 0 ;
   Length := LengthC( Text ) ;
   WHILE Index < Length DO
      Ch := Text[ Index ] ;
      IF Ch = '*' THEN
         IF (Index+1) < Length THEN
            INC( Index ) ;
            Ch := Text[ Index ] ;
            IF Ch = 'N' THEN
               Ch := 012C ;
            ELSE
               Wrch( '*' ) ;
            END (* if *) ;
         END ;
      END (* if *) ;
      Wrch( Ch ) ;
      INC( Index ) ;
   END (* while *) ;
END WriteS ;

(* ========================================================================= *)


PROCEDURE Check( ThisResult : INTEGER ) ;
BEGIN
   IF ( ThisResult < 0 ) AND ( Result >= 0 ) THEN
      Result := DescribeResult( ThisResult ) ;
   END (* if *) ;
END Check ;

(* ========================================================================= *)

PROCEDURE DescribeResult( Result : INTEGER ) : INTEGER;
VAR
   DetectBuff                  : ARRAY [ 0..20 ] OF CHAR ;
   InterBuff                   : ARRAY [ 0..20 ] OF CHAR ;
   Buffer                      : ARRAY [ 0..79 ] OF CHAR ;
   Length1,Length2,Length3     : CARDINAL ;
   res                         : INTEGER ;
   junk                        : CARDINAL ;

BEGIN
  
   IF (Result < 0) AND
      (Fields.Extract (CARDINAL(Result),28,30) # 0) THEN
      res := Error.GetErrorMessage(DetectBuff, Length1,
                                      InterBuff, Length2,
                                      Buffer, Length3,
                                      Result) ;
      Debug.WriteS("+++ ");
      Debug.WriteS( Buffer ) ;
      Debug.WriteS("*N+++ Detected in module "); 
      Debug.WriteS( DetectBuff ) ;
      Debug.Writeln ;
      junk := CARDINAL( Result ) ;
      Fields.Insert(junk, 28, 30, 0) ;
      Result := INTEGER (junk) ;
   END (* if *) ;

   RETURN Result ;

END DescribeResult ;

(* ========================================================================= *)

PROCEDURE ReadLine( VAR Buffer : ARRAY OF CHAR ) : INTEGER ;
VAR
   Index                    : CARDINAL ;
   Length                   : CARDINAL ;
   Result                   : INTEGER ;
BEGIN
   Index := 0 ;
   LOOP
      Result := IO.ReadByte() ;
      IF Result < 0 THEN
         EXIT ;
      END (* if *) ;
      IF Result = 10 THEN
         EXIT ;
      END (* if *) ;
      IF Index <= HIGH( Buffer ) THEN
         Buffer[ Index ] := CHAR( Result ) ;
         INC( Index ) ;
      END (* if *) ;
   END (* loop *) ;
   IF Index <= HIGH( Buffer ) THEN
      Buffer[ Index ] := 0C ;
   END (* if *) ;
   IF XEndOfFile ( XInputStream() ) THEN
      Finished := TRUE ;
      RETURN 0 ;
   ELSE
      RETURN Result ;
   END (* if *) ;
END ReadLine ;

(* ========================================================================= *)

PROCEDURE ObeyFile( Name , Arguments : ARRAY OF CHAR ) : INTEGER ;
VAR
   Input                        : INTEGER ;
   OldArgHandle                 : DecodeArg.DecodedInformation ;
   OldFinished                  : BOOLEAN ;
   OldInteractive               : BOOLEAN ;
   OldEchoing                   : BOOLEAN ;
   OldResult                    : INTEGER ;
   ResultCode                   : INTEGER ;
   Buffer                       : ARRAY [ 0..255 ] OF CHAR ;
   Length                       : CARDINAL ;
   Junk                         : INTEGER ;

   PROCEDURE CheckObey( ThisResult : INTEGER ) ;
   BEGIN
      IF ( ThisResult < 0 ) AND ( Result >= 0 ) THEN
         Result := ThisResult ;
      END (* if *) ;
   END CheckObey ;

   PROCEDURE DotKeyString( VAR Line : ARRAY OF CHAR ) : BOOLEAN ;
   VAR
      Index                     : CARDINAL ;
      Index2                    : CARDINAL ;
      DotKey                    : ARRAY [ 0..5 ] OF CHAR ;

      PROCEDURE SkipSpaces ;
      BEGIN
         WHILE ( Index < HIGH( Line ) ) AND ( Line[ Index ] = ' ' ) DO
            INC( Index ) ;
         END (* while *) ;
      END SkipSpaces ;

   BEGIN
      IF Line[ 0 ] = '$' THEN
         Index := 1 ;
      ELSE
         Index := 0 ;
      END (* if *) ;
      SkipSpaces ;
      IF LengthC( Line ) < ( Index + 4 ) THEN
         RETURN FALSE ;
      END (* if *) ;
      ExtractCC( Line , Index , Index + 4 , DotKey ) ;
      Capitalize( DotKey ) ;
      IF EqualLC( ".KEY" , DotKey ) THEN
         FOR Index2 := 0 TO Index + 3 DO
            Line[ Index2 ] := ' ' ;
         END (* for *) ;
         RETURN TRUE ;
      END (* if *) ;
      RETURN FALSE ;
   END DotKeyString ;

BEGIN
   OldResult := Result ;
   OldArgHandle := ArgHandle ;
   OldFinished := Finished ;
   OldInteractive := Interactive ;
   OldEchoing := Echoing ;
   IF GetGlobalString( Buffer , Length , "CLI$Echo" ) >= 0 THEN
      Capitalize( Buffer ) ;
      Echoing := EqualLC( "YES" , Buffer ) OR EqualLC( "TRUE" , Buffer ) ;
   ELSE
      Echoing := FALSE ;
   END (* if *) ;
   Interactive := FALSE ;
   Result := 0 ;
   Input := IO.FindInput( Name ) ;
   CheckObey( Input ) ;
   IF Result >= 0 THEN
      CheckObey( IO.SelectInput( Input ) ) ;
      CheckObey( ReadLine( Buffer ) ) ;
   END (* if *) ;
   IF Result >= 0 THEN
      IF DotKeyString( Buffer ) THEN
         CheckObey( DecodeArg.DecodeInit( ArgHandle , Buffer , Arguments ) ) ;
      ELSE
         ArgHandle := DecodeArg.DecodedInformation( NIL ) ;
         CheckObey( InterpretString( Buffer ) ) ;
      END (* if *) ;
   END (* if *) ;
   IF Result >= 0 THEN
      Result := InterpretCommands() ;
   END (* if *) ;
   Junk := DecodeArg.DecodeEnd( ArgHandle ) ;
   IF ( NOT Interactive ) AND Echoing THEN
      WriteS( "[[[ End of command file '" ) ;
      WriteS( Name ) ;
      WriteS( "' ]]]*N" ) ;
   END (* if *) ;
   ArgHandle := OldArgHandle ;
   Echoing := OldEchoing ;
   Interactive := OldInteractive ;
   Finished := OldFinished ;
   ResultCode := Result ;
   Result := OldResult ;
   RETURN ResultCode ;
END ObeyFile ;

(* ========================================================================= *)

PROCEDURE InterpretCommands() : INTEGER ;
VAR
   Length                     : CARDINAL ;
   Buffer                     : ARRAY [ 0..255 ] OF CHAR ;
   StopCode                   : INTEGER ;
   ResultCode                 : INTEGER ;

   PROCEDURE GetStopCode() : INTEGER ;
   VAR result : INTEGER ;
   BEGIN

      IF ( GetGlobalString (Buffer, Length, "CLI$Stop" ) >= 0 ) THEN

        result := StringToInteger(StopCode,Buffer) ;  
        IF result >= 0  THEN
           RETURN StopCode ;
        END;
      END;

      RETURN ErrorCode (ControlFacility,CARDINAL (InvalidStopCode),"") ;
   END GetStopCode;

   PROCEDURE GetResultCode() : INTEGER ;
   VAR result : INTEGER ;
   BEGIN
      IF ( GetGlobalString (Buffer, Length, "CLI$ResultCode" ) >= 0 ) THEN
        result := StringToInteger(ResultCode,Buffer) ;  
        IF result >= 0  THEN
           RETURN ResultCode ;
        END;
      END;
      RETURN ErrorCode (ControlFacility,CARDINAL (InvalidResultCode),"") ;
   END GetResultCode;



BEGIN
   Finished := FALSE ;
   REPEAT
       Result := 0 ;
      IF Interactive AND
         ( GetGlobalString( Buffer , Length , "CLI$Prompt" ) >= 0 ) THEN
         IF DecodeArg.Substitute( Buffer , Length ,
                               Buffer ,
                               DecodeArg.DecodedInformation( NIL ) ) >= 0 THEN
         END (* if *) ;
         WriteS( Buffer ) ;
      END (* if *) ;
      Check( ReadLine( Buffer ) ) ;
      IF ( Result < 0 ) THEN
         UpdateResultCode ( Result ) ;
      ELSE
         IF ( NOT Finished ) THEN  Check( InterpretString( Buffer ) ) END ;
      END;

   UNTIL Finished OR
         ( ( NOT Interactive ) AND ( GetResultCode() < GetStopCode() ) ) ;
   RETURN Result ;
END InterpretCommands ;

(* ========================================================================= *)

PROCEDURE Parse( VAR Line : ARRAY OF CHAR ;
                 VAR Program : ARRAY OF CHAR ;
                 VAR Arguments : ARRAY OF CHAR ) : BOOLEAN ;
VAR
   Index                      : CARDINAL ;
BEGIN
   Index := 0 ;
   IF ExtractElement( Line , Index , Program ) THEN
      Capitalize( Program ) ;
      WHILE ( Index <= HIGH( Line ) ) AND
            ( Line[ Index ] = ' ' ) DO
         INC( Index ) ;
      END (* while *) ;
      ExtractCC( Line , Index , LengthC( Line ) , Arguments ) ;
      RETURN TRUE ;
   END (* if *) ;
   RETURN FALSE ;
END Parse ;

(* ========================================================================= *)

PROCEDURE InterpretString( Line : ARRAY OF CHAR ) : INTEGER ;

   PROCEDURE SetEnvironment( ArgumentString : ARRAY OF CHAR ) ;
   VAR
      Result                  : INTEGER ;
      Handle                  : DecodeArg.DecodedInformation ;
      Name                    : ARRAY [ 0..255 ] OF CHAR ;
      Value                   : ARRAY [ 0..255 ] OF CHAR ;
      Length                  : CARDINAL ;
   BEGIN
      Result := DecodeArg.DecodeInit( Handle ,
                                      "Name/A,Value/A" ,
                                      ArgumentString ) ;
      Check( Result ) ;
      IF Result >= 0 THEN
         Check( DecodeArg.GetStringArg( Name , Length ,
                                        "Name" , 1 , Handle ) ) ;
         Check( DecodeArg.GetStringArg( Value , Length ,
                                        "Value" , 1 , Handle ) );
         IF Result >= 0 THEN
            Check( SetGlobalString( Name , Value ) ) ;
         END (* if *) ;
      END (* if *) ;
      Check( DecodeArg.DecodeEnd( Handle ) ) ;
   END SetEnvironment ;

   (* ---------------------------------------------------------------------- *)

   PROCEDURE GiveIdentities( VAR Args : ARRAY OF CHAR ) ;
   BEGIN
      Versions.WriteAllVersions ;
   END GiveIdentities ;

   (* ---------------------------------------------------------------------- *)
   PROCEDURE NewCommand( ) : INTEGER ;
   (* Using the value of  CLI$Path  determine the future set of *)
   (* available commands *)
   VAR Result             : INTEGER;
       GlobalStringBuffer : ARRAY [0..255] OF CHAR ;
       GlobalLength       : CARDINAL ;
   BEGIN
    IF (GetGlobalString(GlobalStringBuffer,GlobalLength, "CLI$Path" ) <0 ) THEN
         RETURN ErrorCode (ControlFacility, CARDINAL (NoCLIPath), "") ;
    END;
      GlobalStringBuffer[GlobalLength] := EndStringCh ;
      IF (Program.SetKnownCommandsPath(GlobalStringBuffer) < 0 ) THEN
         RETURN ErrorCode (ControlFacility, 
                           CARDINAL (UnableToSetNewCommandPath), "") ;
      END;
      RETURN 0;
   END NewCommand ;

   (* ---------------------------------------------------------------------- *)

   PROCEDURE BuiltInCommandHelp( VAR Args : ARRAY OF CHAR ) ;
   BEGIN
      IF Program.Verbosity() > 0 THEN
         Versions.WriteVersion( Command.Version )
      END;
      WriteS( "Commands built into the command interpreter are:*N*N" ) ;
      WriteS( "  .QUIT                   : Leave PANOS*N" ) ;
      WriteS( "  .SET  <Name> <Value>    : Define Global String*N" ) ;
      WriteS( "  .SWD  <path>            : Set Working Directory*N" ) ;
      WriteS( "  .OBEY <File> <ArgList>  : Execute command file*N" ) ;
      WriteS( "  .RUN  <Prog> <ArgList>  : Run a relocatable image*N" ) ;
      WriteS( "  .CALL <Proc> <ArgList>  : Call dynamic loaded procedure*N" ) ;
      WriteS( "  .NEWCOMMAND             : Notify new program creation*N" ) ;
      WriteS( "  .IDENTIFY               : Print PANOS Version numbers*N" ) ;
      WriteS( "  .HELP                   : Print this text, try HELP for*N" ) ;
      WriteS( "                          :   help on utilities*N" ) ;
   END BuiltInCommandHelp ;

   (* ---------------------------------------------------------------------- *)

   PROCEDURE BuiltInCommand( VAR Command : ARRAY OF CHAR ;
                                 VAR Args : ARRAY OF CHAR ) ;
   BEGIN
      IF LengthC( Command ) = 1 THEN
         (* It is a comment *)
      ELSIF EqualLC( ".QUIT" , Command ) THEN
         Finished := TRUE ;
      ELSIF EqualLC( ".SET" , Command ) THEN
         SetEnvironment( Args ) ;
      ELSIF EqualLC( ".SWD" , Command ) THEN
         Check ( SetWorkingDirectory (Args))
      ELSIF EqualLC( ".OBEY" , Command ) THEN
         IF Parse( ArgumentString , ProgramName , ArgumentString ) THEN
            Check( Program.Obey( ProgramName , ArgumentString ) ) ;
         ELSE
            WriteS( ".OBEY : Missing File name*N" ) ;
         END (* if *) ;
      ELSIF EqualLC( ".RUN" , Command ) THEN
         IF Parse( ArgumentString , ProgramName , ArgumentString ) THEN
            Check( Program.Run( ProgramName , ArgumentString ) ) ;
         ELSE
            WriteS( ".RUN : Missing program name*N" ) ;
         END (* if *) ;
      ELSIF EqualLC( ".CALL" , Command ) THEN
         IF Parse( ArgumentString , ProgramName , ArgumentString ) THEN
            Check( Program.Call( "Library" , ProgramName , ArgumentString ) ) ;
         ELSE
            WriteS( ".CALL : Missing program name*N" ) ;
         END (* if *) ;
      ELSIF EqualLC( ".NEWCOMMAND" , Command ) THEN
         Check ( NewCommand() );
      ELSIF EqualLC( ".HELP" , Command ) THEN
         BuiltInCommandHelp( Args ) ;
      ELSIF EqualLC( ".IDENTIFY" , Command ) THEN
         GiveIdentities( ArgumentString ) ;
      ELSIF EqualLC( ".STORECHAINS" , Command ) THEN
         Store.DescribeUsedChain ;
      ELSE
         WriteS( "*NBad built in command*N" ) ;
      END (* if *) ;
   END BuiltInCommand ;

   (* ---------------------------------------------------------------------- *)

VAR
   ExpandedLine                  : ARRAY [ 0..255 ] OF CHAR ;
   ProgramName                   : ARRAY [ 0..255 ] OF CHAR ;
   ArgumentString                : ARRAY [ 0..255 ] OF CHAR ;
   Buffer                        : ARRAY [ 0..255 ] OF CHAR ;
   Length                        : CARDINAL ;
   res                           : INTEGER ;
BEGIN
   Result := 0 ;
   Check( DecodeArg.Substitute( ExpandedLine , Length ,
                                Line ,
                                ArgHandle ) ) ;
   IF Echoing AND ( NOT Interactive ) THEN
      WriteS( ExpandedLine ) ;
      WriteS( "*N" ) ;
   END (* if *) ;
   IF Length > 0 THEN
      IF NOT Interactive THEN
         IF ExpandedLine[ 0 ] = '$' THEN
            ExpandedLine[ 0 ] := ' ' ;
         ELSE
            WriteS( "Line in Command file does not begin with '$'*N" ) ;
            Finished := TRUE ;
            RETURN -1 ;
         END (* if *) ;
      END (* if *) ;
      IF ( Result >= 0 ) AND
         Parse( ExpandedLine , ProgramName , ArgumentString ) THEN
         IF ProgramName[ 0 ] = '.' THEN
            BuiltInCommand( ProgramName , ArgumentString ) ;
         ELSE
            Check( Program.Invoke( ProgramName , ArgumentString ) ) ;
         END (* if *) ;
      END (* if *) ;
   END (* if *) ;
   UpdateResultCode ( Result ) ;
   RETURN Result ;
END InterpretString ;

(* ========================================================================= *)

PROCEDURE UpdateResultCode ( NewResult : INTEGER ) ;
     (* update global variable CLI$ResultCode *)
VAR     
   Buffer                        : ARRAY [ 0..255 ] OF CHAR ;
   Length                        : CARDINAL ;
BEGIN
  IF (CardinalToString( Buffer, Length, CARDINAL(Result), 16) < 0 ) THEN
     Debug.WriteS( "DescribeResult:Integer to string Failed") ;
  END;
  IF (SetGlobalString( "CLI$ResultCode"  ,  Buffer  ) < 0 ) THEN
     Debug.WriteS( "DescribeResult:SetGlobalString(cli$rcode) Failed") ;
  END;

END UpdateResultCode;
(* ========================================================================= *)

PROCEDURE XInterpretString( String : ARRAY OF CHAR ) ;
VAR
   Result                        : INTEGER ;
BEGIN
   Result := InterpretString( String ) ;
   IF Result < 0 THEN
      Handler.Signal( Result , NIL ) ;
   END (* if *) ;
END XInterpretString ;

(* ========================================================================= *)

PROCEDURE XInterpretCommands() ;
VAR
   Result                        : INTEGER ;
BEGIN
   Result := InterpretCommands() ;
   IF Result < 0 THEN
      Handler.Signal( Result , NIL ) ;
   END (* if *) ;
END XInterpretCommands ;


(* ========================================================================= *)

PROCEDURE Initialise() : INTEGER ;
(* Initialises the world for the Command Interpreter.                        *)
(* Called by the SubSystem before the first invokation of the Interpreter.   *)
VAR
   Input                   : INTEGER ;
   Output                  : INTEGER ;
   Error                   : INTEGER ;
BEGIN
   Result := 0 ;
(* Initialise the IO system *)

   Check( IO.Initialise() ) ;

(* Connect up the Terminal Streams *)

   Input := FindInput( "kb:" ) ;
   Check( Input ) ;
   Output := FindOutput( "vdu:" ) ;
   Check( Output ) ;
   Error := FindOutput( "vdu:" ) ;
   Check( Error ) ;
   IF Result <> 0 THEN
      RETURN Result ;
   END (* if *) ;
   Check( SelectInput( Input ) ) ;
   Check( SelectOutput( Output ) ) ;
   Check( SetErrorStream( Error ) ) ;

(* Now set up some sensible values for the Global String Variables *)

   Check( SetGlobalString( "IO$FileSystems" , "ADFS DFS NFS"    ) ) ;
   Check( SetGlobalString( "CLI$Prompt"     , "-> "             ) ) ;
   Check( SetGlobalString( "CLI$Echo"       , "No"              ) ) ;
   Check( SetGlobalString( "CLI$Stop"       , "-16_4"           ) ) ;
   Check( SetGlobalString( "CLI$ResultCode"       , "16_4"      ) ) ;

   Echoing := FALSE ;
   Interactive := TRUE ;

   RETURN Result ;
END Initialise ;

(* ========================================================================= *)


BEGIN
   ArgHandle := DecodeArg.DecodedInformation( NIL ) ;
END Command.
