/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    Argument

Abstract:

    Argument processing for the MODE utility.

    The functions in this file:

    1.- Parse the MODE command line.
    2.- Perform some basic argument validation.
    3.- Make a request packet that will be eventually routed to a
        device handler.


Author:

    Ramon Juan San Andres (ramonsa) 26-Jun-1991

Notes:

    Due to the complexity of the MODE command line, and the fact that
    we have to support both the DOS5 syntax (tagged parameters) and
    the old DOS syntax (positional parameters), MODE does not use
    the standard ULIB argument parsing.  MODE does its own parsing
    instead.

    The mode command-line can take any of the following forms:

    MODE [/?]

    MODE [device] [/STATUS]

    MODE device cp PREPARE=string

    MODE device cp REFRESH

    MODE device cp SELECT=codepage

    MODE device cp [/STATUS]

    MODE LPTn[:] [c][,l][,r]]

    MODE LPTn[:] [COLS=c] [LINES=l] [RETRY=r]

    MODE LPTn[:]=COMm[:]

    MODE COMm[:] [b[,p[,d[,s[,r]]]]]

    MODE COMm[:] [BAUD=b] [PARITY=p] [DATA=d] [STOP=s] [RETRY=r]
                 [to=on|off] [xon=on|off] [odsr=on|off] [octs=on|off]

    MODE [c[,l]]

    MODE CON[:] [COLS=c] [LINES=l]

    MODE CON[:] [RATE=r DELAY=d]


    where:


    device  :=  LPTn[:] | COMm[:] | CON[:]
    cp      :=  CP  |   CODEPAGE



    The argument parsing of MODE does a syntax-directed translation of
    the command line into a request packet. The translation is based on
    the following language.  Note that some terminal symbols (in uppercase)
    might be language dependent.


    mode        :=  MODE  { statusline | lptline | comline | conline | videoline }

    statusline  :=  /STA*

    lptline     :=  lptdev { lptredir | lptsetold | lptsetnew | lptcp | lptstatus }
    lptred      :=  =comdev
    lptset      :=  { n[,n][,c] | [COLS=n] [LINES=n] [RETRY=c] }
    lptcp       :=  cpstuff
    lptstatus   :=  { /STA* | }

    comline     :=  comdev { comset |   comstatus }
    comset      :=  { n[,c[,n[,f[,c]]]] |   [BAUD=n] [PARITY=c] [DATA=n] [STOP=f] [RETRY=c] }
                                            [to=on|off] [xon=on|off] [odsr=on|off] [octs=on|off]
    comstatus   :=  { /STA* | }

    conline     :=  condev { conrc  |   contyp  |   concp   |   constatus }
    conrc       :=  [COLS=n] [LINES=n]
    contyp      :=  RATE=n DELAY=n
    concp       :=  cpstuff
    constatus   :=  { /STA* | }

    videoline   :=  n[,n]

    cpstuff     :=  cp  { prepare | refresh | select | cpstatus}
    cp          :=  CP | CODEPAGE
    prepare     :=  PREPARE=*
    refresh     :=  REFRESH
    select      :=  SELECT=n
    cpstatus    :=  { /STA* | }

    comdev      :=  COMn[:]
    lptdev      :=  LPTn[:]
    condev      :=  CON[:]

    n           :=  Integer number
    f           :=  floating point number
    c           :=  character



    The functions in this file parse the language shown above. Most of
    the functions have names that correspond to non-terminal symbols in
    the language.


    There are 3 main functions used for reading the command line:

    Match()     -   This function matches a pattern against whatever
                    is in the command line at the current position.

                    Note that this does not advance our current position
                    within the command line.

                    If the pattern has a magic character, then the
                    variables MatchBegin and MatchEnd delimit the
                    substring of the command line that matched that
                    magic character.


    Advance()   -   This functions advances our current position within
                    the command line. The amount by which the position
                    is advanced is determined by the the last Match().


    EndOfInput()-   Returns TRUE if the command line has been consumed.



    e.g.    If the command line has the string "MODE COM1: 1200"

            This is what the following sequence would do

            Match( "*" );       //  TRUE (matches "MODE")
            Advance();

            //
            //  Note that Match() does not advance our position
            //
            MATCH( "LPT" );     //  FALSE (no match)
            MATCH( "COM#" );    //  TRUE (matches "COM" )
            //
            //  At this point, MatchBegin and MatchEnd delimit the
            //  substring "1"
            //
            MATCH( "FOO" );     //  FALSE (no match)

            MATCH( "C*" );      //  TRUE (matches "COM1:");
            //
            //  At this point, MatchBegin and MatchEnd delimit the
            //  substring "OM1:"
            //
            Advance();

            Match( "#" );       //  TRUE (matches "1200");
            Advance();

            EndOfInput();       //  TRUE



Revision History:


--*/


#include "mode.hxx"
#include "common.hxx"
#include "lpt.hxx"
#include "com.hxx"
#include "cons.hxx"


extern "C" {

    #include <ctype.h>
    #include <string.h>

}


//
//Static data
//
PWSTRING    CmdLine;        //  The command line
CHNUM       CharIndex;      //  Index of current character
CHNUM       AdvanceIndex;   //  Index of next parameter
CHNUM       ParmIndex;      //  Index of current parameter
CHNUM       MatchBegin;     //  First index of match
CHNUM       MatchEnd;       //  Last index of match

//
//  Patterns.
//
//  Most patterns contain terminal symbols. Certain characters in a
//  pattern have a magic meaning:
//
//  '*' Matches everything up to the end of the parameter (parameters are
//      delimited by blank space).
//
//  '#' Matches a sequence of digits
//
//  '@' Matches a single character
//
//  '[' Starts an optional sequence. If the first character in the
//      the sequence matches, then all the sequence should match. If
//      the first character in the sequence does not match, then the
//      sequence is skipped.
//
//  ']' End of optional sequence
//
//


