;[]-----------------------------------------------------------------[]
;|      SETARGV.ASM -- Parse Command Line                            |
;[]-----------------------------------------------------------------[]

;
;       C/C++ Run Time Library - Version 5.0
; 
;       Copyright (c) 1987, 1992 by Borland International
;       All Rights Reserved.
; 

        INCLUDE RULES.ASI

;       Segment and Group declarations

Header@

;       External references

ExtSym@         _C0argc,        WORD,   __CDECL__
dPtrExt@        _C0argv,                __CDECL__
ExtSym@         _psp,           WORD,   __CDECL__
ExtSym@         _envseg,        WORD,   __CDECL__
ExtSym@         _envLng,        WORD,   __CDECL__
ExtSym@         _osmajor,       BYTE,   __CDECL__
ExtProc@        abort,                  __CDECL__

_DATA           segment
ifdef   WILD
 ExtSym@        _argc,          WORD,                   __CDECL__
 dPtrExt@       _argv,                                  __CDECL__
else
 PubSym@        _argc,          <dw     0>,             __CDECL__
 dPtrPub@       _argv,          0,                      __CDECL__
endif
SavedReturn     dw              ?
SavedDS2        dw              ?
SavedReturn2    dw              ?
SavedSI         dw              ?
SavedDI         dw              ?
                ends

ifdef   WILD
ExtProc@        sbrk,                   __CDECL__
endif

        SUBTTL  Parse Command Line
        PAGE
;/*                                                     */
;/*-----------------------------------------------------*/
;/*                                                     */
;/*     Parse Command Line                              */
;/*     ------------------                              */
;/*                                                     */
;/*-----------------------------------------------------*/
;/*                                                     */
PSPCmd          equ     00080h

_TEXT           segment

SavedDS         dw      ?

ifdef   WILD

;------------------------------------------------------------------------------
;
; Not enough space on stack for the program name.
;
BadProgName     label   near
                jmp     abort@

                public  __wildargv
__wildargv      proc near

else

;This is the symbol which pulls in _setargv
                public __setargv__
__setargv__     equ    0

_INIT_          SEGMENT WORD PUBLIC 'INITDATA'
                db      0                       ;near call
                db      16                      ;priority 16
                dw      offset __setargv
                dw      ?
_INIT_          ENDS

__setargv       proc near
endif

;==============================================================================
;       First, save caller context and Return Address
; Please be aware that this code was hacked from TC 2.0 to work with the
; new startup/exit method.  Since setargv tries to put the arguments on
; the stack it has to make an assumption about what is already on the
; stack.  Under 2.0 all that was on the stack was the return address into
; the startup code.  Under the new startup/exit method there is a return
; address (always near), a saved DS, and another near return address.
; setargv really needs to be rewritten but for now this hack is in place
; to make it work.  If you change what is on the stack in any way you will
; have to change this code!

                pop     word ptr SavedReturn
                pop     word ptr SavedDS2
                pop     word ptr SavedReturn2
                mov     SavedDS, ds
                mov     SavedSI, si
                mov     SavedDI, di
                cld

;       Compute Command Line size

                mov     es, _psp@
                mov     si, PSPCmd      ; ES: SI = Command Line address
                xor     ah, ah
                lods    byte ptr es:[si]
                inc     ax              ; AX = Command Line size including \r
                mov     bp, es
                xchg    dx, si          ; BP:DX = Command Line address
                xchg    bx, ax          ; BX    = Command line size

;       Compute Program Name size

                mov     si, _envLng@
                inc     si
                inc     si              ; SI = Program name offset
                mov     cx, 1           ; CX = Filename size (includes \0)
                cmp     _osmajor@, 3
                jb      NoProgramName
                mov     es, _envseg@
                mov     di, si          ; SI = argv[0] address
                mov     cl, 07fh
                xor     al, al
                repnz   scasb
                jcxz    BadProgName
                xor     cl, 07fh        ; CX = Filename size (includes \0)
NoProgramName   label   near

;       Reserve space for the arguments

                push    ax              ; To be sure nothing in SS:FFFF
                mov     ax, cx
ifndef  WILD
                add     ax, bx
endif
                inc     ax
                and     ax, not 1
                mov     di, sp
                sub     di, ax
                jb      BadProgName
                mov     sp, di          ; SS:DI = Command Line storage address

;       Copy ProgName to the stack

                push    es
                pop     ds
                push    ss
                pop     es
