IMPLEMENTATION MODULE DecodeArg ;
(*
        Title:          PANOS Argument decoding module.
        Author:         Keith Rautenbach
        History:
           20 Jul 84   Initial Version.
           08 Aug 84   Conformance to spec, '-', and "Help", and "Identity"
                       Errors are IMPORTed from MODULE Errors.
           14 Aug 84   Two bugs in Dispose fixed.
                       Extensive use of Lee's module String.
                       Identify not Identity cos Marks say so.
                       Correct bug with null strings, only allowed if quoted.
           22 Aug 84   Conformance to spec for /E-ext. Does not return -ext
           30 Aug 84   Declare Procedure etc removed.
           30 Aug 84   Install System Condition Handler.
           31 Aug 84   Addition of Substitute.
           11 Sep 84   Correction of String result ( Too long errors )
           12 Sep 84   Change of Substitute of State keywords.
                       Correction of -32 looking for keyword "32" bug.
           24 Sep 84   New Error routine.
           27 Sep 84   Bug fix to GrowValueArray
           12 Oct 84   Change in substitute to allow <-fred> to strip extension
                       value of /E arg includes def extension if appropriate
           22 Oct 84   Multi Value substitution "," correctly added.
           23 Oct 84   AnyThing<Substitution> fix.
           19 Nov 84   /L type added.
*)


FROM SYSTEM IMPORT SIZE , ADR , ADDRESS ;
FROM String IMPORT STRING , EndStringCh ,
                   LengthC , LengthS , EqualCC , EqualCS , EqualSS , EqualLS ,
                   EqualLC , CopyLC , CopyCC , CopyCS , CopySS , CopySC ,
                   ConcatSS , FindS , FindC ,
                   ExtractSS , ExtractCS , ExtractSC ,
                   AppendCS ,
                   Dispose ;
FROM Store IMPORT Allocate , Deallocate ;
FROM Error IMPORT ErrorCode , Facility ;
FROM Utils IMPORT Capital , Capitalize , Cardinality ;
FROM Convert IMPORT StringToInteger , StringToCardinal ,
                    XCardinalToString , XIntegerToString ;
FROM GlobalString IMPORT GetGlobalString ;
FROM Error IMPORT ArgumentFacilityErrors ;
FROM Handler IMPORT  SystemSignal,InstallPANOSHandler;

(*
IMPORT Debug ;
*)


CONST
   Success                    = 0 ;

TYPE
   KeithsName                 = ARRAY [ 0..4 ] OF CHAR ;
   StringRange                = [ 0..255 ] ;
   DecodedInformation         = POINTER TO DecodedInformationR ;
   KeywordP                   = POINTER TO KeywordR ;
   ValueArrayP                = POINTER TO ValueArray ;
   TypeOfArgument             = ( Cardinal ,
                                  Integer ,
                                  State ,
                                  Literal ,
                                  String ,
                                  Existent
                                ) ;
   ArgumentTypeSet            = SET OF TypeOfArgument ;
   DecodedInformationR        = RECORD
      Validity                   : KeithsName ;
      Keywords                   : KeywordP ;
                                END ;
   KeywordR                   = RECORD
      Next                       : KeywordP ;
      Name                       : STRING ;
      Quantity                   : CARDINAL ;
      Type                       : TypeOfArgument ;
      Values                     : RECORD
         Size                       : CARDINAL ;
         Array                      : ValueArrayP ;
                                   END ;
      Parsing                    : RECORD
         Always                     : BOOLEAN ;
         Extension                  : STRING ;
         KeywordNeeded              : BOOLEAN ;
         MaximumQuantity            : CARDINAL ;
         ExactQuantityRequired      : BOOLEAN ;
         NextArgumentIndex          : CARDINAL ;
                                   END ;
                                END ;
   ValueR                     = RECORD
      CASE TypeOfArgument OF
         Cardinal :
            CardinalValue        : CARDINAL ;
      |
         Integer :
            IntegerValue         : INTEGER ;
      |
         State :
            StateValue           : BOOLEAN ;
      |
         String , Existent , Literal :
            StringValue          : STRING ;
      END (* case *) ;
                                END ;
   ValueArray                 = ARRAY StringRange OF ValueR ;
   CharacterArray             = ARRAY StringRange OF CHAR ;


VAR
   ValidPassword              : KeithsName ;


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

PROCEDURE Version( VAR String : ARRAY OF CHAR ) ;
BEGIN
   CopyLC( "DecodeArg       0.02/25  20 Nov 84 08:40:16" , String ) ;
END Version ;

(* ------------------------------------------------------------------------- *)
(* ErrorKey : Calls the PANOS internal error code generator with             *)
(*            Facility = ArgumentKeyFacility                                 *)
(* ------------------------------------------------------------------------- *)
PROCEDURE ErrorKey( Code : ArgumentFacilityErrors ) : INTEGER ;
BEGIN
   RETURN ErrorCode( ArgumentKeyFacility , CARDINAL( Code ), "" ) ;
END ErrorKey ;


(* ------------------------------------------------------------------------- *)
(* Erroruser : Calls the PANOS internal error code generator with            *)
(*             Facility = ArgumentUserFacility                               *)
(* ------------------------------------------------------------------------- *)
PROCEDURE ErrorUser( Code : ArgumentFacilityErrors ) : INTEGER ;
BEGIN
   RETURN ErrorCode( ArgumentUserFacility , CARDINAL( Code ), "" ) ;
END ErrorUser ;


(* ------------------------------------------------------------------------- *)
(* Valid : Checks a DecodedInformation pointer to make sure we havent been   *)
(*         given garbage.                                                    *)
(* ------------------------------------------------------------------------- *)
PROCEDURE Valid( DecodedArguments : DecodedInformation ) : BOOLEAN ;
BEGIN
   IF DecodedArguments <> NIL THEN
      IF EqualCC( DecodedArguments^.Validity , ValidPassword ) THEN
         RETURN TRUE ;
      END (* if *) ;
   END (* if *) ;
   RETURN FALSE ;
END Valid ;


(* ------------------------------------------------------------------------- *)
(* FindKeyword :  Searches the given chain of keywords, and return a pointer *)
(*                to the keyword if desired word is found.                   *)
(*                If the word is not found then the keyword parameter is     *)
(*                left unchanged.                                            *)
(* ------------------------------------------------------------------------- *)
PROCEDURE FindKeyword( Word : STRING ;
                       KeywordChain : KeywordP ;
                   VAR Keyword : KeywordP ) : BOOLEAN ;
BEGIN
   WHILE KeywordChain <> NIL DO
      WITH KeywordChain^ DO
         IF EqualSS( Name , Word ) THEN
            Keyword := KeywordChain ;
            RETURN TRUE ;
         END (* if *) ;
         KeywordChain := KeywordChain^.Next ;
      END (* with *) ;
   END (* while *) ;
   RETURN FALSE ;
END FindKeyword ;


(* ------------------------------------------------------------------------- *)
(* GetArgument : Attempts to obtain the required argument from the decoded   *)
(*               arguments structure.                                        *)
(*               Returns 0 if successful, else a PANOS error code.           *)
(* ------------------------------------------------------------------------- *)
PROCEDURE GetArgument( DecodedArguments : DecodedInformation ;
                   VAR ArgumentName : ARRAY OF CHAR ;
                       AllowedTypes : ArgumentTypeSet ;
                   VAR Keyword : KeywordP ) : INTEGER ;

VAR
   Result                     : INTEGER ;
BEGIN
   IF NOT Valid( DecodedArguments ) THEN
      RETURN ErrorKey( BadInformation ) ;
   END (* if *) ;
   Capitalize( ArgumentName ) ;
   Keyword := DecodedArguments^.Keywords ;
   WHILE Keyword <> NIL DO
      WITH Keyword^ DO
         IF EqualCS( ArgumentName , Name) THEN
            IF Type IN AllowedTypes THEN
               RETURN Success ;
            ELSE
               RETURN ErrorKey( TypeMismatch ) ;
            END (* if *) ;
         END (* if *) ;
         Keyword := Keyword^.Next ;
      END (* with *) ;
   END (* while *) ;
   RETURN ErrorKey( KeywordNotKnown ) ;
END GetArgument ;


