use std::future::Future;
use std::marker::PhantomPinned;
use std::pin::Pin;
use fauxgen::__private::SyncGenerator;
use fauxgen::Generator;
use fauxgen::GeneratorState;
use fauxgen::GeneratorToken;
use frunk_core::coproduct::{CoprodInjector, CoprodUninjector, Coproduct};
use crate::coproduct::{EmbedEffect, ProjectResume};
use crate::effect::{CanStart, Effect, Effects, MapResume, Resumes, Start};
use crate::locality::{Local, Locality, Sendable};
use crate::program::Effectful;
type Gen<'a, Effs, Return, L> = SyncGenerator<
<L as Locality>::PinBoxFuture<'a, Return>,
CanStart<Effs>,
Resumes<'a, CanStart<Effs>>,
>;
pub type Co<'a, Effs, Return> = GenericCo<'a, Effs, Return, Local>;
pub type CoSend<'a, Effs, Return> = GenericCo<'a, Effs, Return, Sendable>;
pub struct GenericCo<'a, Effs, Return, L: Locality = Local>
where
Effs: Effects<'a>,
{
generator: Gen<'a, Effs, Return, L>,
_pin: PhantomPinned,
}
macro_rules! make_co {
($f:expr, $cast:ty) => {{
let token = fauxgen::__private::token();
let marker = token.marker();
let fut = Box::pin(async move {
let token = fauxgen::__private::register_owned(token).await;
let _ = token.argument().await;
$f(Yielder::new(token)).await
}) as $cast;
let generator = fauxgen::__private::gen_sync(marker, fut);
Self {
generator,
_pin: PhantomPinned,
}
}};
}
impl<'a, Effs, Return> Co<'a, Effs, Return>
where
Effs: Effects<'a>,
{
pub fn new<F>(f: impl FnOnce(Yielder<'a, Effs>) -> F + 'a) -> Self
where
F: Future<Output = Return>,
{
make_co!(f, Pin<Box<dyn Future<Output = Return> + 'a>>)
}
}
impl<'a, Effs, Return> CoSend<'a, Effs, Return>
where
Effs: Effects<'a>,
for<'r> Resumes<'r, CanStart<Effs>>: Send + Sync,
{
pub fn new<F>(f: impl FnOnce(Yielder<'a, Effs>) -> F + Send + 'a) -> Self
where
F: Future<Output = Return> + Send,
{
make_co!(f, Pin<Box<dyn Future<Output = Return> + Send + 'a>>)
}
}
impl<'a, Effs, Return, L: Locality> GenericCo<'a, Effs, Return, L>
where
Effs: Effects<'a>,
{
#[inline]
pub(crate) fn resume(
self: Pin<&mut Self>,
resume: Resumes<'a, CanStart<Effs>>,
) -> GeneratorState<CanStart<Effs>, Return> {
let mut g = unsafe { self.map_unchecked_mut(|s| &mut s.generator) };
Generator::resume(g.as_mut(), resume)
}
#[inline]
pub(crate) fn resume_with<R, Index>(
self: Pin<&mut Self>,
resume: R,
) -> GeneratorState<CanStart<Effs>, Return>
where
Resumes<'a, CanStart<Effs>>: CoprodInjector<R, Index>,
{
self.resume(Resumes::<'a, CanStart<Effs>>::inject(resume))
}
}
pub struct Yielder<'a, Effs>
where
Effs: MapResume,
{
token: GeneratorToken<CanStart<Effs>, Resumes<'a, CanStart<Effs>>>,
}
impl<'a, Effs> Yielder<'a, Effs>
where
Effs: MapResume,
{
#[inline]
fn new(token: GeneratorToken<CanStart<Effs>, Resumes<'a, CanStart<Effs>>>) -> Self {
Self { token }
}
#[inline]
pub async fn yield_<E, Index>(&self, effect: E) -> E::Resume<'a>
where
E: Effect,
Effs: CoprodInjector<E, Index>,
<Effs as MapResume>::Output<'a>: CoprodUninjector<E::Resume<'a>, Index>,
{
let resume = self
.token
.yield_(Coproduct::Inr(Effs::inject(effect)))
.await;
match resume {
Coproduct::Inr(value) => match value.uninject() {
Ok(value) => value,
Err(_) => {
debug_unreachable!("uninject failed: handler resumed at wrong coproduct index")
}
},
Coproduct::Inl(_) => {
debug_unreachable!("Start (Inl) arm should never be sent as a resume value")
}
}
}
#[inline]
pub async fn invoke<'b, SubEffs, R, L, Indices>(
&self,
program: Effectful<'b, SubEffs, R, L>,
) -> R
where
'a: 'b,
SubEffs: Effects<'b> + EmbedEffect<Effs, Indices>,
Resumes<'a, Effs>: ProjectResume<'a, SubEffs, Indices>,
L: Locality,
{
let mut co = std::pin::pin!(program.co);
let mut yielded = co.as_mut().resume_with(Start);
loop {
match yielded {
GeneratorState::Complete(value) => break value,
GeneratorState::Yielded(effect) => {
let subeffect = match effect {
Coproduct::Inl(_) => debug_unreachable!(
"Start (Inl) arm should never be yielded after initialization"
),
Coproduct::Inr(subeffect) => subeffect,
};
let outer_effect: Effs = subeffect.embed();
let resume = self.token.yield_(Coproduct::Inr(outer_effect)).await;
let resume_long: Resumes<'a, SubEffs> = match resume {
Coproduct::Inr(value) => value.project(),
Coproduct::Inl(_) => {
debug_unreachable!(
"Start (Inl) arm should never be sent as a resume value"
)
}
};
let resume_short: Resumes<'b, SubEffs> = SubEffs::shorten_resumes(resume_long);
yielded = co.as_mut().resume(Coproduct::Inr(resume_short));
}
}
}
}
}