ifndef  WILD
                push    cx
endif
                dec     cx
                rep     movsb
                xor     al, al
                stosb                   ; ASCIIZ string

;       Process Command Line.

;==============================================================================
ifdef WILD
;==============================================================================

;
; The value of "wild_attr" is used in the "findfirst" call as the file
; attribute.
;
; The default value is 0, which will only include "regular" files.
;
; Adding 10H to this value will include directories, 04h will include system
; files, and 02h will include hidden files.
;

wild_attr       equ     0                       ; include only regular files

;------------------------------------------------------------------------------

ffblk           struc

ff_reserved     db      21 dup (?)
ff_attrib       db      ?
ff_ftime        dw      ?
ff_fdate        dw      ?
ff_fsize        dd      ?
ff_name         db      14 dup (?)

ffblk           ends

wild_init_space equ     128                     ; initial buffer allocation
wild_more_space equ     256                     ; buffer size increment

;------------------------------------------------------------------------------

wild_buff_addr  equ     [bp]
wild_buff_size  equ     [bp+4]
wild_buff_max   equ     [bp+6]
wild_arg_src    equ     [bp+8]
wild_arg_dst    equ     [bp+10]

wild_argument   equ     [bp+12]
wild_destin     equ     [bp+16]
wild_path_len   equ     [bp+20]
wild_argc       equ     [bp+22]

wild_DTA_save   equ     [bp+24]
wild_ffblk      equ     [bp+28]

wild_frame_size equ     28 + TYPE ffblk

;------------------------------------------------------------------------------

                mov     cx, bp                  ; save segment of command line
                dec     bx                      ; don't need trailing \0

                sub     sp, wild_frame_size
                mov     bp, sp                  ; bp points at local variables

                push    dx                      ; save cmd line addr
                push    cx                      ; save cmd line seg
                push    bx                      ; save cmd line size
                mov     ax, wild_init_space
                mov     wild_buff_size, ax      ; save initial size
ifndef  __HUGE__
                mov     ds, SavedDS
endif
                push    ax
                call    sbrk@
                pop     cx                      ; toss parameter
                pop     cx                      ; restore cmd line size
                pop     ds                      ; restore cmd line seg
                pop     si                      ; restore cmd line addr

                mov     wild_buff_addr, ax      ; save offset
if      LDATA
                mov     wild_buff_addr+2, dx    ; save segment
                and     ax, dx
else
                mov     wild_buff_addr+2, ss    ; seg = SS
endif
                cmp     ax, -1
                je      NoSbrkSpace             ; abort if not enough space
                add     ax, wild_buff_size
                mov     wild_buff_max, ax       ; save max offset

                mov     ah, 2fh
                int     21h                     ; get current DTA
                mov     wild_DTA_save, bx
                mov     wild_DTA_save+2, es
                push    ds
                push    ss                      ; fflbk is on stack
                pop     ds
                lea     dx, wild_ffblk
                mov     ah, 1ah
                int     21h                     ; switch DTA to ffblk
                pop     ds

                les     di, dword ptr wild_buff_addr
                xor     dx, dx                  ; dx = # of arguments
;
; Start new argument.
;
NewArg:         mov     wild_arg_dst, di
                xor     bh, bh                  ; bh = wildcard flag
;
; Skip leading whitespace.
;
ArgCopy:        mov     wild_arg_src, si        ; save address of argument
                call    GetChar
                jc      ArgCopyDone             ; jump if no more characters
                jz      ArgCopyLoop
                cmp     al, ' '
                je      ArgCopy                 ; skip whitespace
                cmp     al, 9
                je      ArgCopy
                cmp     al, 13
                je      ArgCopy
                cmp     al, '"'
                je      ArgQuote                ; jump if quoted string
;
; Loop to copy unquoted argument.
;
ArgCopyLoop:    call    ArgPushChar             ; store character in destination
                call    GetChar
                jc      ArgComplete             ; jump if end of line
                jz      ArgCopyLoop             ; jump if \"
                cmp     al, ' '
                je      ArgComplete             ; whitespace terminates
                cmp     al, 9
                je      ArgComplete
                cmp     al, 13
                je      ArgComplete             ; whitespace terminates
                cmp     al, '"'
                jne     ArgCopyLoop
ArgComplete:    call    ProcessArg              ; copy or expand argument
                jmp     SHORT NewArg

NoSbrkSpace:    jmp     abort@                  ; error jump

