        page    ,132
        TITLE   GPFILT
        subttl  General Purpose Filter Template
;
; GPFILT.ASM
; This file contains a template for a general-purpose assembly language
; filter program.
;
; Fill in the blanks for what you wish to do.  The program is set up to
; accept a command line in the form:
;       COMMAND [{-|/}options] [infile [outfile]]
;
; If infile is not specified, stdin is used.
; If outfile is not specified, stdout is used.
;
; To compile and link:
;    MASM GPFILT ;
;    LINK GPFILT ;
;    EXE2BIN GPFILT GPFILT.COM
;
; Standard routines supplied in the general shell are:
;
; get_arg - returns the address of the next command line argument in
;           DX.  Since this is a .COM file, the routine assumes DS will
;           be the same as the command line segment.
;           The routine will return with Carry set when it reaches the end
;           of the command line.
;
; err_msg - displays an ASCIIZ string on the STDERR device.  Call with the
;           address of the string in ES:DX.
;
; do_usage- displays the usage message on the STDERR device and exits
;           with an error condition (errorlevel 1).  This routine will
;           never return.
;
; getch   - returns the next character from the input stream in AL.
;           It will return with carry set if an error occurs during read.
;           It will return with the ZF set at end of file.
;
; putch   - writes a character from AL to the output stream.  Returns with
;           carry set if a write error occurs.
;
cseg    segment
        assume cs:cseg, ds:cseg, es:cseg, ss:cseg

        org     0100h                   ;for .COM files

start:  jmp     main                    ;jump around data area

;
; Equates and global data area.
;
; The following equates and data areas are required by the general filter
; routines.  User data area follows.
;
STDIN   equ     0
STDOUT  equ     1
STDERR  equ     2
STDPRN  equ     3
cr      equ     0dh
lf      equ     0ah
space   equ     32
tab     equ     9

infile  dw      STDIN                   ;default input file is stdin
outfile dw      STDOUT                  ;default output file is stdout
errfile dw      STDERR                  ;default error file is stderr
prnfile dw      STDPRN                  ;default print file is stdprn
cmd_ptr dw      0081h                   ;address of first byte of command tail
PSP_ENV equ     002ch                   ;The segment address of the environment
                                        ;block is stored here.

infile_err      db      cr, lf, 'Error opening input file', 0
outfile_err     db      cr, lf, 'Error opening output file', 0
aborted         db      07, cr, lf, 'Program aborted', 0
usage           db      cr, lf, 'Usage: ', 0
crlf            db      cr, lf, 0

;************************************************************************
;*                                                                      *
;* Buffer sizes for input and output files.  The buffers need not be    *
;* the same size.  For example, a program that removes tabs from a text *
;* file will output more characters than it reads.  Therefore, the      *
;* output buffer should be slightly larger than the input buffer.  In   *
;* general, the larger the buffer, the faster the program will run.     *
;*                                                                      *
;* The only restriction here is that the combined size of the buffers   *
;* plus the program code and data size cannot exceed 64K.               *
;*                                                                      *
;* The easiest way to determine maximum available buffer memory is to   *
;* assemble the program with minimum buffer sizes and examine the value *
;* of the endcode variable at the end of the program.  Subtracting this *
;* value from 65,536 will give you the total buffer memory available.   *
;*                                                                      *
;************************************************************************
;
INNBUF_SIZE     equ     31              ;size of input buffer (in K)
OUTBUF_SIZE     equ     31              ;size of output buffer (in K)

;
;************************************************************************
;*                                                                      *
;* Data definitions for input and output buffers.  DO NOT modify these  *
;* definitions unless you know exactly what it is you're doing!         *
;*                                                                      *
;************************************************************************
;
; Input buffer
ibfsz   equ     1024*INNBUF_SIZE        ;input buffer size in bytes
inbuf   equ     endcode                 ;input buffer
ibfend  equ     inbuf + ibfsz           ;end of input buffer
;
; ibfptr is initialized to point past end of input buffer so that the first
; call to getch will result in a read from the file.
;
ibfptr  dw      inbuf+ibfsz