//
//  Prototypoes
//

PREQUEST_HEADER
LptLine (
    );

PREQUEST_HEADER
LptRedir (
    IN  ULONG   DeviceNumber
    );

PREQUEST_HEADER
LptSet (
    IN  ULONG   DeviceNumber
    );

PREQUEST_HEADER
LptCp (
    IN  ULONG   DeviceNumber
    );

PREQUEST_HEADER
ComLine (
    );

PREQUEST_HEADER
ComSet (
    IN  ULONG   DeviceNumber
    );

PREQUEST_HEADER
ConLine (
    );

PREQUEST_HEADER
ConRc (
    );

PREQUEST_HEADER
ConTyp (
    );

PREQUEST_HEADER
ConCp (
    );

PREQUEST_HEADER
VideoLine (
    );

PREQUEST_HEADER
CpStuff (
    IN  DEVICE_TTYPE DeviceType,
    IN  ULONG        DeviceNumber
    );

BOOLEAN
AllocateResource(
    );

VOID
DeallocateResource(
    );

BOOLEAN
Match(
    IN  PCSTR   Pattern
    );

BOOLEAN
Match(
    IN  PCWSTRING   Pattern
    );

VOID
Advance(
    );

BOOLEAN
EndOfInput(
    );

PREQUEST_HEADER
MakeRequest(
    IN  DEVICE_TTYPE    DeviceType,
    IN  LONG            DeviceNumber,
    IN  REQUEST_TYPE    RequestType,
    IN  ULONG           Size
    );

ULONG
GetNumber(
    );




INLINE
BOOLEAN
EndOfInput(
    )

/*++

Routine Description:

    Finds out if we are at the end of the command line.

Arguments:

    None

Return Value:

    BOOLEAN -   TRUE if at the end of input, FALSE otherwise


--*/

{

    return (CharIndex >= CmdLine->QueryChCount());

}

PREQUEST_HEADER
GetRequest(
    )

/*++

Routine Description:

    Parses the command line and makes a device request.

Arguments:

    None.

Return Value:

    Pointer to the device request.

Notes:

--*/

{

    PREQUEST_HEADER Request = NULL;
    DSTRING         Switches;

    //
    //  Allocate strings (i.e. patterns ) from the resource
    //

    //
    //  Get the command line and parse it
    //
    if (Switches.Initialize("/-") &&
        AllocateResource() &&
        CmdLine->Initialize( GetCommandLine() )) {

        //
        //  Before anything else, we look for a help switch somewhere
        //  in the command line. This kind of stinks, but this is how
        //  MODE works under DOS, se let's be compatible...
        //
        CharIndex       = 0;

        while ( TRUE ) {

            //
            //  Look for a switch
            //
            CharIndex = CmdLine->Strcspn( &Switches, CharIndex );

            if ( CharIndex != INVALID_CHNUM ) {

                //
                //  There is a switch, see if it is the help switch
                //
                CharIndex++;

                if ( Match( "?" )) {

                    //
                    //  This is a help switch, Display help
                    //
                    DisplayMessageAndExit( MODE_MESSAGE_HELP, NULL, EXIT_SUCCESS );

                }
            } else {
                break;
            }
        }

        //
        //  No help requested, now we can parse the command line. First we
        //  initialize our indeces.
        //
        ParmIndex       = 0;
        CharIndex       = 0;
        AdvanceIndex    = 0;

        //
        //  Match the program name
        //
        Advance();

        Match( "*" );
        Advance();

        //
        //  If there are no parameters, or the only parameter is the
        //  status switch, then this is a request for the status of
        //  all devices.
        //
        if ( EndOfInput() ) {

            Request = MakeRequest( DEVICE_TYPE_ALL,
                                   ALL_DEVICES,
                                   REQUEST_TYPE_STATUS,
                                   sizeof( REQUEST_HEADER ) );

        } else if ( Match( "/STA*"  ) ) {

            Advance();

            if ( !EndOfInput() ) {

                ParseError();
            }

            Request = MakeRequest( DEVICE_TYPE_ALL,
                                   ALL_DEVICES,
                                   REQUEST_TYPE_STATUS,
                                   sizeof( REQUEST_HEADER ) );


        } else if ( Match( "LPT#[:]" ) ) {

            //
            //  lptline
            //
            Request = LptLine();

        } else if ( Match( "COM#[:]"    ) ) {

            //
            //  comline
            //
            Request = ComLine();

        } else if ( Match( "CON[:]" ) ) {

            //
            //  conline
            //
            Request = ConLine();

        } else if ( Match( "#" ) ) {

            //
            //  videoline
            //
            Request = VideoLine();

        } else {

            //
            //  Parse error
            //
            ParseError();
        }

    } else {

        DisplayMessageAndExit( MODE_ERROR_NO_MEMORY, NULL, (ULONG)EXIT_ERROR );

    }

    //
    //  Deallocate strings from resource
    //
    DeallocateResource();

    //
    //  Return the request
    //
    return Request;

}

PREQUEST_HEADER
LptLine (
    )

