[Python-Dev] Simple generators, round 2

Neil Schemenauer nas@arctrix.com
Wed, 21 Mar 2001 21:44:32 -0800


[Tim on comparing fringes of two trees]:
> In Icon you need to create co-expressions to solve this
> problem, because its generators aren't explicitly resumable,
> and Icon has no way to spell "kick a pair of generators in
> lockstep".  But explicitly resumable generators are in fact
> "good enough" for this classic example, which is usually used
> to motivate coroutines.

Apparently they are good for lots of other things too.  Tonight I
implemented passing values using resume().  Next, I decided to
see if I had enough magic juice to tackle the coroutine example
from Gordon's stackless tutorial.  Its turns out that I didn't
need the extra functionality.  Generators are enough.

The code is not too long so I've attached it.  I figure that some
people might need a break from 2.1 release issues.  I think the
generator version is even simpler than the coroutine version.

  Neil

# Generator example:
# The program is a variation of a Simula 67 program due to Dahl & Hoare,
# who in turn credit the original example to Conway.
#
# We have a number of input lines, terminated by a 0 byte.  The problem
# is to squash them together into output lines containing 72 characters
# each.  A semicolon must be added between input lines.  Runs of blanks
# and tabs in input lines must be squashed into single blanks.
# Occurrences of "**" in input lines must be replaced by "^".
#
# Here's a test case:

test = """\
   d    =   sqrt(b**2  -  4*a*c)
twoa    =   2*a
   L    =   -b/twoa
   R    =   d/twoa
  A1    =   L + R
  A2    =   L - R\0
"""

# The program should print:
# d = sqrt(b^2 - 4*a*c);twoa = 2*a; L = -b/twoa; R = d/twoa; A1 = L + R;
#A2 = L - R
#done
# getlines: delivers the input lines
# disassemble: takes input line and delivers them one
#    character at a time, also inserting a semicolon into
#    the stream between lines
# squasher:  takes characters and passes them on, first replacing
#    "**" with "^" and squashing runs of whitespace
# assembler: takes characters and packs them into lines with 72
#    character each; when it sees a null byte, passes the last
#    line to putline and then kills all the coroutines

from Generator import Generator

def getlines(text):
    g = Generator()
    for line in text.split('\n'):
        g.suspend(line)
    g.end()

def disassemble(cards):
    g = Generator()
    try:
        for card in cards:
            for i in range(len(card)):
                if card[i] == '\0':
                    raise EOFError 
                g.suspend(card[i])
            g.suspend(';')
    except EOFError:
        pass
    while 1:
        g.suspend('') # infinite stream, handy for squash()

def squash(chars):
    g = Generator()
    while 1:
        c = chars.next()
        if not c:
            break
        if c == '*':
            c2 = chars.next()
            if c2 == '*':
                c = '^'
            else:
                g.suspend(c)
                c = c2
        if c in ' \t':
            while 1:
                c2 = chars.next()
                if c2 not in ' \t':
                    break
            g.suspend(' ')
            c = c2
        if c == '\0':
            g.end()
        g.suspend(c)
    g.end()

def assemble(chars):
    g = Generator()
    line = ''
    for c in chars:
        if c == '\0':
            g.end()
        if len(line) == 72:
            g.suspend(line)
            line = ''
        line = line + c
    line = line + ' '*(72 - len(line))
    g.suspend(line)
    g.end()


if __name__ == '__main__':
    for line in assemble(squash(disassemble(getlines(test)))):
        print line
    print 'done'