home *** CD-ROM | disk | FTP | other *** search
/ Oakland CPM Archive / oakcpm.iso / cpm / asmutl / meyertut.ark / MEYER07.TXT < prev    next >
Encoding:
Text File  |  1987-12-04  |  14.9 KB  |  365 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
  105.                      ; (source file)
  106.         CALL GCOPEN  ;open it for reading
  107.         JC   IOERR   ;complain if error
  108.         LXI  D,FCB2  ;point to 2nd FCB
  109.                      ; (destination)
  110.         CALL PCOPEN  ;open it for writing
  111.         JC   IOERR   ;complain if error
  112. ;
  113. LOOP:   CALL GETCH   ;get a character
  114.         JC   IOERR   ;complain if error
  115.         CPI  1AH     ;EOF?
  116.         JZ   DONE    ;quit if at end of file
  117.         CALL FILTER  ;process it in some way
  118.         JMP  LOOP    ;keep going
  119. ;
  120. DONE:   CALL PCLOSE  ;close the output file
  121.         JC   IOERR   ;error?
  122.         RET          ;all finished
  123. ;
  124. IOERR:  RET          ;error? just quit, for now
  125. ;
  126. ;Here is the processing routine
  127. FILTER: CALL PUTCH   ;just write it out, for now
  128.         RET
  129. ;
  130. ;*** Be sure to include here the following disk
  131. ;*** file subroutines from our previous column:
  132. ;*** GETCH, PUTCH, GCOPEN, PCOPEN, PCLOSE
  133. ;
  134.         END
  135.  
  136.      If you assemble this as written here, you will have a
  137. program called FILTER.COM, that will simply make a copy of a disk
  138. file; i.e., if you say
  139.  
  140. A>filter oldfile newfile<cr>
  141.  
  142. and FILTER will read OLDFILE and construct an identical copy
  143. NEWFILE.
  144.      If you want, you can spruce it up a bit, by adding a signon
  145. message like FILTER 1.0 (8/19/86) at the START, or an error
  146. message like I/O ERROR at the IOERR routine. (Use BDOS function 9
  147. or the SPMSG routine, described in earlier columns.)
  148.      Of course, what we really want is to do something to the
  149. text enroute. As you can see, you can put any further code you
  150. want at the location FILTER, which now just writes the character
  151. out as is. For example, you can add the UCASE routine above, and
  152. you will have a program that makes an uppercase copy of a file.
  153.  
  154.  
  155. 3. The WordStar To ASCII Filter
  156.      To get the FILTER program to convert a WordStar DOC to a
  157. plain ASCII file, you have to know what's in a DOC file.
  158.      We've already said that a lot of characters have their high
  159. bits set (such as "soft" spaces and returns), so the first thing
  160. we want to do to them is ANI 7FH to strip that off.
  161.      But there's more than that!
  162.      For example, there's hyphens. WordStar has "soft hyphens",
  163. which are represented by 1Eh (when not in use) or 1Fh (when in
  164. use).
  165.      Thus you want to ignore 1Eh, and translate 1Fh to a real
  166. hyphen. Adding this also to our FILTER routine would produce:
  167.  
  168. FILTER:   ANI  7FH     ;strip parity bit
  169.           CPI  1EH     ;is it dead soft hyphen?
  170.           RZ           ;if so, quit (ignore it)
  171.           CPI  1FH     ;is it live soft hyphen?
  172.           JNZ  FLT1    ;if not, skip following
  173.           MVI  A,'-'   ;if so, replace with '-'
  174. FLT1:     CALL PUTCH   ;okay, now write it out
  175.           RNC          ;return if all clear
  176.           POP  H       ;ERROR, kill return to
  177.                        ; LOOP
  178.           JMP  IOERR   ;and go here instead
  179.  
  180.      If you use this code above, you will have a FILTER program
  181. that does a pretty credible job of converting WordStar DOC to
  182. ASCII files.
  183.      FILTER.COM will take up only 1k on disk, and will be quite
  184. fast, and much easier to use than the equivalent program in, say,
  185. MBASIC.
  186.      Of course, you will eventually want to add more processing,
  187. to suit your taste. For example you may decide you want to ignore
  188. all the funny control codes like ^S that WordStar uses for
  189. printer functions, or instead, translate them to the actual
  190. control codes your printer will need to perform those functions.
  191.      It's your program; you are in control.
  192.  
  193.  
  194. 4. Buffering Characters
  195.      Now let's consider how you might write another filter
  196. program to go the other way.
  197.      How often have you encountered files you'd like to edit (and
  198. reformat) with WordStar, but they're full of hard returns, so you
  199. can't?
  200.      This is a slightly harder problem.
  201.      You don't just want to turn all hard returns into soft ones,
  202. because there are places where you want them left hard (like the
  203. end of a paragraph).
  204.      How can we tell when this is the case?
  205.      No routine will do this perfectly. However, if you can
  206. assume that paragraphs are always indented (always good
  207. practice), you can use the following pretty good rule:
  208.  
  209.      A return is the end of a paragraph, and should be left hard,
  210. if:
  211.  
  212.      (1) the next line is blank;
  213.      (2) the next line begins with a space.
  214.  
  215.      In terms of character values, this means that the next
  216. character, after this CR and LF, is (1) another CR, or (2) a
  217. space.
  218.      Notice that what we do with the current character (in this
  219. case a soft CR) depends on the value of the character after next!
  220. How can we cope with this?
  221.      We must be able to look ahead and see what's coming, without
  222. affecting our position in the file: to read characters from the
  223. source file, but then save them to read again later.
  224.      This can be done by storing them in a special little buffer,
  225. and modifying our GETCH routine to see if there are any
  226. characters in this buffer before going to look in the file again.
  227.      Here's the new UNGETC routine, which will "unget" a
  228. character:
  229.  
  230. ;Routine to UNGET a character, saving
  231. ; it for GETCH
  232. UNGETC: PUSH H         ;save registers here
  233.         PUSH D         ;(if you don't do this,
  234.         PUSH B         ; UNGETCwill be a
  235.                        ; hassle to use)
  236.         PUSH PSW       ;save the character last
  237.         LDA  BUFCNT    ;fetch buffer count
  238.         CPI  5         ;already maximal?
  239.         JNC  UNG0      ;yes, leave it
  240.         INR  A         ;no, increase it
  241.         STA  BUFCNT    ;and put it back
  242. UNG0:   LXI  H,UGBUF+3 ;point from next-last
  243.         LXI  D,UGBUF+4 ;to last position
  244.         MVI  B,4       ;prepare to move 4 bytes
  245. UNGLP:  MOV  A,M       ;get a byte
  246.         STAX D         ;move it up ahead
  247.         DCX  H         ;back up
  248.         DCX  D         ;to previous
  249.         DCR  B         ;count down on B
  250.         JNZ  UNGLP     ;loop if more to go
  251.         POP  PSW       ;recover new character
  252.         STA  UGBUF     ;put it at front of
  253.                        ; buffer
  254.         POP  B         ;restore
  255.         POP  D         ;  the
  256.         POP  H         ;    registers
  257.         RET
  258. BUFCNT: DB   0         ;count chars in UGBUF
  259. UGBUF:  DS   5         ;room for 5 characters
  260.  
  261.      UNGETC maintains a list of characters read, and put back for
  262. future use, at UGBUF. The most recently read one is first, the
  263. oldest last -- BUFCNT holds the count.
  264.      To unget a character, we increment the count, move the
  265. existing ones ahead to make room, and then put in the new one.
  266. (Don't try to unget more than the maximum of 5 characters, or the
  267. earlier ones will disappear into the bit bucket.
  268.      Of course, you could make this value larger if you want.)
  269.      Now what does GETCH have to do?
  270.  
  271. ;Modified GETCH routine for use with UNGETC
  272. GETCH:  LDA  BUFCNT    ;check UNGETC buffer
  273.         CPI  0         ;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
  279.                        ; version
  280.         LXI  H,UGBUF   ;point to buffer
  281.         DAD  D         ;now HL points to eldest
  282.                        ; character
  283.         MOV  A,M       ;get it
  284.         STC
  285.         CMC            ;clear C flag
  286.         RET            ;and return
  287. FGETCH: ....           ;put the old GETCH here
  288.  
  289.      If there are characters in the UGBUF buffer, we decrement
  290. the count, then fetch the oldest one and return with it; if the
  291. buffer is empty, we just go ahead and do the usual read from the
  292. file.
  293.  
  294.  
  295. 5. The ASCII to WordStar Filter
  296.      If you will add UNGETC, and make the above changes to GETCH,
  297. we can now get the FILTER program to "soften" CRs more or less
  298. properly. The processing routine will look like this:
  299.  
  300. FILTER: CPI  0DH     ;is it a CR?
  301.         JNZ  FLT1    ;no, just go on
  302.         CALL GETCH   ;get the next char (LF?)
  303.         JC   FLTERR  ;error?
  304.         MOV  D,A     ;and save it
  305.         CALL GETCH   ;once more we want this one
  306.         JC   FLTERR  ;error?
  307.         MOV  E,A     ;save it too
  308.         MOV  A,D     ;recover the first
  309.         CALL UNGETC  ;unget it
  310.         MOV  A,E     ;now the second
  311.         CALL UNGETC  ;unget it too
  312.         MOV  A,E     ;okay, here it is
  313.         CPI  0DH     ;here goes: is it a CR?
  314.         JZ   FLTH    ;yes, make current CR HARD
  315.         CPI  ' '     ;or a space?
  316.         JZ   FLTH    ;yes, HARD again
  317. FLTS:   MVI  A,8DH   ;no, use a SOFT CR here
  318.         JMP  FLT1
  319. FLTH:   MVI  A,0DH   ;use a HARD CR
  320. FLT1:   CALL PUTCH   ;write the char out
  321.         RNC          ;return if all clear
  322. FLTERR: POP  H       ;ERROR, kill return to LOOP
  323.         JMP  IOERR   ;and go here, instead
  324.  
  325.      If we've read a CR, and the character after next (the second
  326. LOOK ahead) is a space or CR, we write a hard CR; otherwise, it
  327. gets softened. Other characters go through unaffected.
  328.      This is the central task in creating DOC files from ASCII
  329. files. Of course you can do as much more as you want: e.g.,
  330. soften hyphens if they occur at the end of a line (before a CR).
  331. It's all up to you.
  332.  
  333.  
  334. 6. Other Applications
  335.      You probably will be able to think of other filtering tasks
  336. as well.
  337.      One possibility is communication with various mainframe
  338. computers, which have differing requirements for text formats.
  339. Another is encrypting and decrypting text, using anything from a
  340. simple substitution cipher on up.
  341.      And if you eliminate the output file routines, you can turn
  342. the FILTER program into a simple SEARCH program that just reads
  343. through a disk file: perhaps counting words, or looking for a
  344. particular string and printing out every line that contains it.
  345.      You will find that the resulting program is remarkably
  346. compact and fast.
  347.      If you want to make it even more efficient, you can try your
  348. hand at increasing the buffering of the GETCH and PUTCH routines.
  349.      As they stand they use a simple 128-byte DMA, which means
  350. your computer will have to alternately read data from the source,
  351. and write to the destination, in small pieces (the BDOS does its
  352. own buffering, in units of "blocks", usually from 1K to 4K in
  353. size).
  354.      You can speed all this up if you use buffers larger than
  355. this; 16K apiece would be a good choice. This would require
  356. increasing the GCDMA and PCDMA buffers from 128 bytes to 16*1024
  357. bytes, and modifying the read/write code in GETCH and PUTCH to do
  358. the whole 16K a record at a time, stepping the DMA address along
  359. in 128-byte increments. (An exercise for the stout-hearted
  360. reader.)
  361.  
  362.  
  363. 7. Coming Up
  364.      Next time we'll learn how to input and output numbers.
  365.