/*++

Routine Description:

    Takes care of parsing the lptline non-terminal symbol

Arguments:

    None.

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER Request;
    ULONG           DeviceNumber;

    //
    //  Note that at this point we have matched the lpt device.
    //  Get the device number;
    //
    DeviceNumber =  GetNumber();

    Advance();

    if ( EndOfInput() ) {

        //
        //  End redirection
        //
        Request = MakeRequest( DEVICE_TYPE_LPT,
                               DeviceNumber,
                               REQUEST_TYPE_LPT_ENDREDIR,
                               sizeof( REQUEST_HEADER ) );

    } else if ( Match( "/STA*" ) ) {

        //
        //  Status request
        //
        Request = MakeRequest( DEVICE_TYPE_LPT,
                               DeviceNumber,
                               REQUEST_TYPE_STATUS,
                               sizeof( REQUEST_HEADER ) );

    } else if ( Match ( "=" ) ) {

        //
        //  lptredir
        //
        Request = LptRedir( DeviceNumber );

    } else if ( Match( "#" )        || Match( "COLS=#" ) ||
                Match( "LINES=#" )  || Match( "RETRY=@" ) ) {

        //
        //  lptset
        //
        Request = LptSet( DeviceNumber );

    } else if ( Match( "CP" ) || Match( "CODEPAGE" ) ) {

        //
        //  lptcp
        //
        Request = LptCp( DeviceNumber );

    } else {

        //
        //  Error
        //
        ParseError();

    }

    return Request;

}

PREQUEST_HEADER
LptRedir (
    IN  ULONG   DeviceNumber
    )

/*++

Routine Description:

    Takes care of parsing the lptredir non-terminal symbol

Arguments:

    DeviceNumber    -   Supplies the device number

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER     Request;
    PLPT_REQUEST        LptRequest;
    ULONG               ComDevice;

    Advance();

    //
    //  Can only redirect to COM devices
    //
    if ( Match( "COM#[:]" ) ) {

        ComDevice = GetNumber();

        Request = MakeRequest( DEVICE_TYPE_LPT,
                               DeviceNumber,
                               REQUEST_TYPE_LPT_REDIRECT,
                               sizeof(LPT_REQUEST ) );

        LptRequest = (PLPT_REQUEST)Request;

        LptRequest->Data.Redirect.DeviceType    = DEVICE_TYPE_COM;
        LptRequest->Data.Redirect.DeviceNumber  = ComDevice;

    } else {

        //
        //  Error
        //
        ParseError();

    }

    return Request;
}

PREQUEST_HEADER
LptSet (
    IN  ULONG   DeviceNumber
    )

/*++

Routine Description:

    Takes care of parsing the lptset non-terminal symbol

Arguments:

    DeviceNumber    -   Supplies the device number

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER     Request;
    PLPT_REQUEST        LptRequest;
    BOOLEAN             SetCols     =   FALSE;
    BOOLEAN             SetLines    =   FALSE;
    BOOLEAN             SetRetry    =   FALSE;
    ULONG               Cols;
    ULONG               Lines;
    WCHAR               Retry;


    if ( Match( "#" ) ) {

        //
        //  Old syntax, where parameter are positional and comma-delimited.
        //
        //  We will use the following automata for parsing the input
        //  (eoi = end of input)
        //
        //
        //          eoi
        //  [Cols]------------->[End]
        //    |            ^
        //    |,           |eoi
        //    v            |
        //   [X]-----------+
        //    |            ^
        //    | #          |eoi
        //    +-->[Lines]--+
        //    |     |      ^
        //    |     |,     |
        //    |<----+      |
        //    |            |
        //    |,           |eoi
        //    |            |
        //    v            |
        //   [Y]-----------+
        //    |            ^
        //    | @          |eoi
        //    +-->[Retry]--+
        //
        //

        Cols = GetNumber();
        SetCols = TRUE;
        Advance();

        //
        //  X:
        //
        if ( !Match( "," ) ) {
            goto Eoi;
        }
        Advance();

        if ( Match( "#" ) ) {

            //  n
            //  Lines
            //
            Lines = GetNumber();
            SetLines = TRUE;
            Advance();
        }

        //
        //  Y:
        //
        if ( !Match ( "," ) ) {
            goto Eoi;
        }

        if ( Match( "@" ) ) {

            //
            //  Retry
            //
            Retry = CmdLine->QueryChAt( MatchBegin );
            SetRetry = TRUE;
            Advance();
        }

Eoi:
        if ( !EndOfInput() ) {

            //
            //  Error
            //
            ParseError();

        }

    } else {

        //
        //  New syntax, where parameters are tagged. The language assumes
        //  that all parameters are optional (as long as there is at least
        //  one present). If some is required, it is up to the Device
        //  handler to complain latter on.
        //

        while ( !EndOfInput() ) {

            if ( Match( "COLS=#" ) ) {
                //
                //  COLS=
                //
                Cols = GetNumber();
                SetCols = TRUE;
                Advance();

            } else if ( Match( "LINES=#" ) ) {
                //
                //  LINES=
                //
                Lines = GetNumber();
                SetLines = TRUE;
                Advance();

            } else if ( Match( "RETRY=@" ) ) {
                //
                //  RETRY=
                //
                Retry = CmdLine->QueryChAt( MatchBegin );
                SetRetry = TRUE;
                Advance();

            } else {

                ParseError();
            }
        }

    }


    //
    //  Now that we parsed all the parameters, we make the request
    //  packet.
    //
    Request = MakeRequest( DEVICE_TYPE_LPT,
                           DeviceNumber,
                           REQUEST_TYPE_LPT_SETUP,
                           sizeof(LPT_REQUEST ) );

    LptRequest = (PLPT_REQUEST)Request;

    LptRequest->Data.Setup.SetCol   =   SetCols;
    LptRequest->Data.Setup.SetLines =   SetLines;
    LptRequest->Data.Setup.SetRetry =   SetRetry;
    LptRequest->Data.Setup.Col      =   Cols;
    LptRequest->Data.Setup.Lines    =   Lines;
    LptRequest->Data.Setup.Retry    =   Retry;

    return Request;
}

PREQUEST_HEADER
LptCp (
    IN  ULONG   DeviceNumber
    )

/*++

Routine Description:

    Takes care of parsing the lptcp non-terminal symbol

Arguments:

    DeviceNumber    -   Supplies the device number

Return Value:

    Pointer to the device request.

Notes:

--*/