;
; Here if quoted argument.
;
ArgQuote:       call    GetChar
                jc      QuoteDone
                jz      QuoteNext
                cmp     al, '"'                 ; terminating quote ?
                je      QuoteDone
                cmp     al, 13                  ; hit end of line (CR)?
                je      QuoteDone
QuoteNext:      call    ArgPushChar             ; store character in destination
                jmp     SHORT ArgQuote
;
; End of a quoted argument. Push terminating null, do not expand.
;
QuoteDone:      xor     al, al
                call    ArgPushChar             ; push terminating null
                inc     dx                      ; bump arg count
                jmp     SHORT NewArg            ; go get more

;------------------------------------------------------------------------------
;
; Here when done expanding command line. Go build the argv array.
;
ArgCopyDone:    mov     ax, di                  ; ax = unused space
                sub     ax, wild_buff_max
                jz      ArgNoWaste              ; skip if all used
                push    dx
                push    di
ifndef  __HUGE__
                mov     ds, SavedDS
endif
                push    ax
                call    sbrk@                   ; release unused memory
                pop     cx                      ; toss parameter
                pop     di
                pop     dx
ArgNoWaste:     lds     si, dword ptr wild_buff_addr
                mov     cx, di
                sub     cx, si                  ; cx = number of bytes in expanded line
                inc     dx                      ; count program name
                jmp     BuildArgv