; output buffer
obfsz   equ     1024*OUTBUF_SIZE        ;output buffer size in bytes
outbuf  equ     ibfend                  ;output buffer
obfend  equ     outbuf + obfsz          ;end of output buffer
obfptr  dw      outbuf                  ;start at beginning of buffer

;************************************************************************
;*                                                                      *
;*                            USER DATA AREA                            *
;*                                                                      *
;*      Insert any data declarations specific to your program here.     *
;*                                                                      *
;* NOTE:  The prog_name, use_msg, and use_msg1 variables MUST be        *
;*        defined.                                                      *
;*                                                                      *
;************************************************************************
;
; This is the program name.  Under DOS 3.x, this is not used because we
; can get the program name from the environment.  Prior to 3.0, this
; information is not supplied by the OS.
;
prog_name       db      'GPFILT', 0
;
; This is the usage message.  The first two lines are required.
; The first line is the programs title line.
;   Make sure to include the 0 at the end of the first line!!
; The second line shows the syntax of the program.
; Following lines (which are optional), are discussion of options, features,
; etc...
; The message MUST be terminated by a 0.
;
use_msg db      ' - General Purpose FILTer program.', cr, lf, 0
use_msg1        label byte
        db      '[{-|/}options] [infile [outfile]]', cr, lf
        db      cr, lf
        db      'If infile is not specified, STDIN is used', cr, lf
        db      'If outfile is not specified, STDOUT is used', cr, lf
        db      0
;
;************************************************************************
;*                                                                      *
;* The main routine parses the command line arguments, opens files, and *
;* does other initialization tasks before calling the filter procedure  *
;* to do the actual work.                                               *
;* For a large number of filter programs, this routine will not need to *
;* be modified.  Options are parsed in the get_options proc., and the   *
;* filter proc. does all of the 'filter' work.                          *
;*                                                                      *
;************************************************************************
;
main:   cld
        call    get_options             ;process options
        jc      gofilter                ;carry indicates end of arg list
        mov     ah,3dh                  ;open file
        mov     al,0                    ;read access
        int     21h                     ;open the file
        mov     word ptr ds:[infile], ax ;save file handle
        jnc     main1                   ;carry clear indicates success
        mov     dx,offset infile_err
        jmp     short err_exit
main1:  call    get_arg                 ;get cmd line arg in DX
        jc      gofilter                ;carry indicates end of arg list
        mov     ah,3ch                  ;create file
        mov     cx,0                    ;normal file
        int     21h                     ;open the file
        mov     word ptr ds:[outfile],ax ;save file handle
        jnc     gofilter                ;carry clear indicates success
        mov     dx,offset outfile_err
        jmp     short err_exit
gofilter:
        call    filter                  ;do the work
        jc      err_exit                ;exit immediately on error
        mov     ah,3eh
        mov     bx,word ptr [infile]
        int     21h                     ;close input file
        mov     ah,3eh
        mov     bx,word ptr [outfile]
        int     21h                     ;close output file
        mov     ax,4c00h
        int     21h                     ;exit with no error
err_exit:
        call    err_msg                 ;output error message
        mov     dx,offset aborted
        call    err_msg
        mov     ax,4c01h
        int     21h                     ;and exit with error
;
;************************************************************************
;*                                                                      *
;* get_options processes any command line options.  Options are         *
;* preceeded by either - or /.  There is a lot of flexibility here.     *
;* Options can be specified separately, or as a group.  For example,    *
;* the command "GPFILT -x -y -z" is equivalent to "GPFILT -xyz".        *
;*                                                                      *
;* This routine MUST return the address of the next argument in DX or   *
;* carry flag set if there are no more options.  In other words, return *
;* what was returned by the last call to get_arg.                       *
;*                                                                      *
;************************************************************************
;
get_options     proc
        call    get_arg                 ;get command line arg
        jnc     opt1