{

    //
    //  Since this is the same for LPT and CON, we use the same
    //  function to handle both.
    //
    return  CpStuff( DEVICE_TYPE_LPT, DeviceNumber );

}

PREQUEST_HEADER
ComLine (
    )

/*++

Routine Description:

    Takes care of parsing the comline non-terminal symbol

Arguments:

    None.

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER Request;
    ULONG           DeviceNumber;

    //
    //  Note that we have already matched the COM device.
    //  Get the device number;
    //
    DeviceNumber = GetNumber();

    Advance();

    if ( Match( "/STA*" ) || EndOfInput() ) {

        //
        //  Status request
        //
        Request = MakeRequest( DEVICE_TYPE_COM,
                               DeviceNumber,
                               REQUEST_TYPE_STATUS,
                               sizeof( REQUEST_HEADER ) );


    } else {

        //
        //  comset
        //
        Request = ComSet( DeviceNumber );

    }

    return Request;

}

PREQUEST_HEADER
ComSet (
    IN  ULONG   DeviceNumber
    )

/*++

Routine Description:

    Takes care of parsing the comset non-terminal symbol

Arguments:

    DeviceNumber    -   Supplies the device number

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER     Request;
    PCOM_REQUEST        ComRequest;


    BOOLEAN         SetBaud         =   FALSE;
    BOOLEAN         SetDataBits     =   FALSE;
    BOOLEAN         SetStopBits     =   FALSE;
    BOOLEAN         SetParity       =   FALSE;
    BOOLEAN         SetRetry        =   FALSE;
    BOOLEAN         SetTimeOut      =   FALSE;
    BOOLEAN         SetXon          =   FALSE;
    BOOLEAN         SetOdsr         =   FALSE;
    BOOLEAN         SetIdsr         =   FALSE;
    BOOLEAN         SetOcts         =   FALSE;
    BOOLEAN         SetDtrControl   =   FALSE;
    BOOLEAN         SetRtsControl   =   FALSE;

    ULONG           Baud;
    ULONG           DataBits;
    STOPBITS        StopBits;
    PARITY          Parity;
    WCHAR           Retry;
    BOOLEAN         TimeOut;
    BOOLEAN         Xon;
    BOOLEAN         Odsr;
    BOOLEAN         Idsr;
    BOOLEAN         Octs;
    DTR_CONTROL     DtrControl;
    RTS_CONTROL     RtsControl;


    if ( Match( "#" ) ) {

        //
        //  Old syntax, where parameter are positional and comma-delimited.
        //
        //  We will use the following automata for parsing the input
        //  (eoi = end of input):
        //
        //          eoi
        //  [Baud]------------->[End]
        //    |            ^
        //    |,           |eoi
        //    v            |
        //   [a]-----------+
        //    |            ^
        //    | @          |eoi
        //    +-->[Parity]-+
        //    |     |      ^
        //    |     |,     |
        //    |<----+      |
        //    |            |
        //    |,           |eoi
        //    |            |
        //    v            |
        //   [b]-----------+
        //    |            ^
        //    | #          |eoi
        //    +-->[Data]---+
        //    |     |      ^
        //    |     |,     |
        //    |<----+      |
        //    |            |
        //    |,           |eoi
        //    v            |
        //   [c]-----------+
        //    |            ^
        //    | #          |eoi
        //    +-->[Stop]---+
        //

        //
        // Assume xon=off
        //

        SetXon      = TRUE;
        SetOdsr     = TRUE;
        SetOcts     = TRUE;
        SetDtrControl = TRUE;
        SetRtsControl = TRUE;
        Xon         = FALSE;
        Odsr        = FALSE;
        Octs        = FALSE;
        DtrControl  = DTR_ENABLE;
        RtsControl  = RTS_ENABLE;

        Baud = ConvertBaudRate( GetNumber() );
        SetBaud = TRUE;
        Advance();

        //
        //  A:
        //
        if ( !Match( "," ) ) {
            goto Eoi;
        }
        Advance();

        if ( !Match( "," ) && Match( "@" ) ) {

            //
            //  Parity
            //
            Parity = ConvertParity( CmdLine->QueryChAt( MatchBegin ) );
            SetParity = TRUE;
            Advance();
        }

        //
        //  B:
        //
        if ( !Match( "," )) {
            goto Eoi;
        }
        Advance();

        if ( Match( "#" )) {

            //
            //  Data bits
            //
            DataBits = ConvertDataBits( GetNumber() );
            SetDataBits = TRUE;
            Advance();
        }

        //
        //  C:
        //
        if ( !Match( "," )) {
            goto Eoi;
        }
        Advance();

        if ( Match( "1.5" ) ) {
            StopBits =  COMM_STOPBITS_15;
            SetStopBits = TRUE;
            Advance();
        } else if ( Match( "#" ) ) {
            StopBits = ConvertStopBits( GetNumber() );
            SetStopBits = TRUE;
            Advance();
        }

        if (!Match( "," )) {
            goto Eoi;
        }

        Advance();

        if ( Match( "x" )) {

            //
            // XON=ON
            //
            SetXon      = TRUE;
            SetOdsr     = TRUE;
            SetOcts     = TRUE;
            SetDtrControl = TRUE;
            SetRtsControl = TRUE;
            Xon         = TRUE;
            Odsr        = FALSE;
            Octs        = FALSE;
            DtrControl = DTR_ENABLE;
            RtsControl = RTS_ENABLE;
            Advance();

        } else if ( Match( "p" )) {
            
            //
            // Permanent retry - Hardware handshaking
            //

            SetXon      = TRUE;
            SetOdsr     = TRUE;
            SetOcts     = TRUE;
            SetDtrControl = TRUE;
            SetRtsControl = TRUE;
            Xon         = FALSE;
            Odsr        = TRUE;
            Octs        = TRUE;
            DtrControl = DTR_HANDSHAKE;
            RtsControl = RTS_HANDSHAKE;
            Advance();

        } else {

            //
            // XON=OFF
            //
            SetXon      = TRUE;
            SetOdsr     = TRUE;
            SetOcts     = TRUE;
            SetDtrControl = TRUE;
            SetRtsControl = TRUE;
            Xon         = FALSE;
            Odsr        = FALSE;
            Octs        = FALSE;
            DtrControl = DTR_ENABLE;
            RtsControl = RTS_ENABLE;
        }

Eoi:
        if ( !EndOfInput() ) {

            //
            //  Error
            //
            ParseError();

        }

    } else {

        //
        //  New syntax, where parameters are tagged. The language assumes
        //  that all parameters are optional (as long as there is at least
        //  one present). If some is required, it is up to the Device
        //  handler to complain latter on.
        //


        while ( !EndOfInput() ) {

            if ( Match( "BAUD=#" ) ) {
                //
                //  BAUD=
                //
                Baud = ConvertBaudRate( GetNumber() );
                SetBaud = TRUE;
                Advance();

            } else if ( Match( "PARITY=@"   ) ) {
                //
                //  PARITY=
                //
                Parity = ConvertParity( CmdLine->QueryChAt( MatchBegin ) );
                SetParity = TRUE;
                Advance();

            } else if ( Match( "DATA=#" ) ) {
                //
                //  DATA=
                //
                DataBits = ConvertDataBits( GetNumber() );
                SetDataBits = TRUE;
                Advance();

            } else if ( Match( "STOP=1.5" ) ) {
                //
                //  STOP=1.5
                //
                StopBits =  COMM_STOPBITS_15;
                SetStopBits = TRUE;
                Advance();

            } else if ( Match( "STOP=#" ) ) {
                //
                //  STOP=
                //
                StopBits = ConvertStopBits( GetNumber() );
                SetStopBits = TRUE;
                Advance();

            } else if ( Match( "RETRY=@" ) ) {
                //
                //  RETRY=
                //
                Retry = ConvertRetry( CmdLine->QueryChAt( MatchBegin ) );
                SetRetry = TRUE;
                Advance();

            } else if ( Match( "TO=ON" ) ) {
                //
                //  TO=ON
                //
                SetTimeOut = TRUE;
                TimeOut    = FALSE;   // FALSE means finite timeout
                Advance();

            } else if ( Match( "TO=OFF" ) ) {
                //
                //  TO=OFF
                //
                SetTimeOut  = TRUE;
                TimeOut     = TRUE;   // TRUE means infinite timeout
                Advance();

            } else if ( Match( "XON=ON" ) ) {
                //
                //  XON=ON
                //
                SetXon  = TRUE;
                Xon     = TRUE;
                Advance();

            } else if ( Match( "XON=OFF" ) ) {
                //
                //  XON=OFF
                //
                SetXon  = TRUE;
                Xon     = FALSE;
                Advance();

            } else if ( Match( "ODSR=ON" ) ) {
                //
                //  ODSR=ON
                //
                SetOdsr = TRUE;
                Odsr    = TRUE;
                Advance();

            } else if ( Match( "ODSR=OFF" ) ) {
                //
                //  ODSR=OFF
                //
                SetOdsr = TRUE;
                Odsr    = FALSE;
                Advance();

            } else if ( Match( "IDSR=ON" ) ) {
                //
                //  IDSR=ON
                //
                SetIdsr = TRUE;
                Idsr    = TRUE;
                Advance();

            } else if ( Match( "IDSR=OFF" ) ) {
                //
                //  IDSR=OFF
                //
                SetIdsr = TRUE;
                Idsr    = FALSE;
                Advance();

            } else if ( Match( "OCTS=ON" ) ) {
                //
                //  OCS=ON
                //
                SetOcts = TRUE;
                Octs    = TRUE;
                Advance();

            } else if ( Match( "OCTS=OFF" ) ) {
                //
                //  OCS=OFF
                //
                SetOcts = TRUE;
                Octs    = FALSE;
                Advance();

            } else if ( Match( "DTR=*"   ) ) {
                //
                //  DTR=
                //
                DtrControl = ConvertDtrControl( CmdLine, MatchBegin, MatchEnd ) ;
                SetDtrControl = TRUE;
                Advance();

            } else if ( Match( "RTS=*"   ) ) {
                //
                //  RTS=
                //
                RtsControl = ConvertRtsControl( CmdLine, MatchBegin, MatchEnd ) ;
                SetRtsControl = TRUE;
                Advance();

            } else {

                ParseError();
            }
        }
    }

    //
    //  Now that parsing is done, we can make the request packet.
    //
    Request = MakeRequest( DEVICE_TYPE_COM,
                           DeviceNumber,
                           REQUEST_TYPE_COM_SET,
                           sizeof(COM_REQUEST ) );

    ComRequest = (PCOM_REQUEST)Request;

    ComRequest->Data.Set.SetBaud        =   SetBaud;
    ComRequest->Data.Set.SetDataBits    =   SetDataBits;
    ComRequest->Data.Set.SetStopBits    =   SetStopBits;
    ComRequest->Data.Set.SetParity      =   SetParity;
    ComRequest->Data.Set.SetRetry       =   SetRetry;
    ComRequest->Data.Set.SetTimeOut     =   SetTimeOut;
    ComRequest->Data.Set.SetXon         =   SetXon;
    ComRequest->Data.Set.SetOdsr        =   SetOdsr;
    ComRequest->Data.Set.SetIdsr        =   SetIdsr;
    ComRequest->Data.Set.SetOcts        =   SetOcts;
    ComRequest->Data.Set.SetDtrControl  =   SetDtrControl;
    ComRequest->Data.Set.SetRtsControl  =   SetRtsControl;


    ComRequest->Data.Set.Baud           =   Baud;
    ComRequest->Data.Set.DataBits       =   DataBits;
    ComRequest->Data.Set.StopBits       =   StopBits;
    ComRequest->Data.Set.Parity         =   Parity;
    ComRequest->Data.Set.Retry          =   Retry;
    ComRequest->Data.Set.TimeOut        =   TimeOut;
    ComRequest->Data.Set.Xon            =   Xon;
    ComRequest->Data.Set.Odsr           =   Odsr;
    ComRequest->Data.Set.Idsr           =   Idsr;
    ComRequest->Data.Set.Octs           =   Octs;
    ComRequest->Data.Set.DtrControl     =   DtrControl;
    ComRequest->Data.Set.RtsControl     =   RtsControl;

    return Request;

}

PREQUEST_HEADER
ConLine (
    )

/*++

Routine Description:

    Takes care of parsing ConLine

Arguments:

    None.

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER Request;

    Advance();

    if ( Match( "/STA*" ) || EndOfInput() ) {

        //
        //  Status request
        //
        Request = MakeRequest( DEVICE_TYPE_CON,
                               0,
                               REQUEST_TYPE_STATUS,
                               sizeof( REQUEST_HEADER ) );


    } else if ( Match( "COLS=#" ) || Match( "LINES=#" ) ) {

        //
        //  conrc
        //
        Request = ConRc();

    } else if ( Match( "RATE=#" ) || Match( "DELAY=#" ) ) {

        //
        //  contyp
        //
        Request = ConTyp();

    } else if ( Match( "CP" ) || Match( "CODEPAGE" ) ) {

        //
        //  concp
        //
        Request = ConCp();

    } else {

        //
        //  Error
        //
        ParseError();

    }

    return Request;

}

PREQUEST_HEADER
ConRc (
    )

/*++

Routine Description:

    Takes care of parsing the conrc non-terminal

Arguments:

    None

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER Request;
    PCON_REQUEST    ConRequest;

    BOOLEAN         SetCol      =   FALSE;
    BOOLEAN         SetLines    =   FALSE;

    ULONG           Col;
    ULONG           Lines;

    while ( !EndOfInput() ) {

        if ( Match( "LINES=#" )) {
            //
            //  LINES=
            //
            Lines = GetNumber();
            SetLines = TRUE;
            Advance();

        } else if ( Match( "COLS=#" )) {
            //
            //  COLS=
            //
            Col = GetNumber();
            SetCol = TRUE;
            Advance();

        } else {

            ParseError();
        }
    }

    //
    //  We are done parsing, we make the request packet.
    //
    Request = MakeRequest( DEVICE_TYPE_CON,
                           0,
                           REQUEST_TYPE_CON_SET_ROWCOL,
                           sizeof(CON_REQUEST ) );

    ConRequest = (PCON_REQUEST)Request;

    ConRequest->Data.RowCol.SetCol      =   SetCol;
    ConRequest->Data.RowCol.SetLines    =   SetLines;
    ConRequest->Data.RowCol.Col         =   Col;
    ConRequest->Data.RowCol.Lines       =   Lines;

    return Request;

}

PREQUEST_HEADER
ConTyp (
    )

/*++

Routine Description:

    Takes care of parsing the contyp non-terminal

Arguments:

    None

Return Value:

    Pointer to the device request.

Notes:

--*/

{
    PREQUEST_HEADER Request;
    PCON_REQUEST    ConRequest;

    BOOLEAN         SetRate     =   FALSE;
    BOOLEAN         SetDelay    =   FALSE;

    ULONG           Rate;
    ULONG           Delay;


    //
    //  RATE=
    //
    if ( Match( "RATE=#" )) {
        Rate = GetNumber();
        SetRate = TRUE;
        Advance();
    }

    //
    //  DELAY=
    //
    if ( Match( "DELAY=#" )) {
        Delay = GetNumber();
        SetDelay = TRUE;
        Advance();
    }

    if ( !EndOfInput() ) {
        //
        //  Error
        //
        ParseError();
    }

    //
    //  We are don parsing, we make the request packet.
    //
    Request = MakeRequest( DEVICE_TYPE_CON,
                           0,
                           REQUEST_TYPE_CON_SET_TYPEMATIC,
                           sizeof(CON_REQUEST ) );

    ConRequest = (PCON_REQUEST)Request;

    ConRequest->Data.Typematic.SetRate  =   SetRate;
    ConRequest->Data.Typematic.SetDelay =   SetDelay;
    ConRequest->Data.Typematic.Rate     =   Rate;
    ConRequest->Data.Typematic.Delay    =   Delay;

    return Request;

}

