'===========================================================================
' Subject: READING COMMAND-LINE PARAMS        Date: 05-23-99 (01:03)       
'  Author: Brian Marstella                    Code: QB, PDS                
'  Origin: brimars@mis.net                  Packet: DOS.ABC
'===========================================================================
' FileName:  CMDDEMO.BAS
' FileDate:  05/23/1999
' FileVer:   1.0
'
' Purpose:  Demonstrates how to read command-line parameters in and parse
'   them neatly into an array.  Reads the command-line case correctly and
'   strip out command prefixes (- and / usually).  Also retains quoted
'   strings as 1 string, rather than splitting to multiple arguments.
'
' Status:   Public Domain
'
' Language: QB45 (maybe others with some work; will not work with QBasic
'   that comes with DOS unless you have someone's subroutines that will
'   allow you to simulate DEF SEG and CALL INTERRUPT)
'
'   Will not work properly from the QB45 programming environment, due to
'   the calls it uses.  It will find the command line entered when starting
'   QB, rather than any other parms.
'
' O/S:      Works with DOS 3.3+, WIN95, WIN98.  I'm not sure if it will
'   work with WINNT, OS/2, etc., due to the vastly different systems.
'
' Notes:    Well, I actually wrote the subroutine for my own use.  However, I
'   know that I had a great deal of trouble figuring out how to use DEF SEG,
'   call interrupt and some others, so hopefully I've documented this file
'   well enough that it will help someone a little.
'
'   Just so you know, this program is provided with absolutely no warranty
'   of any kind.  I don't guarantee that it will or won't do anything at all.
'
'   If you would like to contact me for clarifications or whatever, you may
'   e-mail me at brimars@yahoo.com.
'
'   I'm sure that many of you who have programmed for years are disgusted
'   by my formatting, extensive commenting, non-cryptic usages, child-like
'   syntax, or whatever.  Sorry.  I wrote this for me and average people
'   who might need to access the command-line.  For C and assembly
'   programmers, yes I know that there are many ways to do this much more
'   cleanly, etc.  Some people don't program in C and assembly, though, and
'   might wish to have a way to do this in a somewhat clearer, more homey
'   atmosphere than C (try to figure out how to get command line parms on
'   your own in C; then try to use them).
'
'   If you have constructive suggestions or find that I've missed an
'   important method that a user can use to crash this, let me know.  I'll
'   try to make changes accordingly.
'
' ToDo:     Add a little routine that will get the complete program path and
'   name and store it to element 0 in the array.
'
' Oh, also this program does not reference QB.BI; however, you must have
' QB.LIB available to compile.  If QB.LIB is not present, you must expand it
' off of your distribution disks.  Since this file may not have installed
' properly when you installed QB45, make sure you install it in your library
' directory.  This library is absolutely required to compile files using
' the CALL INTERRUPT, CALL ABSOLUTE, etc.

'**************************************
' Precompile instructions
'**************************************

'**************************************
' dynamic simply means that arrays can
' be redimensioned on the fly and
' allows ERASE to destroy arrays and
' free their memory.
'**************************************

    '$DYNAMIC

'**************************************
' Type Declarations
'**************************************

'**************************************
' type reg is required for call
' interrupt
'**************************************

    TYPE reg
        ax AS INTEGER
        bx AS INTEGER
        cx AS INTEGER
        dx AS INTEGER
        bp AS INTEGER
        di AS INTEGER
        si AS INTEGER
        flags AS INTEGER
    END TYPE
  
'**************************************
' Function Declarations
'**************************************

'**************************************
' Subroutine Declarations
'**************************************

    DECLARE SUB getcmdline (argc%, arg$())
    DECLARE SUB interrupt (intnum%, regsin AS reg, regsout AS reg)
    DECLARE SUB main (argc%, arg$())

'**************************************
' Constant Declarations
'**************************************

    CONST DOSINT = &H21
    CONST FALSE = 0
    CONST TRUE = NOT FALSE

