160 lines
5.2 KiB
Rust
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(),
|
|
])
|
|
}
|