PREQUEST_HEADER
ConCp (
    )

/*++

Routine Description:

    Takes care of parsing the concp non-terminal symbol

Arguments:

    None

Return Value:

    Pointer to the device request.

Notes:

--*/

{

    return  CpStuff( DEVICE_TYPE_CON, 0 );

}

PREQUEST_HEADER
VideoLine (
    )

/*++

Routine Description:

    Takes care of parsing the videoline non-terminal symbol

Arguments:

    None.

Return Value:

    Pointer to the device request.

Notes:

--*/

{

    PREQUEST_HEADER Request;
    PCON_REQUEST    ConRequest;

    BOOLEAN         SetCol      =   FALSE;
    BOOLEAN         SetLines    =   FALSE;

    ULONG           Col;
    ULONG           Lines;

    //
    //  This is in the old syntax, where parameter are positional
    //  and comma-delimited.
    //
    //  We will use the following automata for parsing the input
    //  (eoi = end of input):
    //
    //          eoi
    //  [Cols]--------->[End]
    //    |         ^
    //    |,        |
    //    v         |
    //   [ ]        |
    //    |         |
    //    |#        |
    //    |         |
    //    v     eoi |
    //  [Lines]-----+
    //


    if ( Match( "#" )) {
        //
        //  Cols
        //
        Col = GetNumber();
        SetCol = TRUE;
        Advance();
    }

    if ( Match( "," ) ) {

        Advance();

        if ( Match( "#" )) {

            Lines = GetNumber();
            SetLines = TRUE;
            Advance();

        } else {

            ParseError();

        }
    }

    if ( !EndOfInput() ) {
        //
        //  Error
        //
        ParseError();
    }


    //
    //  We are done parsing, make the request packet
    //
    Request = MakeRequest( DEVICE_TYPE_CON,
                           0,
                           REQUEST_TYPE_CON_SET_ROWCOL,
                           sizeof(CON_REQUEST ) );

    ConRequest = (PCON_REQUEST)Request;

    ConRequest->Data.RowCol.SetCol      =   SetCol;
    ConRequest->Data.RowCol.SetLines    =   SetLines;
    ConRequest->Data.RowCol.Col         =   Col;
    ConRequest->Data.RowCol.Lines       =   Lines;

    return Request;

}

