home *** CD-ROM | disk | FTP | other *** search
/ ftp.barnyard.co.uk / 2015.02.ftp.barnyard.co.uk.tar / ftp.barnyard.co.uk / cpm / walnut-creek-CDROM / JSAGE / ZSUS / PROGPACK / MEYERTUT.LBR / MEYER07.TZT / MEYER07.TXT
Text File  |  2000-06-30  |  14KB  |  368 lines

  1.              CP/M Assembly Language
  2.             Part VII: Filter Programs
  3.               by Eric Meyer
  4.  
  5.      Last time we put together the basic subroutines needed to
  6. read and write text files.
  7.      Now we'll use these to construct "filter programs": programs
  8. that read text in, process it in some way, then write the result
  9. back out.
  10.      One use for such a program is to convert text back and forth
  11. from Wordstar document (henceforth "DOC") to non-document (plain
  12. ASCII) form.
  13.  
  14.  
  15. 1. AND and/or OR
  16.      First we need to introduce a family of 8080 instructions we
  17. avoided until now: the logical operations ANA (and), ORA (or),
  18. and XRA (exclusive or), and their immediate cousins ANI, ORI,
  19. XRI.
  20.      Each operates with the accumulator and another 8-bit value,
  21. combining them one bit at a time ("bitwise") to produce a result.
  22.      Logical AND, e.g., produces a 1 if both its arguments are 1,
  23. and a 0 otherwise. That is, 1 and 1 is 1, and anything else (1
  24. and 0, or 0 and 0) is 0.
  25.      When applied bitwise, you will find for example that 61h AND
  26. 5Fh is 41h:
  27.  
  28.            61h =  01100001 binary
  29.       AND  5Fh =  01011111
  30.            ---------------
  31.            41h =  01000001
  32.  
  33.      Why is this interesting?
  34.      Well, 61h is the ASCII code for the character a, and 41h is
  35. A. (If you don't have a nice ASCII table with hex/decimal/binary
  36. values, make or find one.)
  37.      That is, by ANDing it with 5Fh, we have uppercased the
  38. letter a. Because of the way the ASCII codes are assigned, upper
  39. and lower case letters all differ only by a single bit (the 6th
  40. from the right, "bit 5" in assembler speak), and the same trick
  41. works for all letters.
  42.      Note, of course, that the operation ANI 5FH changes (zeros)
  43. not only bit 5, but also bit 7, the high (parity) bit.
  44.      (You can also zero just bit 7, by using ANI 7FH, since 7Fh =
  45. 01111111b.)  Remember that much of the difference between a
  46. WordStar and a plain ASCII file is that Wordstar sets the high
  47. bit on many characters; so undoing this is part of the task of
  48. converting between these two formats.
  49.      Logical OR produces a 1 if either argument is 1, and a 0
  50. otherwise. So 1 or 1, 1 or 0 are both 1, while 0 or 0 is 0.
  51.      Thus you can turn on certain bits, by ORing with a certain
  52. value. For example, we can undo what we did above:
  53.  
  54.            41h =  01000001b
  55.        OR  20h =  00100000
  56.            ---------------
  57.            61h =  01100001
  58.  
  59.      Thus the operation ORI 20h will uppercase a letter.
  60.      Logical XOR produces a 1 if either argument, but not both,
  61. is 1, and a 0 otherwise.
  62.      We won't have any immediate use for this now, but you might
  63. note that programmers commonly use XRA A to zero the accumulator,
  64. since any value XORed with itself gives 0.
  65.      Let's quickly embody this case business in two routines
  66. which you may find useful: UCASE and LCASE. These respectively
  67. convert the ASCII value in the accumulator to upper or lower
  68. case.
  69.  
  70. UCASE:    CPI    'a'        LCASE:    CPI    'A'
  71.     RC                RC
  72.     CPI    'z'+1            CPI    'Z'+1
  73.     RNC                RNC
  74.     ANI    5FH            ORI    20H
  75.     RET                RET
  76.  
  77.      Note that before applying the ANI or ORI operation, we first
  78. check to make sure the character in A is in fact a letter!
  79.      For example, in UCASE, we simply return if the value is less
  80. than a or greater than z (not less than z+1). This is because
  81. ANDing other characters with 5FH could change them in undesirable
  82. ways. (It would convert - to ^M.)
  83.      The logical operations affect the flags, too: the Z flag
  84. will be set if the result of the operation is 0, otherwise
  85. cleared. The C flag will always be cleared. (You will often see
  86. something like ORA A used just to clear Carry, instead of STC,
  87. CMC.)
  88.  
  89.  
  90. 2. The Filter Program
  91.      The basic "filter" program reads a byte of text from an
  92. input file, processes it in some way, then writes it to an output
  93. file. It would look something like this:
  94.  
  95. ; *** FILTER.ASM
  96. ; *** General Filter Program
  97. ;
  98. BDOS    EQU    0005H    ; Basic equates
  99. FCB1    EQU    005CH
  100. FCB2    EQU    006CH
  101. ;
  102.     ORG    0100H    ; Programs start here
  103. ;
  104. START:    LXI    D,FCB1    ; Point to 1st FCB (source file)
  105.     CALL    GCOPEN    ; Open it for reading
  106.     JC    IOERR    ; Complain if error
  107.     LXI    D,FCB2    ; Point to 2nd FCB (destination)
  108.     CALL    PCOPEN    ; Open it for writing
  109.     JC    IOERR    ; Complain if error
  110. ;
  111. LOOP:    CALL    GETCH    ; Get a character
  112.     JC    IOERR    ; Complain if error
  113.     CPI    1AH    ; EOF?
  114.     JZ    DONE    ; Quit if at end of file
  115.     CALL    FILTER    ; Process it in some way
  116.     JMP    LOOP    ; Keep going
  117. ;
  118. DONE:    CALL    PCLOSE    ; Close the output file
  119.     JC    IOERR    ; Error?
  120.     RET        ; All finished
  121. ;
  122. IOERR:    RET        ; Error? just quit, for now
  123. ;
  124. ; Here is the processing routine
  125. ;
  126. FILTER: CALL    PUTCH    ; Just write it out, for now
  127.     RET
  128. ;
  129. ; *** Be sure to include here the following disk
  130. ; *** file subroutines from our previous column:
  131. ; *** GETCH, PUTCH, GCOPEN, PCOPEN, PCLOSE
  132. ;
  133.     END
  134.  
  135.      If you assemble this as written here, you will have a
  136. program called FILTER.COM, that will simply make a copy of a disk
  137. file i.e., if you say
  138.  
  139. A>FILTER OLDFILE NEWFILE<ret>
  140.  
  141. and FILTER will read OLDFILE and construct an identical copy
  142. NEWFILE.
  143.      If you want, you can spruce it up a bit, by adding a signon
  144. message like FILTER 1.0 (8/19/86) at the START, or an error
  145. message like I/O ERROR at the IOERR routine. (Use BDOS function 9
  146. or the SPMSG routine, described in earlier columns.)
  147.      Of course, what we really want is to do something to the
  148. text enroute. As you can see, you can put any further code you
  149. want at the location FILTER, which now just writes the character
  150. out as is. For example, you can add the UCASE routine above, and
  151. you will have a program that makes an uppercase copy of a file.
  152.  
  153.  
  154. 3. The WordStar To ASCII Filter
  155.      To get the FILTER program to convert a WordStar DOC to a
  156. plain ASCII file, you have to know what's in a DOC file.
  157.      We've already said that a lot of characters have their high
  158. bits set (such as "soft" spaces and returns), so the first thing
  159. we want to do to them is ANI 7FH to strip that off.
  160.      But there's more than that!
  161.      For example, there's hyphens. WordStar has "soft hyphens",
  162. which are represented by 1Eh (when not in use) or 1Fh (when in
  163. use).
  164.      Thus you want to ignore 1Eh, and translate 1Fh to a real
  165. hyphen. Adding this also to our FILTER routine would produce:
  166.  
  167. FILTER: ANI    7FH    ; Strip parity bit
  168.     CPI    1EH    ; Is it dead soft hyphen?
  169.     RZ        ; If so, quit (ignore it)
  170.     CPI    1FH    ; Is it live soft hyphen?
  171.     JNZ    FLT1    ; If not, skip following
  172.     MVI    A,'-'    ; If so, replace with '-'
  173. FLT1:    CALL    PUTCH    ; Okay, now write it out
  174.     RNC        ; Return if all clear
  175.     POP    H    ; ERROR, kill return to loop
  176.     JMP    IOERR    ; And go here instead
  177.  
  178.      If you use this code above, you will have a FILTER program
  179. that does a pretty credible job of converting WordStar DOC to
  180. ASCII files.
  181.      FILTER.COM will take up only 1k on disk, and will be quite
  182. fast, and much easier to use than the equivalent program in, say,
  183. MBASIC.
  184.      Of course, you will eventually want to add more processing,
  185. to suit your taste. For example you may decide you want to ignore
  186. all the funny control codes like ^S that WordStar uses for
  187. printer functions, or instead, translate them to the actual
  188. control codes your printer will need to perform those functions.
  189.      It's your program; you are in control.
  190.  
  191.  
  192. 4. Buffering Characters
  193.      Now let's consider how you might write another filter
  194. program to go the other way.
  195.      How often have you encountered files you'd like to edit (and
  196. reformat) with WordStar, but they're full of hard returns, so you
  197. can't?
  198.      This is a slightly harder problem.
  199.      You don't just want to turn all hard returns into soft ones,
  200. because there are places where you want them left hard (like the
  201. end of a paragraph).
  202.      How can we tell when this is the case?
  203.      No routine will do this perfectly. However, if you can
  204. assume that paragraphs are always indented (always good
  205. practice), you can use the following pretty good rule:
  206.  
  207.      A return is the end of a paragraph, and should be left hard,
  208. if:
  209.  
  210.      (1) the next line is blank;
  211.      (2) the next line begins with a space.
  212.  
  213.      In terms of character values, this means that the next
  214. character, after this CR and LF, is (1) another CR, or (2) a
  215. space.
  216.      Notice that what we do with the current character (in this
  217. case a soft CR) depends on the value of the character after next!
  218. How can we cope with this?
  219.      We must be able to look ahead and see what's coming, without
  220. affecting our position in the file: to read characters from the
  221. source file, but then save them to read again later.
  222.      This can be done by storing them in a special little buffer,
  223. and modifying our GETCH routine to see if there are any
  224. characters in this buffer before going to look in the file again.
  225.      Here's the new UNGETC routine, which will "unget" a
  226. character:
  227.  
  228. ; Routine to UNGET a character, saving it for GETCH
  229. ;
  230. UNGETC: PUSH    H    ; Save registers here
  231.     PUSH    D    ;   (if you don't do this, UNGETC
  232.     PUSH    B    ;   will be a hassle to use)
  233.     PUSH    PSW    ; Save the character last
  234.     LDA    BUFCNT    ; Fetch buffer count
  235.     CPI    5    ; Already maximal?
  236.     JNC    UNG0    ; Yes, leave it
  237.     INR    A    ; No, increase it
  238.     STA    BUFCNT    ; And put it back
  239. ;
  240. UNG0:    LXI    H,UGBUF+3 ; Point from next-last
  241.     LXI    D,UGBUF+4 ; To last position
  242.     MVI    B,4      ; Prepare to move 4 bytes
  243. ;
  244. UNGLP:    MOV    A,M    ; Get a byte
  245.     STAX    D    ; Move it up ahead
  246.     DCX    H    ; Back up
  247.     DCX    D    ; To previous
  248.     DCR    B    ; Count down on B
  249.     JNZ    UNGLP    ; Loop if more to go
  250.     POP    PSW    ; Recover new character
  251.     STA    UGBUF    ; Put it at front of buffer
  252.     POP    B    ; Restore the registers
  253.     POP    D
  254.     POP    H
  255.     RET
  256. ;
  257. BUFCNT: DB    0    ; Count chars in UGBUF
  258. UGBUF:    DS    5    ; Room for 5 characters
  259.  
  260.      UNGETC maintains a list of characters read, and put back for
  261. future use, at UGBUF. The most recently read one is first, the
  262. oldest last -- BUFCNT holds the count.
  263.      To unget a character, we increment the count, move the
  264. existing ones ahead to make room, and then put in the new one.
  265. (Don't try to unget more than the maximum of 5 characters, or the
  266. earlier ones will disappear into the bit bucket.
  267.      Of course, you could make this value larger if you want.)
  268.      Now what does GETCH have to do?
  269.  
  270. ; Modified GETCH routine for use with UNGETC
  271. ;
  272. GETCH:    LDA    BUFCNT    ; Check UNGETC buffer
  273.     ORA    A    ; Is it empty?
  274.     JZ    FGETCH    ; If so go read file
  275.     DCR    A    ; Decrease count
  276.     STA    BUFCNT    ; And put it back
  277.     MOV    E,A    ; Put count (less 1) in E
  278.     MVI    D,0    ; Now D-E is 16-bit version
  279.     LXI    H,UGBUF ; Point to buffer
  280.     DAD    D    ; Now HL points to eldest char.
  281.     MOV    A,M    ; Get it
  282.     STC
  283.     CMC        ; Clear C flag
  284.     RET        ; And return
  285. ;
  286. FGETCH: ....        ; Put the old GETCH here
  287.  
  288.      If there are characters in the UGBUF buffer, we decrement
  289. the count, then fetch the oldest one and return with it; if the
  290. buffer is empty, we just go ahead and do the usual read from the
  291. file.
  292.  
  293.  
  294. 5. The ASCII to WordStar Filter
  295.      If you will add UNGETC, and make the above changes to GETCH,
  296. we can now get the FILTER program to "soften" CRs more or less
  297. properly. The processing routine will look like this:
  298.  
  299. FILTER: CPI    0DH    ; Is it a CR?
  300.     JNZ    FLT1    ; No, just go on
  301.     CALL    GETCH    ; Get the next char (LF?)
  302.     JC    FLTERR    ; Error?
  303.     MOV    D,A    ; And save it
  304.     CALL    GETCH    ; Once more we want this one
  305.     JC    FLTERR    ; Error?
  306.     MOV    E,A    ; Save it too
  307.     MOV    A,D    ; Recover the first
  308.     CALL    UNGETC    ; Unget it
  309.     MOV    A,E    ; Now the second
  310.     CALL    UNGETC    ; Unget it too
  311.     MOV    A,E    ; Okay, here it is
  312.     CPI    0DH    ; Here goes: is it a CR?
  313.     JZ    FLTH    ; Yes, make current CR HARD
  314.     CPI    ' '    ; Or a space?
  315.     JZ    FLTH    ; Yes, HARD again
  316. ;
  317. FLTS:    MVI    A,8DH    ; No, use a SOFT CR here
  318.     JMP    FLT1
  319. ;
  320. FLTH:    MVI    A,0DH    ; Use a HARD CR
  321. ;
  322. FLT1:    CALL    PUTCH    ; Write the char out
  323.     RNC        ; Return if all clear
  324. ;
  325. FLTERR: POP  H        ; ERROR, kill return to LOOP
  326.     JMP  IOERR    ; And go here, instead
  327.  
  328.      If we've read a CR, and the character after next (the second
  329. LOOK ahead) is a space or CR, we write a hard CR; otherwise, it
  330. gets softened. Other characters go through unaffected.
  331.      This is the central task in creating DOC files from ASCII
  332. files. Of course you can do as much more as you want: e.g.,
  333. soften hyphens if they occur at the end of a line (before a CR).
  334. It's all up to you.
  335.  
  336.  
  337. 6. Other Applications
  338.      You probably will be able to think of other filtering tasks
  339. as well.
  340.      One possibility is communication with various mainframe
  341. computers, which have differing requirements for text formats.
  342. Another is encrypting and decrypting text, using anything from a
  343. simple substitution cipher on up.
  344.      And if you eliminate the output file routines, you can turn
  345. the FILTER program into a simple SEARCH program that just reads
  346. through a disk file: perhaps counting words, or looking for a
  347. particular string and printing out every line that contains it.
  348.      You will find that the resulting program is remarkably
  349. compact and fast.
  350.      If you want to make it even more efficient, you can try your
  351. hand at increasing the buffering of the GETCH and PUTCH routines.
  352.      As they stand they use a simple 128-byte DMA, which means
  353. your computer will have to alternately read data from the source,
  354. and write to the destination, in small pieces (the BDOS does its
  355. own buffering, in units of "blocks", usually from 1K to 4K in
  356. size).
  357.      You can speed all this up if you use buffers larger than
  358. this; 16K apiece would be a good choice. This would require
  359. increasing the GCDMA and PCDMA buffers from 128 bytes to 16*1024
  360. bytes, and modifying the read/write code in GETCH and PUTCH to do
  361. the whole 16K a record at a time, stepping the DMA address along
  362. in 128-byte increments. (An exercise for the stout-hearted
  363. reader.)
  364.  
  365.  
  366. 7. Coming Up
  367.      Next time we'll learn how to input and output numbers.
  368.