2025-04-27 07:49:33 -04:00

587 lines
16 KiB
NASM
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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