Sam Sartor
Git(Hub|Lab)
# MPC-49: Yield Closures 2020-6-26
Hey! This page is a draft. Don't expect much from it.

This was originally intended to follow-up my post about generalized coroutines with a proposal for "Yield Closures". However, almost everything in it is better presented in the MCP-49 issue. I'm just going to leave a couple of examples here as a kind of appendix to that issue.

Here is a relatively simple lisp repl:

// Will run until all whitespace chars are consumed.
fn whitespace(c: char) -> Poll<()> {
    match c.is_whitespace() {
        true => Pending,
        false => Ready(()),
    }
}

// Will run until all chars in an expression are consumed.
// Returns the value of the expression.
fn expression() -> impl FnMut(char) -> Poll<Result<u32, Invalid>> {
    |c| {
        poll!(whitespace, c);
        if c == '(' {
            yield Pending; // Consume (
            poll!(whitespace, c); // Consume whitespace

            let (mut value, op): (u32, fn(u32, u32) -> u32) = match c {
                '+' => (0, |a, b| a + b),
                '*' => (1, |a, b| a * b),
                '|' => (0, |a, b| a | b),
                '&' => (!0, |a, b| a & b),
                _ => return Ready(Err(Invalid::Operator)),
            };
            yield Pending; // Consume operator

            if !c.is_whitespace() && c != ')' {
                return Ready(Err(Invalid::Spacing));
            }
            poll!(whitespace, c); // Consume whitespace

            let mut args = expression();
            while c != ')' {
                value = op(value, poll!(args, c)?); // Consume expression
                poll!(whitespace, c); // Consume whitespace
            }

            yield Pending; // Consume )
            Ready(Ok(value))
        } else if c.is_digit() {
            let mut value = 0;
            while c.is_digit() {
                value *= 10;
                value += char::from_digit(c).ok_or(Invalid::Number)?;
                yield Pending; // Consume digit
            }
            Ready(Ok(value))
        } else {
            Ready(Err(Invalid::Expression))
        }
    }
}

// REPL
fn main() -> Result<(), Error> {
    let mut eval = expression();
    for line in stdin().lock()?.lines() {
        for c in line?.chars() {
            if let Ready(x) = eval(c) {
                println!("= {}", x?);
            }
        }
    }
    Ok(())
}

Eventually we could add a .await(...) syntax which polls FnPin::call_pin with the given expressions as arguments. It would be internally very similar to .await, which polls Future::poll with the implicit async context. The key difference being, .await(..) can be used anywhere yield is permitted. This is useful for interacting with arbitrary sorts of poll functions & closures:

move mut |ctx: &mut Context| {
    let mut buffer = [0u8; 4096];
    pin_mut!(read);

    loop {
        let n = AsyncRead::poll_read.await(read.as_mut(), ctx, &mut buffer)?;

        if n == 0 {
            return Ready(None);
        }

        for &byte in buffer.iter().take(n) {
            yield Ready(Some(Ok(byte)));
        }
    }
}