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_derive::Coding; use orchid_base::{OrcRes, Sym, fmt, is, mk_errv, sym}; use orchid_extension::gen_expr::{bot, call, call_v, lam, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; use orchid_extension::{ Atomic, ExecHandle, Expr, ExprHandle, OwnedAtom, OwnedVariant, TAtom, ToExpr, exec, }; use crate::macros::utils::{build_macro, mactree, mactreev}; use crate::std::reflection::sym_atom::SymAtom; use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple, api}; #[derive(Clone, Coding)] pub struct MatcherData { keys: Vec, matcher: api::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, val)).await } pub fn keys(&self) -> impl Stream { stream(async |mut h| { for tk in &self.keys { h.emit(Sym::from_api(*tk).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_v(then, values.0).await), OrcOpt(None) => Ok(default.to_gen().await), }) .await }, ), fun(true, "matcher", async |names: HomoTpl>, matcher: Expr| { new_atom(MatcherAtom { keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0).await)).await, matcher, }) }), build_macro(None, ["match", "match_rule", "_row", "=>"]) .rule(mactreev!("pattern::match" "...$" value 0 { "..$" rules 0 }), [ async |mut cx, [value, rules]| { let rule_lines: HomoTpl> = cx.exec(cx.recur(mactree!(macros::common::semi_list "push" rules.clone();))).await?; let mut rule_atoms = Vec::<(TAtom, Expr)>::new(); for line_mac in rule_lines.0.iter() { let Tpl((matcher, body)) = cx.exec(cx.recur(mactree!(pattern::_row "push" line_mac.own().await ;))).await?; rule_atoms.push((matcher, body)); } let base_case = lam(async |_| { bot(mk_errv( is("No branches match").await, "None of the patterns matches this value", [rules.pos()], )) }) .await; let match_expr = stream::iter(rule_atoms.into_iter().rev()) .fold(base_case, |tail, (mat, body)| { lam(async move |x| { call(sym!(pattern::match_one), (mat, x, body, call(tail, x))).await }) }) .await; Ok(call(match_expr, cx.recur(value))) }, ]) .rule(mactreev!(pattern::match_rule (( "...$" pattern 0 ))), [async |cx, [pattern]| { cx.recur(mactree!(pattern::match_rule "push" pattern; )).to_gen().await }]) .rule(mactreev!(pattern::match_rule ( macros::common::_ )), [async |_cx, []| { Ok(new_atom(MatcherAtom { keys: Vec::new(), matcher: lam(async |_| OrcOpt(Some(Tpl(()))).to_gen().await).await.create().await, })) }]) .rule(mactreev!(pattern::_row ( "...$" pattern 0 pattern::=> "...$" value 1 )), [ async |mut cx, [pattern, mut value]| -> OrcRes, _)>> { let Ok(pat): OrcRes> = cx.exec(cx.recur(mactree!(pattern::match_rule "push" pattern.clone();))).await else { return Err(mk_errv( is("Invalid pattern").await, format!("Could not parse {} as a match pattern", fmt(&pattern).await), [pattern.pos()], )); }; value = (pat.keys()) .fold(value, async |value, name| mactree!("l_" name; ( "push" value ; ))) .await; Ok(Tpl((pat, cx.recur(value)))) }, ]) .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 |_cx, [name]| { let MacTok::Name(name) = name.tok() else { return Err(mk_errv( is("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).await ), [name.pos()], )); }; Ok(new_atom(MatcherAtom { keys: vec![name.clone()], matcher: sym!(pattern::ref_body).to_expr().await, })) }]) .finish(), ]) }