'**************************************
' dimension the array to hold command
' line parameters
'**************************************

    DIM args$(0)

'**************************************
' Begin Main Program
'**************************************

    getcmdline argc%, args$()
    main argc%, args$()

'**************************************
' End Main Program
'**************************************

    END

REM $STATIC
SUB getcmdline (argc%, arg$())

'This subroutine returns the command line as entered (case of characters IS
'retained).  Strips normal terminators (/ and -) and single & double quotes.
'Text enclosed in single and double quotes is kept as a continous string.

'**************************************
' dimension working variables as needed
'**************************************

    DIM regs AS reg
    DIM temparg$(0)

'**************************************
' call the routine to get the PSP
' offset address (stores the command
' line parms among other things...)
'**************************************

    regs.ax = &H6200
    CALL interrupt(DOSINT, regs, regs)
  
'**************************************
' now change the working memory segment
' to whatever value was returned in BX
' by the above call.  Offset 80 of
' this segment stores the entire length
' of the parameters entered (including
' the space between the program name
' and the parms!!!), so the command
' length is actually 1 less than what
' you are led to believe by good sense
' or experience on other systems
'**************************************

    DEF SEG = regs.bx
    clen% = PEEK(&H80) - 1
    cl$ = ""
   
'**************************************
' if offset &h80 was greater than -1
' then parms were entered; this section
' assembles them into a continuous
' string and notes that the argument
' counter (argc%) is at least 1
'**************************************

    IF clen% > -1 THEN
        FOR xloop% = 0 TO clen%
            cl$ = cl$ + CHR$(PEEK(&H80 + xloop% + 1))
        NEXT xloop%
        argc% = 1
    ELSE

'**************************************
' if the value at offset 80 was 0 then
' no command line parms were entered
' and the argument counter is set to 0
' and the sub is terminated
'**************************************

        argc% = 0
        EXIT SUB
    END IF
  
'**************************************
' now change back to the default memory
' segment; otherwise, you'll get tons
' of errors regarding corrupt string
' space and stuff.  Besides, it's good
' practice
'**************************************

    DEF SEG

'**************************************
' begin assembling the arguments by
' first setting up the input array for
' 1 length, initialize it to null, and
' initialize a couple of other vars as
' needed for the subroutine
'**************************************

    REDIM arg$(argc%)
    arg$(argc%) = ""
    c$ = ""
    quotesopen% = FALSE
  
'**************************************
' now start a loop to assemble the
' actual arguments entered into the
' input array area (arg$)
'**************************************
   
    FOR xloop% = 1 TO clen% + 1
       
'**************************************
' set lc$ (last character) equal to the
' current character c$ then set c$
' equal to the next character in the
' command line string (cl$)
'**************************************

        lc$ = c$
        c$ = MID$(cl$, xloop%, 1)

'**************************************
' the select case determines what to do
' depending on what the next character
' c$ is
'**************************************

        SELECT CASE c$
           
'**************************************
' if c$ is a space, then we first find
' out if quotes have started.  if it's
' part of a quoted string, we simply
' add it to the current parameter and
' go about our business.  however, if
' lc$ (last character) was not a space,
' it's not part of a quoted string, and
' it's not the first character in the
' parameter, then we assume that we
' have ended the current parameter.
'
' if the last character was a space,
' there's nothing that needs to be done
'
' if it's part of a quoted string,
' we've already taken care of it
'
' if it's the first character in the
' parms, then there's no sense making
' it a part of the current parm
'
' as an afterthought, we also check if
' the last character was a terminator.
' if so, this evaluation has no meaning
'**************************************

            CASE " "
                IF quotesopen% = TRUE THEN
                    arg$(argc%) = arg$(argc%) + c$
                END IF
                IF (lc$ <> " " AND lc$ <> "-" AND lc$ <> "/" AND quotesopen% = FALSE AND xloop% > 1) THEN
                  
'**************************************
' if we made it this far, then we've
' reached the end of the current parm
' and need to prep for the next one
' first we redimension a temporary
' array to hold all the current parms,
' since we can't redimension the actual
' parm array (arg$) without destroying
' the parms we already collected
'**************************************

                    REDIM temparg$(argc%)
                   
'**************************************
' now we copy all the parms from the
' parm array arg$ to the temporary
' array temparg$
'**************************************

                    FOR yloop% = 1 TO argc%
                        temparg$(yloop%) = arg$(yloop%)
                    NEXT yloop%
                   
'**************************************
' increment the argument counter (good
' a place as any to do it...)
'**************************************

                    argc% = argc% + 1
                   
'**************************************
' now that all the parms are in a temp
' array, we'll redimension our actual
' array arg$ to the new number of args
' expected and then we'll copy the
' parms back using the same method as
' above
'**************************************

                    REDIM arg$(argc%)
                    FOR yloop% = 1 TO argc% - 1
                        arg$(yloop%) = temparg$(yloop%)
                    NEXT yloop%

'**************************************
' next line initializes the current
' arg to NUL and the following lines
' dimension the temporary array back
' to zero to free memory
'**************************************

                    arg$(argc%) = ""
                    ERASE temparg$
                    DIM temparg$(0)
                    firstchar% = TRUE
                END IF

'**************************************
' if the character in c$ is a single '
' or double " quote, then we change the
' status of quotesopen%.  this value is
' used simply to track whether we are
' in the middle of a text string (TRUE)
' or not (FALSE)
'**************************************

            CASE CHR$(34), CHR$(39)
                IF quotesopen% = FALSE THEN
                    quotesopen% = TRUE
                ELSE
                    quotesopen% = FALSE
                END IF

'**************************************
' the next case checks parm terminators
' which I simply strip unless they are
' part of a text string (quotesopen% is
' TRUE or the length of the parm is
' greater than zero).  you can add more
' terminators or other characters to
' strip simply by adding them to the
' case statement separating each by a
' comma.  make sure you also add them
' to the "space" evaluation case above
'**************************************

            CASE "-", "/"
                IF quotesopen% = TRUE OR LEN(arg$(argc%)) > 0 THEN
                    arg$(argc%) = arg$(argc%) + c$
                END IF
           
'**************************************
' if it's any other character, we just
' tack it onto the end of the command
' string and go on about our merry way
'**************************************

            CASE ELSE
                arg$(argc%) = arg$(argc%) + c$
       
        END SELECT

    NEXT xloop%
  
'**************************************
' if the last argument is empty (which
' means that your user entered spaces
' or terminators at the end of the
' command line), then we kill it much
' the same way we added to the array.
' we also subtract 1 from the argument
' counter
'**************************************

    IF LEN(arg$(argc%)) = 0 THEN
        argc% = argc% - 1
       
'**************************************
' if we no longer have any parms left
' then we can simply set the counter to
' zero, destroy the argument array and
' exit the sub
'**************************************

        IF argc% = 0 THEN
            REDIM arg$(0)
            EXIT SUB
        END IF
       
'**************************************
' continue copying the array
'**************************************

        REDIM temparg$(argc%)
        FOR xloop% = 1 TO argc%
            temparg$(xloop%) = arg$(xloop%)
        NEXT xloop%
        REDIM arg$(argc%)
        FOR xloop% = 1 TO argc%
            arg$(xloop%) = temparg$(xloop%)
        NEXT xloop%
        ERASE temparg$
        REDIM temparg$(0)

    END IF

'**************************************
' set c$ and lc$ = NULL to kill the
' space they're taking up
'**************************************

    c$ = ""
    lc$ = ""

END SUB

SUB main (argc%, args$())

' Sample subroutine that uses the arguments found on the command line.

    PRINT "Total Arguments: "; argc%
    PRINT
    IF argc% > 0 THEN
        FOR xloop% = 1 TO argc%
            PRINT "Argument #"; xloop%; " = "; args$(xloop%)
        NEXT xloop%
    END IF
    
END SUB
