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 % in () do ; where may contain references of the form %, which are ; later substituted with the items in . The for-loop structure is ; set-up by the procedure '$for'; successive calls to 'forproc' execute ; once for each item in . 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 . 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 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 ; % is replaced by [forbuf] ; time to form the 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 arg, byte by byte inc si cmp AL,'%' ; looking for % jne for_stosb ; no % ... add byte to string cmp BYTE PTR [SI], DH ; got the right ? jne for_stosb ; got a %, but wrong inc SI ; skip over push SI mov SI, forbuf ; substitute the for ; to make a final to execute sloop: lodsb ; grab all those bytes, and stosb ; add 'em to the string, or AL, AL ; until we run into a null loopnz sloop dec DI ; adjust length and pointer inc CX ; so we can overwrite the null pop SI jmp for_make_loop ; got back for more bytes for_stosb: stosb ; take a byte from the arg dec CX ; and put it into the to be ; executed (and note length, too) cmp al,0dh ; If not done, loop. jne for_make_loop for_made_com: ; finished all the 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 , 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 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 ? jc forerrorj cmp ax, nullrparen ; special case: null list jne for_list jmp forterm forerrorjj: jmp forerror for_list: ; skip over rest of mov CX, DX ; first arg of 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 je for_end_list ; Yes, exit loop. call nextarg ; No, get next arg jc forerrorjj ; If no more and no rparen, error. jmp skip_list for_end_list: mov DI, DX ; record position of last arg in 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 jc forerrorjj ; null 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 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 return trancode ends end