PREQUEST_HEADER
CpStuff (
    IN  DEVICE_TTYPE DeviceType,
    IN  ULONG        DeviceNumber
    )

/*++

Routine Description:

    Takes care of parsing the cpstuff non-terminal symbol

Arguments:

    DeviceType      -   Supplies device type
    DeviceNumber    -   Supplies device number

Return Value:

    Pointer to the device request.

Notes:

--*/

{

    PREQUEST_HEADER Request;
    PCON_REQUEST    ConRequest;

    Advance();

    if ( Match( "PREPARE=*" ) ) {

        //
        //
        //  PREPARE=
        //
        //  This is a No-Op
        //
        Request = MakeRequest( DeviceType,
                               DeviceNumber,
                               REQUEST_TYPE_CODEPAGE_PREPARE,
                               sizeof( REQUEST_HEADER ) );

    } else if ( Match( "SELECT=#" ) ) {

        //
        //
        //  SELECT=
        //
        //  Note that this relies on the fact that codepage requests
        //  are identical for all devices.
        //

        Request = MakeRequest( DeviceType,
                               DeviceNumber,
                               REQUEST_TYPE_CODEPAGE_SELECT,
                               sizeof( CON_REQUEST ) );

        ConRequest = (PCON_REQUEST)Request;

        ConRequest->Data.CpSelect.Codepage = GetNumber();

    } else if ( Match( "/STA*" ) || EndOfInput() ) {

        //
        //  /STATUS
        //
        Request = MakeRequest( DeviceType,
                               DeviceNumber,
                               REQUEST_TYPE_CODEPAGE_STATUS,
                               sizeof( REQUEST_HEADER ) );

    } else if ( Match( "REFRESH" ) ) {

        //
        //
        //  REFRESH
        //
        //  This is a No-Op
        //
        Request = MakeRequest( DeviceType,
                               DeviceNumber,
                               REQUEST_TYPE_CODEPAGE_REFRESH,
                               sizeof( REQUEST_HEADER ) );

    } else {

        ParseError();

    }

    return Request;

}

BOOLEAN
AllocateResource(
    )

/*++

Routine Description:

    Allocate strings from the resource

Arguments:

    None.

Return Value:

    None

Notes:

--*/

{

    CmdLine =   NEW DSTRING;

    return (NULL == CmdLine) ? FALSE : TRUE;

}

VOID
DeallocateResource(
    )

/*++

Routine Description:

    Deallocate strings from the resource

Arguments:

    None.

Return Value:

    None

Notes:

--*/

{

    DELETE( CmdLine );

}

BOOLEAN
Match(
    IN  PCSTR   Pattern
    )

/*++

Routine Description:

    This function matches a pattern against whatever
    is in the command line at the current position.

    Note that this does not advance our current position
    within the command line.

    If the pattern has a magic character, then the
    variables MatchBegin and MatchEnd delimit the
    substring of the command line that matched that
    magic character.

Arguments:

    Pattern -   Supplies pointer to the pattern to match

Return Value:

    BOOLEAN -   TRUE if the pattern matched, FALSE otherwise

Notes:

--*/

{

    DSTRING     PatternString;
    BOOLEAN     StatusOk;

    StatusOk = PatternString.Initialize( Pattern );

    DebugAssert( StatusOk );

    if ( StatusOk ) {

        return Match( &PatternString );

    } else {

        DisplayMessageAndExit( MODE_ERROR_NO_MEMORY, NULL, (ULONG)EXIT_ERROR );

    }
    //NOTREACHED
    return StatusOk;
}

BOOLEAN
Match(
    IN  PCWSTRING   Pattern
    )

/*++

Routine Description:

    This function matches a pattern against whatever
    is in the command line at the current position.

    Note that this does not advance our current position
    within the command line.

    If the pattern has a magic character, then the
    variables MatchBegin and MatchEnd delimit the
    substring of the command line that matched that
    magic character.

Arguments:

    Pattern -   Supplies pointer to the pattern to match

Return Value:

    BOOLEAN -   TRUE if the pattern matched, FALSE otherwise

Notes:

--*/

