You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
16 KiB
587 lines
16 KiB
page ,132
|
|
; SCCSID = @(#)tfor.asm 4.1 85/09/17
|
|
; SCCSID = @(#)tfor.asm 4.1 85/09/17
|
|
TITLE Part3 COMMAND Transient Routines
|
|
;/*
|
|
; * Microsoft Confidential
|
|
; * Copyright (C) Microsoft Corporation 1991
|
|
; * All Rights Reserved.
|
|
; */
|
|
|
|
; For loop processing routines
|
|
|
|
|
|
.xlist
|
|
.xcref
|
|
include comsw.asm
|
|
include dossym.inc
|
|
include syscall.inc
|
|
include find.inc
|
|
include devsym.inc
|
|
include comseg.asm
|
|
include comequ.asm
|
|
.list
|
|
.cref
|
|
|
|
|
|
DATARES SEGMENT PUBLIC BYTE ;AC000;
|
|
EXTRN BATCH:WORD
|
|
EXTRN ECHOFLAG:BYTE
|
|
EXTRN FORFLAG:BYTE
|
|
EXTRN FORPTR:WORD
|
|
EXTRN NEST:WORD
|
|
EXTRN NULLFLAG:BYTE
|
|
EXTRN PIPEFILES:BYTE
|
|
EXTRN SINGLECOM:WORD
|
|
DATARES ENDS
|
|
|
|
TRANDATA SEGMENT PUBLIC BYTE ;AC000;
|
|
EXTRN Extend_buf_ptr:word ;AN000;
|
|
extrn fornestmes_ptr:word
|
|
EXTRN msg_disp_class:byte ;AN000;
|
|
extrn string_buf_ptr:word
|
|
TRANDATA ENDS
|
|
|
|
TRANSPACE SEGMENT PUBLIC BYTE ;AC000;
|
|
extrn arg:byte ; the arg structure!
|
|
EXTRN COMBUF:BYTE
|
|
EXTRN RESSEG:WORD
|
|
EXTRN string_ptr_2:word
|
|
TRANSPACE ENDS
|
|
|
|
TRANCODE SEGMENT PUBLIC BYTE
|
|
|
|
ASSUME CS:TRANGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING
|
|
|
|
EXTRN cerror:near
|
|
EXTRN docom:near
|
|
EXTRN docom1:near
|
|
EXTRN forerror:near
|
|
EXTRN tcommand:near
|
|
|
|
PUBLIC $for
|
|
PUBLIC forproc
|
|
|
|
|
|
; All batch proccessing has DS set to segment of resident portion
|
|
ASSUME DS:RESGROUP,ES:TRANGROUP
|
|
|
|
|
|
FORTERM:
|
|
push cs ;AN037; Get local segment into
|
|
pop ds ;AN037; DS, ES
|
|
push cs ;AN037;
|
|
pop es ;AN037;
|
|
call ForOff
|
|
mov ds,ResSeg
|
|
ASSUME DS:RESGROUP
|
|
CMP [SINGLECOM],0FF00H
|
|
JNZ BATCRLF
|
|
CMP NEST,0 ;G See if we have nested batch files
|
|
JNZ BATCRLF ;G Yes - don't exit just yet
|
|
MOV [SINGLECOM],-1 ; Cause a terminate
|
|
JMP SHORT NOFORP2
|
|
|
|
BATCRLF:
|
|
test [ECHOFLAG],1 ;G Is echo on?
|
|
JZ NOFORP2 ;G no - exit
|
|
TEST [BATCH], -1 ;G print CRLF if in batch
|
|
JZ NOFORP2 ;G
|
|
invoke CRLF2
|
|
|
|
NOFORP2:
|
|
JMP TCOMMAND
|
|
|
|
|
|
;------
|
|
; For-loop processing. For loops are of the form:
|
|
; for %<loop-variable> in (<list>) do <command>
|
|
; where <command> may contain references of the form %<variable>, which are
|
|
; later substituted with the items in <list>. The for-loop structure is
|
|
; set-up by the procedure '$for'; successive calls to 'forproc' execute
|
|
; <command> once for each item in <list>. All of the information needed for
|
|
; loop processing is stored on a piece of memory gotten from 'alloc'. This
|
|
; structure is actually fairly large, on the order of 700 bytes, and includes
|
|
; a complete copy of the original command-line structure as parsed by
|
|
; 'parseline', loop control variables, and a dma buffer for the
|
|
; 'FindFirst/FindNext' expansion of wildcard filenames in <list>. When loop
|
|
; processing has completed, this chunk of memory is returned to the system.
|
|
;
|
|
; All of the previously defined variables, in 'datares', used for loop
|
|
; processing may be erased. Only one, (DW) ForPtr, need be allocated.
|
|
;
|
|
; The error message, 'for_alloc_mes', should be moved into the file
|
|
; containing all of the other error messages.
|
|
;
|
|
; Referencing the allocated for-loop structure is a little tricky.
|
|
; At the moment, a byte is defined as part of a new segment, 'for_segment'.
|
|
; When 'forproc' actually runs, ES and DS are set to point to the base of the
|
|
; new chunk of memory. References to this byte, 'f', thus assemble correctly
|
|
; as offsets of ES or DS. 'f' would not be necessary, except that the
|
|
; assembler translates an instruction such as 'mov AX, [for_minarg]' as an
|
|
; immediate move of the offset of 'for_minarg' into AX. In other words, in
|
|
; terms of PDP-11 mnemonics, the assembler ACTUALLY assembles
|
|
; mov AX, #for_minarg ; AX := 02CA (for example)
|
|
; instead of
|
|
; mov AX, for_minarg ; AX := [02CA] (contents of 02CA)
|
|
; By using 'f', we pretend that we are actually referencing an allocated
|
|
; structure, and the assembler coughs up the code we want. Notice that it
|
|
; doesn't matter whether we put brackets around the location or not -- the
|
|
; assembler is "smart" enough to know that we want an address instead of the
|
|
; contents of that location.
|
|
;
|
|
; Finally, there now exists the potential to easily implement nested loops.
|
|
; One method would be to have a link field in each for-structure pointing to
|
|
; its parent. Variable references that couldn't be resolved in the local
|
|
; frame would cause a search of prior frames. For-structures would still be
|
|
; allocated and released in exactly the same fashion. The only limit on the
|
|
; number of nested loops would be memory size (although at 700 bytes a pop,
|
|
; memory wouldn't last THAT long). Alternately, a small structure could be
|
|
; maintained in the resident data area. This structure would be an array of
|
|
; control-variable names and pointers to for-structure blocks. This would
|
|
; greatly speed up the resolution of non-local variable references. However,
|
|
; since space in the resident is precious, we would have to compromise on a
|
|
; "reasonable" level of nesting -- 10, 16, 32 levels, whatever. For-structure
|
|
; allocation and de-allocation would have to be modified slightly to take this
|
|
; new structure into account.
|
|
;
|
|
; Oops, just one more thing. Forbuf need not be a part of the for-structure.
|
|
; It could just as well be one structure allocated in 'transpace'. Actually,
|
|
; it may be easier to allocate it as part of 'for_segment'.
|
|
;------
|
|
|
|
include fordata.asm
|
|
|
|
$for_exit:
|
|
jmp forterm ; exceeding maxarg means all done
|
|
|
|
forproc:
|
|
assume DS:resgroup
|
|
mov AX, [ForPtr]
|
|
mov DS, AX
|
|
mov ES, AX ; operate in for-info area
|
|
assume DS:for_segment, ES:for_segment
|
|
|
|
mov DX, fordma
|
|
trap Set_Dma
|
|
for_begin:
|
|
cmp f.for_expand, 0 ; non-zero for_expand equals FALSE
|
|
je for_begin1
|
|
inc f.for_minarg
|
|
for_begin1:
|
|
mov BX, f.for_minarg ; current item in <list> to examine
|
|
cmp BX, f.for_maxarg
|
|
jg $for_exit ; exceeding maxarg means all done
|
|
mov AX, for_args.argv
|
|
invoke argv_calc ; compute argv[x] address
|
|
|
|
mov CX, [BX].argstartel
|
|
mov DX, [BX].argpointer
|
|
test [bx].argflags,00000100b ; Is there a path separator in this arg?
|
|
jnz forsub ; Yes, argstartel should be correct
|
|
mov si, [BX].argpointer
|
|
mov al,lparen
|
|
cmp byte ptr [si-1],al ; If the current token is the first
|
|
jnz forsub ; one in the list and originally had
|
|
inc cx ; the opening paren as its first char,
|
|
; the argstartel ptr needs to be
|
|
; advanced passed it before the prefix
|
|
; length is computed.
|
|
mov al,':'
|
|
cmp byte ptr [si+1],al ; If the token begins with "(d:",
|
|
jnz forsub ; argstartel has to be moved over the
|
|
add cx,2 ; rest of the prefix as well.
|
|
|
|
forsub:
|
|
sub CX, DX ; compute length of pathname prefix
|
|
cmp f.for_expand, 0 ; are we still expanding a name?
|
|
je for_find_next ; if so, get next matching filename
|
|
|
|
test [BX].argflags, MASK wildcard
|
|
jnz for_find_first ; should we expand THIS (new) arg?
|
|
mov CX, [BX].arglen ; else, just copy all of it directly
|
|
jmp short for_smoosh
|
|
|
|
for_find_first:
|
|
PUSH CX
|
|
XOR CX,CX
|
|
trap Find_First ; and search for first filename match
|
|
POP CX
|
|
jmp short for_result
|
|
for_find_next:
|
|
trap Find_Next ; search for next filename match
|
|
|
|
for_result:
|
|
mov AX, -1 ; assume worst case
|
|
jc forCheck
|
|
mov ax,0
|
|
forCheck: ; Find* returns 0 for SUCCESS
|
|
mov f.FOR_EXPAND, AX ; record success of findfirst/next
|
|
or AX, AX ; anything out there?
|
|
jnz for_begin ; if not, try next arg
|
|
|
|
for_smoosh:
|
|
mov SI, [BX].argpointer ; copy argv[arg][0,CX] into destbuf
|
|
mov DI, forbuf ; some days this will be the entire
|
|
rep movsb ; arg, some days just the path prefix
|
|
|
|
cmp f.FOR_EXPAND, 0 ; if we're not expanding, we can
|
|
jnz for_make_com ; skip the following
|
|
|
|
mov SI, fordma.find_buf_pname
|
|
for_more: ; tack on matching filename
|
|
cmp BYTE PTR [SI], 0
|
|
je for_make_com
|
|
movsb
|
|
jnz for_more
|
|
|
|
for_make_com:
|
|
xor AL, AL ; tack a null byte onto the end
|
|
stosb ; of the substitute string
|
|
|
|
xor CX, CX ; character count for command line
|
|
not CX ; negate it -- take advantage of loopnz
|
|
xor BX, BX ; argpointer
|
|
mov DI, OFFSET TRANGROUP:COMBUF+2
|
|
mov bl, f.FOR_COM_START ; argindex
|
|
mov DH, f.FOR_VAR ; %<for-var> is replaced by [forbuf]
|
|
; time to form the <command> string
|
|
push CS
|
|
pop ES
|
|
assume ES:trangroup
|
|
|
|
mov AX, for_args ; translate offset to pointer
|
|
invoke argv_calc
|
|
mov si,[bx].arg_ocomptr
|
|
inc si ; mov ptr passed beginning space
|
|
|
|
for_make_loop:
|
|
mov al,[si] ; the <command> arg, byte by byte
|
|
inc si
|
|
cmp AL,'%' ; looking for %<control-variable>
|
|
jne for_stosb ; no % ... add byte to string
|
|
cmp BYTE PTR [SI], DH ; got the right <variable>?
|
|
jne for_stosb ; got a %, but wrong <variable>
|
|
inc SI ; skip over <for-variable>
|
|
|
|
push SI
|
|
mov SI, forbuf ; substitute the <item> for <variable>
|
|
; to make a final <command> to execute
|
|
sloop:
|
|
lodsb ; grab all those <item> bytes, and
|
|
stosb ; add 'em to the <command> string,
|
|
or AL, AL ; until we run into a null
|
|
loopnz sloop
|
|
dec DI ; adjust length and <command> pointer
|
|
inc CX ; so we can overwrite the null
|
|
|
|
pop SI
|
|
jmp for_make_loop ; got back for more <command> bytes
|
|
for_stosb:
|
|
stosb ; take a byte from the <command> arg
|
|
dec CX ; and put it into the <command> to be
|
|
; executed (and note length, too)
|
|
cmp al,0dh ; If not done, loop.
|
|
jne for_make_loop
|
|
|
|
for_made_com: ; finished all the <command> args
|
|
not CL ; compute and record command length
|
|
mov [COMBUF+1], CL
|
|
|
|
mov DS, [RESSEG]
|
|
assume DS:resgroup
|
|
|
|
test [ECHOFLAG],1 ; shall we echo this <command>, dearie?
|
|
jz noecho3
|
|
cmp nullflag,nullcommand ;G was there a command last time?
|
|
jz No_crlf_pr ;G no - don't print crlf
|
|
invoke CRLF2 ;G Print out prompt
|
|
|
|
no_crlf_pr:
|
|
mov nullflag,0 ;G reset no command flag
|
|
push CS
|
|
pop DS
|
|
assume DS:trangroup
|
|
push di
|
|
invoke PRINT_PROMPT ;G Prompt the user
|
|
pop di
|
|
mov BYTE PTR ES:[DI-1],0 ; yeah, PRINT it out...
|
|
mov string_ptr_2,OFFSET TRANGROUP:COMBUF+2
|
|
mov dx,offset trangroup:string_buf_ptr
|
|
invoke std_printf
|
|
mov BYTE PTR ES:[DI-1], 0DH
|
|
jmp DoCom
|
|
noecho3: ; run silent, run deep...
|
|
assume DS:resgroup
|
|
mov nullflag,0 ;G reset no command flag
|
|
push CS
|
|
pop DS
|
|
assume DS:trangroup
|
|
jmp docom1
|
|
|
|
|
|
fornesterrj: ; no multi-loop processing... yet!
|
|
assume ES:resgroup
|
|
call ForOff
|
|
jmp fornesterr
|
|
|
|
forerrorj:
|
|
jmp forerror
|
|
|
|
break $For
|
|
assume ds:trangroup,es:trangroup
|
|
|
|
$for:
|
|
mov ES, [RESSEG]
|
|
assume ES:resgroup
|
|
|
|
cmp ForFlag,0 ; is another one already running?
|
|
jnz fornesterrj ; if flag is set.... boom!
|
|
|
|
;
|
|
; Turn off any pipes in progress.
|
|
;
|
|
cmp [PIPEFILES],0 ; Only turn off if present.
|
|
jz NoPipe
|
|
invoke PipeDel
|
|
NoPipe:
|
|
xor DX, DX ; counter (0 <= DX < argvcnt)
|
|
call nextarg ; move to next argv[n]
|
|
jc forerrorj ; no more args -- bad forloop
|
|
cmp AL,'%' ; next arg MUST start with '%'...
|
|
jne forerrorj
|
|
mov BP, AX ; save forloop variable
|
|
lodsb
|
|
or AL, AL ; and MUST end immediately...
|
|
jne forerrorj
|
|
|
|
call nextarg ; let's make sure the next arg is 'in'
|
|
jc forerrorj
|
|
and AX, NOT 2020H ; uppercase the letters
|
|
cmp AX, in_word
|
|
jne forerrorj
|
|
lodsb
|
|
or AL, AL ; it, too, must end right away
|
|
|
|
; Compaq bug fix -- exit from this loop on error
|
|
|
|
ifndef NEC_98
|
|
jne forerrorj ; jump on error
|
|
|
|
;; je CheckLParen
|
|
else ;NEC_98
|
|
;; jne forerrorj ; jump on error ;NEC00
|
|
|
|
je CheckLParen
|
|
endif ;NEC_98
|
|
;
|
|
; Not null. Perhaps there are no spaces between this and the (:
|
|
; FOR %i in(foo bar...
|
|
; Check for the Lparen here
|
|
;
|
|
ifndef NEC_98
|
|
;; CMP AL,lparen
|
|
;; JNZ forerrorj
|
|
else ;NEC_98
|
|
CMP AL,lparen
|
|
JNZ forerrorj
|
|
endif ;NEC_98
|
|
;
|
|
; The token was in(... We strip off the "in" part to simulate a separator
|
|
; being there in the first place.
|
|
;
|
|
ifndef NEC_98
|
|
;; ADD [BX].argpointer,2 ; advance source pointer
|
|
;; ADD [BX].arg_ocomptr,2 ; advance original string
|
|
;; SUB [BX].arglen,2 ; decrement the appropriate length
|
|
else ;NEC_98
|
|
ADD [BX].argpointer,2 ; advance source pointer
|
|
ADD [BX].arg_ocomptr,2 ; advance original string
|
|
SUB [BX].arglen,2 ; decrement the appropriate length
|
|
endif ;NEC_98
|
|
;
|
|
; SI now points past the in(. Simulate a nextarg call that results in the
|
|
; current value.
|
|
;
|
|
ifndef NEC_98
|
|
;; MOV ax,[si-1] ; get lparen and next char
|
|
;; jmp short lpcheck
|
|
else ;NEC_98
|
|
MOV ax,[si-1] ; get lparen and next char
|
|
jmp short lpcheck
|
|
endif ;NEC_98
|
|
;
|
|
;; end of Compaq bug fix
|
|
|
|
CheckLParen:
|
|
call nextarg ; lparen delimits beginning of <list>
|
|
jc forerrorj
|
|
lpcheck:
|
|
cmp al, lparen
|
|
jne forerrorj
|
|
cmp ah,0
|
|
je for_paren_token
|
|
|
|
cmp ah, rparen ; special case: null list
|
|
jne for_list_not_empty
|
|
jmp forterm
|
|
|
|
for_list_not_empty:
|
|
inc [bx].argpointer ; Advance ptr past "("
|
|
; Adjust the rest of this argv entry
|
|
dec [bx].arglen ; to agree.
|
|
inc si ; Inc si so check for ")" works
|
|
jmp short for_list
|
|
|
|
for_paren_token:
|
|
call nextarg ; what have we in our <list>?
|
|
jc forerrorj
|
|
cmp ax, nullrparen ; special case: null list
|
|
jne for_list
|
|
jmp forterm
|
|
|
|
forerrorjj:
|
|
jmp forerror
|
|
|
|
for_list: ; skip over rest of <list>
|
|
mov CX, DX ; first arg of <list>
|
|
skip_list:
|
|
add si,[bx].arglen
|
|
sub si,3 ; si = ptr to last char of token
|
|
mov al,rparen
|
|
cmp byte ptr [si],al ; Is this the last element in <list>
|
|
je for_end_list ; Yes, exit loop.
|
|
call nextarg ; No, get next arg <list>
|
|
jc forerrorjj ; If no more and no rparen, error.
|
|
jmp skip_list
|
|
for_end_list:
|
|
mov DI, DX ; record position of last arg in <list>
|
|
mov byte ptr [si],0 ; Zap the rparen
|
|
cmp ax,nullrparen ; Was this token only a rparen
|
|
jz for_do ; Yes, continue
|
|
inc di ; No, inc position of last arg
|
|
|
|
for_do:
|
|
call nextarg ; now we had BETTER find a 'do'...
|
|
jc forerrorjj
|
|
and AX, NOT 2020H ; uppercase the letters
|
|
cmp AX, do_word
|
|
jne forerrorjj
|
|
lodsb
|
|
or AL, AL ; and it had BETTER be ONLY a 'do'...
|
|
jne forerrorjj
|
|
|
|
call nextarg ; on to the beginning of <command>
|
|
jc forerrorjj ; null <command> not legal
|
|
|
|
push AX
|
|
push BX
|
|
push CX
|
|
push DX ; preserve registers against disaster
|
|
push DI
|
|
push SI
|
|
push BP
|
|
invoke FREE_TPA ; need to make free memory, first
|
|
ASSUME ES:RESGROUP
|
|
call ForOff
|
|
mov BX, SIZE for_info - SIZE arg_unit
|
|
invoke Save_Args ; extra bytes needed for for-info
|
|
pushf
|
|
mov [ForPtr], AX
|
|
invoke ALLOC_TPA ; ALLOC_TPA clobbers registers...
|
|
popf
|
|
pop BP
|
|
pop SI
|
|
pop DI
|
|
pop DX
|
|
pop CX
|
|
pop BX
|
|
pop AX
|
|
jc for_alloc_err
|
|
|
|
push ES ; save resgroup seg...
|
|
push [ForPtr]
|
|
pop ES
|
|
assume ES:for_segment ; make references to for-info segment
|
|
|
|
dec CX ; forproc wants min pointing before
|
|
dec DI ; first arg, max right at last one
|
|
mov f.for_minarg, CX
|
|
mov f.for_maxarg, DI
|
|
mov f.for_com_start, DL
|
|
mov f.for_expand, -1 ; non-zero means FALSE
|
|
mov AX, BP
|
|
mov f.for_var, AH
|
|
pop ES
|
|
assume ES:resgroup
|
|
|
|
inc [FORFLAG]
|
|
cmp [SINGLECOM], -1
|
|
jnz for_ret
|
|
mov [SINGLECOM], 0FF00H
|
|
for_ret:
|
|
ret
|
|
|
|
for_alloc_err:
|
|
mov msg_disp_class,ext_msg_class ;AN000; set up extended error msg class
|
|
mov dx,offset TranGroup:Extend_Buf_ptr ;AC000; get extended message pointer
|
|
mov Extend_Buf_ptr,error_not_enough_memory ;AN000; get message number in control block
|
|
jmp cerror
|
|
|
|
nextarg:
|
|
inc DX ; next argv[n]
|
|
cmp DX, arg.argvcnt ; make sure we don't run off end
|
|
jge nextarg_err ; of argv[]...
|
|
mov BX, DX
|
|
mov AX, OFFSET TRANGROUP:arg.argv
|
|
invoke argv_calc ; convert array index to pointer
|
|
mov SI, [BX].argpointer ; load pointer to argstring
|
|
lodsw ; and load first two chars
|
|
clc
|
|
ret
|
|
nextarg_err:
|
|
stc
|
|
ret
|
|
|
|
|
|
ASSUME DS:TRANGROUP,ES:TRANGROUP
|
|
|
|
FORNESTERR:
|
|
PUSH DS
|
|
MOV DS,[RESSEG]
|
|
ASSUME DS:RESGROUP
|
|
MOV DX,OFFSET TRANGROUP:FORNESTMES_ptr
|
|
CMP [SINGLECOM],0FF00H
|
|
JNZ NOFORP3
|
|
MOV [SINGLECOM],-1 ; Cause termination
|
|
NOFORP3:
|
|
POP DS
|
|
ASSUME DS:TRANGROUP
|
|
JMP CERROR
|
|
;
|
|
; General routine called to free the for segment. We also clear the forflag
|
|
; too. Change no registers.
|
|
;
|
|
PUBLIC ForOff
|
|
ForOff:
|
|
assume DS:NOTHING,ES:NOTHING
|
|
SaveReg <AX,ES>
|
|
mov es,ResSeg
|
|
assume es:ResGroup
|
|
mov AX,ForPtr
|
|
or ax,ax
|
|
jz FreeDone
|
|
push es
|
|
mov es,ax
|
|
mov ah,dealloc
|
|
int 21h
|
|
pop es
|
|
FreeDone:
|
|
mov ForPtr,0
|
|
mov ForFlag,0
|
|
RestoreReg <ES,AX>
|
|
return
|
|
|
|
trancode ends
|
|
end
|
|
|