2 ; SCCSID = @(#)tfor.asm 4.1 85/09/17
3 ; SCCSID = @(#)tfor.asm 4.1 85/09/17
4 TITLE Part3 COMMAND Transient Routines
6 ; For loop processing routines
20 DATARES
SEGMENT PUBLIC BYTE ;AC000;
31 TRANDATA
SEGMENT PUBLIC BYTE ;AC000;
32 EXTRN Extend_buf_ptr
:word ;AN000;
33 extrn fornestmes_ptr
:word
34 EXTRN msg_disp_class
:byte ;AN000;
35 extrn string_buf_ptr
:word
38 TRANSPACE
SEGMENT PUBLIC BYTE ;AC000;
39 extrn arg
:byte ; the arg structure!
42 EXTRN string_ptr_2
:word
45 TRANCODE
SEGMENT PUBLIC BYTE
47 ASSUME
CS:TRANGROUP
,DS:NOTHING
,ES:NOTHING
,SS:NOTHING
59 ; All batch proccessing has DS set to segment of resident portion
60 ASSUME
DS:RESGROUP
,ES:TRANGROUP
64 push cs ;AN037; Get local segment into
71 CMP [SINGLECOM
],0FF00H
73 CMP NEST
,0 ;G See if we have nested batch files
74 JNZ BATCRLF
;G Yes - don't exit just yet
75 MOV [SINGLECOM
],-1 ; Cause a terminate
79 test [ECHOFLAG
],1 ;G Is echo on?
80 JZ NOFORP2
;G no - exit
81 TEST [BATCH
], -1 ;G print CRLF if in batch
90 ; For-loop processing. For loops are of the form:
91 ; for %<loop-variable> in (<list>) do <command>
92 ; where <command> may contain references of the form %<variable>, which are
93 ; later substituted with the items in <list>. The for-loop structure is
94 ; set-up by the procedure '$for'; successive calls to 'forproc' execute
95 ; <command> once for each item in <list>. All of the information needed for
96 ; loop processing is stored on a piece of memory gotten from 'alloc'. This
97 ; structure is actually fairly large, on the order of 700 bytes, and includes
98 ; a complete copy of the original command-line structure as parsed by
99 ; 'parseline', loop control variables, and a dma buffer for the
100 ; 'FindFirst/FindNext' expansion of wildcard filenames in <list>. When loop
101 ; processing has completed, this chunk of memory is returned to the system.
103 ; All of the previously defined variables, in 'datares', used for loop
104 ; processing may be erased. Only one, (DW) ForPtr, need be allocated.
106 ; The error message, 'for_alloc_mes', should be moved into the file
107 ; containing all of the other error messages.
109 ; Referencing the allocated for-loop structure is a little tricky.
110 ; At the moment, a byte is defined as part of a new segment, 'for_segment'.
111 ; When 'forproc' actually runs, ES and DS are set to point to the base of the
112 ; new chunk of memory. References to this byte, 'f', thus assemble correctly
113 ; as offsets of ES or DS. 'f' would not be necessary, except that the
114 ; assembler translates an instruction such as 'mov AX, [for_minarg]' as an
115 ; immediate move of the offset of 'for_minarg' into AX. In other words, in
116 ; terms of PDP-11 mnemonics, the assembler ACTUALLY assembles
117 ; mov AX, #for_minarg ; AX := 02CA (for example)
119 ; mov AX, for_minarg ; AX := [02CA] (contents of 02CA)
120 ; By using 'f', we pretend that we are actually referencing an allocated
121 ; structure, and the assembler coughs up the code we want. Notice that it
122 ; doesn't matter whether we put brackets around the location or not -- the
123 ; assembler is "smart" enough to know that we want an address instead of the
124 ; contents of that location.
126 ; Finally, there now exists the potential to easily implement nested loops.
127 ; One method would be to have a link field in each for-structure pointing to
128 ; its parent. Variable references that couldn't be resolved in the local
129 ; frame would cause a search of prior frames. For-structures would still be
130 ; allocated and released in exactly the same fashion. The only limit on the
131 ; number of nested loops would be memory size (although at 700 bytes a pop,
132 ; memory wouldn't last THAT long). Alternately, a small structure could be
133 ; maintained in the resident data area. This structure would be an array of
134 ; control-variable names and pointers to for-structure blocks. This would
135 ; greatly speed up the resolution of non-local variable references. However,
136 ; since space in the resident is precious, we would have to compromise on a
137 ; "reasonable" level of nesting -- 10, 16, 32 levels, whatever. For-structure
138 ; allocation and de-allocation would have to be modified slightly to take this
139 ; new structure into account.
141 ; Oops, just one more thing. Forbuf need not be a part of the for-structure.
142 ; It could just as well be one structure allocated in 'transpace'. Actually,
143 ; it may be easier to allocate it as part of 'for_segment'.
149 jmp forterm
; exceeding maxarg means all done
155 mov ES, AX ; operate in for-info area
156 assume
DS:for_segment
, ES:for_segment
158 mov DX, OFFSET fordma
161 cmp f
.for_expand
, 0 ; non-zero for_expand equals FALSE
165 mov BX, f
.for_minarg
; current item in <list> to examine
167 jg $for_exit
; exceeding maxarg means all done
168 mov AX, OFFSET for_args
.argv
169 invoke argv_calc
; compute argv[x] address
171 mov CX, [BX].argstartel
172 mov DX, [BX].argpointer
173 test [bx].argflags
,00000100b ; Is there a path separator in this arg?
174 jnz forsub
; Yes, argstartel should be correct
175 mov si, [BX].argpointer
177 cmp byte ptr [si-1],al ; If the current token is the first
178 jnz forsub
; one in the list and originally had
179 inc cx ; the opening paren as its first char,
180 ; the argstartel ptr needs to be
181 ; advanced passed it before the prefix
182 ; length is computed.
184 cmp byte ptr [si+1],al ; If the token begins with "(d:",
185 jnz forsub
; argstartel has to be moved over the
186 add cx,2 ; rest of the prefix as well.
189 sub CX, DX ; compute length of pathname prefix
190 cmp f
.for_expand
, 0 ; are we still expanding a name?
191 je for_find_next
; if so, get next matching filename
193 test [BX].argflags
, MASK wildcard
194 jnz for_find_first
; should we expand THIS (new) arg?
195 mov CX, [BX].arglen
; else, just copy all of it directly
201 trap Find_First
; and search for first filename match
205 trap Find_Next
; search for next filename match
208 mov AX, -1 ; assume worst case
211 forCheck: ; Find* returns 0 for SUCCESS
212 mov f
.FOR_EXPAND
, AX ; record success of findfirst/next
213 or AX, AX ; anything out there?
214 jnz for_begin
; if not, try next arg
217 mov SI, [BX].argpointer
; copy argv[arg][0,CX] into destbuf
218 mov DI, OFFSET forbuf
; some days this will be the entire
219 rep movsb ; arg, some days just the path prefix
221 cmp f
.FOR_EXPAND
, 0 ; if we're not expanding, we can
222 jnz for_make_com
; skip the following
224 mov SI, OFFSET fordma
.find_buf_pname
225 for_more: ; tack on matching filename
232 xor AL, AL ; tack a null byte onto the end
233 stosb ; of the substitute string
235 xor CX, CX ; character count for command line
236 not CX ; negate it -- take advantage of loopnz
237 xor BX, BX ; argpointer
238 mov DI, OFFSET TRANGROUP
:COMBUF
+2
239 mov bl, f
.FOR_COM_START
; argindex
240 mov DH, f
.FOR_VAR
; %<for-var> is replaced by [forbuf]
241 ; time to form the <command> string
246 mov AX, OFFSET for_args
; translate offset to pointer
248 mov si,[bx].arg_ocomptr
249 inc si ; mov ptr passed beginning space
252 mov al,[si] ; the <command> arg, byte by byte
254 cmp AL,'%' ; looking for %<control-variable>
255 jne for_stosb
; no % ... add byte to string
256 cmp BYTE PTR [SI], DH ; got the right <variable>?
257 jne for_stosb
; got a %, but wrong <variable>
258 inc SI ; skip over <for-variable>
261 mov SI, OFFSET forbuf
; substitute the <item> for <variable>
262 ; to make a final <command> to execute
264 lodsb ; grab all those <item> bytes, and
265 stosb ; add 'em to the <command> string,
266 or AL, AL ; until we run into a null
268 dec DI ; adjust length and <command> pointer
269 inc CX ; so we can overwrite the null
272 jmp for_make_loop
; got back for more <command> bytes
274 stosb ; take a byte from the <command> arg
275 dec CX ; and put it into the <command> to be
276 ; executed (and note length, too)
277 cmp al,0dh ; If not done, loop.
280 for_made_com: ; finished all the <command> args
281 not CL ; compute and record command length
287 test [ECHOFLAG
],1 ; shall we echo this <command>, dearie?
289 cmp nullflag
,nullcommand
;G was there a command last time?
290 jz No_crlf_pr
;G no - don't print crlf
291 invoke CRLF2
;G Print out prompt
294 mov nullflag
,0 ;G reset no command flag
299 invoke PRINT_PROMPT
;G Prompt the user
301 mov BYTE PTR ES:[DI-1],0 ; yeah, PRINT it out...
302 mov string_ptr_2
,OFFSET TRANGROUP
:COMBUF
+2
303 mov dx,offset trangroup
:string_buf_ptr
305 mov BYTE PTR ES:[DI-1], 0DH
307 noecho3: ; run silent, run deep...
309 mov nullflag
,0 ;G reset no command flag
316 fornesterrj: ; no multi-loop processing... yet!
325 assume
ds:trangroup
,es:trangroup
331 cmp ForFlag
,0 ; is another one already running?
332 jnz fornesterrj
; if flag is set.... boom!
335 ; Turn off any pipes in progress.
337 cmp [PIPEFILES
],0 ; Only turn off if present.
341 xor DX, DX ; counter (0 <= DX < argvcnt)
342 call nextarg
; move to next argv[n]
343 jc forerrorj
; no more args -- bad forloop
344 cmp AL,'%' ; next arg MUST start with '%'...
346 mov BP, AX ; save forloop variable
348 or AL, AL ; and MUST end immediately...
351 call nextarg
; let's make sure the next arg is 'in'
353 and AX, NOT 2020H
; uppercase the letters
357 or AL, AL ; it, too, must end right away
360 ; Not null. Perhaps there are no spaces between this and the (:
361 ; FOR %i in(foo bar...
362 ; Check for the Lparen here
367 ; The token was in(... We strip off the "in" part to simulate a separator
368 ; being there in the first place.
370 ADD [BX].argpointer
,2 ; advance source pointer
371 ADD [BX].arg_ocomptr
,2 ; advance original string
372 SUB [BX].arglen
,2 ; decrement the appropriate length
374 ; SI now points past the in(. Simulate a nextarg call that results in the
377 MOV ax,[si-1] ; get lparen and next char
381 call nextarg
; lparen delimits beginning of <list>
389 cmp ah, rparen
; special case: null list
390 jne for_list_not_empty
394 inc [bx].argpointer
; Advance ptr past "("
395 ; Adjust the rest of this argv entry
396 dec [bx].arglen
; to agree.
397 inc si ; Inc si so check for ")" works
401 call nextarg
; what have we in our <list>?
403 cmp ax, nullrparen
; special case: null list
410 for_list: ; skip over rest of <list>
411 mov CX, DX ; first arg of <list>
414 sub si,3 ; si = ptr to last char of token
416 cmp byte ptr [si],al ; Is this the last element in <list>
417 je for_end_list
; Yes, exit loop.
418 call nextarg
; No, get next arg <list>
419 jc forerrorjj
; If no more and no rparen, error.
422 mov DI, DX ; record position of last arg in <list>
423 mov byte ptr [si],0 ; Zap the rparen
424 cmp ax,nullrparen
; Was this token only a rparen
425 jz for_do
; Yes, continue
426 inc di ; No, inc position of last arg
429 call nextarg
; now we had BETTER find a 'do'...
431 and AX, NOT 2020H
; uppercase the letters
435 or AL, AL ; and it had BETTER be ONLY a 'do'...
438 call nextarg
; on to the beginning of <command>
439 jc forerrorjj
; null <command> not legal
444 push DX ; preserve registers against disaster
448 invoke FREE_TPA
; need to make free memory, first
451 mov BX, SIZE for_info
- SIZE arg_unit
452 invoke Save_Args
; extra bytes needed for for-info
455 invoke ALLOC_TPA
; ALLOC_TPA clobbers registers...
466 push ES ; save resgroup seg...
469 assume
ES:for_segment
; make references to for-info segment
471 dec CX ; forproc wants min pointing before
472 dec DI ; first arg, max right at last one
475 mov f
.for_com_start
, DL
476 mov f
.for_expand
, -1 ; non-zero means FALSE
485 mov [SINGLECOM
], 0FF00H
490 mov msg_disp_class
,ext_msg_class
;AN000; set up extended error msg class
491 mov dx,offset TranGroup
:Extend_Buf_ptr
;AC000; get extended message pointer
492 mov Extend_Buf_ptr
,error_not_enough_memory
;AN000; get message number in control block
496 inc DX ; next argv[n]
497 cmp DX, arg
.argvcnt
; make sure we don't run off end
498 jge nextarg_err
; of argv[]...
500 mov AX, OFFSET TRANGROUP
:arg
.argv
501 invoke argv_calc
; convert array index to pointer
502 mov SI, [BX].argpointer
; load pointer to argstring
503 lodsw ; and load first two chars
511 ASSUME
DS:TRANGROUP
,ES:TRANGROUP
517 MOV DX,OFFSET TRANGROUP
:FORNESTMES_ptr
518 CMP [SINGLECOM
],0FF00H
520 MOV [SINGLECOM
],-1 ; Cause termination
526 ; General routine called to free the for segment. We also clear the forflag
527 ; too. Change no registers.
531 assume
DS:NOTHING
,ES:NOTHING