; If at least one argument is required, use this line
;       call    do_usage                ;displays usage msg and exits
; If there are no required args, use this line
        ret                             ;if no args, just return
opt1:   mov     di, dx
        mov     al,byte ptr ds:[di]
        cmp     al,'-'                  ;if first character of arg is '-'
        jz      opt_parse
        cmp     al,'/'                  ;or '/', then get options
        jz      opt_parse
        ret                             ;otherwise exit
opt_parse:
        inc     di
        mov     al,byte ptr ds:[di]
        or      al,al                   ;if end of options string
        jz      nxt_opt                 ;get cmd. line arg
        cmp     al,'?'                  ;question means show usage info
        jz      do_usage
;
;************************************************************************
;*                                                                      *
;* Code for processing other options goes here.  The current option     *
;* character is in AL, and the remainder of the option string is pointed*
;* to by DS:DI.                                                         *
;*                                                                      *
;************************************************************************
;
        jmp     short opt_parse

nxt_opt:
        call    get_arg                 ;get next command line arg
        jnc     opt1                    ;if carry
vld_args:                               ;then validate arguments
;
;************************************************************************
;*                                                                      *
;* Validate arguments.  If some options are mutually exclusive/dependent*
;* use this area to validate them.  Whatever the case, if you must      *
;* abort the program, call the do_usage procedure to display the usage  *
;* message and exit the program.                                        *
;*                                                                      *
;************************************************************************
;
        ret                             ; no more options
;
;************************************************************************
;*                                                                      *
;* Filter does all the work.  Modify this routine to do what it is you  *
;* need done.                                                           *
;*                                                                      *
;************************************************************************
;
filter  proc
        call    getch                   ;get a character from input into AL
        jbe     filt_done               ;exit on error or EOF
        and     al, 7fh                 ;strip the high bit
        call    putch                   ;and output it
        jc      filt_ret                ;exit on error
        jmp     short filter
filt_done:
        jc      filt_ret                ;carry set is error
        call    write_buffer            ;output what remains of the buffer
filt_ret:
        ret
filter  endp
;
;************************************************************************
;*                                                                      *
;*              Put any program-specific routines here                  *
;*                                                                      *
;************************************************************************

;
;************************************************************************
;*                                                                      *
;* For most programs, nothing beyond here should require modification.  *
;* The routines that follow are standard routines used by almost every  *
;* filter program.                                                      *
;*                                                                      *
;************************************************************************
;
;************************************************************************
;*                                                                      *
;* This routine outputs the usage message to the STDERR device and      *
;* aborts the program with an error code.  A little processing is done  *
;* here to get the program name and format the output.                  *
;*                                                                      *
;************************************************************************
;
do_usage:
        mov     dx, offset crlf
        call    err_msg                 ;output newline
        mov     ah,30h                  ;get DOS version number
        int     21h
        sub     al,3                    ;check for version 3.x
        jc      lt3                     ;if carry, earlier than 3.0
;
; For DOS 3.0 and later the full pathname of the file used to load this
; program is stored at the end of the environment block.  We first scan
; all of the environment strings in order to find the end of the env, then
; scan the load pathname looking for the file name.
;
        push    es
        mov     ax, word ptr ds:[PSP_ENV]
        mov     es, ax                  ;ES is environment segment address
        mov     di, 0
        mov     cx, 0ffffh              ;this ought to be enuf
        xor     ax, ax
getvar: scasb                           ;get char
        jz      end_env                 ;end of environment
gv1:    repnz   scasb                   ;look for end of variable
        jmp     short getvar            ;and loop 'till end of environment
end_env:
        inc     di
        inc     di                      ;bump past word count