;------------------------------------------------------------------------------
;
; Routine to retrieve the next character from the command line.
;       Sets CF when end of line reached.
;       Sets ZF when \ character found (i.e. \")
;
;       bh.bit0 set if wildcard chars found (* or ?)
;       bh.bit1 set if \ character found (\")
;
GetChar         proc    near

                jcxz    GchEnd                  ; jump if no more
                lodsb
                dec     cx
                cmp     al, '\'                 ; escape ?
                je      GchEsc
                cmp     al, '?'
                je      GchWild
                cmp     al, '*'
                je      GchWild
GchRet:         or      ah, 1                   ; clear CF and ZF
                ret
GchWild:        test    bh, bh
                jnz     GchRet                  ; give up if \" has been found
                or      bh, 1
                ret
GchEsc:         jcxz    GchRet                  ; check for \ at end of line
                cmp     byte ptr [si],'"'
                jne     GchRet                  ; only \" is special
                lodsb
                dec     cx
                mov     bh, 2                   ; set \ flag
                xor     ah, ah                  ; clear CF, set ZF
                ret
GchEnd:         stc
                ret

GetChar         endp

;------------------------------------------------------------------------------
;
; Routine to expand a wildcard parameter.
;
;       DS:SI   =       argument address
;       ES:DI   =       destination
; Returns:
;       CX      =       number of expanded arguments (0 = no match)
;
WildExpand      proc    near

                push    ds
                mov     wild_argument, si
                mov     wild_argument+2, ds
                mov     wild_destin, di
                mov     wild_destin+2, es
                mov     word ptr wild_argc, 0
;
; Find the length of the path prefix, if any.
;
                mov     bx, si
WildFindPath:   lodsb
                and     al, al
                jz      WildEndPath
                cmp     al, '\'
                je      WildDelimiter
                cmp     al, ':'
                je      WildDelimiter
                cmp     al, '\'
                jne     WildFindPath
WildDelimiter:  mov     bx, si                  ; save addr past last delimiter
                jmp     SHORT WildFindPath
WildEndPath:    sub     bx, wild_argument
                mov     wild_path_len, bx

                mov     ah, 4eh
                mov     cx, wild_attr           ; file attribute
                lds     dx, dword ptr wild_argument
                int     21h                     ; find first matching file ...
                jc      WildDone
;
; We have a matching file. Add it to the destination string (unless "." or "..")
;
WildAddArg:
;
; If directories are included (10h set in wild_attr), ignore "." and ".."
;
if      wild_attr  AND 16
                push    ss
                pop     ds
                lea     si,wild_ffblk.ff_name
                cmp     byte ptr [si],'.'       ; skip if doesn't start with "."
                jne     WildNoDir
                cmp     byte ptr [si+1],0       ; check for "."
                je      WildNameNext
                cmp     word ptr [si+1],'.'     ; check for ".."
                je      WildNameNext
WildNoDir:
endif

                inc     word ptr wild_argc
                les     di, dword ptr wild_destin
                mov     cx, wild_path_len       ; prefix filename with path
                jcxz    WildCopyName
                lds     si, dword ptr wild_argument
WildCopyPath:   lodsb
                call    ArgPushChar
                loop    WildCopyPath
WildCopyName:   lea     si,wild_ffblk.ff_name   ; copy filename from ffblk
WildNameLoop:   lods    byte ptr ss:[si]
                push    ax
                call    ArgPushChar             ; store char in destination
                pop     ax
                and     al, al                  ; continue until \0
                jnz     WildNameLoop
                mov     wild_destin, di
WildNameNext:   mov     ah, 4fh
                int     21h                     ; find next matching file
                jnc     WildAddArg
;
; Done with expansion. Restore ES:DI, set CX, and return.
;
WildDone:       mov     cx, wild_argc
                les     di, dword ptr wild_destin
                pop     ds
                ret

WildExpand      endp

;------------------------------------------------------------------------------
;
; Routine to store a character in the destination string.
;
ArgPushChar     proc    near

                cmp     di, wild_buff_max       ; space available ?
                jae     ArgMoreSpace
                stosb                           ; yes --> store character
                ret
;
; No more argument space. Grab some more memory through sbrk.
;
ArgMoreSpace:   push    ds
                push    es
                push    si
                push    di
                push    ax
                push    bx
                push    cx
                push    dx

ifndef  __HUGE__
                mov     ds, SavedDS
endif
                mov     ax, wild_more_space
                add     wild_buff_size, ax      ; bump allocated size
                add     wild_buff_max, ax       ; bump end pointer
                push    ax
                call    sbrk@
                pop     cx
if      LDATA
                and     ax, dx
endif
                cmp     ax, -1
                je      NoArgSpace              ; abort if not enough space

                pop     dx
                pop     cx
                pop     bx
                pop     ax
                pop     di
                pop     si
                pop     es
                pop     ds
                stosb                           ; store character
                ret

ArgPushChar     endp

;------------------------------------------------------------------------------
;
; Not enough space to process the command line .... abort.
;
NoArgSpace:     jmp     abort@

;------------------------------------------------------------------------------
;
; Routine to process an argument.
;
ProcessArg      proc    near

                push    bx
                xor     al, al
                call    ArgPushChar             ; null-terminate
                pop     bx
                test    bh, 1                   ; wildcards present ?
                jnz     ArgWild
                inc     dx                      ; bump arg count
                ret
;
; We have a wildcard argument. Expand it.
;
ArgWild:        push    cx
                push    [si]                    ; save word following argument
                mov     byte ptr [si],0         ; null-terminate argument
                xchg    si, wild_arg_src        ; si = argument address
                push    di
                mov     di, wild_arg_dst
                push    dx
                call    WildExpand
                pop     dx
                pop     bx
                and     cx, cx                  ; see if any matched
                jnz     ArgWildSome
                mov     di, bx                  ; none ---> use unexpanded argument
                mov     cx, 1                   ; bump arg count by 1
ArgWildSome:    add     dx, cx
                mov     si, wild_arg_src
                pop     [si]                    ; restore word following argument
                pop     cx
                ret

ProcessArg      endp

;------------------------------------------------------------------------------
;
; Build the argv array. [DS:SI] is the expanded command line, CX its length.
; DX has the number of arguments (including the program name).
;
BuildArgv:      push    ds
                push    dx
                lds     dx, dword ptr wild_DTA_save
                mov     ah, 1ah
                int     21h                     ; switch to original DTA
                pop     dx
                pop     ds

                add     sp, wild_frame_size     ; remove local variables

                mov     es,SavedDS
                mov     es:[DGROUP:_argc@], dx
                inc     dx                      ; argv ends with a NULL pointer
                shl     dx, 1                   ;   argc * 2    (LDATA = 0)
IF      LDATA
                shl     dx, 1                   ;   argc * 4    (LDATA = 1)
ENDIF
                mov     bx, sp                  ; point to program name
                mov     bp, sp
                sub     bp, dx
                jb      NoArgSpace
                mov     sp, bp                  ; SS:BP = argv array address
                mov     word ptr es:[DGROUP:_argv@], bp
IF      LDATA
                mov     word ptr es:[DGROUP:_argv@+2], ss
ENDIF
                mov     [bp], bx                ; set argv[0] to program name
IF      LDATA
                mov     [bp+2], ss              ; program name is on the stack
ENDIF
                add     bp, dPtrSize

SetArgvX        label   near
                jcxz    SetLastArg
                mov     [bp], si                ; Set argv[n]
IF      LDATA
                mov     [bp+2], ds
ENDIF
                add     bp, dPtrSize
CopyArg         label   near
                lodsb
                or      al, al
                loopnz  CopyArg
                jz      SetArgvX
SetLastArg      label   near
                xor     ax, ax
                mov     [bp], ax
IF      LDATA
                mov     [bp+2], ax
ENDIF
                mov     ds, SavedDS

;==============================================================================
else
;==============================================================================

                mov     ds, bp
                xchg    si, dx          ; DS: SI = Command Line address
                xchg    bx, cx          ; CX = Command Line size including \r
                mov     ax, bx
                mov     dx, ax          ; AX = BX = DX = 0
                inc     bx              ; BX = Nb of arguments (at least 1)
Processing      label   near
                call    NextChar
                ja      NotQuote        ; Not a quote and there are more
InString        label   near
                jb      BuildArgv       ; Command line is empty now
                cmp     al, 13
                je      EndArgument
                call    NextChar        ; \r    is an argument separator
                ja      InString        ; Not a quote and there are more
NotQuote        label   near
                cmp     al, ' '
                je      EndArgument     ; Space is an argument separator
                cmp     al, 13
                je      EndArgument     ; \r    is an argument separator
                cmp     al, 9
                jne     Processing      ; \t    is an argument separator
EndArgument     label   near
                xor     al, al          ; Space and TAB are argument separators
                jmp     short Processing

;       Character test function used in SetArgs
;               On entry AL holds the previous character
;               On exit  AL holds the next character
;                        ZF on if the next character is quote (") and AL = 0
;                        CF on if end of command line and AL = 0

NextChar        PROC    NEAR
                or      ax, ax
                jz      NextChar0
                inc     dx              ; DX = Actual length of CmdLine
                stosb
                or      al, al
                jnz     NextChar0
                inc     bx              ; BX = Number of parameters
NextChar0       label   near
                xchg    ah, al
                xor     al, al
                stc
                jcxz    NextChar2       ; End of command line --> CF ON
                lodsb
                dec     cx
                sub     al, '"'
                jz      NextChar2       ; Quote found --> AL = 0 and ZF ON
                add     al, '"'
                cmp     al,'\'
                jne     NextChar1       ; It is not a \
                cmp     byte ptr ds:[si], '"'
                jne     NextChar1       ; Only " is transparent after \
                lodsb
                dec     cx
NextChar1       label   near
                or      si, si          ; Be sure both CF & ZF are OFF
NextChar2       label   near
                ret
NextChar        ENDP

;       Invalid program name

BadProgName     label   near
                jmp     abort@

;       Now, build the argv array

BuildArgv       label   near
                pop     cx
                add     cx, dx          ; CX = Argument area size
                mov     ds, SavedDS
                mov     _argc@, bx
                inc     bx              ; argv ends with a NULL pointer
                add     bx, bx          ;        argc * 2       (LDATA = 0)
IF      LDATA
                add     bx, bx          ;        argc * 4       (LDATA = 1)
ENDIF
                mov     si, sp
                mov     bp, sp
                sub     bp, bx
                jb      BadProgName
                mov     sp, bp          ; SS:BP = argv array address
                mov     word ptr _argv@, bp
IF      LDATA
                mov     word ptr _argv@+2, ss
ENDIF
SetArgvX        label   near
                jcxz    SetLastArg
                mov     [bp], si        ; Set argv[n]
IF      LDATA
                mov     [bp+2], ss
ENDIF
                add     bp, dPtrSize
CopyArg         label   near
                lods    byte ptr ss:[si]
                or      al, al
                loopnz  CopyArg
                jz      SetArgvX
SetLastArg      label   near
                xor     ax, ax
                mov     [bp], ax
IF      LDATA
                mov     [bp+2], ax
ENDIF

;==============================================================================
endif           ;       ifdef WILD
;==============================================================================

;       Restore caller context and exit

                mov     ds,SavedDS
                mov     si,SavedSI
                mov     di,SavedDI
                push    word ptr SavedReturn2
                push    word ptr SavedDS2
                mov     ax,__argc
                mov     __C0argc,ax
IF      LDATA
                mov     ax,word ptr __argv+2
                mov     word ptr __C0argv+2,ax
ENDIF
                mov     ax,word ptr __argv
                mov     word ptr __C0argv,ax

                jmp     word ptr SavedReturn

                endp
                ends

                END
