; DETABZ.MAC ; Vers equ 13 ; version number SubVers equ ' ' ; modification level ; ; Replaces ASCII tabs in text files with the correct number of spaces. ; For ZCPR3 only. ; ; USAGE: ; ; DETABZ {dir:}infile {dir:}{Outfile} {{/}options} ; ; If no outfile name is given, it will have the same name as the infile. ; If a file exists with the same name as the outfile, it will be renamed ; to a filetype of BAK. ; ; OPTIONS: ; ; n Increment for tab expansion (1-255). If no increment is ; given, the CP/M and MS-DOS default of 8 is used. ; ; Q Toggles quiet mode. Quiet mode is set by the ZCPR3 quiet ; flag or by a configuration byte (non-zero defaults to ; quiet mode). Option Q toggles the current mode. ; ; For more information, see accompanying documentation file. ; ; Version 1.3 -- October 5, 1990 -- Gene Pizzetta ; Corrected bugs that caused unpredictable destination for outfile if ; no filespec was given and a jump to the wrong error routine if the ; input file was not found. Now checks for sufficient TPA space. ; Several other code optimizations, mostly at the suggestion of ; Howard Goldstein. (Thanks, Howard.) ; ; Version 1.2 -- September 22, 1990 -- Gene Pizzetta ; Corrected bug that caused incorrect error messages to be displayed ; under some circumstances. ; ; Version 1.1 -- September 1, 1990 -- Gene Pizzetta ; If source file has a modify date, but no create date, the original ; modify date will become the create date of the new file. ; ; Version 1.0 -- August 25, 1990 -- Gene Pizzetta ; Initial release. ; ; Gene Pizzetta ; 481 Revere Street ; Revere, MA 02151 ; ; Newton Centre Z-Node: (617) 965-7259 ; GEnie: E.PIZZETTA ; Voice: (617) 284-0891 ; ; Developed with SLRMAC and SLRNK+: ; slrmac detabz/m ; slrnkp detabz/n,/v,/a:100,/j,detabz,zslib/s,vlib/s,z3lib/s,syslib/s,/e ; ; System addresses . . . ; Bdos equ 05h CpmFcb equ 5Ch AltFcb equ 6Ch CpmDma equ 80h TPA equ 100h ; ; ASCII characters . . . ; CtrlC equ 03h ; ^C BS equ 08h ; backspace TAB equ 09h ; tab LF equ 0Ah ; linefeed CR equ 0Dh ; carriage return CpmEof equ 1Ah ; CP/M end-of-file character (^Z) ; ; The following output buffer size is in sectors (records) of 128 bytes. ; It may may be set to any number of sectors up to a maximum of 256 (32K). ; BufSiz equ 256 ; output buffer in sectors ; MACLIB Z80 ; ; Following routines are from ZSLIB, VLIB, Z3LIB, and SYSLIB ; ext f$exist,f$open,f$make,f$rename,f$delete,f$close,f$read ext f$write,initfcb,setdma,isdigit,eval10,codend,condin ext retud,logud,epstr,pfn2,cout,pafdc,phlfdc,subhd ext z3vinit,stndout,stndend,getquiet,gzmtop,prtname ext puter2,inverror,zsyschk ext getstp,setstp ; public pstr ; jmp Start ; db 'Z3ENV' db 1 Z3EAdr: dw 0FE00h ; address of environment descriptor ; ; Configuration . . . ; db 0,0 db 'DETABZ' ; filename for ZCNFG CFG file db Vers/10+'0',Vers mod 10+'0' QtFlag: db 0 ; ..non-zero defaults to quiet mode DefInc: db 8 ; default tab increment (8 recommended) ; ; Messages . . . ; MsgUse: db 'DETAB-Z Version ' db Vers/10+'0','.',Vers mod 10+'0',SubVers,CR,LF db 'Replaces ASCII tabs with spaces.',CR,LF db 'Usage:',CR,LF,' ',0 MsgUs1: db ' {dir:}infile {dir:}{outfile} {{/}options}',CR,LF db 'An input filename is required.',CR,LF db 'Options:',CR,LF db ' n tab increment (1-255, default ',0 MsgUs2: db ')',CR,LF db ' Q toggle quiet mode ',0 MsgUon: db 'on',0 MsgUof: db 'off',0 MsgRd: db ' Detabbing ',0 MsgWrt: db ' to ',0 MsgDot: db ' .. ',0 MsgFNF: db 'File not found.',0 MsgWEr: db 'Disk full!',0 MsgNSp: db 'No directory space.',0 MsgCEr: db 'Close error.',0 MsgAmb: db 'No ambiguous filenames.',0 MsgDir: db 'Invalid directory.',0 MsgBdO: db 'Invalid option.',0 MsgBdN: db 'Invalid increment.',0 MsgMem: db 'Insufficient memory.',0 MsgAbt: db 'User aborted.',0 MsgDne: db ' tabs replaced.',0 ; Start: lhld Z3EAdr ; set up environment call zsyschk ; is this a Z-system? rnz ; (nope) call z3vinit sspd OldStk ; save old stack pointer call gzmtop ; get top of memory sphl ; ..and set up new stack push h ; save top of memory ; call codend ; set buffer address shld InpBuf lxi d,128 dad d shld OutBuf dad d ; add stack size and safety buffer xchg ; put in DE pop h ; recover top of memory in HL call subhd ; determine memory size mov a,h ; put number of pages in A cpi BufSiz/2 ; compare to buffer size jrnc Start1 ; (TPA okay) mvi a,12 ; set error code lxi h,MsgMem ; insufficient memory jmp ErExit ; Start1: call getquiet ; is ZCPR quiet flag set? jrnz Start2 ; (yes) lda QtFlag ; no, get quiet config byte Start2: sta OpQFlg ; ..and store in Q option flag lda DefInc ; get default tab increment sta TabInc ; ..and store it xra a ; clear tab counter sta TabCnt sta TabCnt+1 lxi h,CpmDma ; see if there's a tail mov a,m ora a jz Usage ; (no) call GetOpt ; get options ; Check for input file specification lda CpmFcb+1 ; check input filename cpi ' ' ; is there one? jz Usage ; (nope) cpi '/' ; start with a slash? jrnz AmbInp ; (no, move it) lda CpmFcb ; do we have a drive? ora a jz Usage ; (nope) AmbInp: lxi h,CpmFcb+1 ; check for ambiguous name call ChkAmb MovInp: lxi h,CpmFcb+1 ; move filename to input fcb lxi d,InpFcb+1 lxi b,11 ldir lda CpmFcb+15 ; check for valid directory ora a jnz InvDir ; (nope) lda CpmFcb ; get drive (A=1) ora a ; is there one? cz GetDft ; (no, get default) dcr a ; make A=0 mov b,a ; put drive in B lda CpmFcb+13 ; get user mov c,a ; put user in C sbcd InUsr ; store user and drive ; Check for output file specification lda AltFcb+1 ; check output filename cpi '/' ; start with a slash? jrnz ChkOut ; (no, check it) mov b,a ; save character in B lda AltFcb ; yes, do we have a drive? ora a jrz In2Out ; (must be options, use input filename) mov a,b ; get back character ChkOut: cpi ' ' ; do we have an output filename? jrnz AmbOut ; (yes) In2Out: lxi h,CpmFcb+1 ; no, use input filename jr MovFn ; AmbOut: lxi h,AltFcb+1 ; check for ambiguous name call ChkAmb MovOut: lxi h,AltFcb+1 ; move filename to output fcb MovFn: lxi d,OutFcb+1 lxi b,11 ldir lda AltFcb+15 ; check for valid directory ora a jnz InvDir ; (nope) lda AltFcb ; get drive (A=1) ora a ; is there one? cz GetDft ; (no, get default) dcr a ; make A=0 mov b,a ; put drive in B lda AltFcb+13 ; get user mov c,a ; put user in C sbcd OutUsr ; store user and drive lxi h,OutFcb+1 ; save real output filename lxi d,OutFn lxi b,11 ldir lxi h,TmpTyp ; make filetype $$$ lxi d,OutFcb+9 lxi b,3 ldir ; call OpnInp ; open input file call OpnOut ; open output file ; lda OpQFlg ; quiet option? ora a jrnz NoPrt ; (yes, don't print anything) lxi h,MsgRd ; print the file we're reading call epstr lbcd InUsr ; B=drive, C=user lxi d,InpFcb+1 call PrtFn lxi h,MsgWrt ; ..and the file we're writing call epstr lbcd OutUsr ; B=drive, C=user lxi d,OutFn call PrtFn lxi h,MsgDot call epstr ; NoPrt: lxi d,0 ; zero column count call RdLoop ; read and write files call ClsInp ; close input file call WrLast ; close output file call DatStp ; get date stamp, if any lxi h,OutFn+8 ; move real filetype into FCB lxi d,OutFcb+9 lxi b,3 ldir lxi d,OutFcb call initfcb call OutDU lxi d,OutFcb call f$exist ; see if xxx already exists jrz SkpBak ; (it doesn't) lxi h,BakTyp lxi d,OutFcb+9 lxi b,3 ldir lxi d,OutFcb ; point to existing BAK name call f$delete ; erase any existing file lxi h,OutFcb+1 lxi d,OutFcb+13 lxi b,11 ldir lxi h,OutFn lxi d,OutFcb+1 lxi b,11 ldir lxi d,OutFcb ; point to old xxx name lxi h,OutFcb+12 ; point to new BAK name call f$rename ; ..and rename file SkpBak: lxi h,TmpTyp ; rename temporary $$$ file lxi d,OutFcb+9 ; ..to output filename lxi b,3 ldir lxi h,OutFn lxi d,OutFcb+13 lxi b,11 ldir lxi h,OutFcb+12 ; point to new xxx name lxi d,OutFcb ; point to old $$$ name call f$rename ; Finish: lda OpQFlg ; quiet option? ora a mvi a,0 ; reset error flag jrnz Exit ; (yes) lhld TabCnt ; get tab count call phlfdc lxi h,MsgDne ErExit: call epstr ; print message Exit: call puter2 ; set error code mov b,a ; put error code in B ora a ; do we have one? cnz inverror ; (yes, call error handler) lspd OldStk ; restore old stack pointer ret ; ..and return to CCP ; Abort: push psw ; save error code push h ; ..and message call OutDU lxi d,OutFcb call f$close ; close output file call f$delete ; erase it pop h ; get back message pop psw ; ..and error code jr ErExit ; InvDir: mvi a,2 ; invalid directory lxi h,MsgDir jr ErExit ; ; Subroutines . . . ; ; OpnInp -- open input file ; OpnInp: mvi a,128 ; initialize pointer sta GetPtr sub a ; initialize end-of-file flag sta GetFlg lxi d,InpFcb call initfcb call InDU ; set drive/user for input call f$open rz ; (all okay) mvi a,10 ; set error flag lxi h,MsgFNF ; file not found jr ErExit ; ; OpnOut -- open output file ; OpnOut: sub a ; initialize counters sta PutCnt sta PutSec lhld OutBuf ; initialize buffer pointer shld PutPtr lxi d,OutFcb call initfcb ; initialize FCB call OutDU call f$make ; create and open file cpi 0FFh rnz ; (okay) mvi a,11 ; set error flag lxi h,MsgNSp ; no directory space jmp ErExit ; ; RdLoop -- reads and writes until end of file ; RdLoop: call GetC ; get a character rz ; (end of file) cpi CpmEof ; end of file? rz ; (yes) ; cpi TAB ; is it a tab? jz IsTab ; (yes) call PutC ; output character jrc WrtErr ; (write error) cpi ' ' ; is it a control character? jnc RdLp3 ; (no) cpi CR ; is it a carriage return? jnz RdLp2 ; (no) lxi d,0 ; zero column counter jr RdLp4 ; RdLp2: cpi BS ; backspace? jnz RdLp4 ; (no) mov h,a ; save character mov a,d ; test column count for zero ora e mov a,h ; get back character jz RdLp4 ; column count is 0 (don't back up) dcx d ; decrement column count jr RdLp4 ; RdLp3: cpi 07Fh jnc RdLp4 inx d ; increment column count ; RdLp4: call condin ; user aborting? jrz RdLoop ; (nope) cpi CtrlC jrnz RdLoop mvi a,4 ; we're aborting lxi h,MsgAbt jmp Abort ; ; IsTab -- We have a tab, so expand it ; IsTab: lhld TabCnt ; increment tab counter inx h shld TabCnt lda TabInc ; get increment mov h,d ; duplicate column counter in HL mov l,e mvi b,16 ; loop count in B mov c,a ; divisor in C xra a ; zero remainder IsTab1: dad h ; shift left, dividend bit in carry ral ; accumulate dividend bit in A cmp c ; size of divisor yet? jrc IsTab2 ; (yes) sub c ; subtract divisor from remainder inr l ; make quotient low bit 1 IsTab2: djnz IsTab1 ; (repeat 16 times) neg ; negative of column mod TabInc lxi h,TabInc add m ; A is number of spaces needed mov b,a ; B is loop counter mvi a,' ' IsTab3: call PutC ; output space jrc WrtErr ; (write error) inx d ; increment column counter djnz IsTab3 jr RdLp4 ; WrtErr: mvi a,11 ; set error flag lxi h,MsgWEr ; no disk space jmp Abort ; ; ClsInp -- close input file ; ClsInp: call InDU ; close input file lxi d,InpFcb call f$close rz ; (okay) mvi a,4 ; set error flag lxi h,MsgCEr ; close error jmp Abort ; ; WrLast -- closes output file ; WrLast: call OutDU mvi a,CpmEof ; put end-of-file character call PutC lda PutCnt ; check pointer ora a jz WrLst1 ; (sector finished) mov b,a mvi a,128 ; fill rest of sector with ^Z sub b mov b,a FillZ: mvi a,CpmEof call PutC djnz FillZ ; jr WrLst1 ; WrLst1: lded OutBuf ; get beginning buffer address to DE lxi h,PutPtr ; HL -> buffer pointer mov a,e ; is pointer at zero? cmp m jrnz WrLst2 ; (no) mov a,d inx h cmp m jrnz WrLst2 ; (no) lda PutCnt ; is counter at zero? ora a jrnz WrLst2 ; (no) jr ClsOut ; nothing to write, so close it ; WrLst2: call WrtF ; write what's left jnz WrtErr ; (disk full, abort) ; ClsOut: call OutDU ; close output file lxi d,OutFcb call f$close rz ; (okay) mvi a,4 ; set error flag lxi h,MsgCEr ; close error jmp Abort ; ; GetDft -- gets default drive and user ; GetDft: call retud ; C=user, B=drive (A=0) mov a,b ; A=drive (A=0) inr a ; A=drive (A=1) ret ; ; PrtFn -- Prints drive/user and filename on console ; PrtFn: call stndout mov a,b ; get drive adi 'A' ; make it printable call cout ; ..and print it mov a,c ; get user call pafdc ; ..and print it mvi a,':' call cout call pfn2 ; print filename call stndend ret ; ; EatSpc -- gobbles up spaces and tabs ; EatSpc: mov a,m ora a rz inx h cpi ' ' ; is it a space? jrz EatSpc ; (yes) cpi TAB ; is it a tab? jrz EatSpc ; (yes) dcx h ret ; ; OutDU -- sets default drive and user for output file ; OutDU: lbcd OutUsr ; B=drive, C=user call logud ret ; ; InDU -- sets default drive and user for input file ; InDU: lbcd InUsr ; B=drive, C=user call logud ret ; ; GetC -- returns character from file. Assumes file has been ; successfully opened. Returns character or ^Z (end-of-file) in ; A. Zero set (Z) on end of file. Carry set (C) if error. ; Affected: AF ; GetC: push b push d push h lda GetFlg ; check end-of-file flag ora a jrnz GetEof ; (yes) lda GetPtr ; get pointer cpi 128 ; done with buffer? jrc GetChr ; (no, get a character) lhld InpBuf call setdma ; set DMA address call InDU ; set DU lxi d,InpFcb call f$read ; read more file jrnz GetEof ; (end of file) sta GetPtr ; put 0 in pointer ; GetChr: lhld InpBuf ; point to DMA buffer mov e,a ; put pointer in DE mvi d,0 dad d ; add it to HL mov a,m ; get next character lxi h,GetPtr ; increment pointer inr m stc cmc ; clear carry pop h pop d pop b ret ; GetEof: mvi a,CpmEof sta GetFlg ; set end-of-file flag stc cmc ; clear carry pop h pop d pop b ret ; ; PutC -- Writes character to file. Assumes file has been successfully ; opened. Expects character in A. Returns carry set (C) on error. ; Affected: Flags. ; PutC: push b push d push h mov c,a ; save character in C lda PutCnt ; get counter cpi 128 ; buffer full? jrc PutChr ; (no, so do it) push b ; the character is threatened from all sides lda PutSec ; get sector count cpi BufSiz-1 ; end of buffer? jrz PutC2 ; (yes, we need to write it) inr a ; increment sector count sta PutSec ; ..and store it lhld PutPtr ; get current sector pointer lxi d,128 ; ..add 128 to it dad d shld PutPtr ; ..and store it xra a ; make A = 0 jr PutC3 ; PutC2: call WrtF ; write buffer to disk jnz WrtErr ; (disk full) push psw lhld OutBuf ; reset buffer pointer shld PutPtr xra a ; reset sector counter sta PutSec pop psw ; PutC3: pop b ; get back output character ora a ; return code? jrnz PutErr ; (problem) sta PutCnt ; reset counter to 0 ; PutChr: lhld PutPtr ; point to current DMA buffer mov e,a ; move counter to DE mvi d,0 dad d ; ..and add it to HL mov m,c ; write character lda PutCnt inr a ; increment counter sta PutCnt sub a ; clear carry mov a,c ; recover character pop h pop d pop b ret ; PutErr: stc ; set carry mov a,c ; recover character pop h pop d pop b ret ; ; WrtF -- write output buffer to disk ; WrtF: lda PutSec ; get buffer sector count mov b,a ; put it in B lhld OutBuf ; point to beginning of buffer ; lded OutBuf ; point to beginning of buffer WrtF2: push b ; save sector count push h ; save DMA address call WrtSec ; write the sector pop h ; recover DMA address pop b ; get back sector count rnz ; (write error) cmp b ; end of buffer? rz ; (yes) dcr b ; decrement sector count lxi d,128 ; increment DMA address dad d jr WrtF2 ; WrtSec: call setdma ; set DMA address call OutDu ; set drive and user lxi d,OutFcb ; ..and write sector call f$write ret ; ; GetOpt -- checks command tail for user supplied options and sets ; appropriate option flags. ; GetOpt: lxi h,CpmDma ; point to command tail mov a,m ; anything there? ora a rz ; (no) inx h call EatSpc cpi '/' jz IsSlsh FnLp1: inx h ; move past first filename mov a,m ora a rz ; (end of tail) cpi ' ' jrz GetOp1 cpi TAB jrz GetOp1 jr FnLp1 ; GetOp1: call EatSpc ora a rz ; (end of tail) cpi '/' ; is it a slash? jz IsSlsh ; (yes) FnLp2: inx h ; move past second filename mov a,m ora a rz ; (end of tail) cpi ' ' jrz GetOp2 cpi TAB jrz GetOp2 jr FnLp2 ; GetOp2: call EatSpc ora a rz ; (end of tail) cpi '/' ; a slash? jrnz GotOpt ; (no, get options) IsSlsh: inx h ; move past slash GotOpt: mov a,m ; get option inx h ora a rz ; (end of tail) cpi ' ' ; space? jrz GotOpt ; (skip it) cpi '/' ; usage request? jrz Usage ; (yes) cpi 'Q' ; quiet flag? jrz OptQ ; (yes) call isdigit ; check for digit? jrz OptI ; (yes) mvi a,19 ; set invalid option error lxi h,MsgBdO jmp ErExit ; ; Option setting routines ; Usage: lxi h,MsgUse call epstr call prtname lxi h,MsgUs1 call epstr lda DefInc call pafdc lxi h,MsgUs2 call epstr lda OpQFlg ; check quiet mode ora a jrz Usage2 ; (off) xra a ; reset error flag lxi h,MsgUof jmp ErExit Usage2: xra a ; reset error flag lxi h,MsgUon jmp ErExit ; OptI: dcx h ; point HL to number string call eval10 ; convert to binary mov a,d ; is it less than 256? ora a jrnz OptI2 ; (yes) mov a,e ; it it greater than 0? ora a jrz OptI2 ; (no) sta TabInc ; store it jmp GotOpt ; OptI2: mvi a,9 ; it's a bad number lxi h,MsgBdN jmp ErExit ; OptQ: lda OpQFlg ; get Q flag ora a jrz OptQ2 ; (not set) sub a sta OpQFlg ; zero it jmp GotOpt ; OptQ2: mvi a,1 sta OpQFlg ; set it jmp GotOpt ; ; DatStp -- Get create stamp from original file, if available, and ; transfer it to new file. ; DatStp: call InDU ; setup for input file lxi d,InpFcb lxi h,CpmDma ; point to DMA buffer call getstp ; get ZSDOS file stamp rnz ; (error) lda CpmDma+1 ; check for create date ora a jrnz DatSt1 ; (we've got a create date) lda CpmDma+11 ; none, so check for modify date ora a rz ; (no date stamp) lxi h,CpmDma+10 ; point to modify date jr DatSt2 DatSt1: lxi h,CpmDma ; point to create date DatSt2: lxi d,StpTmp ; move date to storage lxi b,5 ldir call OutDU ; setup for output file lxi d,OutFcb lxi h,CpmDma ; point to DMA buffer call getstp ; get file stamp rnz ; (error) lxi h,StpTmp lxi d,CpmDma ; move old create stamp to date string lxi b,5 ldir lxi d,OutFcb ; setup for file stamping lxi h,CpmDma call setstp ; set file stamp ret ; ; ChkAmb -- checks for ambiguous filename. Expects address of filename ; in HL. ; ChkAmb: lxi b,11 mvi a,'?' ccir rnz ; (no question marks) mvi a,8 ; ambiguous filename lxi h,MsgAmb jmp ErExit ; ; PSTR -- forces ZSYSCHK to use EPSTR ; pstr: jmp epstr ; ; Data storage . . . ; BakTyp: db 'BAK' ; for BAK file TmpTyp: db '$$$' ; for temporary filename ; DSEG ; ; Uninitialized storage . . . ; OutFn: ds 11 ; save original output filename here InpFcb: ds 36 ; input file fcb OutFcb: ds 36 ; output file fcb StpTmp: ds 5 ; temporary create date storage TabInc: ds 1 ; current tab increment TabCnt: ds 2 ; number of tabs replaced OpQFlg: ds 1 ; current quiet mode flag GetFlg: ds 1 ; GetC end-of-file flag GetPtr: ds 1 ; GetC pointer PutCnt: ds 1 ; PutC counter PutPtr: ds 2 ; PutC pointer PutSec: ds 1 ; PutC sector counter InUsr: ds 1 ; input file user InDrv: ds 1 ; input file drive OutUsr: ds 1 ; output file user OutDrv: ds 1 ; output file drive OldStk: ds 2 ; old stack pointer InpBuf: ds 2 ; input buffer address OutBuf: ds 2 ; output buffer address ; end