;
; ES:DI is now pointing to the beginning of the pathname used to load the
; program.  We will now scan the filename looking for the last path specifier
; and use THAT address to output the program name.  The program name is
; output WITHOUT the extension.
;
        mov     dx, di
fnloop: mov     al, byte ptr es:[di]
        or      al, al                  ;if end of name
        jz      do30                    ;then output it
        inc     di
        cmp     al, '\'                 ;if path specifier
        jz      updp                    ;then update path pointer
        cmp     al, '.'                 ;if '.'
        jnz     fnloop
        mov     byte ptr es:[di-1], 0   ;then place a 0 so we don't get ext
        jmp     short fnloop            ; when outputting prog name
updp:   mov     dx, di                  ;store
        jmp     short fnloop
;
; ES:DX now points to the filename of the program loaded (without extension).
; Output the program name and then go on with rest of usage message.
;
do30:   call    err_msg                 ;output program name
        pop     es                      ;restore
        jmp     short gopt3
;
; We arrive here if the current DOS version is earlier than 3.0.  Since the
; loaded program name is not available from the OS, we'll output the name
; entered in the 'prog_name' field above.
;
lt3:    mov     dx, offset prog_name
        call    err_msg                 ;output the program name
;
; After outputting program name, we arrive here to output the rest of the
; usage message.  This code assumes that the usage message has been
; written as specified in the data area.
;
gopt3:  mov     dx, offset use_msg
        call    err_msg                 ;output the message
        mov     dx, offset usage
        call    err_msg
        mov     dx, offset use_msg1
        call    err_msg
        mov     ax,4c01h
        int     21h                     ;and exit with error
get_options     endp

;
;************************************************************************
;*                                                                      *
;* Output a message (ASCIIZ string) to the standard error device.       *
;* Call with address of error message in ES:DX.                         *
;*                                                                      *
;************************************************************************
;
err_msg proc
        cld
        mov     di,dx                   ;string address in di
        mov     cx,0ffffh
        xor     ax,ax
        repnz   scasb                   ;find end of string
        xor     cx,0ffffh
        dec     cx                      ;CX is string length
        push    ds
        mov     ax,es
        mov     ds,ax                   ;DS is segment address
        mov     ah,40h
        mov     bx,word ptr cs:[errfile]
        int     21h                     ;output message
        pop     ds
        ret
err_msg endp

;
;************************************************************************
;*                                                                      *
;* getch returns the next character from the file in AL.                *
;* Returns carry = 1 on error                                           *
;*         ZF = 1 on EOF                                                *
;* Upon exit, if either Carry or ZF is set, the contents of AL is       *
;* undefined.                                                           *
;*                                                                      *
;************************************************************************
;
; Local variables used by the getch proc.
eof     db      0                       ;set to 1 when EOF reached in read
last_ch dw      ibfend                  ;pointer to last char in buffer

getch   proc
        mov     si,word ptr ds:[ibfptr] ;get input buffer pointer
        cmp     si,word ptr ds:[last_ch];if not at end of buffer
        jz      getch_eob
getch1: lodsb                           ;character in AL
        mov     word ptr ds:[ibfptr],si ;save buffer pointer
        or      ah,1                    ;will clear Z flag
        ret                             ;and done

getch_eob:                              ;end of buffer processing
        cmp     byte ptr ds:[eof], 1    ;end of file?
        jnz     getch_read              ;nope, read file into buffer
getch_eof:
        xor     ax, ax                  ;set Z to indicate EOF
        ret                             ;and return

getch_read:                     ; Read the next buffer full from the file.
        mov     ah,3fh                  ;read file function
        mov     bx,word ptr ds:[infile] ;input file handle
        mov     cx,ibfsz                ;#characters to read
        mov     dx,offset inbuf         ;read into here
        int     21h                     ;DOS'll do it for us
        jc      read_err                ;Carry means error
        or      ax,ax                   ;If AX is zero,
        jz      getch_eof               ;we've reached end-of-file
        add     ax,offset inbuf
        mov     word ptr ds:[last_ch],ax;and save it
        mov     si,offset inbuf
        jmp     short getch1            ;and finish processing character

