15.2 A text filter

image_pdfimage_print

Here we show an example in the domain of text processing. On the Macintosh, one may set an option that implies that two blanks in a text is replaced by a dot ('.') and a blank. The coroutine doubleSpaceToDot below scans through a text string and makes these replacements.

charFilter: obj 
   nextChar: var char
   input: var String 
   i: var integer 
   next -> c: var char:
      i := i + 1
      c := input.get[i]
   send(ch: var char):
      nextChar := ch
   doubleSpaceToDot: obj 
      ch: var char
      doubleSpaceToDot.suspend
      cycle
         ch := next
         if (ch = ' ') :then
            ch := next
            if (ch = ' ') :then
               send('.')
               doubleSpaceToDot.suspend
               send(' ')
               doubleSpaceToDot.suspend
            :else
               send(' ')
               doubleSpaceToDot.Suspend
               send(ch)
               doubleSpaceToDot.Suspend
         :else
            send(ch)
            doubleSpaceToDot.Suspend
   input := "Hello world  What a nice day!\n"
   loop: do
      cycle 
         doubleSpaceToDot.call
         put(nextChar)
         if (nextChar = ascii.newline) :then
            leave(loop)

The central element of this program is the coroutine object doubleSpaceToDot. When the object is generated, it starts by executing a suspend.

The object charFilter has a local String input, which holds the text string to be filtered. For the purpose of the example, we assign it the String "Hello world What a nice day!\n".

The charFilter then starts executing a cycle and the first statement in the cycle is a call of doubleSpaceToDot. This implies that doubleSpaceToDot is resumed and executes its cycle-statement.

It assigns the next char in input to ch by executing ch := next.

If ch = ' ', it reads the next char and checks if it is also a blank. If blank it sends a dot ('.') and suspends executions, and when resumed it sends a blank. If the next char is not a blank, it sends a blank one at a time and suspends in between.

The else-part of the outermost if-statement, is executed If ch is not a blank and here it just sends ch and suspends execution.

A simple version using a method

A coroutine object may invoke methods and a method may suspend the coroutine object. In the next example we use this to make a simpler version of doubleSpaceToDot. Here the send-method is made a local method of doubleSpaceToDot and send executes the suspend, suspending the enclosing doubleSpaceToDot coroutine:

   doubleSpaceToDot: obj 
     ch: var char
     send(ch: var char):
        c := ch
        doubleSpaceToDot.suspend	
     doubleSpaceToDot.suspend
     cycle
        ch := next
        if (ch = ' ') :then
           ch := next
           if (ch = ' ') :then
              send('.')
              send(' ')
           :else
              send(' ')
              send(ch)
        :else
           send(ch)

When send executes a suspend, we have a situation where doubleSpaceToDot has invoked send after having read the 'o' in "Hello" and thus i = 5. In the figure below, the left-side (A) shows the situation before suspend and the middle part (B) shows the situation after suspend.

The figure shows the activation stack. (A) shows that charFilter has called doubleSpaceToDot, which has called send. Send is currently the active object executing statements. Note that the elements of the activation stack may be objects as well as method objects. Here the bottom element is an object and the other elements are method objects.

When send executes suspend, the situation is shown at (B). CharFilter.suspend has the effect that control is returned to after charFilter has called doubleSpaceToDot. The arrow from doubleSpaceToDot illustrates that it is suspended and that send is at the top of its activation stack.

When charFilter makes another call to doubleSpaceToDot, the situation is as in the (C)-part, but with ch = ' ' and i = 6. As can be seen, doubleSpaceToDot now again points to the calling charFilter-object.