{

    CHNUM   CmdIndex;       //  Index within command line
    CHNUM   PatternIndex;   //  Index within pattern
    WCHAR   PatternChar;    //  Character in pattern
    WCHAR   CmdChar;        //  Character in command line;

    DebugPtrAssert( Pattern );

    CmdIndex        = CharIndex;
    PatternIndex    = 0;

    while ( (PatternChar = Pattern->QueryChAt( PatternIndex )) != INVALID_CHAR ) {

        switch ( PatternChar ) {

        case '#':

            //
            //  Match a number
            //
            MatchBegin = CmdIndex;
            MatchEnd   = MatchBegin;

            //
            //  Get all consecutive digits
            //
            while ( ((CmdChar = CmdLine->QueryChAt( MatchEnd )) != INVALID_CHAR) &&
                    isdigit( (char)CmdChar ) ) {
                MatchEnd++;
            }
            MatchEnd--;

            if ( MatchBegin > MatchEnd ) {
                //
                //  No number
                //
                return FALSE;
            }

            CmdIndex = MatchEnd + 1;
            PatternIndex++;

            break;


        case '@':

            //
            //  Match one character
            //
            if ( CmdIndex >= CmdLine->QueryChCount() ) {
                return FALSE;
            }

            MatchBegin = MatchEnd = CmdIndex;
            CmdIndex++;
            PatternIndex++;

            break;


        case '*':

            //
            //  Match everything up to next blank (or end of input)
            //
            MatchBegin  = CmdIndex;
            MatchEnd    = MatchBegin;

            while ( ( (CmdChar = CmdLine->QueryChAt( MatchEnd )) != INVALID_CHAR )  &&
                    ( CmdChar !=  (WCHAR)' ' ) ) {

                MatchEnd++;
            }
            MatchEnd--;

            CmdIndex = MatchEnd+1;
            PatternIndex++;

            break;

        case '[':

            //
            //  Optional sequence
            //
            PatternIndex++;

            PatternChar = Pattern->QueryChAt( PatternIndex );
            CmdChar     = CmdLine->QueryChAt( CmdIndex );

            //
            //  If the first charcter in the input does not match the
            //  first character in the optional sequence, we just
            //  skip the optional sequence.
            //
            if ( ( CmdChar == INVALID_CHAR ) ||
                 ( CmdChar == ' ')           ||
                 ( towupper(CmdChar) != towupper(PatternChar) ) ) {

                while ( PatternChar != ']' ) {
                    PatternIndex++;
                    PatternChar = Pattern->QueryChAt( PatternIndex );
                }
                PatternIndex++;

            } else {

                //
                //  Since the first character in the sequence matched, now
                //  everything must match.
                //
                while ( PatternChar != ']' ) {

                    if ( towupper(PatternChar) != towupper(CmdChar) ) {
                        return FALSE;
                    }
                    CmdIndex++;
                    PatternIndex++;
                    CmdChar = CmdLine->QueryChAt( CmdIndex );
                    PatternChar = Pattern->QueryChAt( PatternIndex );
                }

                PatternIndex++;
            }

            break;

        default:

            //
            //  Both characters must match
            //
            CmdChar = CmdLine->QueryChAt( CmdIndex );

            if ( ( CmdChar == INVALID_CHAR ) ||
                 ( towupper(CmdChar) != towupper(PatternChar) ) ) {

                return FALSE;

            }

            CmdIndex++;
            PatternIndex++;

            break;

        }
    }

    AdvanceIndex = CmdIndex;

    return TRUE;

}

VOID
Advance(
    )

/*++

Routine Description:

    Advances our pointers to the beginning of the next lexeme

Arguments:

    None

Return Value:

    None


--*/

{

    CharIndex = AdvanceIndex;

    //
    //  Skip blank space
    //
    if ( CmdLine->QueryChAt( CharIndex ) == ' ' ) {

        while ( CmdLine->QueryChAt( CharIndex ) == ' ' ) {

            CharIndex++;
        }

        ParmIndex = CharIndex;

    }
}

VOID
ParseError(
    )

/*++

Routine Description:

    Display Invalid parameter error message and exits

Arguments:

    None

Return Value:

    None


--*/

{
    DSTRING Parameter;
    CHNUM   ParmEnd;

    //
    //  Look for end of parameter
    //
    ParmEnd = CmdLine->Strchr( ' ', ParmIndex );


    Parameter.Initialize( CmdLine,
                          ParmIndex,
                          (ParmEnd == INVALID_CHNUM) ? TO_END : ParmEnd - ParmIndex );

    DisplayMessageAndExit( MODE_ERROR_INVALID_PARAMETER,
                           &Parameter,
                           (ULONG)EXIT_ERROR );

}

PREQUEST_HEADER
MakeRequest(
    IN  DEVICE_TTYPE    DeviceType,
    IN  LONG            DeviceNumber,
    IN  REQUEST_TYPE    RequestType,
    IN  ULONG           Size
    )

/*++

Routine Description:

    Makes a request and initializes its header.

Arguments:

    DeviceType      -   Supplies the type of device
    DeviceNumber    -   Supplies the device number
    RequestType     -   Supplies the type of request
    Size            -   Supplies size of the request packet

Return Value:

    Pointer to the device request.

Notes:

--*/

{

    PREQUEST_HEADER Request;

    DebugAssert( Size >= sizeof( REQUEST_HEADER )) ;

    Request = (PREQUEST_HEADER)MALLOC( (unsigned int)Size );

    DebugPtrAssert( Request );

    if ( !Request ) {
        DisplayMessageAndExit( MODE_ERROR_NO_MEMORY, NULL, (ULONG)EXIT_ERROR );
    }

    Request->DeviceType     =   DeviceType;
    Request->DeviceNumber   =   DeviceNumber;
    Request->DeviceName     =   NULL;
    Request->RequestType    =   RequestType;

    return Request;

}

ULONG
GetNumber(
    )

/*++

Routine Description:

    Converts the substring delimited by MatchBegin and MatchEnd into
    a number.

Arguments:

    None

Return Value:

    ULONG   -   The matched string converted to a number


--*/

{
    LONG    Number;


    DebugAssert( MatchEnd >= MatchBegin );

    if ( !CmdLine->QueryNumber( &Number, MatchBegin, (MatchEnd-MatchBegin)+1 ) ) {
        ParseError();
    }

    return (ULONG)Number;

}