use std::borrow::Cow; use async_fn_stream::stream; use futures::future::join_all; use futures::{Stream, StreamExt, stream}; use never::Never; use orchid_api::ExprTicket; use orchid_api_derive::Coding; use orchid_base::error::{OrcRes, mk_errv}; use orchid_base::format::fmt; use orchid_base::name::Sym; use orchid_base::sym; use orchid_extension::atom::{Atomic, TAtom}; use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; use orchid_extension::context::i; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::{ExecHandle, exec}; use orchid_extension::expr::{Expr, ExprHandle}; use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref}; use orchid_extension::tree::{GenMember, fun, prefix}; use crate::macros::resolve::resolve; use crate::macros::utils::{build_macro, mactree, mactreev}; use crate::std::reflection::sym_atom::SymAtom; use crate::std::tuple::Tuple; use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple, api}; #[derive(Clone, Coding)] pub struct MatcherData { keys: Vec, matcher: ExprTicket, } impl MatcherData { async fn matcher(&self) -> Expr { Expr::from_handle(ExprHandle::from_ticket(self.matcher).await) } pub async fn run_matcher( &self, h: &mut ExecHandle<'_>, val: impl ToExpr, ) -> OrcRes>> { h.exec::>>(call(self.matcher().await.to_gen().await, [val.to_gen().await])) .await } pub fn keys(&self) -> impl Stream { stream(async |mut h| { for tk in &self.keys { h.emit(Sym::from_api(*tk, &i()).await).await } }) } } #[derive(Clone)] pub struct MatcherAtom { /// The names that subresults may be bound to pub(super) keys: Vec, /// Takes the value-to-be-matched, returns an `option (tuple T1..TN)` of the /// subresults to be bound to the names returned by [Self::keys] pub(super) matcher: Expr, } impl Atomic for MatcherAtom { type Data = MatcherData; type Variant = OwnedVariant; } impl OwnedAtom for MatcherAtom { type Refs = Never; async fn val(&self) -> std::borrow::Cow<'_, Self::Data> { Cow::Owned(MatcherData { keys: self.keys.iter().map(|t| t.to_api()).collect(), matcher: self.matcher.handle().ticket(), }) } } pub async fn gen_match_macro_lib() -> Vec { prefix("pattern", [ fun( true, "match_one", async |mat: TAtom, value: Expr, then: Expr, default: Expr| { exec(async move |mut h| match mat.run_matcher(&mut h, value).await? { OrcOpt(Some(values)) => Ok(call(then.to_gen().await, join_all(values.0.into_iter().map(|x| x.to_gen())).await)), OrcOpt(None) => Ok(default.to_gen().await), }) .await }, ), fun(true, "matcher", async |names: HomoTpl>, matcher: Expr| MatcherAtom { keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0, &i()).await)).await, matcher, }), build_macro(None, ["match", "match_rule", "_row", "=>"]) .rule(mactreev!("pattern::match" { "..$" rules 0 }), [async |[rules]| { exec(async move |mut h| { let rule_lines = h .exec::>(call(sym_ref(sym!(macros::resolve; i())), [ mactree!(macros::common::semi_list "push" rules.clone();).to_gen().await, ])) .await?; let mut rule_atoms = Vec::<(TAtom, Expr)>::new(); for line_exprh in rule_lines.iter() { let line_mac = h .exec::>(Expr::from_handle(ExprHandle::from_ticket(*line_exprh).await)) .await?; let Tpl((matcher, body)) = h .exec(call(sym_ref(sym!(macros::resolve; i())), [ mactree!(pattern::_row "push" own(&line_mac).await ;).to_gen().await, ])) .await?; rule_atoms.push((matcher, body)); } let base_case = lambda(0, [bot(mk_errv( i().i("No branches match").await, "None of the pattern provided matches this value", [rules.pos()], ))]); let match_expr = stream::iter(rule_atoms.into_iter().rev()) .fold(base_case, async |tail, (mat, body)| { lambda(0, [call(sym_ref(sym!(pattern::match_one; i())), [ mat.to_gen().await, arg(0), body.to_gen().await, call(tail, [arg(0)]), ])]) }) .await; Ok(match_expr) }) .await }]) .rule(mactreev!(pattern::match_rule (( "...$" pattern 0 ))), [async |[pattern]| { resolve(mactree!(pattern::match_rule "push" pattern; )).await }]) .rule(mactreev!(pattern::_row ( "...$" pattern 0 pattern::=> "...$" value 1 )), [ async |[pattern, mut value]| { exec(async move |mut h| -> OrcRes, GExpr)>> { let Ok(pat) = h .exec::>(call(sym_ref(sym!(macros::resolve; i())), [ mactree!(pattern::match_rule "push" pattern.clone();).to_gen().await, ])) .await else { return Err(mk_errv( i().i("Invalid pattern").await, format!("Could not parse {} as a match pattern", fmt(&pattern, &i()).await), [pattern.pos()], )); }; value = (pat.keys()) .fold(value, async |value, name| mactree!("l_" name; ( "push" value ; ))) .await; Ok(Tpl((pat, resolve(value).await))) }) .await }, ]) .finish(), fun(true, "ref_body", async |val| OrcOpt(Some(UntypedTuple(vec![val])))), build_macro(None, ["ref"]) .rule(mactreev!(pattern::match_rule(pattern::ref "$" name)), [async |[name]| { let MacTok::Name(name) = name.tok() else { return Err(mk_errv( i().i("pattern 'ref' requires a name to bind to").await, format!( "'ref' was interpreted as a binding matcher, \ but it was followed by {} instead of a name", fmt(&name, &i()).await ), [name.pos()], )); }; Ok(MatcherAtom { keys: vec![name.clone()], matcher: sym_ref(sym!(pattern::ref_body; i())).to_expr().await, }) }]) .finish(), ]) }