Last active
August 29, 2015 14:25
-
-
Save eddyb/a5c2d92666cd3b39dac9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// desugaring for await/async, see: https://gist.github.com/eddyb/822c658190ccf18058db | |
pub mod gen { | |
use std::marker::PhantomData; | |
#[cfg_attr(std, lang = "yield")] | |
pub enum Yield<T, R> { | |
Value(T), | |
Return(R) | |
} | |
#[cfg_attr(std, lang = "generator_base")] | |
pub trait GeneratorBase { | |
type Value; | |
type Return; | |
} | |
fn type_name<T: ?Sized>() -> &'static str { | |
"<unknown>" | |
/*unsafe { | |
::std::intrinsics::type_name::<T>() | |
}*/ | |
} | |
#[cfg_attr(std, lang = "generator")] | |
pub trait Generator<Input>: GeneratorBase { | |
fn next(&mut self, _: Input) -> Yield<Self::Value, Self::Return> { | |
panic!("generator `{}` does not take `{}` as input", | |
type_name::<Self>(), type_name::<Input>()) | |
} | |
} | |
#[cfg(std)] | |
impl<G: Generator<(), Return=()>> Iterator for G { | |
type Item = G::Value; | |
fn next(&mut self) -> Option<G::Value> { | |
match self.next(()) { | |
Yield::Value(x) => Some(x), | |
Yield::Return(_) => None | |
} | |
} | |
} | |
pub trait Generate { | |
type G: GeneratorBase; | |
/// Run up to the first yield, return that and the generator. | |
fn generate(self) -> Yield<(<Self::G as GeneratorBase>::Value, Self::G), | |
<Self::G as GeneratorBase>::Return>; | |
} | |
pub struct YieldAnd<V, G>(pub V, pub G); | |
impl<V, G: GeneratorBase<Value = V>> Generate for YieldAnd<V, G> { | |
type G = G; | |
fn generate(self) -> Yield<(V, G), G::Return> { | |
Yield::Value((self.0, self.1)) | |
} | |
} | |
pub struct Mirror<V, R> { | |
_data: PhantomData<(V, R)> | |
} | |
impl<V, R> Mirror<V, R> { | |
pub fn new() -> Mirror<V, R> { | |
Mirror { | |
_data: PhantomData | |
} | |
} | |
} | |
impl<V, R> GeneratorBase for Mirror<V, R> { | |
type Value = V; | |
type Return = R; | |
} | |
impl<V, R> Generator<R> for Mirror<V, R> { | |
fn next(&mut self, value: R) -> Yield<V, R> { | |
Yield::Return(value) | |
} | |
} | |
pub type YieldAndMirror<V, R> = YieldAnd<V, Mirror<V, R>>; | |
} | |
pub mod io { | |
use gen::{Generate, GeneratorBase, Yield}; | |
use fs; | |
pub use std::io::{Error, Result}; | |
pub enum Action { | |
File(fs::File, fs::Action) | |
} | |
pub trait Async<R>: Generate | |
where Self::G: GeneratorBase<Value=Action, Return=Result<R>> | |
+ fs::Async { | |
fn run_sync(self) -> Result<R> where Self: Sized { | |
match self.generate() { | |
Yield::Value((mut action, mut gen)) => { | |
loop { | |
match Self::do_io(&mut gen, action) { | |
Yield::Value(next) => action = next, | |
Yield::Return(res) => return res | |
}; | |
} | |
} | |
Yield::Return(res) => res | |
} | |
} | |
fn do_io(gen: &mut Self::G, action: Action) -> Yield<Action, Result<R>> { | |
match action { | |
Action::File(file, action) => fs::Async::do_file(gen, file, action) | |
} | |
} | |
} | |
impl<T: Generate, R> Async<R> for T | |
where T::G: GeneratorBase<Value=Action, Return=Result<R>> | |
+ fs::Async {} | |
macro_rules! await { | |
($x:expr) => (try!(yield+ $x)) | |
} | |
} | |
pub mod fs { | |
use gen::{Generator, Mirror, Yield, YieldAnd, YieldAndMirror}; | |
use io; | |
use std::fs as sync; | |
use std::io::prelude::*; | |
use std::os::unix::prelude::*; | |
use std::path::Path; | |
pub struct File { sync: sync::File } | |
pub use std::fs::Metadata; | |
impl File { | |
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<File> { | |
sync::File::open(path).map(|file| File { sync: file }) | |
} | |
pub fn create<P: AsRef<Path>>(path: P) -> io::Result<File> { | |
sync::File::create(path).map(|file| File { sync: file }) | |
} | |
} | |
impl Clone for File { | |
fn clone(&self) -> File { | |
extern "C" { | |
fn dup(fd: i32) -> i32; | |
} | |
File { | |
sync: unsafe { | |
sync::File::from_raw_fd(dup(self.sync.as_raw_fd())) | |
} | |
} | |
} | |
} | |
pub enum Action { | |
Read(Vec<u8>), | |
Write(Vec<u8>), | |
GetMetadata | |
} | |
pub trait Async: Generator<io::Result<Vec<u8>>> | |
+ Generator<io::Result<sync::Metadata>> { | |
fn do_file(&mut self, mut file: File, action: Action) | |
-> Yield<Self::Value, Self::Return> { | |
match action { | |
Action::Read(mut buffer) => { | |
// Nasty zero-fill because read takes &mut [u8]. | |
let extra = buffer.capacity() - buffer.len(); | |
buffer.extend((0..extra).map(|_| 0)); | |
self.next(file.sync.read(&mut buffer).map(|bytes| { | |
buffer.truncate(bytes); | |
buffer | |
})) | |
} | |
Action::Write(buffer) => { | |
self.next(file.sync.write(&buffer).map(|_| buffer)) | |
} | |
Action::GetMetadata => { | |
self.next(file.sync.metadata()) | |
} | |
} | |
} | |
} | |
impl<G> Async for G | |
where G: Generator<io::Result<Vec<u8>>> | |
+ Generator<io::Result<Metadata>> {} | |
macro_rules! action { | |
($fun:ident => $act:ident -> $ret:ty) => { | |
pub fn $fun(&self) -> YieldAndMirror<io::Action, io::Result<$ret>> { | |
YieldAnd(io::Action::File(self.clone(), Action::$act), Mirror::new()) | |
} | |
}; | |
($fun:ident => $act:ident($($arg:ident: $ty:ty),+) -> $ret:ty) => { | |
pub fn $fun(&self, $($arg: $ty),+) -> YieldAndMirror<io::Action, io::Result<$ret>> { | |
YieldAnd(io::Action::File(self.clone(), Action::$act($($arg),+)), Mirror::new()) | |
} | |
} | |
} | |
impl File { | |
action!(read => Read(buffer: Vec<u8>) -> Vec<u8>); | |
action!(write => Write(buffer: Vec<u8>) -> Vec<u8>); | |
action!(metadata => GetMetadata -> Metadata); | |
} | |
} | |
use gen::{Generate, Generator, GeneratorBase, Yield, Mirror}; | |
use fs::File; | |
use io::Async; | |
use std::mem; | |
use std::path::Path; | |
struct Copy<'a, 'b> { src: &'a Path, dst: &'b Path } | |
fn copy<'a, 'b>(src: &'a Path, dst: &'b Path) -> Copy<'a, 'b> { | |
Copy { src: src, dst: dst } | |
} | |
impl<'a, 'b> Generate for Copy<'a, 'b> { | |
type G = S; | |
// NOTE due to how generators can return immediatelly, | |
// it's theoretically possible to have `copy` finish | |
// without yielding any io::Action (e.g. a sync mock-up). | |
// However, inlining the src.read(buffer).generate() call | |
// from S::advance should kill the rest of that loop here. | |
fn generate(self) -> Yield<(io::Action, S), io::Result<()>> { | |
let Copy { src, dst } = self; | |
let src = match File::open(src) { | |
Ok(file) => file, | |
Err(e) => return Yield::Return(Err(e)) | |
}; | |
let dst = match File::create(dst) { | |
Ok(file) => file, | |
Err(e) => return Yield::Return(Err(e)) | |
}; | |
// Copy a kiB at a time. | |
let buffer = Vec::with_capacity(1024); | |
let mut gen = S::Read { src: src, dst: dst, buffer: buffer }; | |
match gen.advance() { | |
Yield::Value(x) => Yield::Value((x, gen)), | |
Yield::Return(r) => Yield::Return(r) | |
} | |
} | |
} | |
enum S { | |
Read { src: File, dst: File, buffer: Vec<u8> }, | |
ReadY { src: File, dst: File, gen: Mirror<io::Action, io::Result<Vec<u8>>> }, | |
Write { src: File, dst: File, buffer: Vec<u8> }, | |
WriteY{ src: File, dst: File, gen: Mirror<io::Action, io::Result<Vec<u8>>> }, | |
SMeta { src: File, dst: File, buffer: Vec<u8> }, | |
SMetaY{ src: File, dst: File, buffer: Vec<u8>, gen: Mirror<io::Action, io::Result<fs::Metadata>> }, | |
DMeta { src: File, dst: File, buffer: Vec<u8>, s_meta: fs::Metadata }, | |
DMetaY{ src: File, dst: File, buffer: Vec<u8>, s_meta: fs::Metadata, gen: Mirror<io::Action, io::Result<fs::Metadata>> }, | |
Print { src: File, dst: File, buffer: Vec<u8>, s_meta: fs::Metadata, d_meta: fs::Metadata }, | |
Completed | |
} | |
impl GeneratorBase for S { | |
type Value = io::Action; | |
type Return = io::Result<()>; | |
} | |
impl S { | |
// Continue execution until the next yield or return. | |
fn advance(&mut self) -> Yield<io::Action, io::Result<()>> { | |
loop { | |
match mem::replace(self, S::Completed) { | |
S::Read { src, dst, buffer } => { | |
match src.read(buffer).generate() { | |
Yield::Value((action, gen)) => { | |
*self = S::ReadY { src: src, dst: dst, gen: gen }; | |
return Yield::Value(action); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::Write { src: src, dst: dst, buffer: res }; | |
} | |
} | |
} | |
S::Write { src, dst, buffer } => { | |
if buffer.is_empty() { return Yield::Return(Ok(())); } | |
match dst.write(buffer).generate() { | |
Yield::Value((action, gen)) => { | |
*self = S::WriteY { src: src, dst: dst, gen: gen }; | |
return Yield::Value(action); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::SMeta { src: src, dst: dst, buffer: res }; | |
} | |
} | |
} | |
S::SMeta { src, dst, mut buffer } => { | |
buffer.clear(); | |
match src.metadata().generate() { | |
Yield::Value((action, gen)) => { | |
*self = S::SMetaY { src: src, dst: dst, buffer: buffer, gen: gen }; | |
return Yield::Value(action); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::DMeta { src: src, dst: dst, buffer: buffer, s_meta: res }; | |
} | |
} | |
} | |
S::DMeta { src, dst, buffer, s_meta } => { | |
match dst.metadata().generate() { | |
Yield::Value((action, gen)) => { | |
*self = S::DMetaY { src: src, dst: dst, buffer: buffer, s_meta: s_meta, gen: gen }; | |
return Yield::Value(action); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::Print { src: src, dst: dst, buffer: buffer, s_meta: s_meta, d_meta: res }; | |
} | |
} | |
} | |
S::Print { src, dst, buffer, s_meta, d_meta } => { | |
println!("{}B copied out of {}B", d_meta.len(), s_meta.len()); | |
*self = S::Read { src: src, dst: dst, buffer: buffer }; | |
} | |
S::ReadY {..} | S::WriteY {..} | S::SMetaY {..} | S::DMetaY {..} => { | |
unreachable!("generator needs input at yield point") | |
} | |
S::Completed => unreachable!("generator has already completed") | |
} | |
} | |
} | |
} | |
impl Generator<io::Result<Vec<u8>>> for S { | |
fn next(&mut self, input: io::Result<Vec<u8>>) -> Yield<io::Action, io::Result<()>> { | |
match mem::replace(self, S::Completed) { | |
S::ReadY { src, dst, mut gen } => { | |
match gen.next(input) { | |
Yield::Value(next) => { | |
*self = S::ReadY { src: src, dst: dst, gen: gen }; | |
return Yield::Value(next); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::Write { src: src, dst: dst, buffer: res }; | |
} | |
} | |
} | |
S::WriteY { src, dst, mut gen } => { | |
match gen.next(input) { | |
Yield::Value(next) => { | |
*self = S::WriteY { src: src, dst: dst, gen: gen }; | |
return Yield::Value(next); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::SMeta { src: src, dst: dst, buffer: res }; | |
} | |
} | |
} | |
_ => unreachable!("generator state does not accept Vec<u8> as input") | |
} | |
self.advance() | |
} | |
} | |
impl Generator<io::Result<fs::Metadata>> for S { | |
fn next(&mut self, input: io::Result<fs::Metadata>) -> Yield<io::Action, io::Result<()>> { | |
match mem::replace(self, S::Completed) { | |
S::SMetaY { src, dst, buffer, mut gen } => { | |
match gen.next(input) { | |
Yield::Value(next) => { | |
*self = S::SMetaY { src: src, dst: dst, buffer: buffer, gen: gen }; | |
return Yield::Value(next); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::DMeta { src: src, dst: dst, buffer: buffer, s_meta: res }; | |
} | |
} | |
} | |
S::DMetaY { src, dst, buffer, s_meta, mut gen } => { | |
match gen.next(input) { | |
Yield::Value(next) => { | |
*self = S::DMetaY { src: src, dst: dst, buffer: buffer, s_meta: s_meta, gen: gen }; | |
return Yield::Value(next); | |
} | |
Yield::Return(Err(e)) => return Yield::Return(Err(e)), | |
Yield::Return(Ok(res)) => { | |
*self = S::Print { src: src, dst: dst, buffer: buffer, s_meta: s_meta, d_meta: res }; | |
} | |
} | |
} | |
_ => unreachable!("generator state does not accept fs::Metadata as input") | |
} | |
self.advance() | |
} | |
} | |
fn main() { | |
match copy(Path::new("/proc/self/exe"), Path::new("/tmp/foobar")).run_sync() { | |
Ok(()) => println!("Success!"), | |
Err(e) => println!("Oops: {}", e) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment