Files
orchid/orchid-std/src/macros/match_macros.rs
Lawrence Bethlenfalvy 9b4c7fa7d7
Some checks failed
Rust / build (push) Has been cancelled
partway through fixes, macro system needs resdesign
2026-04-08 18:02:20 +02:00

160 lines
5.2 KiB
Rust

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<api::TStrv>,
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<OrcOpt<HomoTpl<Expr>>> {
h.exec::<OrcOpt<HomoTpl<Expr>>>(call(self.matcher().await, val)).await
}
pub fn keys(&self) -> impl Stream<Item = Sym> {
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<Sym>,
/// 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<GenMember> {
prefix("pattern", [
fun(
true,
"match_one",
async |mat: TAtom<MatcherAtom>, 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<TAtom<SymAtom>>, 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<TAtom<MacTree>> =
cx.exec(cx.recur(mactree!(macros::common::semi_list "push" rules.clone();))).await?;
let mut rule_atoms = Vec::<(TAtom<MatcherAtom>, 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<Tpl<(TAtom<MatcherAtom>, _)>> {
let Ok(pat): OrcRes<TAtom<MatcherAtom>> =
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(),
])
}