read_err:                               ;return with error and...
        mov     dx,offset read_err_msg  ; DX pointing to error message string
        ret
read_err_msg    db      'Read error', cr, lf, 0
getch   endp

;
;************************************************************************
;*                                                                      *
;* putch writes the character passed in AL to the output file.          *
;* Returns carry set on error.  The character in AL is retained.        *
;*                                                                      *
;************************************************************************
;
putch   proc
        mov     di,word ptr ds:[obfptr] ;get output buffer pointer
        stosb                           ;save the character
        mov     word ptr ds:[obfptr],di ;and update buffer pointer
        cmp     di,offset obfend        ;if buffer pointer == buff end
        clc
        jnz     putch_ret
        push    ax
        call    write_buffer            ;then we've got to write the buffer
        pop     ax
putch_ret:
        ret
putch   endp

;
;************************************************************************
;*                                                                      *
;* write_buffer writes the output buffer to the output file.            *
;* This routine should not be called except by the putch proc. and at   *
;* the end of all processing (as demonstrated in the filter proc).      *
;*                                                                      *
;************************************************************************
;
write_buffer    proc                    ;write buffer to output file
        mov     ah, 40h                 ;write to file function
        mov     bx, word ptr ds:[outfile];output file handle
        mov     cx, word ptr ds:[obfptr]
        sub     cx, offset outbuf       ;compute #bytes to write
        mov     dx, offset outbuf       ;from this buffer
        int     21h                     ;DOS'll do it
        jc      write_err               ;carry is error
        or      ax,ax                   ;return value of zero
        jz      putch_full              ;indicates disk full
        mov     word ptr ds:[obfptr],offset outbuf
        clc
        ret

putch_full:                             ;disk is full
        mov     dx,offset disk_full
        stc                             ;exit with error
        ret

write_err:                              ;error occured during write
        mov     dx,offset write_err_msg
        stc                             ;return with error
        ret
write_err_msg   db      'Write error', cr, lf, 0
disk_full       db      'Disk full', cr, lf, 0

write_buffer    endp

;
;************************************************************************
;*                                                                      *
;* get_arg - Returns the address of the next command line argument in   *
;* DX.  The argument is in the form of an ASCIIZ string.                *
;* Returns Carry = 1 if no more command line arguments.                 *
;* Upon exit, if Carry is set, the contents of DX is undefined.         *
;*                                                                      *
;************************************************************************
;
get_arg proc
        mov     si,word ptr [cmd_ptr]
skip_space:                             ;scan over leading spaces and commas
        lodsb
        cmp     al,0                    ;if we get a null
        jz      sk0
        cmp     al,cr                   ;or a CR,
        jnz     sk1
sk0:    stc                             ;set carry to indicate failure
        ret                             ;and exit
sk1:    cmp     al,space
        jz      skip_space              ;loop until no more spaces
        cmp     al,','
        jz      skip_space              ;or commas
        cmp     al,tab
        jz      skip_space              ;or tabs

        mov     dx,si                   ;start of argument
        dec     dx
get_arg1:
        lodsb                           ;get next character
        cmp     al,cr                   ;argument seperators are CR,
        jz      get_arg2
        cmp     al,space                ;space,
        jz      get_arg2
        cmp     al,','                  ;comma,
        jz      get_arg2
        cmp     al,tab                  ;and tab
        jnz     get_arg1

get_arg2:
        mov     byte ptr ds:[si-1], 0   ;delimit argument with 0
        cmp     al, cr                  ;if char is CR then we've reached
        jnz     ga2                     ; the end of the argument list
        dec     si
ga2:    mov     word ptr ds:[cmd_ptr], si ;save for next time 'round
        ret                             ;and return
get_arg endp

endcode equ     $

cseg    ends
        end     start