(* ------------------------------------------------------------------------- *)
(* GetValue : Attempts to obtain the Index'th value from the given argument  *)
(*            from the decoded arguments structure.                          *)
(*            Returns 0 if successful, else a PANOS error code.              *)
(* ------------------------------------------------------------------------- *)
PROCEDURE GetValue( DecodedArguments : DecodedInformation ;
                VAR ArgumentName : ARRAY OF CHAR ;
                    Index : CARDINAL ;
                    AllowedTypes : ArgumentTypeSet ;
                VAR Value : ValueR ) : INTEGER ;

VAR
   Keyword                  : KeywordP ;
   Result                   : INTEGER ;
BEGIN
   Result := GetArgument( DecodedArguments ,
                          ArgumentName ,
                          AllowedTypes ,
                          Keyword ) ;
   IF Result = 0 THEN
      IF Index = 0 THEN
         RETURN ErrorKey( BadIndexParameter ) ;
      ELSIF Index > Keyword^.Quantity THEN
         RETURN ErrorKey( IndexGreaterThanNumberOfArguments ) ;
      ELSE
         Value := Keyword^.Values.Array^[ Index - 1 ] ;
      END (* if *) ;
   END (* if *) ;
   RETURN Result ;
END GetValue ;



(* ------------------------------------------------------------------------- *)
(* ConvertString : Generates a Cross-Language compatable string from the     *)
(*                 internal representation. Truncates if necessary.          *)
(*                 The result is also Modula2 comaptible.                    *)
(* ------------------------------------------------------------------------- *)
PROCEDURE ConvertString( String : STRING ;
                     VAR Arg : ARRAY OF CHAR ;
                     VAR ArgLength : CARDINAL ) : INTEGER ;
VAR
   I                  : CARDINAL ;
BEGIN
   IF LengthS( String ) > HIGH( Arg ) THEN
      RETURN ErrorUser( StringResultBufferTooSmall ) ;
   END (* if *) ;
   FOR I := 0 TO HIGH( Arg ) DO
      Arg[ I ] := String^[ I ] ;
      IF String^[ I ] = EndStringCh THEN
         ArgLength := I ;
         RETURN 0 ;
      END (* if *) ;
   END (* for *) ;
   ArgLength := HIGH( Arg ) + 1 ;
   RETURN 0 ;
END ConvertString ;


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

PROCEDURE DisposeString( VAR String : STRING ) ;
BEGIN
   IF String <> NIL THEN
      Dispose( String ) ;
      String := NIL ;
   END (* if *) ;
END DisposeString ;

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

MODULE Decode ;

IMPORT STRING , LengthC , LengthS , EqualLS , EqualLC ,
       CopyCC , CopyCS , CopySS , CopySC , ConcatSS , FindS , FindC ,
       ExtractSS , ExtractCS , ExtractSC , AppendCS ;
IMPORT Allocate , Deallocate ;
IMPORT StringToInteger , StringToCardinal ,
       XCardinalToString , XIntegerToString ;
IMPORT GetGlobalString ;
IMPORT ErrorCode , Facility , Capital , Capitalize , Cardinality ;
IMPORT ArgumentFacilityErrors ;
IMPORT SIZE , ADR , ADDRESS ;
(*
IMPORT Debug ;
*)
IMPORT EndStringCh ,
       Success ,
       Debugging , DebugStore , DisposeError ,
       KeithsName ,
       DecodedInformation ,
       KeywordP ,
       ValueArrayP ,
       TypeOfArgument ,
       ArgumentTypeSet ,
       DecodedInformationR ,
       KeywordR ,
       ValueR ,
       ValueArray ,
       CharacterArray ,
       ValidPassword ,
       Valid ,
       FindKeyword ,
       GetArgument ,
       ErrorKey ,
       ErrorUser ,
       ConvertString ,
       DisposeString ;
EXPORT QUALIFIED Describe , Parse , End , SubstituteString ;

CONST
   MaxStringSize              = 255 ;
   MaximumNumberOfArguments   = 10000 ;

TYPE
   FunctionCHAR               = PROCEDURE( VAR CHAR ) : BOOLEAN ;
   SlashOptions               = ( OptionAlways ,    (* Order is VITAL *)
                                  OptionKeywordNeeded ,
                                  OptionTypeState ,
                                  OptionTypeInteger ,
                                  OptionTypeCardinal ,
                                  OptionTypeExistent ,
                                  OptionTypeLiteral ,
                                  OptionQuantity
                                ) ;
   SlashOptionSet             = SET OF SlashOptions ;

VAR
   ErrorReason                : INTEGER ;
   Offset                     : CARDINAL ;
   LastOffset                 : CARDINAL ;
   CurrentString              : POINTER TO CharacterArray ;
   CurrentStringLength        : CARDINAL ;
   EndWordChars               : ARRAY [ 0..10 ] OF CHAR ;
   WasQuoted                  : BOOLEAN ;
   EscapedCh                  : BOOLEAN ;
   CurrentFacility            : Facility ;


(*
   PROCEDURE DescribeValue( Type : TypeOfArgument ; Value : ValueR ) ;
   BEGIN
      CASE Type OF
         String ,
         Existent ,
         Literal :
            Debug.WriteSTRING( Value.StringValue ) ;
      |
         State :
            Debug.WriteB( Value.StateValue ) ;
      |
         Cardinal :
            Debug.WriteC( Value.CardinalValue ) ;
      |
         Integer :
            Debug.WriteI( Value.IntegerValue ) ;
      ELSE
         Debug.WriteS( " Word( " ) ;
         Debug.WriteH( Value.CardinalValue ) ;
         Debug.WriteS( " )" ) ;
      END (* case *) ;
   END DescribeValue ;



   PROCEDURE DescribeKeyword( Keyword : KeywordR ) ;
   VAR
      I                        : CARDINAL ;
   BEGIN
      WITH Keyword DO
         Debug.WriteS( "Next                           : " ) ;
         Debug.WritePointer( Next ) ;
         Debug.Writeln ;
         Debug.WriteS( "Name                           : " ) ;
         Debug.WriteSTRING( Name ) ;
         Debug.Writeln ;
         Debug.WriteS( "Quantity                       : " ) ;
         Debug.WriteC( Quantity ) ;
         Debug.Writeln ;
         Debug.WriteS( "Type                           : " ) ;
         IF Type = Cardinal THEN
            Debug.WriteS(  "Cardinal" ) ;
         ELSIF Type = Integer THEN
            Debug.WriteS(  "Integer" ) ;
         ELSIF Type = State THEN
            Debug.WriteS(  "State" ) ;
         ELSIF Type = String THEN
            Debug.WriteS(  "String" ) ;
         ELSIF Type = Existent THEN
            Debug.WriteS(  "Existent" ) ;
         ELSIF Type = Literal THEN
            Debug.WriteS(  "Literal" ) ;
         ELSE
            Debug.WriteS(  "?????" ) ;
         END (* if *) ;
         Debug.Writeln ;
         Debug.WriteS( "Values.Size                    : " ) ;
         Debug.WriteC( Values.Size ) ;
         Debug.Writeln ;
         Debug.WriteS( "Values.Array                   : " ) ;
         Debug.WritePointer( Values.Array ) ;
         Debug.Writeln ;
         I := 0 ;
         WHILE I < Quantity DO
            Debug.WriteS( "Values.Array^[ " ) ;
            Debug.WriteC( I ) ;
            Debug.WriteS( " ]             : " ) ;
            DescribeValue( Type , Values.Array^[ I ] ) ;
            Debug.Writeln ;
            INC( I ) ;
         END (* while *) ;
         Debug.WriteS( "Parsing.Always                 : " ) ;
         Debug.WriteB( Parsing.Always ) ;
         Debug.Writeln ;
         Debug.WriteS( "Parsing.Extension              : " ) ;
         Debug.WriteSTRING( Parsing.Extension ) ;
         Debug.Writeln ;
         Debug.WriteS( "Parsing.MaximumQuantity        : " ) ;
         Debug.WriteC( Parsing.MaximumQuantity ) ;
         Debug.Writeln ;
         Debug.WriteS( "Parsing.KeywordNeeded          : " ) ;
         Debug.WriteB( Parsing.KeywordNeeded ) ;
         Debug.Writeln ;
         Debug.WriteS( "Parsing.ExactQuantityRequired  : " ) ;
         Debug.WriteB( Parsing.ExactQuantityRequired ) ;
         Debug.Writeln ;
         Debug.WriteS( "Parsing.NextArgumentIndex      : " ) ;
         Debug.WriteC( Parsing.NextArgumentIndex ) ;
         Debug.Writeln ;
      END (* with *) ;
   END DescribeKeyword ;

*)

   PROCEDURE Ok() : BOOLEAN ;
   BEGIN
      RETURN ErrorReason = 0 ;
   END Ok ;


   PROCEDURE SetErrorReason( Why : ArgumentFacilityErrors ) ;
   BEGIN
      IF (* was *) Ok() THEN
         IF CurrentFacility = ArgumentKeyFacility THEN
            ErrorReason := ErrorKey( Why ) ;
         ELSE
            ErrorReason := ErrorUser( Why ) ;
         END (* if *) ;
      END (* if *) ;
   END SetErrorReason ;


   PROCEDURE MustNotError( Code : INTEGER ) ;
   BEGIN
      IF ( Code < 0 ) AND (* was *) Ok() THEN
         ErrorReason := Code ;
      END (* if *) ;
   END MustNotError ;


   PROCEDURE SelectCharacterInput( VAR String : ARRAY OF CHAR ) ;
   BEGIN
      CurrentString := ADR( String[ 0 ] ) ;
      CurrentStringLength := LengthC( String ) ;
      Offset := 0 ;
   END SelectCharacterInput ;


   PROCEDURE NextCh( VAR Ch : CHAR ) : BOOLEAN ;
   BEGIN
      LastOffset := Offset ;
      IF Offset < CurrentStringLength THEN
         Ch := CurrentString^[ Offset ] ;
         INC( Offset ) ;
         RETURN TRUE ;
      ELSE
         Ch := EndStringCh ;
         RETURN FALSE ;
      END (* if *) ;
   END NextCh ;


   PROCEDURE PeekCh( VAR Ch : CHAR ) : BOOLEAN ;
   BEGIN
      IF Offset < CurrentStringLength THEN
         Ch := CurrentString^[ Offset ] ;
         RETURN TRUE ;
      ELSE
         Ch := EndStringCh ;
         RETURN FALSE ;
      END (* if *) ;
   END PeekCh ;


   PROCEDURE DidNotWantCh ;
   BEGIN
      IF EscapedCh THEN
         EscapedCh := FALSE ;
         Offset := LastOffset - 1 ;
      ELSE
         Offset := LastOffset ;
      END (* if *) ;
   END DidNotWantCh ;


   PROCEDURE NextUpperCaseCh( VAR Ch : CHAR ) : BOOLEAN ;
   BEGIN
      IF NextCh( Ch ) THEN
         Capital( Ch ) ;
         RETURN TRUE ;
      ELSE
         RETURN FALSE ;
      END (* if *) ;
   END NextUpperCaseCh ;


   PROCEDURE NextEscapedCh( VAR Ch : CHAR ) : BOOLEAN ;
   BEGIN
      EscapedCh := FALSE ;
      IF NextCh( Ch ) THEN
         IF Ch = '|' THEN
            IF NextCh( Ch ) THEN
               EscapedCh := TRUE ;
               RETURN TRUE ;
            ELSE
               SetErrorReason( MissingCharacterAfterEscape ) ;
               RETURN FALSE ;
            END (* if *) ;
         END (* if *) ;
      ELSE
         RETURN FALSE ;
      END (* if *) ;
      RETURN TRUE ;
   END NextEscapedCh  ;


   PROCEDURE CheckQuoted( NoAngleBrackets : BOOLEAN ;
                          WordEndingChars : ARRAY OF CHAR ) ;
   VAR
      Ch                  : CHAR ;
   BEGIN
      CopyCC( WordEndingChars , EndWordChars ) ;
      IF NextCh( Ch ) THEN
         IF ( Ch = '"' ) OR ( Ch = "'" ) THEN
            EndWordChars[ 0 ] := Ch ;
            EndWordChars[ 1 ] := EndStringCh ;
            WasQuoted := TRUE ;
         ELSIF ( Ch = '<' ) AND ( NoAngleBrackets = FALSE ) THEN
            EndWordChars := ">>" ;
            SkipWhiteSpace ;
            WasQuoted := FALSE ;
         ELSE
            DidNotWantCh ;
            WasQuoted := FALSE ;
         END (* if *) ;
      END (* if *) ;
   END CheckQuoted ;


   PROCEDURE SkipWhiteSpace ;
   VAR
      Ch                  : CHAR ;
   BEGIN
      WHILE NextCh( Ch ) AND ( Ch = ' ' ) DO
         (* nothing *) ;
      END (* while *) ;
      DidNotWantCh ;
   END SkipWhiteSpace ;


   PROCEDURE NotEndWordCh( VAR Ch : CHAR ) : BOOLEAN ;
   BEGIN
      RETURN EscapedCh OR ( NOT Instr( EndWordChars , Ch ) ) ;
   END NotEndWordCh ;


   PROCEDURE Instr( Target : ARRAY OF CHAR ; Ch : CHAR ) : BOOLEAN ;
   VAR
      Index                  : CARDINAL ;
   BEGIN
      Index := 0 ;
      RETURN FindC( Target , Ch , Index ) ;
   END Instr ;

   PROCEDURE New( VAR Pointer : ADDRESS ; Size : CARDINAL ) ;
   VAR
      Result                  : INTEGER ;
   BEGIN
      Result := Allocate( Pointer , Size ) ;
      IF Result < 0 THEN
         Pointer := NIL ;
         ErrorReason := Result ;
      END (* if *) ;
   END New ;


   PROCEDURE Dispose( VAR Pointer : ADDRESS ) ;
   VAR
      Result                        : INTEGER ;
   BEGIN
      IF Pointer <> NIL THEN
         Result := Deallocate( Pointer ) ;
         IF Result < 0 THEN
            ErrorReason := Result ;
         END (* if *) ;
         Pointer := NIL ;
      END (* if *) ;
   END Dispose ;



   PROCEDURE ReadWord( VAR String : STRING ) : BOOLEAN ;
   VAR
      I                           : CARDINAL ;
      Buffer                      : ARRAY [ 0..MaxStringSize ] OF CHAR ;
      Ch                          : CHAR ;
   BEGIN
      I := 0 ;
      WHILE NextEscapedCh( Ch ) AND NotEndWordCh( Ch ) DO
         Buffer[ I ] := Ch ;
         IF I < MaxStringSize THEN
            INC( I ) ;
         ELSE
            SetErrorReason( WordTooLarge ) ;
         END (* if *) ;
      END (* while *) ;
      DidNotWantCh ;
      IF WasQuoted THEN
         IF NOT ( NextCh( Ch ) AND Instr( EndWordChars , Ch ) ) THEN
            SetErrorReason( MissingTrailingQuote ) ;
         END (* if *) ;
      END (* if *) ;

      (* Null strings are only allowed if they are quoted *)

      IF Ok() AND ( WasQuoted OR ( I > 0 ) ) THEN
         IF I <= HIGH( Buffer ) THEN
            Buffer[ I ] := EndStringCh ;
         END (* if *) ;
         String := CopyCS( Buffer ) ;
      ELSE
         String := NIL ;
      END (* if *) ;
      RETURN Ok() AND ( String <> NIL ) ;
   END ReadWord ;

   PROCEDURE MakeNewHeader( VAR Header : DecodedInformation ) : BOOLEAN ;
   BEGIN
      New( Header , SIZE( Header^ ) ) ;
      IF Ok() THEN
         WITH Header^ DO
            Validity := ValidPassword ;
            Keywords := NIL ;
         END (* with *) ;
      END (* if *) ;
      RETURN Ok() ;
   END MakeNewHeader ;


   PROCEDURE ParseKeyName( VAR Keyword : KeywordR ) : BOOLEAN ;
   VAR
      Ch                      : CHAR ;
   BEGIN
      CheckQuoted( TRUE , "/, [" ) ;
      IF ReadWord( Keyword.Name ) THEN
         Capitalize( Keyword.Name^ ) ;
      ELSE
         SetErrorReason( MissingKeywordName ) ;
      END (* if *) ;
      RETURN Ok() ;
   END ParseKeyName ;

   PROCEDURE ParseSlashOptions( VAR Keyword : KeywordR ) : BOOLEAN ;
      PROCEDURE TranslateSlashCh( Ch : CHAR ;
                              VAR Option : SlashOptions ;
                              VAR Keyword : KeywordR ) : BOOLEAN ;
      VAR
         I                  : CARDINAL ;
         NumberString       : STRING ;
         Number             : CARDINAL ;
         AKSICEL            : ARRAY [ 0..6 ] OF CHAR ;
      BEGIN
         I := 0 ;
         AKSICEL := "AKSICEL" ;
         IF FindC( AKSICEL , Ch , I ) THEN
            Option := VAL( SlashOptions , I ) ;
         ELSIF Ch = '?' THEN
            Option := OptionQuantity ;
            Keyword.Parsing.MaximumQuantity := MaximumNumberOfArguments ;
         ELSIF Instr( "=123456789" , Ch ) THEN
            Option := OptionQuantity ;
            IF Ch = '=' THEN
               Keyword.Parsing.ExactQuantityRequired := TRUE ;
               IF NOT NextCh( Ch ) THEN
                  SetErrorReason( BadNumberInQuantity ) ;
               END (* if *) ;
            END (* if *) ;
            DidNotWantCh ;
            WITH Keyword.Parsing DO
               CheckQuoted( TRUE , "/, [" ) ;
               IF ReadWord( NumberString ) THEN
                  MustNotError( StringToCardinal( Number , NumberString^ ) ) ;
               END (* if *) ;
               IF Number > MaximumNumberOfArguments THEN
                  SetErrorReason( TooLargeAQuantity ) ;
                  MaximumQuantity := 1 ;
               END (* if *) ;
               MaximumQuantity := Number ;
            END (* with *) ;
            DidNotWantCh ;
            IF Number = 0 THEN
               SetErrorReason( CannotHaveQuantityZero ) ;
            END (* *if *) ;
            IF ( Keyword.Type = State ) AND
               ( Keyword.Parsing.MaximumQuantity <> 1 ) THEN
               SetErrorReason( StateCanOnlyHaveQuantityOne ) ;
            END (* if *) ;
            IF ( Keyword.Type = Literal ) AND
               ( Keyword.Parsing.MaximumQuantity <> 1 ) THEN
               SetErrorReason( LiteralCanOnlyHaveQuantityOne ) ;
            END (* if *) ;
         ELSE
            SetErrorReason( MissingSlashOption ) ;
         END (* if *) ;
         RETURN Ok() ;
      END TranslateSlashCh ;

   VAR
      Ch                        : CHAR ;
      Option                    : SlashOptions ;
      Options                   : SlashOptionSet ;
      TypeOptions               : SlashOptionSet ;
      Nil                       : STRING ;
   BEGIN
      Nil := NIL ;
      Options := SlashOptionSet{ } ;
      WHILE Ok() AND NextCh( Ch ) AND ( Ch = '/' ) DO
         IF NextUpperCaseCh( Ch ) THEN
            IF TranslateSlashCh( Ch , Option , Keyword ) THEN
               IF Option IN Options THEN
                  SetErrorReason( DuplicatedOptions ) ;
               ELSE
                  INCL( Options , Option ) ;
               END (* if *) ;
            END (* if *) ;
         ELSE
            SetErrorReason( MissingSlashOption ) ;
         END (* if *) ;
      END (* while *) ;
      DidNotWantCh ;
      TypeOptions := Options * SlashOptionSet{ OptionTypeState ,
                                               OptionTypeInteger ,
                                               OptionTypeCardinal ,
                                               OptionTypeExistent ,
                                               OptionTypeLiteral } ;
      WITH Keyword DO
         Parsing.Always := OptionAlways IN Options ;
         Parsing.KeywordNeeded := OptionKeywordNeeded IN Options ;
         IF Cardinality( TypeOptions ) > 1 THEN
            SetErrorReason( ConflictingTypeSpecification ) ;
         ELSE
            IF OptionTypeState IN TypeOptions THEN
               Type := State ;
               Parsing.KeywordNeeded := TRUE ;
               IF Parsing.MaximumQuantity <> 1 THEN
                  SetErrorReason( StateCanOnlyHaveQuantityOne ) ;
               END (* if *) ;
               Parsing.MaximumQuantity := 1 ;
               Quantity := 1 ;
               ConvertToValue( Keyword , Nil , 0 ) ; (* Set argument FALSE *)
            ELSIF OptionTypeInteger IN TypeOptions THEN
               Type := Integer ;
            ELSIF OptionTypeCardinal IN TypeOptions THEN
               Type := Cardinal ;
            ELSIF OptionTypeExistent IN TypeOptions THEN
               Type := Existent ;
               IF PeekCh( Ch ) AND ( Ch = '-' ) THEN
                  CheckQuoted( TRUE , "/, [" ) ;
                  IF ReadWord( Parsing.Extension ) THEN
                  END (* if *) ;
               END (* if *) ;
            ELSIF OptionTypeLiteral IN TypeOptions THEN
               Type := Literal ;
            END (* if *) ;
         END (* if *) ;
      END (* with *) ;
      RETURN Ok() ;
   END ParseSlashOptions ;


   PROCEDURE ConvertToValue( VAR Keyword : KeywordR ;
                             VAR StringArg : STRING ;
                             Index : CARDINAL ) ;
   VAR
      Result                  : INTEGER ;
      Value                   : ValueR ;

   BEGIN
      WITH Keyword DO
         IF ( StringArg = NIL ) AND ( Type <> State ) THEN
            RETURN ;
         END (* if *) ;
         CASE Type OF
            State :
               Value.StateValue := StringArg <> NIL ;
         |
            Integer :
               MustNotError( StringToInteger( Value.IntegerValue ,
                                              StringArg^ ) ) ;
         |
            Cardinal :
               MustNotError( StringToCardinal( Value.CardinalValue ,
                                               StringArg^ ) ) ;
         |
            Existent :
               (* Must do the existence test, ... later *)
               IF Instr( StringArg^ , '-' ) THEN
                  Value.StringValue := StringArg ;
               ELSE
                  Value.StringValue := ConcatSS( StringArg ,
                                                 Parsing.Extension ) ;
               END (* if *) ;
               StringArg := NIL ; (* Prevents it being discarded *)
         |
            String, Literal :
               Value.StringValue := StringArg ;
               StringArg := NIL ; (* Prevents it being discarded *)
         END (* case *) ;
         DisposeString( StringArg ) ;
      END (* with *) ;
      IF Ok() THEN
         AddValue( Keyword , Index , Value ) ;
      END (* if *) ;
   END ConvertToValue ;

   PROCEDURE AddValue( VAR Keyword : KeywordR ;
                           Index : CARDINAL ;
                           Value : ValueR ) ;
   BEGIN
      WITH Keyword DO
         IF Index >= Quantity THEN
            Quantity := Index + 1 ;
         END (* if *) ;
         IF Quantity > Parsing.MaximumQuantity THEN
            SetErrorReason( TooManyArguments ) ;
            RETURN ;
         END (* if *) ;
         IF Index >= Values.Size THEN
            GrowValueArray( Keyword ) ;
         END (* if *) ;
         IF Ok() THEN
            Values.Array^[ Index ] := Value ;
         END (* if *) ;
      END (* with *) ;
   END AddValue ;


   PROCEDURE GrowValueArray( VAR Keyword : KeywordR ) ;
   CONST
      GrowIncrement               = 4 ;
   VAR
      BiggerValueArray            : ValueArrayP ;
      BiggerSize                  : CARDINAL ;
      V                           : CARDINAL ;
   BEGIN

      WITH Keyword.Values DO
         IF Size = 0 THEN
            BiggerSize := 1 ;
         ELSE
            BiggerSize := Size + GrowIncrement ;
         END (* if *) ;
         New( BiggerValueArray ,
              BiggerSize * SIZE( BiggerValueArray^[ 0 ] ) ) ;
         IF Ok() THEN
            V := 0 ;
            WHILE V < BiggerSize DO
               BiggerValueArray^[ V ].StringValue := NIL ;
               INC( V ) ;
            END (* while *) ;

            V := 0 ;
            WHILE V < Size DO
               BiggerValueArray^[ V ] := Array^[ V ] ;
               INC( V ) ;
            END (* while *) ;

            Dispose(Array) ;
            Array := BiggerValueArray ;
            Size := BiggerSize ;
         END (* if *) ;
      END (* with *) ;
   
END GrowValueArray ;



   PROCEDURE RemoveValueArray( VAR Keyword : KeywordR ) ;
   VAR
      Index                        : CARDINAL ;
   BEGIN
      WITH Keyword DO
         IF Type IN ArgumentTypeSet{ String , Existent , Literal } THEN
            Index := 0;
            WHILE Index <  Values.Size DO
               DisposeString( Values.Array^[ Index ].StringValue ) ;
               INC( Index ) ;
            END (* while *) ;
         END (* if *) ;
         Dispose( Values.Array ) ;
         Values.Size := 0 ;
      END (* with *) ;
   END RemoveValueArray ;

   PROCEDURE ParseDefaults( VAR Keyword : KeywordR ) : BOOLEAN ;
   VAR
      Ch                        : CHAR ;
      StringValue               : STRING ;
      Value                     : ValueR ;
      ValueIndex                : CARDINAL ;
   BEGIN
      IF NextCh( Ch ) AND ( Ch = '[' ) THEN
         IF Keyword.Type = State THEN
            SetErrorReason( NoDefaultsWithStateKeywords ) ;
            RETURN FALSE ;
         END (* if *) ;
         IF Keyword.Type = Literal THEN
            SetErrorReason( NoDefaultsWithLiteralKeywords ) ;
            RETURN FALSE ;
         END (* if *) ;
         IF Keyword.Parsing.ExactQuantityRequired THEN
            SetErrorReason( NoDefaultsWithExactQuantity ) ;
            RETURN FALSE ;
         END (* if *) ;
         ValueIndex := 0 ;
         REPEAT
            SkipWhiteSpace ;
            CheckQuoted( TRUE , ", ]" ) ;
            IF ReadWord( StringValue ) THEN
               ConvertToValue( Keyword , StringValue , ValueIndex ) ;
            END (* if *) ;
            INC( ValueIndex ) ;
            DisposeString( StringValue ) ;
         UNTIL ( NOT Ok() ) OR
               ( NOT NextCh( Ch ) ) OR
               ( Ch = ']' ) ;
         IF Ch <> ']' THEN
            SetErrorReason( MissingCloseSquareBracket ) ;
         END (* if *) ;
      ELSE
         DidNotWantCh ;
      END (* if *) ;
      RETURN Ok() ;
   END ParseDefaults ;

   PROCEDURE AddKeyword( Information : DecodedInformation ;
                     VAR Keyword : KeywordR ) ;
   VAR
      KeywordPointer               : KeywordP ;

      PROCEDURE Append( VAR Chain : KeywordP ) ;
      BEGIN
         IF Chain = NIL THEN
            Chain := KeywordPointer ;
         ELSE
            Append( Chain^.Next ) ;
         END (* if *) ;
      END Append ;

   BEGIN
      New( KeywordPointer , SIZE( KeywordPointer^ ) ) ;
      IF Ok() THEN
         KeywordPointer^ := Keyword ;
         Append( Information^.Keywords ) ;
      END (* if *) ;
   END AddKeyword ;

   PROCEDURE Parse( VAR DecodedArguments : DecodedInformation ;
                    VAR KeyString : ARRAY OF CHAR ;
                    VAR ArgumentString : ARRAY OF CHAR ) : INTEGER ;
   VAR
      Information             : DecodedInformation ;
      Junk                    : INTEGER ;


      PROCEDURE ParseKeywords() : BOOLEAN ;
      VAR
         Keyword                 : KeywordR ;
         Ch                      : CHAR ;
      BEGIN
         CurrentFacility := ArgumentKeyFacility ;
         SelectCharacterInput( KeyString ) ;
         SkipWhiteSpace ;
         REPEAT
            Keyword.Next := NIL ;
            Keyword.Name := NIL ;
            Keyword.Quantity := 0 ;
            Keyword.Type := String ;
            Keyword.Values.Size := 0 ;
            Keyword.Values.Array := NIL ;
            Keyword.Parsing.Always := FALSE ;
            Keyword.Parsing.Extension := NIL ;
            Keyword.Parsing.KeywordNeeded := FALSE ;
            Keyword.Parsing.MaximumQuantity := 1 ;
            Keyword.Parsing.ExactQuantityRequired := FALSE ;
            Keyword.Parsing.NextArgumentIndex := 0 ;
            IF ParseKeyName( Keyword ) THEN
               IF ParseSlashOptions( Keyword ) THEN
                  IF ParseDefaults( Keyword ) THEN
                     AddKeyword( Information , Keyword ) ;
                  ELSE
                     RemoveValueArray( Keyword ) ;
                  END (* if *) ;
               END (* if *) ;
            END (* if *) ;
            IF NextCh( Ch ) THEN
               IF NOT Instr( ", " , Ch ) THEN
                  SetErrorReason( MissingKeywordDelimiter ) ;
               END (* if *) ;
            END (* if *) ;
            WHILE NextCh( Ch ) AND Instr( ", " , Ch ) DO
               (* skip them *) ;
            END (* while *) ;
            DidNotWantCh ;
         UNTIL ( Ch = EndStringCh ) OR ( NOT Ok() ) ;
         RETURN Ok() ;
      END ParseKeywords ;

      PROCEDURE ParseArguments() : BOOLEAN ;
      VAR
         LiteralStart             : CARDINAL ;
         Word                     : STRING ;
         Keyword                  : KeywordP ;
         Value                    : ValueR ;
         Ch                       : CHAR ;

         PROCEDURE ProcessKeyword() : BOOLEAN ;
         VAR
            CapitalisedWord          : STRING ;

            PROCEDURE ProcessLiteralArgument() : BOOLEAN ;
            VAR
               Keyword                  : KeywordP ;
               LiteralEnd               : CARDINAL ;
            BEGIN
               LOOP
                  DisposeString( CapitalisedWord ) ;
                  SkipWhiteSpace ;
                  LiteralEnd := Offset ;
                  IF ( NextCh( Ch ) AND ( Ch = '-' ) ) AND
                     ( PeekCh( Ch ) AND (NOT Instr( "0123456789" , Ch ) )) THEN
                  ELSE
                     DidNotWantCh ;
                  END (* if *) ;
                  CheckQuoted( TRUE , "  " ) ;
                  IF NOT ReadWord( CapitalisedWord ) THEN
                     EXIT ;
                  END (* if *) ;
                  Capitalize( CapitalisedWord^ ) ;
                  IF FindKeyword( CapitalisedWord ,
                                     Information^.Keywords ,
                                     Keyword ) THEN
                     EXIT ;
                  END (* if *) ;
               END (* loop *) ;
               DisposeString( Word ) ;
               Word := ExtractCS( CurrentString^ , LiteralStart , LiteralEnd );
               RETURN TRUE ;
            END ProcessLiteralArgument ;

         BEGIN
            CapitalisedWord := CopySS( Word ) ;
            IF CapitalisedWord = NIL THEN
               (* NotEnoughMemory *)
               RETURN FALSE ;
            END (* if *) ;
            Capitalize( CapitalisedWord^ ) ;
            IF FindKeyword( CapitalisedWord ,
                            Information^.Keywords ,
                            Keyword ) THEN
               LiteralStart := Offset ;
               IF Keyword^.Type = Literal THEN
                  RETURN ProcessLiteralArgument() ;
               ELSE
                  IF PeekCh( Ch ) AND ( Ch = ',' ) THEN
                     DisposeString( CapitalisedWord ) ;
                     RETURN FALSE ; (* Cannot be keyword, followed by a ',' *)
                  END (* if *) ;
                  IF Keyword^.Type = State THEN
                     (* /S keywords do not have arguments *)
                  ELSE
                     DisposeString( Word ) ;
                     SkipWhiteSpace ;
                     CheckQuoted( TRUE , ", " ) ;
                     IF NOT ReadWord( Word ) THEN
                        SetErrorReason( MissingArgumentAfterKeyword ) ;
                     END (* if *) ;
                  END (* if *) ;
                  DisposeString( CapitalisedWord ) ;
                  RETURN TRUE ;
               END (* if *) ;
            ELSE
               IF ( Keyword <> NIL ) AND ( Keyword^.Type = Literal ) THEN
                  RETURN ProcessLiteralArgument() ;
               END (* if *) ;
            END (* if *) ;
            DisposeString( CapitalisedWord ) ;
            RETURN FALSE ;
         END ProcessKeyword ;


         PROCEDURE FindKeywordForUnKeywordedItems( VAR Keyword : KeywordP ) ;
         BEGIN
            WHILE ( Keyword <> NIL ) AND
                  ( Keyword^.Parsing.KeywordNeeded ) DO
               Keyword := Keyword^.Next ;
            END (* while *) ;
         END FindKeywordForUnKeywordedItems ;


(*    PROCEDURE ParseArguments() : BOOLEAN ; *)

         PROCEDURE CheckArgumentsSatisfied( Keyword : KeywordP ) ;
         BEGIN
            WHILE Keyword <> NIL DO
               WITH Keyword^ DO
                  IF Parsing.Always AND ( Quantity < 1 ) THEN
                     SetErrorReason( MissingArgumentForSlashAlways ) ;
                  END (* if *) ;
                  IF Parsing.ExactQuantityRequired AND
                     ( Quantity < Parsing.MaximumQuantity ) THEN
                     SetErrorReason( TooFewArgumentsForExactQuantity ) ;
                  END (* if *) ;
               END (* with *) ;
               Keyword := Keyword^.Next ;
            END (* while *) ;
         END CheckArgumentsSatisfied ;


(*    PROCEDURE ParseArguments() : BOOLEAN ; *)

      BEGIN
         CurrentFacility := ArgumentUserFacility ;
         SelectCharacterInput( ArgumentString ) ;
         Keyword := Information^.Keywords ;
         IF PeekCh( Ch ) THEN
         END (* if *) ;
         WHILE Ch <> EndStringCh DO                  
            FindKeywordForUnKeywordedItems( Keyword ) ;
            LiteralStart := Offset ;
            SkipWhiteSpace ;
            IF ( NextCh( Ch ) AND ( Ch = '-' ) ) AND
               ( PeekCh( Ch ) AND ( NOT Instr( "0123456789" , Ch ) ) ) THEN
               CheckQuoted( TRUE , ", " ) ;
               IF NOT ( ReadWord( Word ) AND ProcessKeyword() ) THEN
                  SetErrorReason( MissingKeywordAfterDash ) ;
               END (* if *) ;
            ELSE
               DidNotWantCh ;
               CheckQuoted( TRUE , ", " ) ;
               IF ReadWord( Word ) AND ProcessKeyword() THEN
                  (* Ok *) ;
               END (* if *) ;
            END (* if *) ;
            IF Keyword = NIL THEN
               SetErrorReason( CannotAttachArgumentToKeyword ) ;
               RETURN FALSE ;
            END (* if *) ;
            IF Ok() THEN
               ConvertToValue( Keyword^ ,
                               Word ,
                               Keyword^.Parsing.NextArgumentIndex ) ;
               INC( Keyword^.Parsing.NextArgumentIndex ) ;
            END (* if *) ;
            DisposeString( Word ) ;
            SkipWhiteSpace ;
            IF NextCh( Ch ) AND ( Ch = ',' ) THEN
               (* Leave keyword as current one *)
            ELSE
               Keyword := Keyword^.Next ;
               FindKeywordForUnKeywordedItems( Keyword ) ;
               DidNotWantCh ;
            END (* if *) ;
            SkipWhiteSpace ;
            IF PeekCh( Ch ) THEN
            END (* if *) ;
         END (* while *) ;
         CheckArgumentsSatisfied( Information^.Keywords ) ;
         RETURN Ok() ;
      END ParseArguments ;

      PROCEDURE HandleHelpOrIdentifyRequest ;
      VAR
         Word                     : STRING ;
         Keyword                  : KeywordP ;
         Ch                       : CHAR ;
         RealErrorReason          : INTEGER ;


         PROCEDURE IsAKeyword() : BOOLEAN ;
         BEGIN
            Capitalize( Word^ ) ;
            RETURN FindKeyword( Word , Information^.Keywords , Keyword ) ;
         END IsAKeyword ;

(*    PROCEDURE HandleHelpOrIdentifyRequest ; *)

      BEGIN
         RealErrorReason := ErrorReason ;
         ErrorReason := 0 ;
         SelectCharacterInput( ArgumentString ) ;
         REPEAT
            SkipWhiteSpace ;
            IF NextCh( Ch ) AND ( Ch <> '-' ) THEN
               DidNotWantCh ;
            END (* if *) ;
            CheckQuoted( TRUE , ", " ) ;
            IF ReadWord( Word ) THEN
               IF IsAKeyword() THEN
                  IF EqualLS( "HELP" , Keyword^.Name ) THEN
                     ErrorReason := ErrorUser( HelpWantedButBadArguments ) ;
                     DisposeString( Word ) ;
                     RETURN ;
                  ELSIF EqualLS( "IDENTIFY" , Keyword^.Name ) THEN
                     ErrorReason := ErrorUser( IdentifyWantedButBadArguments );
                     DisposeString( Word ) ;
                     RETURN ;
                  END (* if *) ;
               END (* if *) ;
               DisposeString( Word ) ;
            END (* if *) ;
            SkipWhiteSpace ;
            IF NextCh( Ch ) AND ( Ch = ',' ) THEN
               SkipWhiteSpace ;
            ELSE
               DidNotWantCh ;
            END (* if *) ;
         UNTIL Ch = EndStringCh ;
         ErrorReason := RealErrorReason ;
      END HandleHelpOrIdentifyRequest ;

(* PROCEDURE Parse( VAR DecodedArguments : DecodedInformation ;
                    VAR KeyString : ARRAY OF CHAR ;
                    VAR ArgumentString : ARRAY OF CHAR ) : INTEGER ;
*)

   BEGIN
      ErrorReason := 0 ; (* Nothing wrong yet !! *)
      CurrentFacility := ArgumentKeyFacility ;
      IF MakeNewHeader( Information ) THEN
         IF ParseKeywords() THEN
            IF ParseArguments() THEN
               DecodedArguments := Information ;
(*
IF Debugging THEN
IF Describe( DecodedArguments ) < 0 THEN
END;
END (* Debugging *) ;
*)
               RETURN Success ;
            ELSE
               HandleHelpOrIdentifyRequest ;
            END (* if *) ;
         END (* if *) ;
      END (* if *) ;
      Junk := End( Information ) ; (* Discard the data structures *)
      RETURN ErrorReason ;
   END Parse ;


   PROCEDURE End( Information : DecodedInformation ) : INTEGER ;
   VAR
      P                        : KeywordP ;
   BEGIN
      IF Valid( Information ) THEN
         WHILE Information^.Keywords <> NIL DO
            P := Information^.Keywords ;
            DisposeString( P^.Name ) ;
            RemoveValueArray( P^ ) ;
            Information^.Keywords := P^.Next ;
            Dispose( P ) ;
         END (* while *) ;
         Information^.Validity := "David" ;
         Dispose( Information ) ;
         RETURN Success ;
      END (* if *) ;
      RETURN ErrorKey( BadInformation ) ;      
   END End ;

   PROCEDURE Describe( Information : DecodedInformation ) : INTEGER ;
   VAR
      KeywordNumber                     : CARDINAL ;
      I                                 : CARDINAL ;

(*
      PROCEDURE DescribeKeywords( Keywords : KeywordP ) ;
      BEGIN
         REPEAT
            Debug.WriteS( "Keyword #" ) ;
            Debug.WriteC( KeywordNumber ) ;
            Debug.WriteS( " @ " ) ;
            Debug.WritePointer( Keywords ) ;
            Debug.Writeln ;
            IF Keywords <> NIL THEN
               DescribeKeyword( Keywords^ ) ;
               Keywords := Keywords^.Next ;
            END (* if *) ;
            INC( KeywordNumber ) ;
         UNTIL Keywords = NIL ;
      END DescribeKeywords ;
*)
   BEGIN
(*
      Debug.WriteS( "Information @ " ) ;
      Debug.WritePointer( Information ) ;
      Debug.WriteS( " is " ) ;
      IF Valid( Information ) THEN
         Debug.WriteS( "valid*N" ) ;
         KeywordNumber := 1 ;
         DescribeKeywords( Information^.Keywords ) ;
         RETURN Success ;
      ELSE
         Debug.WriteS( "invalid*N" ) ;
         RETURN ErrorKey( BadInformation ) ;
      END (* if *) ;
*)
 END Describe ;

   PROCEDURE SubstituteString( VAR ResultString : ARRAY OF CHAR ;
                               VAR ResultLength : CARDINAL ;
                               OriginalLine : ARRAY OF CHAR ;
                               Information : DecodedInformation ) : INTEGER ;
   VAR
      Keyword                  : KeywordP ;
      Ch                       : CHAR ;
      LastParsedOffset         : CARDINAL ;
      StartOfWordOffset        : CARDINAL ;
      Buffer                   : ARRAY [ 0..MaxStringSize ] OF CHAR ;
      Length                   : CARDINAL ;
      SubstitutedString        : STRING ;
      Word                     : STRING ;
      WordStartedWithMinus     : BOOLEAN ;


      PROCEDURE ExtractName( VAR String : STRING ) ;
      VAR
         StartIndex               : CARDINAL ;
         StopIndex                : CARDINAL ;
         NewString                : STRING ;
         Result                   : BOOLEAN ;
      BEGIN
         StartIndex := 0 ;
         WHILE String^[ StartIndex ] = ' ' DO
            INC( StartIndex ) ;
         END (* if *) ;
         IF String^[ StartIndex ] = '-' THEN
            INC( StartIndex ) ;
            WordStartedWithMinus := TRUE ;
         ELSE
            WordStartedWithMinus := FALSE ;
         END (* if *) ;
         StopIndex := StartIndex ;
         IF FindS( String , ' ' , StopIndex ) OR ( StartIndex > 0 ) THEN
            NewString := ExtractSS( String , StartIndex , StopIndex ) ;
            DisposeString( String ) ;
            String := NewString ;
         END (* if *) ;
      END ExtractName ;


      PROCEDURE AppendBuffer( VAR Buffer : ARRAY OF CHAR ;
                              VAR String : STRING ) ;
      VAR
         Temp                     : STRING ;
         Temp2                    : STRING ;
         Temp3                    : STRING ;
      BEGIN
         Temp := ExtractCS( OriginalLine ,
                            LastParsedOffset ,
                            StartOfWordOffset ) ;
         Temp2 := ConcatSS( String , Temp ) ;
         DisposeString( Temp ) ;
         Temp3 := AppendCS( Buffer , Temp2 ) ;
         DisposeString( Temp2 ) ;
         DisposeString( String ) ;
         String := Temp3 ;
      END AppendBuffer ;


      PROCEDURE InsertValues( Keyword : KeywordP ) ;
      VAR
         Index , MinusIndex        : CARDINAL ;
         Value                     : ValueR ;
      BEGIN
         WITH Keyword^ DO
            IF Quantity = 0 THEN
               (* Name is valid but has no arguments *)
               Buffer[ 0 ] := EndStringCh ;
               AppendBuffer( Buffer , SubstitutedString ) ;
               LastParsedOffset := Offset ;
            ELSE
               FOR Index := 1 TO Quantity DO
                  Value := Values.Array^[ Index - 1 ] ;
                  CASE Type OF
                  String , Literal :
                     CopySC( Value.StringValue , Buffer ) ;
               |
                  Existent :
                     IF WordStartedWithMinus THEN
                        MinusIndex := 0 ;
                        IF FindS ( Value.StringValue , '-', MinusIndex ) THEN
                        END ;
                        ExtractSC( Value.StringValue ,
                                   0 , MinusIndex , Buffer );
                     ELSE
                        CopySC( Value.StringValue , Buffer ) ;
                     END (* if *) ;
               |
                  Cardinal :
                     Length := XCardinalToString( Buffer ,
                                                  Value.CardinalValue ,
                                                  16 ) ;
               |
                  Integer :
                     Length := XIntegerToString( Buffer ,
                                                 Value.IntegerValue ,
                                                 16 ) ;
               |
                  State :
                     IF Value.StateValue THEN
                        CopySC( Keyword^.Name , Buffer ) ;
                     ELSE
                        Buffer[ 0 ] := EndStringCh ;
                     END (* if *) ;
                  END (* case *) ;
                  Length := LengthC( Buffer ) ;
                  IF ( Index < Quantity ) AND ( Length <= HIGH( Buffer ) ) THEN
                     Buffer[ Length ] := ',' ;
                     INC( Length ) ;
                     IF Length <= HIGH( Buffer ) THEN
                        Buffer[ Length ] := EndStringCh;
                     END (* if *);
                  END (* if *) ;                  
                  AppendBuffer( Buffer , SubstitutedString ) ;
                  LastParsedOffset := Offset ;
               END (* for *) ;
            END (* if *) ;
         END (* with *) ;
      END InsertValues ;


   VAR
      Junk                     : CARDINAL ;
   BEGIN
      SubstitutedString := NIL ;
      LastParsedOffset := 0 ;         
      ErrorReason := 0 ;
      SelectCharacterInput( OriginalLine ) ;

      REPEAT
         Buffer[ 0 ] := EndStringCh ;
         SkipWhiteSpace ;
         StartOfWordOffset := Offset ;
         CheckQuoted( FALSE , ", <" ) ;
         IF ReadWord( Word ) THEN
            IF EqualLC( ">>" , EndWordChars ) THEN
               SkipWhiteSpace ;
               IF ( NextCh( Ch ) = FALSE ) OR ( Ch <> '>' ) THEN
                  ErrorReason := ErrorUser( MissingSubstitutionBracket )
               ELSE
                  ExtractName( Word ) ;
                  Capitalize( Word^ ) ;
                  IF GetArgument( Information ,
                                  Word^ ,
                                  ArgumentTypeSet{ String ,
                                                   Existent ,
                                                   Literal ,
                                                   Cardinal ,
                                                   Integer ,
                                                   State } ,
                                  Keyword ) >= 0 THEN
                     IF WordStartedWithMinus
                        AND ( Keyword^.Type <> Existent ) THEN
                        ErrorReason := ErrorKey( 
                                            MinusSubstituteOnlyWithExistant ) ;

                     END (* if *) ;
                     InsertValues( Keyword ) ;
                  ELSIF ( NOT WordStartedWithMinus ) AND
                     ( GetGlobalString( Buffer , Length , Word^ ) >= 0 ) THEN
                     AppendBuffer( Buffer , SubstitutedString ) ;
                  ELSE
                     ErrorReason := ErrorUser( SubstitutionOfNameNotPossible )
                  END (* if *) ;
                  LastParsedOffset := Offset ;
               END (* if *) ;
            END (* if *) ;
            DisposeString( Word ) ;
         END (* if *) ;
         SkipWhiteSpace ;
         IF NextCh( Ch ) AND Instr( ", " , Ch ) THEN
            (* skip them *)
         ELSE
            DidNotWantCh ;
         END (* if *) ;
      UNTIL Ch = EndStringCh ;
      IF Ok() THEN
         StartOfWordOffset := Offset ;
         Buffer[ 0 ] := EndStringCh ;
         AppendBuffer( Buffer , SubstitutedString ) ;
         ErrorReason := ConvertString( SubstitutedString ,
                                       ResultString , ResultLength ) ;
      END (* if *) ;
      DisposeString( SubstitutedString ) ;
      RETURN ErrorReason ;
   END SubstituteString ;

END Decode ;

(* -------------------------------------------------------------------------
   DecodeInit(STRING:KeyString,
              STRING:ArgumentString);INTEGER:Result,
                                     ADDRESS:DecodedArguments
*)
PROCEDURE DecodeInit( VAR DecodedArguments : DecodedInformation ;
                      KeyString : ARRAY OF CHAR ;
                      ArgumentString : ARRAY OF CHAR ) : INTEGER ;
BEGIN
   RETURN Decode.Parse( DecodedArguments , KeyString , ArgumentString ) ;
END DecodeInit ;


(* ----------------------------------------------------------------------------
   DecodeDebug(ADDRESS :DecodedArguments);INTEGER:ResultCode
*)
PROCEDURE DecodeDebug( DecodedArguments : DecodedInformation ) : INTEGER ;
BEGIN
   RETURN Decode.Describe( DecodedArguments ) ;
END DecodeDebug ;


(* ----------------------------------------------------------------------------
   GetStringArg(STRING  :Argumentname,
                CARDINAL:Index,
                ADDRESS :DecodedArguments);INTEGER:ResultCode,
                                           STRING:Arg
*)
PROCEDURE GetStringArg( VAR Arg : ARRAY OF CHAR ;
                        VAR ArgLength : CARDINAL ;
                        ArgumentName : ARRAY OF CHAR ;
                        Index : CARDINAL ;
                        DecodedArguments : DecodedInformation ) : INTEGER ;
VAR
   Result                          : INTEGER ;
   Value                           : ValueR ;
BEGIN
   Result := GetValue( DecodedArguments ,
                       ArgumentName ,
                       Index ,
                       ArgumentTypeSet{ String , Existent , Literal } ,
                       Value ) ;
   IF Result = 0 THEN
      Result := ConvertString( Value.StringValue , Arg , ArgLength ) ;
   END (* if *) ;
   RETURN Result ;
END GetStringArg ;

(* ----------------------------------------------------------------------------
   GetStateArg(STRING  :Argumentname,
               ADDRESS :DecodedArguments);INTEGER:ResultCode,
                                          BOOLEAN:Arg
*)
PROCEDURE GetStateArg( VAR Arg : BOOLEAN ;
                       ArgumentName : ARRAY OF CHAR ;
                       DecodedArguments : DecodedInformation ) : INTEGER ;

VAR
   Result                          : INTEGER ;
   Value                           : ValueR ;
BEGIN
   Result := GetValue( DecodedArguments ,
                       ArgumentName ,
                       1 ,
                       ArgumentTypeSet{ State } ,
                       Value ) ;
   IF Result = 0 THEN
      Arg := Value.StateValue ;
   END (* if *) ;
   RETURN Result ;
END GetStateArg ;

(* ----------------------------------------------------------------------------
   GetIntegerArg(STRING  :Argumentname,
                 CARDINAL:Index,
                 ADDRESS :DecodedArguments);INTEGER:ResultCode,
                                            INTEGER:Arg
*)
PROCEDURE GetIntegerArg( VAR Arg : INTEGER ;
                         ArgumentName : ARRAY OF CHAR ;
                         Index : CARDINAL ;
                         DecodedArguments : DecodedInformation ) : INTEGER ;
VAR
   Result                          : INTEGER ;
   Value                           : ValueR ;
BEGIN
   Result := GetValue( DecodedArguments ,
                       ArgumentName ,
                       Index ,
                       ArgumentTypeSet{ Integer } ,
                       Value ) ;
   IF Result = 0 THEN
      Arg := Value.IntegerValue ;
   END (* if *) ;
   RETURN Result ;
END GetIntegerArg ;

(* ----------------------------------------------------------------------------
   GetCardinalArg(STRING  :Argumentname,
                  CARDINAL:Index,
                  ADDRESS :DecodedArguments);INTEGER:ResultCode,
                                             CARDINAL:Arg
*)
PROCEDURE GetCardinalArg( VAR Arg : CARDINAL ;
                          ArgumentName : ARRAY OF CHAR ;
                          Index : CARDINAL ;
                          DecodedArguments : DecodedInformation ) : INTEGER ;
VAR
   Result                          : INTEGER ;
   Value                           : ValueR ;
BEGIN
   Result := GetValue( DecodedArguments ,
                       ArgumentName ,
                       Index ,
                       ArgumentTypeSet{ Cardinal } ,
                       Value ) ;
   IF Result = 0 THEN
      Arg := Value.CardinalValue ;
   END (* if *) ;
   RETURN Result ;
END GetCardinalArg ;

(* ----------------------------------------------------------------------------
   GetNumberOfValues(STRING  :Argumentname,
                     ADDRESS :DecodedArguments);INTEGER:ResultCode,
                                                CARDINAL:Number
*)
PROCEDURE GetNumberOfValues( VAR Number : CARDINAL ;
                             ArgumentName : ARRAY OF CHAR ;
                             DecodedArguments : DecodedInformation ): INTEGER ;
VAR
   Result                          : INTEGER ;
   Keyword                         : KeywordP ;
BEGIN
   Result := GetArgument( DecodedArguments ,
                          ArgumentName ,
                          ArgumentTypeSet{ Cardinal , Integer , State ,
                                           String , Existent , Literal } ,
                          Keyword ) ;
   IF Result = 0 THEN
      Number := Keyword^.Quantity ;
   END (* if *) ;
   RETURN Result ;
END GetNumberOfValues ;

(* ----------------------------------------------------------------------------
   Substitute(STRING:String, HIDDEN:DecodedInformation);
              INTEGER:ResultCode, STRING:SubstitutedString
*)
PROCEDURE Substitute( VAR ResultString : ARRAY OF CHAR ;
                      VAR ResultLength : CARDINAL ;
                      String : ARRAY OF CHAR ;
                      DecodedArguments : DecodedInformation ) : INTEGER ;
BEGIN
   RETURN Decode.SubstituteString( ResultString , ResultLength ,
                             String ,
                             DecodedArguments ) ;
END Substitute ;

(* ----------------------------------------------------------------------------
   DecodeEnd(ADDRESS :DecodedArguments);INTEGER:ResultCode
*)
PROCEDURE DecodeEnd( DecodedArguments : DecodedInformation ) : INTEGER ;
BEGIN
   RETURN Decode.End( DecodedArguments ) ;
END DecodeEnd ;


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

(*   Xprocedure forms of the above .... *)

(* ----------------------------------------------------------------------------
   XDecodeInit(STRING:KeyString,
               STRING:ArgumentString);ADDRESS:DecodedArguments
*)
PROCEDURE XDecodeInit( KeyString : ARRAY OF CHAR ;
                       ArgumentString : ARRAY OF CHAR ) : DecodedInformation ;
VAR
   Result                              : INTEGER ;
   Handle                              : DecodedInformation ;
BEGIN
   Result := Decode.Parse( Handle , KeyString , ArgumentString ) ;
   IF Result < 0 THEN
      SystemSignal( Result , 4) ;
   END (* if *) ;
   RETURN Handle ;
END XDecodeInit ;


(* ----------------------------------------------------------------------------
   XGetStringArg(STRING  :Argumentname,
                 CARDINAL:Index,
                 ADDRESS :DecodedArguments);STRING:Arg
*)
PROCEDURE XGetStringArg( VAR Arg : ARRAY OF CHAR ;
                         ArgumentName : ARRAY OF CHAR ;
                         Index : CARDINAL ;
                         DecodedArguments : DecodedInformation ) : CARDINAL ;
VAR
   Result                              : INTEGER ;
   Length                              : CARDINAL ;
BEGIN
   Result := GetStringArg( Arg , Length , ArgumentName , Index ,
                           DecodedArguments ) ;
   IF Result < 0 THEN
      Length := 0 ;
      SystemSignal( Result ,6 ) ;
   END (* if *) ;
   RETURN Length ;
END XGetStringArg ;

(* ----------------------------------------------------------------------------
   XGetStateArg(STRING  :Argumentname,
                ADDRESS :DecodedArguments);BOOLEAN:Arg
*)
PROCEDURE XGetStateArg( ArgumentName : ARRAY OF CHAR ;
                        DecodedArguments : DecodedInformation ) : BOOLEAN ;
VAR
   Result                              : INTEGER ;
   Boolean                             : BOOLEAN ;
BEGIN
   Result := GetStateArg( Boolean , ArgumentName , DecodedArguments ) ;
   IF Result < 0 THEN
      SystemSignal( Result , 3 ) ;
   END (* if *) ;
   RETURN Boolean ;
END XGetStateArg ;


(* ----------------------------------------------------------------------------
   XGetIntegerArg(STRING  :Argumentname,
                  CARDINAL:Index,
                  ADDRESS :DecodedArguments);INTEGER:Arg
*)
PROCEDURE XGetIntegerArg( ArgumentName : ARRAY OF CHAR ;
                          Index : CARDINAL ;
                          DecodedArguments : DecodedInformation ) : INTEGER ;
VAR
   Result                              : INTEGER ;
   Integer                             : INTEGER ;
BEGIN
   Result := GetIntegerArg( Integer , ArgumentName , Index ,
                            DecodedArguments ) ;
   IF Result < 0 THEN
      SystemSignal( Result , 4 ) ;
   END (* if *) ;
   RETURN Integer ;
END XGetIntegerArg ;

(* ----------------------------------------------------------------------------
   XGetCardinalArg(STRING  :Argumentname,
                   CARDINAL::Index,
                   ADDRESS :DecodedArguments);CARDINAL:Arg
*)
PROCEDURE XGetCardinalArg( ArgumentName : ARRAY OF CHAR ;
                           Index : CARDINAL ;
                           DecodedArguments : DecodedInformation ) : CARDINAL ;
VAR
   Result                              : INTEGER ;
   Cardinal                            : CARDINAL ;
BEGIN
   Result := GetCardinalArg( Cardinal , ArgumentName , Index ,
                             DecodedArguments ) ;
   IF Result < 0 THEN
      SystemSignal( Result , 4 ) ;
   END (* if *) ;
   RETURN Cardinal ;
END XGetCardinalArg ;

(* ----------------------------------------------------------------------------
   XGetNumberOfValues(STRING  :Argumentname,
                      ADDRESS :DecodedArguments);CARDINAL:Number
*)
PROCEDURE XGetNumberOfValues( ArgumentName : ARRAY OF CHAR ;
                              DecodedArguments : DecodedInformation ):CARDINAL;
VAR
   Result                              : INTEGER ;
   Number                              : CARDINAL ;
BEGIN
   Result := GetNumberOfValues( Number , ArgumentName , DecodedArguments ) ;
   IF Result < 0 THEN
      SystemSignal( Result , 3 ) ;
   END (* if *) ;
   RETURN Number ;
END XGetNumberOfValues ;

(* ----------------------------------------------------------------------------
   XSubstitute(STRING:String, HIDDEN:DecodedInformation);
               STRING:SubstitutedString
*)
PROCEDURE XSubstitute( VAR ResultString : ARRAY OF CHAR ;
                       VAR ResultLength : CARDINAL ;
                       String : ARRAY OF CHAR ;
                       DecodedArguments : DecodedInformation ) ;
VAR
   Result                           : INTEGER ;
BEGIN
   Result := Decode.SubstituteString( ResultString , ResultLength ,
                                String ,
                                DecodedArguments ) ;
   IF Result < 0 THEN
      SystemSignal( Result , 6 ) ;
   END (* if *) ;
END XSubstitute ;

(* ----------------------------------------------------------------------------
   XDecodeEnd(ADDRESS :DecodedArguments)
*)
PROCEDURE XDecodeEnd( DecodedArguments : DecodedInformation ) ;
VAR
   Result                              : INTEGER ;
BEGIN
   Result := Decode.End( DecodedArguments ) ;
   IF Result < 0 THEN
      SystemSignal( Result , 1 ) ;
   END (* if *) ;
END XDecodeEnd ;

BEGIN
   InstallPANOSHandler();
   Debugging := FALSE ;
   DebugStore := FALSE ;
   DisposeError := FALSE ;
   ValidPassword := "Keith" ;
END DecodeArg.
