Skip to content

Instantly share code, notes, and snippets.

@jleedev
Created August 23, 2022 22:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jleedev/1532e30455d1739095a86e2c99a4a625 to your computer and use it in GitHub Desktop.
Save jleedev/1532e30455d1739095a86e2c99a4a625 to your computer and use it in GitHub Desktop.
import collections
import itertools
# Before you can call send, you have to call next to drive it to the first
# yield point. What if it never yields? Then the first call to next will raise
# StopIteration on it.
def parse_message():
print('parse_message')
ret = []
while True:
val = (yield)
if val == '\0':
print('/parse_message')
return ret
else:
print(f'{val} -> {ord(val)}')
ret.append(ord(val))
def drive(parser):
gen = parser()
print()
print(gen)
next(gen)
gen.send('A')
gen.send('B')
gen.send('C')
try:
gen.send('\0')
except StopIteration as e:
print(e.value)
# [65, 66, 67]
drive(parse_message)
def my_parser():
return (yield from parse_message())
# [65, 66, 67]
drive(my_parser)
def complex_parser():
start = (yield)
if start == 'A':
# delegate to parse_message
return (yield from parse_message())
# [66, 67]
drive(complex_parser)
# Whoops, need to thread the value into the subgenerator
def attempt_1():
start = (yield)
if start == 'A':
# delegate to parse_message
gen = parse_message()
next(gen)
gen.send(start)
return (yield from gen)
try:
drive(attempt_1)
except TypeError:
# TypeError: ord() expected string of length 1, but NoneType found
pass
# That's because (yield from gen) is calling next(gen) to start the generator.
# We need to consume that somehow.
# To implement yield from, you first call next, yield that value, then
# alternately send() the value of yield, and yield the value of send().
# To avoid calling next, you need to have on hand the first value to yield.
def unyield(gen, x):
just_started = gen.gi_frame.f_lasti == -1
try:
if just_started:
next(gen)
while True:
x = yield gen.send(x)
except StopIteration as e:
return e.value
def attempt_2():
start = (yield)
if start == 'A':
# delegate to parse_message
return (yield from unyield(parse_message(), start))
drive(attempt_2)
def attempt_3():
start = (yield)
if start == 'A':
# delegate to parse_message
gen = parse_message()
next(gen)
return (yield from unyield(gen, start))
drive(attempt_3)
# Note that both of these work.
# - For convenience, we can decide whether we want to call next(gen) at the
# start
# - Traditional yield from starts with a yield
# - Ours starts with a send
# Otherwise this is the same
#
# This can probably be generalized to unyield multiple times, perhaps with a
# deque. Basically, you want to
# - Pretend to the inner generator that its (yield) is producing values from
# the outside
# - Be able to use use this idiomatically in a yield from expression
#
# Our first call to yield doesn't yield a value. Would probably complicate
# things to thread that through as well.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment