home *** CD-ROM | disk | FTP | other *** search
/ World of Shareware - Software Farm 2 / wosw_2.zip / wosw_2 / CPROG / DDJ1289.ZIP / FRANZ.LST < prev    next >
File List  |  1989-10-30  |  13KB  |  516 lines

  1. WRITING FILTERS IN AN OBJECT-ORIENTED LANGUAGE_
  2. by Marty Franz
  3.  
  4.  
  5. [LISTING ONE]
  6.  
  7. /* header file for RegularExpression class */
  8.  
  9. #define ANY_CHAR  1  /* ^A */
  10. #define A_CHAR 3 /* ^C */
  11. #define BEGIN_STR 2 /* ^B */
  12. #define END_STR 5 /* ^E */
  13. #define INCLUDE_SET 19  /* ^S */
  14. #define OMIT_SET 14 /* ^N */
  15.  
  16.  
  17.  
  18. [LISTING TWO]
  19.  
  20. /* *************************************************
  21.  *   REGULARE.CLS: RegularExpression class file    *
  22.  ************************************************* */
  23.  
  24. /* Class used to hold regular expressions for string
  25. matching. */!!
  26.  
  27. inherit(Object, #RegularExpression, #(
  28. pattern /* a pattern to match */
  29. caseMatch /* nil, case doesn't matter */
  30. arrow /* used to scan strings */
  31. cursor /* used to scan patterns */), 2, nil)!!
  32.  
  33. now(RegularExpressionClass)!!
  34.  
  35. /* Create a new RegularExpression from String s. */
  36. Def  new(self, s | re)
  37. { re := init(new(self:Behavior));
  38.   re.pattern := makePattern(re, s);
  39.   ^re;
  40. }!!
  41.  
  42. now(RegularExpression)!!
  43.  
  44. /* Change every occurrence of string s matching the pattern to string t.
  45.   Does not allow recursion: searching occurs after the new string has been 
  46.   substituted. The target string t is not a pattern, just another string, 
  47.   inserted into the source string at the point of the match.  Returns the 
  48.   changed source string s */
  49. Def  change(self, s, t | from, last, source)
  50. { source :=
  51.   if caseMatch
  52.   then asUpperCase(s);
  53.   else s
  54.   endif;
  55.   from := 0;
  56.   loop
  57.   while from < size(source)
  58.   begin last := aMatch(self, source, from);
  59.     if last
  60.     then s := replace(s, t, 0, size(t), from, last);
  61.       from := from + size(t);
  62.     else from := from + 1;
  63.     endif;
  64.   endLoop;
  65.   ^s;
  66. }!!
  67.  
  68. /* Set case matching.  Converts pattern to upper-case, too.  You should 
  69.   save the old pattern if you plan on toggling case matching a lot.*/
  70. Def  setCaseMatch(self, c)
  71. { caseMatch := c;
  72.   if caseMatch
  73.   then pattern := asUpperCase(pattern);
  74.   endif;
  75.   ^self;
  76. }!!
  77.  
  78. /* Match the String s against the pattern.  Calls aMatch() for each 
  79.   possible substring in the String. If caseMatch flag is set, then fold case
  80.   to upper before doing the search. */
  81. Def  match(self, s)
  82. { if caseMatch
  83.   then s := asUpperCase(s);
  84.   endif;
  85.   do(size(s),
  86.   {using(i)
  87.     if aMatch(self, s, i)
  88.     then ^i;
  89.     endif;
  90.   });
  91.   ^false;
  92. }!!
  93.  
  94. /* Compare string s against pattern starting at position from. Makes 
  95.   successive calls to oneMatch.  Returns index of matching character, or 
  96.   nil. Remember that oneMatch() advances the arrow in the source string. */
  97. Def  aMatch(self, s, from | found)
  98. { arrow := from;
  99.   cursor := 0;
  100.   found := true;
  101.   loop
  102.   while found cand (cursor < size(pattern))
  103.   begin
  104.     if oneMatch(self, s)
  105.     then cursor := cursor + patternSize(self, cursor);
  106.     else ^false;
  107.     endif;
  108.   endLoop;
  109.   ^arrow;
  110. }!!
  111.  
  112. /* Match a single character passed as an argument to the set pointed to by 
  113.   the pattern cursor.  Returns nil if not in the set, otherwise non-nil. */
  114. Def  locate(self, c | setSize, fromCurs, toCurs, found)
  115. { setSize := asInt(pattern[cursor+1]);
  116.   fromCurs := cursor + 2;
  117.   toCurs := fromCurs + setSize;
  118.   found := false;
  119.   do(over(fromCurs, toCurs),
  120.   {using(i)
  121.     if pattern[i] = c
  122.     then found := true;
  123.     endif;
  124.   });
  125.   ^found;
  126. }!!
  127.  
  128. /* Match a single character in the target string against a single 
  129.   character in the pattern.  Returns nil or non-nil.  Also advances arrow 
  130.   scanning target string */
  131. Def  oneMatch(self, s | next, c)
  132. { next := -1;
  133.   if arrow < size(s)
  134.   then c := asInt(pattern[cursor]);
  135.     select
  136.       case c = ANY_CHAR
  137.       is next := 1;
  138.       endCase;
  139.       case c = BEGIN_STR
  140.       is
  141.         if arrow := 0
  142.         then next := 0;
  143.         endif;
  144.       endCase;
  145.       case c = A_CHAR 
  146.       is
  147.         if s[arrow] = pattern[cursor+1]
  148.         then next := 1;
  149.         endif;
  150.       endCase;
  151.       case c = INCLUDE_SET
  152.       is
  153.         if locate(self, s[arrow])
  154.         then next := 1;
  155.         endif;
  156.       endCase
  157.       case c = OMIT_SET
  158.       is
  159.         if not(locate(self, s[arrow]))
  160.         then next := 1;
  161.         endif;
  162.       endCase
  163.     endSelect;
  164.   else /* at end of string, check for $ */
  165.     if asInt(pattern[cursor]) = END_STR 
  166.     then next := 0;
  167.     endif;
  168.   endif;
  169.   if next >= 0
  170.   then arrow := arrow + next;
  171.   endif;
  172.   ^(next >= 0);
  173. }!!
  174.  
  175. /* Get the pattern String from a Regular Expression. */
  176. Def  pattern(self)
  177. { ^pattern;
  178. }!!
  179.  
  180. /* Set the pattern String in a RegularExpression. */
  181. Def  setPattern(self, s)
  182. { pattern := s;
  183.   ^self;
  184. }!!
  185.  
  186. /* Initialize a RegularExpression. */
  187. Def  init(self)
  188. { ^self;
  189. }!!
  190.  
  191. /* Return the size of the Pattern from position p. */
  192. Def  patternSize(self, p | c)
  193. { c := asInt(pattern[p]);
  194.   select
  195.     case c = A_CHAR 
  196.     is ^2;
  197.     endCase;
  198.     case c = ANY_CHAR cor c = BEGIN_STR cor c = END_STR
  199.     is ^1;
  200.     endCase;
  201.     case c = 14 cor c = 19
  202.     is ^asInt(pattern[p+1]+2);
  203.     endCase;
  204.     default ^1;
  205.   endSelect;
  206. }!!
  207.  
  208. /* Return a set of characters for inclusion in a Pattern. The first and 
  209.   last characters should be [ and ].  Returns INCLUDE_SET for set or OMIT_SET
  210.   for exluded set, followed by a count of the characters in the set (just a 
  211.   single character), followed by the characters themselves. */
  212. Def  fillSet(self, s | work, i, count, from, to)
  213. { i := 1;
  214.   count := 0;
  215.   if s[i] = '~'
  216.   then work := asString(asChar(OMIT_SET));
  217.     i := i + 1;
  218.   else work := asString(asChar(INCLUDE_SET));
  219.   endif;
  220.   work := work+asString(asChar(1)); /* holds count later */
  221.   loop
  222.   while i < size(s) and s[i] <> ']'
  223.   begin
  224.     select
  225.       case s[i] = '\'
  226.       is work := work + asString(s[i+1]);
  227.         i := i + 2;
  228.         count := count + 1;
  229.       endCase;
  230.       case s[i] = '-'
  231.       is from := asInt(s[i-1])+1;
  232.         to := asInt(s[i+1])+1;
  233.         do(over(from, to),
  234.         {using(c) work:=work+asString(asChar(c));
  235.         });
  236.         i := i + 2;
  237.       endCase;
  238.       default work := work + asString(s[i]);
  239.       i := i + 1;
  240.       count := count + 1;
  241.     endSelect;
  242.   endLoop;
  243.   work[1] := asChar(count);
  244.   ^work;
  245. }!!
  246.  
  247. /* Convert a normal String into a pattern String.  Needs error checking, 
  248.   and to use more OOP technique. Note that it does not set the Regular- 
  249.   Expression's instance variable. */
  250. Def  makePattern(self, s | work, c, i, j)
  251. { work := "";
  252.   i := 0;
  253.   loop
  254.   while i < size(s)
  255.   begin c := s[i];
  256.     select
  257.       case c = '?'
  258.       is work := work+asString(asChar(ANY_CHAR));
  259.         i := i + 1;
  260.       endCase;
  261.       case c = '%'
  262.       is work := work+asString(asChar(BEGIN_STR));
  263.         i := i + 1;
  264.       endCase;
  265.       case c = '$'
  266.       is work := work+asString(asChar(END_STR));
  267.         i := i + 1;
  268.       endCase;
  269.       case c = '['
  270.       is j := indexOf(s, ']', i+1);
  271.         work := work+fillSet(self, subString(s, i, j+1));
  272.         i := j + 1;
  273.       endCase;
  274.       default work := work+asString(asChar(A_CHAR))+asString(c);
  275.       i := i + 1;
  276.     endSelect;
  277.   endLoop;
  278.   ^work;
  279. }!!
  280.  
  281.  
  282.  
  283.  
  284. [LISTING THREE]
  285.  
  286. /* *************************************************
  287.  *          FILTER.CLS: Filter class file          *
  288.  ************************************************* */
  289.  
  290. /* This is a class that makes possible filter programs, 
  291. like Listers, Greps, etc.  in and out are objects that
  292. respond to Stream or File protocols.  initBlock, processBlock, 
  293. and closeBlock are executed when the filter is started, 
  294. running, and done, respectively. */!!
  295.  
  296. inherit(Object, #Filter, #(inObj
  297. outObj
  298. initBlock
  299. processBlock
  300. closeBlock
  301. ), 2, nil)!!
  302.  
  303. now(FilterClass)!!
  304.  
  305. /* Create a new Filter. */
  306. Def  new(self, input, output, b1, b2, b3 | f)
  307. { f := init(new(self:Behavior), input, output, b1, b2, b3);
  308.   ^f;
  309. }!!
  310.  
  311. now(Filter)!!
  312.  
  313. /* Once a filter has been set up, run it.  Call initBlock to 
  314.    initialize anything other than opening inObj and outObj.  
  315.    Read a line from inObj and call processBlock.  When done, 
  316.    close both objects.  Note that outObj is optional.  Also 
  317.    not the outObj is the receiver of the blocks.  This is because
  318.    it's the object that's programmer-defined. */
  319. Def  run(self | str)
  320. { eval(initBlock, outObj, inObj);
  321.   open(inObj, 0);
  322.   checkError(inObj);
  323.   if outObj
  324.   then create(outObj);
  325.     checkError(outObj);
  326.   endif;
  327.   loop
  328.   while str := readLine(inObj)
  329.   begin eval(processBlock, outObj, str);
  330.   endLoop;
  331.   eval(closeBlock, outObj, inObj);
  332.   close(inObj);
  333.   if outObj
  334.   then close(outObj);
  335.   endif;
  336. }!!
  337.  
  338. /* Initialize a new Filter.  Fill-in all its instance 
  339.    variables. */
  340. Def  init(self, input, output, b1, b2, b3)
  341. { inObj := input;
  342.   outObj := output;
  343.   initBlock := b1;
  344.   processBlock := b2;
  345.   closeBlock := b3;
  346.   ^self;
  347. }!!
  348.  
  349.  
  350.  
  351.  
  352. [LISTING FOUR]
  353.  
  354. /* *************************************************
  355.  *           GREP.CLS: Grep class file             *
  356.  ************************************************* */
  357.  
  358. /* This is a class that holds a single method that will analyze 
  359.    a file for regular expressions.  This method is an example of a 
  360.    generic Filter, too. */!!
  361.  
  362. inherit(Object, #Grep, #(fileName
  363. pattern
  364. matches), 2, nil)!!
  365.  
  366. now(GrepClass)!!
  367.  
  368. /* Create and run Grep for a file and a pattern. */
  369. Def  run(self, file, expr | g)
  370. { g := init(new(self:Behavior), file, expr);
  371.   ^g;
  372. }!!
  373.  
  374. now(Grep)!!
  375.  
  376. /* The Filter will try to close us.  Make sure something 
  377.    safe happens. */
  378. Def  close(self)
  379. { ^self;
  380. }!!
  381.  
  382. /* We need a checkError message because the Filter will try to 
  383.    call one.  For a Grep this doesn't do anything. */
  384. Def  checkError(self)
  385. { ^self;
  386. }!!
  387.  
  388. /* A dummy method, needed because the Filter will try to 
  389.    create() a Grep object with a mode value.  Ours doesn't do 
  390.    anything.  If it were a real file, it would be created. */
  391. Def  create(self)
  392. { ^self;
  393. }!!
  394.  
  395. /* When done, print the number of matches that were found. */
  396. Def  finish(self, inFile)
  397. { printLine(asStringRadix(matches, 10)+" lines matched pattern.");
  398.   ^self;
  399. }!!
  400.  
  401. /* Process a single line.  Compare it against the pattern.  
  402.    If it matches, print it. */
  403. Def  process(self, s)
  404. { if match(pattern, s)
  405.   then printLine(s);
  406.     matches := matches + 1;
  407.   endif;
  408.   ^self;
  409. }!!
  410.  
  411. /* Start grep.  Assign the filename to the TextFile */
  412. Def  start(self, inFile)
  413. { setName(inFile, fileName);
  414.   printLine("");
  415.   printLine("Searching file: "+fileName);
  416.   ^self;
  417. }!!
  418.  
  419. /* Open the input text file and search it for the expression.  
  420.    If found, print it. */
  421. Def  init(self, file, expr | f, start, process, finish, 
  422.           input, output)
  423. { input := new(TextFile);
  424.   setDelimiter(input, CR_LF);
  425.   output := self;
  426.   pattern := new(RegularExpression, expr);
  427.   fileName := file;
  428.   matches := 0;
  429.   start :=
  430.   {using(me, inFile) start(me, inFile);
  431.   };
  432.   process :=
  433.   {using(me, theLine) process(me, theLine);
  434.   };
  435.   finish :=
  436.   {using(me, inFile) finish(me, inFile);
  437.   };
  438.   f := new(Filter, input, output, start, process, finish);
  439.   run(f);
  440.   ^self;
  441. }!!
  442.  
  443.  
  444. [LISTING FIVE]
  445.  
  446. /* *************************************************
  447.  *         REVISER.CLS: Reviser class file         *
  448.  ************************************************* */
  449.  
  450. /* A Reviser is like a Grep, but it takes an additional 
  451. instance variable: the new text to be revised when the 
  452. pattern is found.  Another filename is also required, 
  453. to hold the revised text.  Finally, there is a handle 
  454. for the new file. */!!
  455.  
  456. inherit(Grep, #Reviser, #(newText
  457. newFileName
  458. newFile), 2, nil)!!
  459.  
  460. now(ReviserClass)!!
  461.  
  462. /* Create and run a Reviser. */
  463. Def  run(self, file, outFile, expr, text | r)
  464. { r := init(new(self:Behavior), file, outFile, expr, text);
  465.   ^r;
  466. }!!
  467.  
  468. now(Reviser)!!
  469.  
  470. /* Initialize what's different about the Reviser from 
  471.    the Grep. */
  472. Def  init(self, file, outFile, expr, text)
  473. { newText := text;
  474.   newFileName := outFile;
  475.   ^init(self:Grep, file, expr);
  476. }!!
  477.  
  478. /* Process a single line.  Compare it against the pattern.  If it matches, 
  479.   change it.  For simplicity, this does not handle multiple substitutions in 
  480.   the same line. */
  481. Def  process(self, s)
  482. { if match(pattern, s)
  483.   then write(newFile, change(pattern, s, newText) + CR_LF);
  484.     matches := matches + 1;
  485.   endif;
  486.   ^self;
  487. }!!
  488.  
  489. /* Set the name for the input file, then do whatever else 
  490.    a Grep does. */
  491. Def  start(self, inFile)
  492. { newFile := new(TextFile);
  493.   setName(newFile, newFileName);
  494.   setDelimiter(newFile, CR_LF);
  495.   ^start(self:Grep, inFile);
  496. }!!
  497.  
  498. /* Do a checkError on the newFile after creation. */
  499. Def  checkError(self)
  500. { ^checkError(newFile);
  501. }!!
  502.  
  503. /* Create the new file. */
  504. Def  create(self)
  505. { create(newFile);
  506.   ^self;
  507. }!!
  508.  
  509. /* Close the new file */
  510. Def  close(self)
  511. { close(newFile);
  512.   ^self;
  513. }!!
  514.  
  515.  
  516.