; PERFORM.Z80 ; A ZCPR3 Utility to be used in concert with FOR.COM. ; ; Syntax: ; ( FOR ) ; PERFORM ; ; See the documentation for FOR.COM to review its arguments. ; The command line passed to PERFORM will be executed once for every line ; in FORFILES.SYS (which is created by FOR.COM). The "$" character is an ; escape flag which begins two special symbols recognized by PERFORM. ; The special symbols and their meanings are: ; $X -- the current line from FORFILES.SYS ; $| -- a substitute multiple-command separator (";"). ; ; Any pending commands in the multiple-command-line buffer will ; be saved when PERFORM is invoked and restored when it is completed. ; ; Author: Dreas Nielsen ; History: ; Date Version Comments ; ------ --------- ---------- ; 5/16/87 1.0 Created. RDN. ; 6/3/87 1.1 Added call to PUTCST to allow flow control ; processing under normal ZCPR3. ; ; VERS EQU 11 FALSE EQU 0 TRUE EQU NOT FALSE ; DEBUG EQU FALSE ; CR EQU 0DH LF EQU 0AH BELL EQU 7 CTRLZ EQU 1AH EOF EQU 1AH BDOS EQU 5 INFERR EQU 1 ;error code for "can't open input file" OFERR EQU 2 ;error code for "can't open output file" RDERR EQU 3 ;error code for "can't read input file" WRTERR EQU 4 ;error code for "can't write output file" CLZERR EQU 5 ;error code for "can't close output file" PARMFL EQU '/' CMDLIN EQU 080H SYSFCB EQU 05CH BUFSIZ EQU 16 ;16 128-byte sectors (2k) for I/O buffers FCBLEN EQU 36 BSZOFF EQU 0 ;offset of buffer size indicator from I/O ctl block BADOFF EQU 6 ;offset of buffer addr indic. from I/O ctl block start FCBOFF EQU 8 ;offset of fcb from I/O ctl block start SYSFLG EQU '$' ;Command-line escape character LINFLG EQU 'X' ;Escape argument for current FOR line. SUBSEP EQU '|' ;Escape argument -- substitute command separator. CMDSEP EQU ';' ;Real command separator. ; ; ; External Z3LIB and SYSLIB Routines ; EXT Z3INIT,GETSH2,SHPUSH,SHPOP,QSHELL,GETEFCB,PUTCL,PUTER2 EXT GETCL2,RETUD,F$DELETE,PUTCST EXT CODEND,QPRINT,PRINT,SKSP,FXI$OPEN,FXO$OPEN EXT FX$GET,FX$PUT,FXI$CLOSE,FXO$CLOSE,F$RENAME ; IF DEBUG EXT PHL4HC ENDIF ; ; ;---------------- Code ---------------- ; db 'Z3ENV' ;This is a ZCPR3 Utility db 1 ;External Environment Descriptor z3eadr: dw 00 START: ld hl,(z3eadr) ;pt to ZCPR3 environment call z3init ;initialize the ZCPR3 Environment ; ; Save stack pointer and set a new one ; LD (SAVESP),SP LD HL,(6) ;BDOS jump address LD DE,2056 ;size of ZCPR3 OR A SBC HL,DE LD SP,HL ; ; Allocate buffers for byte-oriented file I/O, first line of file. ; SETBUFS: CALL CODEND LD (FIRSTLIN),HL LD DE,300H ;For 1st line of FORFILES.SYS ADD HL,DE LD (INTLINE),HL ;For skeleton command line. LD DE,200H ADD HL,DE LD (EXPLINE),HL ;For expanded command line. LD DE,200H ADD HL,DE LD (INPLOC),HL LD B,BUFSIZ CALL FBINIT ;allocate I/O ctl buffer for input LD (OUTPLOC),HL CALL FBINIT ;allocate I/O ctl buffer for output ; ; Check for Shell Stack ; CALL GETSH2 ;get shell status JR NZ,START0 ;skip over shell init CALL PRINT DB 'No Shell Stack',0 JP EXIT ; ; See if this program was invoked as a shell ; START0: CALL QSHELL ;find out from ZCPR3 environment JP Z,ISHELL ;do not push onto stack if invoked as a shell ; CALL QPRINT DB 'PERFORM v.',[VERS / 10]+'0','.',[VERS mod 10]+'0',CR,LF,0 ; ; Store a null at end of command line. LD HL,CMDLIN LD A,(HL) INC HL LD E,A XOR A LD D,A ADD HL,DE LD (HL),A ; ; Now parse command line. ; First look for option character. Only option is help, which is exclusive. LD HL,CMDLIN+1 CALL SKSP LD A,(HL) OR A JP Z,HELP CP PARMFL JP Z,HELP CP '?' JP Z,HELP ; ; Set Name of Shell from External FCB if Possible or From Default if Not ; SETSHN: CALL SETDIR ;set name of current directory. CALL GETEFCB ;get ptr to external fcb JR Z,START2 ;no external FCB, so use default name INC HL ;pt to program name LD DE,SHNAME ;pt to string LD BC,8 ;8 chars LDIR ;copy into buffer ; ; Push Name of Shell onto Stack ; START2: LD HL,SHDISK ;pt to name of shell CALL SHPUSH ;push shell onto stack JR NZ,START3 ; ; Save arguments and remaining commands from MCL buffer in PERFORM$.$$$ ; LD HL,(OUTPLOC) LD DE,CTLFIL CALL INITNAM LD DE,(OUTPLOC) CALL FXO$OPEN LD HL,CMDLIN+1 CALL SKSP CALL WRITLN CALL GETCL2 CALL WRITLN LD A,EOF CALL FX$PUT XOR A ;Truncate CL in memory LD (HL),A CALL FXO$CLOSE ;Close PERFORM$.$$$ JR ISHELL ;Go init shell cmdline processing. ; ; Shell Stack Push Error ; START3: CP 2 ;shell stack full? JR NZ,START4 ; ; Shell Stack is Full ; CALL PRINT DB 'Shell Stack Full',0 JR EXIT ; ; Shell Stack Entry Size is too small for command line ; START4: CALL PRINT DB 'Shell Stack Entry Size is too Small',0 ; EXIT: LD HL,(SAVESP) LD SP,HL RET ; ; ;---------------- ; Program invoked as shell-- ; ISHELL: ; ; Look for PERFORM$.$$$; open if available or pop shell if not. ; XOR A CALL PUTCST LD HL,(INPLOC) LD DE,CTLFIL CALL INITNAM LD DE,(INPLOC) CALL FXI$OPEN JR Z,SHEXIT LD HL,(INTLINE) CALL READLN JR NZ,SHELL1 CALL PRINT DB 'Temporary file empty.',CR,LF,0 JR SHEXIT SHELL1: CALL FXI$CLOSE ; ; Get next line from FORFILES.SYS if it exists. ; CALL READNAM JR Z,SHEXIT ; ; Expand skeleton command line read from PERFORM$.$$$. ; LD HL,(INTLINE) LD DE,(EXPLINE) EXPAND: LD A,(HL) CP SYSFLG JR NZ,EXP2 CALL SYSFIL ;Do expansion JR EXPAND EXP2: LD (DE),A INC HL INC DE OR A JR NZ,EXPAND ; ; Now store command line in MCL buffer and exit without popping shell. ; LD HL,(EXPLINE) CALL PUTCL JP EXIT ; ;---------------- ; ; Pop shell and restore command line. ; SHEXIT: CALL RESTORE JP EXIT ; ; Restore command line and erase temporary file. ; RESTORE: CALL SHPOP LD HL,(INPLOC) ;Get command line from temporary file. LD DE,CTLFIL CALL INITNAM LD DE,(INPLOC) CALL FXI$OPEN JR NZ,RSTR1 CALL PRINT DB CR,LF,'Can''t restore command line.',CR,LF,0 JR RSTR3 RSTR1: LD HL,(EXPLINE) CALL READLN JR Z,RSTR4 CALL READLN JR Z,RSTR4 CALL PUTCL JR RSTR2 RSTR4: CALL PRINT DB 'Temporary file empty or damaged.',CR,LF,0 RSTR2: LD DE,(INPLOC) CALL FXI$CLOSE CALL DELINF RSTR3: CALL SETDIR ;Make sure we always get back where we started LD HL,SHNAME-1 XOR A LD (HL),A LD HL,SHDISK CALL PUTCL LD A,0C9H ;RET instruction LD (RESTORE),A ;So this routine is called only once. RET ; ;---------------- ; ; Print help message and exit. ; HELP: CALL PRINT DB 'Performs a command line once for every item specified with FOR.COM',CR,LF DB 'Syntax:',CR,LF DB ' PERFORM ',CR,LF DB 'Two special symbols may be used in the command line:',CR,LF DB ' $X - Substitute the current "FOR" item',CR,LF DB ' $| - Substitute a command separator (;)',CR,LF DB 0 JP EXIT ; ; ;======================== SUBROUTINES ============================= ; ; Set DU of current directory at SHDISK. ; SETDIR: CALL RETUD ;Shell always returns to current directory. LD HL,SHDISK ;pt to shell disk LD A,B ;get disk ADD 'A' ;convert to letter LD (HL),A ;set disk letter INC HL ;pt to user 10's LD A,C ;get user number LD B,10 ;subtract 10's LD D,'0' ;set char SETDIR1: SUB B ;subtract JR C,SETDIR2 INC D ;increment digit JR SETDIR1 SETDIR2: ADD A,B ;get 1's LD (HL),D ;set 10's digit for user INC HL ;pt to 1's digit ADD '0' ;compute 1's digit LD (HL),A ;set 1's digit RET ; ;---------------- ; ; Read first line of FORFILES.SYS into FIRSTLIN buffer. ; If file doesn't exist or is empty, return A=0 and Z; else return A<>0 & NZ. ; READNAM: ; ; Initialize the I/O control buffers with names for R/W of FORFILES.SYS. ; LD HL,(INPLOC) LD DE,INFNAM CALL INITNAM LD HL,(OUTPLOC) LD DE,OFNAM CALL INITNAM ; ; Open the files. ; LD DE,(INPLOC) CALL FXI$OPEN RET Z LD DE,(OUTPLOC) CALL FXO$OPEN ; ; Read line. ; LD HL,(FIRSTLIN) ;place to put characters CALL READLN RET Z ; ; Copy remainder of input file to output file. ; First flush any remaining eoln chars from input file. COPY: CALL FX$GET JR Z,NOCHARS ;no more chars CP EOF JR Z,NOCHARS CALL EOLN JR Z,COPY ; ;there's a legit char in A LD HL,(OUTPLOC) ;write it out EX DE,HL ;swap buffer pointers CALL FX$PUT JR Z,OUTERR ;can't write output file ; ;now write the rest of the chars COPY1: EX DE,HL ;input ptr in DE CALL FX$GET JR Z,CLOSEM ;done EX DE,HL ;output ptr in DE CALL FX$PUT JR NZ,COPY1 ; OUTERR: LD A,WRTERR ;do this if error in writing CALL PUTER2 IF DEBUG CALL ERRADR ENDIF CALL PRINT DB 'Can''t write output file',CR,LF,0 ; CLOSEM: LD DE,(INPLOC) ;do this if any chars written to output CALL FXI$CLOSE LD DE,(OUTPLOC) CALL FXO$CLOSE JR NZ,CHGNAM ;continue if no error LD A,CLZERR CALL PUTER2 ;if can't close output file, set flag... IF DEBUG CALL ERRADR ENDIF CALL PRINT ;...print err msg... DB 'Can''t close output file',CR,LF,0 CALL DELINF ;...delete input file, GOTLIN: XOR A DEC A ;Signal that a line has been read. RET ; ; If no chars written to output file, close and erase the files and pop ; the shell. ; NOCHARS: LD DE,(INPLOC) CALL FXI$CLOSE CALL DELINF LD DE,(OUTPLOC) CALL FXO$CLOSE LD HL,FCBOFF ADD HL,DE EX DE,HL CALL F$DELETE CALL RESTORE JR GOTLIN ;Return with OK flag. ; ; Delete input file and rename output file to infilename CHGNAM: CALL DELINF PUSH DE ;save input fcb addr LD HL,(OUTPLOC) LD DE,FCBOFF ADD HL,DE POP DE EX DE,HL CALL F$RENAME JR GOTLIN ;Return with OK flag. ; ;---------------- ; ; Write line pointed to by HL to output file. Terminating null is translated ; to CR/LF pair. ; WRITLN: PUSH DE PUSH HL LD DE,(OUTPLOC) WRTLN1: LD A,(HL) OR A JR Z,WRTLN2 CALL FX$PUT INC HL JR WRTLN1 WRTLN2: LD A,CR CALL FX$PUT LD A,LF CALL FX$PUT POP HL POP DE RET ; ;---------------- ; ; Read line from input file into buffer pointed to by HL. ; Return A=00 and Z if file empty or no more input (file will also be deleted ; in this case). ; READLN: PUSH HL LD DE,(INPLOC) RD3: CALL FX$GET ;see if file empty or leading eoln chars JR Z,RD5 ;read error must be physical eof CP EOF ;if it's the end of the file... JR Z,RD5 ;...quit and clean up CP CR JR Z,RD4 LD (HL),A INC HL RD1: CALL FX$GET ;now get rest of line JR Z,RD2 ;physical eof, but we've gotten at least 1 char CP CR JR Z,RD4 CP EOF ;...or EOF? JR Z,RD2 ;if so, quit LD (HL),A INC HL ;else point to addr for next char JR RD1 ;and get it ; RD4: CALL FX$GET ;Get 2nd EOLN char (LF). RD2: XOR A ;Return with line read. LD (HL),A ;terminate the line DEC A ;Signal OK -- line read. POP HL RET ; RD5: CALL FXI$CLOSE ;Return without line read. CALL DELINF ;delete the input file XOR A ;signal no more input POP HL LD (HL),A RET ; ;---------------- ; ; Initialize file I/O control buffers. ; Enter with HL = first free address in memory ; B = number of 128-byte sectors for the file buffer ; Return: HL = first free address after buffer ; FBINIT: PUSH DE PUSH BC LD (HL),B LD DE,BADOFF ;loc of buf addr in I/O ctl block ADD HL,DE PUSH HL LD DE,[FCBLEN + FCBOFF - BADOFF] ADD HL,DE EX DE,HL POP HL LD (HL),E INC HL LD (HL),D EX DE,HL ;get buf start addr in HL LD DE,128 ;incr HL by buf len in bytes FBINI1: ADD HL,DE DJNZ FBINI1 POP BC POP DE RET ; ;---------------- ; Move filename into fcb of I/O ctl block. Enter with HL = I/O ctl blk addr, ; DE = addr of string to move. Drive is set to current. INITNAM: PUSH BC PUSH DE ;save while adding fcb offset LD DE,FCBOFF ADD HL,DE ;point to input fcb XOR A ;set current drive LD (HL),A INC HL ;point to name field of fcb POP DE ;get source addr EX DE,HL ;put dest addr in DE, source in HL LD BC,11 LDIR POP BC ;restore original contents RET ; ;---------------- ; Delete input file (file pointed to by INPLOC) DELINF: LD HL,(INPLOC) LD DE,FCBOFF ADD HL,DE EX DE,HL CALL F$DELETE RET ; ;---------------- ; ; Check for end of line -- CR or LF. Return Z if true. EOLN: CP CR RET Z CP LF RET ; ;---------------- IF DEBUG ; ; Write the return address on the console ; ERRADR: CALL PRINT DB CR,LF,'Error at ',0 EX (SP),HL CALL PHL4HC EX (SP),HL RET ; ENDIF ; ;---------------- ; ; The following routine expands "$X" and "$|" references. ; Inputs : HL contains a pointer to the command line being interpreted; ; DE points to the string being built. ; Outputs: HL points to the next char in the CL to interpret. ; DE points to the next location to fill in the CL being built ; If the next character in the line is not one of the recognized flags, it ; is passed through unchanged (but the "$" will not be). ; SYSFIL: INC HL ;point to next character LD A,(HL) ;get character OR A ;end of line? RET Z CP SUBSEP JR NZ,SYSFIL1 LD A,CMDSEP LD (DE),A INC DE INC HL RET SYSFIL1: CP LINFLG JR NZ,SYSFIL4 INC HL PUSH HL LD HL,(FIRSTLIN) SYSFIL2: LD A,(HL) OR A JR Z,SYSFIL3 INC HL LD (DE),A INC DE JR SYSFIL2 SYSFIL3: POP HL RET SYSFIL4: LD (DE),A INC HL INC DE RET ; ; ;================[ Buffers ]================ ; SAVESP: DS 2 FIRSTLIN: DS 2 ;Ptr to 1st line from FORFILES.SYS. INTLINE: DS 2 ;Ptr to skeleton command line from PERFORM$.$$ EXPLINE: DS 2 ;Ptr to expanded command line. INPLOC: DS 2 ;Ptr to file buffer for reading. OUTPLOC: DS 2 ;Ptr to file buffer for writing. INFNAM: DB 'FORFILESS','Y'+80H,'S' ;file should be system OFNAM: DB 'FORFILES' OUTTYP: DB '$$$' CTLFIL: DB 'PERFORM$$','$'+80H,'$' ;Control file. SHDISK: db 'A' ;disk letter db '00' ;user number db ':;' ;separator SHNAME: db 'PERFORM ',0 ;name of shell to go onto stack ; END START