Updated macro system to process and return mactree and then lower it separately to gexpr
Some checks failed
Rust / build (push) Has been cancelled

This commit is contained in:
2026-04-11 11:00:22 +00:00
parent 9b4c7fa7d7
commit b44f3c1832
14 changed files with 460 additions and 469 deletions

View File

@@ -16,7 +16,7 @@ use task_local::task_local;
use crate::gen_expr::GExpr;
use crate::tree::{GenTok, GenTokTree};
use crate::{Expr, ToExpr, api, request, sys_id};
use crate::{ExecHandle, Expr, ToExpr, TryFromExpr, api, exec, request, sys_id};
/// [PTokTree] without [orchid_base::Pos] metadata
pub type PTok = Token<Expr, Never>;
@@ -118,7 +118,7 @@ impl<'a> ParsCtx<'a> {
pub fn module(&self) -> Sym { self.module.clone() }
}
type BoxConstCallback = Box<dyn FnOnce(ConstCtx) -> LocalBoxFuture<'static, GExpr>>;
type BoxConstCallback = Box<dyn for<'a> FnOnce(ConstCtx<'a>) -> LocalBoxFuture<'a, GExpr>>;
task_local! {
static CONST_TBL: RefCell<HashMap<NonZero<u64>, BoxConstCallback>>;
@@ -143,14 +143,15 @@ impl ParsedLine {
/// Define a constant. The callback is only called later if the constant is
/// referenced, and it can call [crate::refl] to base its value on the module
/// tree or use its argument for context-specific queries
pub fn cnst<'a, R: ToExpr + 'static, F: AsyncFnOnce(ConstCtx) -> R + 'static>(
pub fn cnst<'a, R: ToExpr + 'static, F: for<'b> AsyncFnOnce(ConstCtx<'b>) -> R + 'static>(
sr: &SrcRange,
comments: impl IntoIterator<Item = &'a Comment>,
exported: bool,
name: IStr,
f: F,
) -> Self {
let cb = Box::new(|ctx| async move { f(ctx).await.to_gen().await }.boxed_local());
let cb: BoxConstCallback =
Box::new(|ctx| async move { f(ctx).await.to_gen().await }.boxed_local());
let kind = ParsedLineKind::Mem(ParsedMem { name, exported, kind: ParsedMemKind::Const(cb) });
let comments = comments.into_iter().cloned().collect();
ParsedLine { comments, sr: sr.clone(), kind }
@@ -241,11 +242,11 @@ pub enum ParsedMemKind {
}
/// Enable a generated constant to query about its environment
#[derive(Clone)]
pub struct ConstCtx {
pub struct ConstCtx<'a> {
constid: api::ParsedConstId,
exec: ExecHandle<'a>,
}
impl ConstCtx {
impl<'a> ConstCtx<'a> {
/// Resolve a set of local names into the full names they would point to if
/// they were globally bound. Errors produced at this stage are soft, as the
/// names may still be found to be locally bound within the expression
@@ -272,10 +273,14 @@ impl ConstCtx {
pub async fn names_n<const N: usize>(&self, names: [&Sym; N]) -> [OrcRes<Sym>; N] {
self.names(names).collect::<Vec<_>>().await.try_into().expect("Lengths must match")
}
pub async fn exec<R: TryFromExpr>(&mut self, val: impl ToExpr) -> OrcRes<R> {
self.exec.exec(val).await
}
pub fn handle(&mut self) -> &mut ExecHandle<'a> { &mut self.exec }
}
pub(crate) async fn get_const(id: api::ParsedConstId) -> GExpr {
let cb = CONST_TBL
.with(|ent| ent.borrow_mut().remove(&id.0).expect("Bad ID or double read of parsed const"));
cb(ConstCtx { constid: id }).await
exec(async move |exec| cb(ConstCtx { constid: id, exec }).await).await
}

View File

@@ -11,9 +11,10 @@ use orchid_extension::{
ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser, TAtom, TryFromExpr,
};
use crate::macros::lower::lower;
use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq};
use crate::macros::ph_lexer::PhAtom;
use crate::macros::resolve::{ArgStack, resolve};
use crate::macros::resolve::resolve;
#[derive(Default)]
pub struct LetLine;
@@ -36,20 +37,20 @@ impl Parser for LetLine {
let Parsed { tail, .. } = expect_tok(tail, is("=").await).await?;
let aliased = parse_tokv(tail).await;
eprintln!("Parsed tokv: {}", fmt(&aliased).await);
Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |ctx| {
Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |mut ctx| {
let macro_input =
MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&aliased, &ctx)).await?).at(sr.pos());
eprintln!("Dealiased tokv: {}", fmt(&macro_input).await);
let gx = resolve(macro_input, ArgStack::end()).await;
eprintln!("resolves to: {}", fmt(&gx).await);
let x = gx.create().await;
let resolved = resolve(ctx.handle(), macro_input).await;
eprintln!("resolves to: {}", fmt(&resolved).await);
let x = lower(&resolved, substack::Substack::Bottom).await;
eprintln!("created as: {}", fmt(&x).await);
Ok(x)
})])
}
}
pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx) -> MacTreeSeq {
pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx<'_>) -> MacTreeSeq {
let keys = aliased.glossary().iter().cloned().collect_vec();
let mut names: HashMap<_, _> = HashMap::new();
let mut stream = pin!(ctx.names(&keys).zip(stream::iter(&keys)));

View File

@@ -0,0 +1,80 @@
use std::collections::VecDeque;
use async_fn_stream::stream;
use futures::{FutureExt, StreamExt, stream};
use itertools::Itertools;
use orchid_base::{OrcErrv, Paren, Sym, fmt, is, mk_errv, report};
use orchid_extension::ToExpr;
use orchid_extension::gen_expr::{GExpr, GExprKind, bot, call};
use substack::Substack;
use crate::macros::mactree::MacTreeSeq;
use crate::{MacTok, MacTree};
fn on_err(e: OrcErrv) -> GExpr {
report(e.clone());
bot(e)
}
pub async fn lower(mt: &MacTree, args: Substack<'_, Sym>) -> GExpr {
match mt.tok() {
MacTok::Bottom(b) => on_err(b.clone()),
MacTok::Slot => on_err(mk_errv(
is("Lowering intermediary slotted mactree").await,
"Found a Slot during lowering, which should only exist in temporary templates",
[mt.pos()],
)),
MacTok::Ph(ph) => on_err(mk_errv(
is("Lowering placeholder").await,
format!("Found {ph} during lowering. Placeholders should only exist in macro patterns"),
[mt.pos()],
)),
MacTok::S(Paren::Curly | Paren::Square, _) => on_err(mk_errv(
is("Lowering {...} or [...]").await,
format!("Cannot lower this syntax, probable incorrect macro call: {}", fmt(mt).await),
[mt.pos()],
)),
MacTok::S(Paren::Round, b) if b.items.is_empty() => on_err(mk_errv(
is("Cannot lower empty ()").await,
"Attempted to lower empty (). All expressions must have a value.",
[mt.pos()],
)),
MacTok::Lambda(arg, body) => {
let MacTok::Name(n) = arg.tok() else {
return on_err(mk_errv(
is("Lowering lambda with complex param").await,
format!("In code after macros, lambda params can only be names, not {}", fmt(arg).await),
[arg.pos()],
));
};
if body.items.is_empty() {
return on_err(mk_errv(
is("Lowering lambda with empty body").await,
"Lambdas without a body are invalid, all expressions in Orchid must have a value",
[mt.pos()],
));
}
let body = lower_seq(body, args.push(n.clone())).await;
GExprKind::Lambda(Box::new(body)).at(mt.pos())
},
MacTok::S(Paren::Round, body) => lower_seq(body, args).await,
MacTok::Name(n) => match args.iter().find_position(|b| *b == n) {
Some((i, _)) => GExprKind::Arg(i.try_into().unwrap()).at(mt.pos()),
None => n.clone().to_gen().await,
},
MacTok::Value(expr) => expr.clone().to_gen().await,
}
}
pub async fn lower_seq(mtv: &MacTreeSeq, args: Substack<'_, Sym>) -> GExpr {
let mut exprs = stream(async |mut h| {
for mt in mtv.items.iter() {
h.emit(lower(mt, args.clone()).await).await
}
})
.collect::<VecDeque<_>>()
.boxed_local()
.await;
let first = exprs.pop_front().expect("We checked first that it isn't empty");
stream::iter(exprs).fold(first, async |f, x| call(f, x).await).await
}

View File

@@ -1,79 +1,118 @@
use orchid_base::sym;
use orchid_extension::TAtom;
use orchid_extension::gen_expr::{call, new_atom};
use orchid_extension::gen_expr::new_atom;
use orchid_extension::tree::{GenMember, fun, prefix};
use orchid_extension::{TAtom, exec};
use crate::macros::mactree::MacTree;
use crate::macros::resolve::{ArgStack, resolve};
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::{HomoTpl, UntypedTuple};
pub async fn gen_macro_lib() -> Vec<GenMember> {
prefix("macros", [
fun(true, "resolve", async |tpl: TAtom<MacTree>| {
resolve(tpl.own().await, ArgStack::end()).await
exec(async move |mut h| new_atom(resolve(&mut h, tpl.own().await).await)).await
}),
prefix("common", [
build_macro(None, ["..", "_", "="]).finish(),
build_macro(Some(1), ["+"])
.rule(mactreev!("...$" lhs 1 macros::common::+ "...$" rhs 0), [async |cx, [lhs, rhs]| {
call(sym!(std::ops::add::resolve), (cx.recur(lhs), cx.recur(rhs))).await
}])
.rule(
mactreev!("...$" lhs 1 "macros::common::+" "...$" rhs 0),
async |mut cx, [lhs, rhs]| {
Ok(mactree!("std::ops::add::resolve"
"push" cx.recur(lhs).await;
"push" cx.recur(rhs).await;))
},
)
.finish(),
build_macro(Some(1), ["-"])
.rule(mactreev!("...$" lhs 1 macros::common::- "...$" rhs 0), [async |cx, [lhs, rhs]| {
call(sym!(std::ops::sub::resolve), (cx.recur(lhs), cx.recur(rhs))).await
}])
.rule(
mactreev!("...$" lhs 1 "macros::common::-" "...$" rhs 0),
async |mut cx, [lhs, rhs]| {
Ok(mactree!("std::ops::sub::resolve"
"push" cx.recur(lhs).await;
"push" cx.recur(rhs).await;))
},
)
.finish(),
build_macro(Some(2), ["*"])
.rule(mactreev!("...$" lhs 1 macros::common::* "...$" rhs 0), [async |cx, [lhs, rhs]| {
call(sym!(std::ops::mul::resolve), (cx.recur(lhs), cx.recur(rhs))).await
}])
.rule(
mactreev!("...$" lhs 1 "macros::common::*" "...$" rhs 0),
async |mut cx, [lhs, rhs]| {
Ok(mactree!("std::ops::mul::resolve"
"push" cx.recur(lhs).await;
"push" cx.recur(rhs).await;))
},
)
.finish(),
build_macro(Some(2), ["/"])
.rule(mactreev!("...$" lhs 1 macros::common::/ "...$" rhs 0), [async |cx, [lhs, rhs]| {
call(sym!(std::ops::div::resolve), (cx.recur(lhs), cx.recur(rhs))).await
}])
.rule(
mactreev!("...$" lhs 1 "macros::common::/" "...$" rhs 0),
async |mut cx, [lhs, rhs]| {
Ok(mactree!("std::ops::div::resolve"
"push" cx.recur(lhs).await;
"push" cx.recur(rhs).await;))
},
)
.finish(),
build_macro(Some(2), ["%"])
.rule(mactreev!("...$" lhs 1 macros::common::% "...$" rhs 0), [async |cx, [lhs, rhs]| {
call(sym!(std::ops::mod::resolve), (cx.recur(lhs), cx.recur(rhs))).await
}])
.rule(
mactreev!("...$" lhs 1 "macros::common::%" "...$" rhs 0),
async |mut cx, [lhs, rhs]| {
Ok(mactree!("std::ops::mod::resolve"
"push" cx.recur(lhs).await;
"push" cx.recur(rhs).await;))
},
)
.finish(),
build_macro(Some(3), ["."])
.rule(mactreev!("...$" lhs 1 macros::common::. "...$" rhs 0), [async |cx, [lhs, rhs]| {
call(sym!(std::ops::get::resolve), (cx.recur(lhs), cx.recur(rhs))).await
}])
.rule(
mactreev!("...$" lhs 1 "macros::common::." "...$" rhs 0),
async |mut cx, [lhs, rhs]| {
Ok(mactree!("std::ops::get::resolve"
"push" cx.recur(lhs).await;
"push" cx.recur(rhs).await;))
},
)
.finish(),
build_macro(None, ["comma_list", ","])
.rule(
mactreev!(macros::common::comma_list ( "...$" head 0 macros::common::, "...$" tail 1)),
[async |mut cx, [head, tail]| {
mactreev!("macros::common::comma_list" (
"...$" head 0 "macros::common::," "...$" tail 1
)),
async |mut cx, [head, tail]| {
let mut tail: HomoTpl<TAtom<MacTree>> =
cx.exec(cx.recur(mactree!(macros::common::comma_list "push" tail ;))).await?;
cx.recur_call(mactree!("macros::common::comma_list" "push" tail ;)).await?;
tail.0.insert(0, cx.exec(new_atom(head)).await?);
Ok(tail)
}],
Ok(mactree!("Val" tail;))
},
)
.rule(mactreev!(macros::common::comma_list ( "...$" final_tail 0 )), [
async |_cx, [tail]| HomoTpl(vec![new_atom(tail)]),
])
.rule(mactreev!(macros::common::comma_list()), [async |_cx, []| UntypedTuple(Vec::new())])
.rule(
mactreev!("macros::common::comma_list" ( "...$" final_tail 0 )),
async |_cx, [tail]| Ok(mactree!("Val" HomoTpl(vec![new_atom(tail)]);)),
)
.rule(mactreev!("macros::common::comma_list"()), async |_cx, []| {
Ok(mactree!("Val" UntypedTuple(Vec::new());))
})
.finish(),
build_macro(None, ["semi_list", ";"])
.rule(
mactreev!(macros::common::semi_list ( "...$" head 0 macros::common::; "...$" tail 1)),
[async |mut cx, [head, tail]| {
mactreev!("macros::common::semi_list" (
"...$" head 0 "macros::common::;" "...$" tail 1
)),
async |mut cx, [head, tail]| {
let mut tail: HomoTpl<TAtom<MacTree>> =
cx.exec(cx.recur(mactree!(macros::common::semi_list "push" tail ;))).await?;
cx.recur_call(mactree!("macros::common::semi_list" "push" tail ;)).await?;
tail.0.insert(0, cx.exec(new_atom(head)).await?);
Ok(tail)
}],
Ok(mactree!("Val" tail))
},
)
.rule(mactreev!(macros::common::semi_list ( "...$" final_tail 0 )), [
async |_cx, [tail]| HomoTpl(vec![new_atom(tail)]),
])
.rule(mactreev!(macros::common::semi_list()), [async |_cx, []| UntypedTuple(Vec::new())])
.rule(
mactreev!("macros::common::semi_list" ( "...$" final_tail 0 )),
async |_cx, [tail]| Ok(mactree!("Val" HomoTpl(vec![new_atom(tail)]))),
)
.rule(mactreev!("macros::common::semi_list"()), async |_cx, []| {
Ok(mactree!("Val" UntypedTuple(Vec::new())))
})
.finish(),
]),
])

View File

@@ -13,7 +13,6 @@ use crate::macros::macro_value::Macro;
use crate::macros::mactree_lexer::MacTreeLexer;
use crate::macros::match_macros::{MatcherAtom, gen_match_macro_lib};
use crate::macros::ph_lexer::{PhAtom, PhLexer};
use crate::macros::resolve::ArgStack;
use crate::macros::stdlib::gen_std_macro_lib;
use crate::macros::utils::MacroBodyArgCollector;
use crate::{MacTree, StdSystem};
@@ -39,7 +38,6 @@ impl SystemCard for MacroSystem {
Some(PhAtom::ops()),
Some(MacroBodyArgCollector::ops()),
Some(MatcherAtom::ops()),
Some(ArgStack::ops()),
]
}
}

View File

@@ -1,93 +1,67 @@
use std::borrow::Cow;
use async_fn_stream::stream;
use futures::future::join_all;
use futures::{Stream, StreamExt, stream};
use futures::{StreamExt, stream};
use never::Never;
use orchid_api_derive::Coding;
use orchid_base::{OrcRes, Sym, fmt, is, mk_errv, sym};
use orchid_base::{OrcRes, Sym, fmt, is, mk_errv};
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 orchid_extension::{Atomic, ExecHandle, Expr, 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};
use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple};
#[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,
pub(super) matcher: MacTree,
}
impl Atomic for MatcherAtom {
type Data = MatcherData;
type Data = ();
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(),
})
}
async fn val(&self) -> std::borrow::Cow<'_, Self::Data> { Cow::Owned(()) }
}
pub async fn match_one(
h: &mut ExecHandle<'_>,
mat: impl ToExpr,
value: impl ToExpr,
) -> OrcRes<OrcOpt<HomoTpl<Expr>>> {
h.exec::<OrcOpt<HomoTpl<Expr>>>(call(mat, value)).await
}
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| {
fun(true, "match_one", async |mat: Expr, value: Expr, then: Expr, default: Expr| {
exec(async move |mut h| match match_one(&mut h, mat, 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: TAtom<MacTree>| {
new_atom(MatcherAtom {
keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0).await)).await,
matcher,
matcher: matcher.own().await,
})
}),
build_macro(None, ["match", "match_rule", "_row", "=>"])
.rule(mactreev!("pattern::match" "...$" value 0 { "..$" rules 0 }), [
.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();
cx.recur_call(mactree!("macros::common::semi_list" "push" rules.clone())).await?;
let mut rule_atoms = Vec::<(TAtom<MatcherAtom>, TAtom<MacTree>)>::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?;
cx.recur_call(mactree!("pattern::_row" "push" line_mac.own().await)).await?;
rule_atoms.push((matcher, body));
}
let base_case = lam(async |_| {
@@ -97,30 +71,39 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
[rules.pos()],
))
})
.await
.create()
.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
})
let match_chain = stream::iter(rule_atoms.into_iter().rev())
.fold(mactree!("Val" base_case;), async |tail, (mat, body)| {
mactree!("l" "pattern::x" (
"pattern::match_one"
"Val" mat.ex();
"pattern::x"
"push" body.own().await;
("push" tail;
"pattern::x")
))
})
.await;
Ok(call(match_expr, cx.recur(value)))
Ok(mactree!("push" match_chain; "push" 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 {
)
.rule(mactreev!("pattern::match_rule" (( "...$" pattern 0 ))), async |mut cx, [pattern]| {
Ok(cx.recur(mactree!("pattern::match_rule" "push" pattern)).await)
})
.rule(mactreev!("pattern::match_rule"("macros::common::_")), async |_cx, []| {
let matcher = 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>, _)>> {
matcher: mactree!("l" "pattern::unused" ("Val" OrcOpt(Some(Tpl(()))))),
});
Ok(mactree!("Val" matcher))
})
.rule(
mactreev!("pattern::_row" ( "...$" pattern 0 "pattern::=>" "...$" value 1 )),
async |mut cx, [pattern, mut value]| {
let Ok(pat): OrcRes<TAtom<MatcherAtom>> =
cx.exec(cx.recur(mactree!(pattern::match_rule "push" pattern.clone();))).await
cx.recur_call(mactree!("pattern::match_rule" "push" pattern.clone())).await
else {
return Err(mk_errv(
is("Invalid pattern").await,
@@ -128,16 +111,16 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
[pattern.pos()],
));
};
value = (pat.keys())
.fold(value, async |value, name| mactree!("l_" name; ( "push" value ; )))
value = stream::iter(pat.own().await.keys.iter().cloned())
.fold(value, async |value, name| mactree!("l_" name; ( "push" value )))
.await;
Ok(Tpl((pat, cx.recur(value))))
Ok(mactree!("Val" Tpl((pat, new_atom(cx.recur(value).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 |_cx, [name]| {
.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,
@@ -149,11 +132,12 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
[name.pos()],
));
};
Ok(new_atom(MatcherAtom {
let atom = new_atom(MatcherAtom {
keys: vec![name.clone()],
matcher: sym!(pattern::ref_body).to_expr().await,
}))
}])
matcher: mactree!("pattern::ref_body"),
});
Ok(mactree!("Val" atom))
})
.finish(),
])
}

View File

@@ -1,5 +1,6 @@
mod instantiate_tpl;
mod let_line;
pub mod lower;
mod macro_lib;
mod macro_line;
pub mod macro_system;
@@ -8,7 +9,6 @@ pub mod mactree;
mod mactree_lexer;
pub mod match_macros;
mod ph_lexer;
pub mod postmac;
mod resolve;
mod rule;
pub mod stdlib;

View File

@@ -1,53 +0,0 @@
use std::any::TypeId;
use std::borrow::Cow;
use std::marker::PhantomData;
use never::Never;
use orchid_extension::gen_expr::GExpr;
use orchid_extension::{
Atomic, ClonableToExprDyn, MethodSetBuilder, OwnedAtom, OwnedVariant, ToExpr, ToExprFuture,
};
fn assert_not_gexpr<T: 'static>() {
assert_ne!(
TypeId::of::<T>(),
TypeId::of::<GExpr>(),
"Cannot output GExpr, use PostMac::boxed or PostMac::boxed_clone"
);
}
struct PostMacCx<'a>(PhantomData<&'a ()>);
impl PostMacCx<'_> {
pub fn ex<T: ToExpr>(&self, val: PostMac<T>) -> T { val.0 }
}
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PostMac<T: ToExpr + 'static>(T);
impl<T: ToExpr + 'static> PostMac<T> {
pub fn new(t: T) -> Self {
assert_not_gexpr::<T>();
Self(t)
}
}
impl<T: Future<Output: ToExpr + Clone + 'static> + Clone + 'static> PostMac<ToExprFuture<T>> {
pub fn with(f: impl for<'a> FnOnce(PostMacCx<'a>) -> T + Clone + 'static) -> Self {
assert_not_gexpr::<T>();
PostMac(ToExprFuture(f(PostMacCx(PhantomData))))
}
}
impl<T: ToExpr + Clone + 'static> PostMac<T> {
pub fn atom(self) -> PostMacAtom { PostMac(Box::new(self.0)) }
}
impl<T: ToExpr + 'static> From<T> for PostMac<T> {
fn from(value: T) -> Self { Self(value) }
}
pub type PostMacAtom = PostMac<Box<dyn ClonableToExprDyn>>;
impl Atomic for PostMacAtom {
type Data = ();
type Variant = OwnedVariant;
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new() }
}
impl OwnedAtom for PostMacAtom {
type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
}

View File

@@ -1,50 +1,18 @@
use std::borrow::Cow;
use std::collections::VecDeque;
use std::ops::{Add, Range};
use std::rc::Rc;
use async_fn_stream::stream;
use futures::{FutureExt, StreamExt, stream};
use futures::FutureExt;
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use never::Never;
use orchid_base::{NameLike, Paren, Pos, Sym, VPath, fmt, is, log, mk_errv};
use orchid_extension::gen_expr::{GExpr, GExprKind, bot, call, call_v, new_atom};
use orchid_extension::{
Atomic, ExecHandle, OwnedAtom, OwnedVariant, ReflMemKind, TAtom, ToExpr, ToExprFuture, exec, refl,
};
use orchid_base::{NameLike, Paren, Pos, VPath, fmt, is, log, mk_errv, report};
use orchid_extension::gen_expr::{GExpr, call_v, new_atom};
use orchid_extension::{ExecHandle, ReflMemKind, TAtom, ToExpr, refl};
use subslice_offset::SubsliceOffset;
use crate::macros::macro_value::{Macro, Rule};
use crate::macros::mactree::MacTreeSeq;
use crate::macros::postmac::{PostMac, PostMacAtom};
use crate::macros::rule::state::{MatchState, StateEntry};
use crate::{MacTok, MacTree};
pub enum ArgStackKind {
End,
Cons(Sym, ArgStack),
}
#[derive(Clone)]
pub struct ArgStack {
kind: Rc<ArgStackKind>,
len: usize,
}
impl ArgStack {
pub fn end() -> Self { ArgStack { kind: Rc::new(ArgStackKind::End), len: 0 } }
}
impl Default for ArgStack {
fn default() -> Self { Self::end() }
}
impl Atomic for ArgStack {
type Data = ();
type Variant = OwnedVariant;
}
impl OwnedAtom for ArgStack {
type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
}
/// # TODO
///
/// convert macro system to return MacTree or otherwise bring it up to
@@ -100,7 +68,7 @@ impl OwnedAtom for ArgStack {
/// The best option probably remains for resolve to process and return MacTree,
/// and for there to be a separate "lower" function. Nothing as yet suggests
/// however that macros can't be allowed to return different types
pub async fn resolve(h: &mut ExecHandle<'_>, val: MacTree, arg_stk: ArgStack) -> PostMacAtom {
pub async fn resolve(h: &mut ExecHandle<'_>, val: MacTree) -> MacTree {
writeln!(log("debug"), "Macro-resolving {}", fmt(&val).await).await;
let root = refl();
let mut macros = HashMap::new();
@@ -138,8 +106,9 @@ pub async fn resolve(h: &mut ExecHandle<'_>, val: MacTree, arg_stk: ArgStack) ->
}
}
let mut rctx = ResolveCtx { exclusive, priod };
let gex = resolve_one(&mut rctx, arg_stk, &val).await;
writeln!(log("debug"), "Macro-resolution over {}", fmt(&val).await).await;
let gex = resolve_one(&mut rctx, &val).await.unwrap_or(val.clone());
writeln!(log("debug"), "Macro-resolution over {} yielded {}", fmt(&val).await, fmt(&gex).await)
.await;
gex
}
@@ -158,54 +127,25 @@ struct ResolveCtx<'a> {
pub priod: Vec<FilteredMacroRecord<'a>>,
}
async fn resolve_one(ctx: &mut ResolveCtx<'_>, arg_stk: ArgStack, value: &MacTree) -> PostMacAtom {
async fn resolve_one(ctx: &mut ResolveCtx<'_>, value: &MacTree) -> Option<MacTree> {
eprintln!("Resolving unit {}", fmt(value).await);
match value.tok() {
MacTok::Ph(_) | MacTok::Slot => panic!("Forbidden element in value mactree"),
MacTok::Bottom(err) => PostMac::new(bot(err.clone())).atom(),
MacTok::Value(v) => {
eprintln!("Found value {}", fmt(v).await);
PostMac::new(v.clone()).atom()
},
MacTok::Name(n) => {
eprintln!("Looking for {n} among [");
let mut cur = &arg_stk;
let mut counter = 0;
while let ArgStackKind::Cons(name, next) = &*cur.kind {
cur = next;
counter += 1;
eprintln!("{name}, ");
if name == n {
return PostMac::new(GExprKind::Arg(counter).at(value.pos())).atom();
}
}
PostMac::new(n.clone()).atom()
},
MacTok::Bottom(_) | MacTok::Value(_) | MacTok::Name(_) => None,
MacTok::Lambda(arg, body) => {
eprintln!("Found lambda \\{} {}", fmt(arg).await, fmt(body).await);
let MacTok::Name(name) = &*arg.tok else {
return PostMac::new(bot(mk_errv(
is("Syntax error after macros").await,
"This token ends up as a binding, consider replacing it with a name",
[arg.pos()],
)))
.atom();
};
let arg_stk =
ArgStack { len: arg_stk.len + 1, kind: Rc::new(ArgStackKind::Cons(name.clone(), arg_stk)) };
let body = resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await;
let body2 = body.clone();
let pos = value.pos();
PostMac::with(async |cx| GExprKind::Lambda(Box::new(cx.ex(body).to_gen().await)).at(pos))
.atom()
let new_arg = resolve_one(ctx, arg).boxed_local().await;
let new_body = resolve_seq(ctx, body, value.pos()).await;
if new_arg.is_none() && new_body.is_none() {
return None;
}
let tok = MacTok::Lambda(
new_arg.unwrap_or_else(|| arg.clone()),
new_body.unwrap_or_else(|| body.clone()),
);
Some(tok.at(value.pos()))
},
MacTok::S(Paren::Round, body) => resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await,
MacTok::S(..) => PostMac::new(bot(mk_errv(
is("Leftover [] or {} not matched by macro").await,
format!("{} was not matched by any macro", fmt(value).await),
[value.pos()],
)))
.atom(),
MacTok::S(pty, body) =>
resolve_seq(ctx, body, value.pos()).await.map(|body| MacTok::S(*pty, body).at(value.pos())),
}
}
@@ -225,18 +165,11 @@ fn subsection<T>(
async fn resolve_seq(
ctx: &mut ResolveCtx<'_>,
arg_stk: ArgStack,
val: MacTreeSeq,
val: &MacTreeSeq,
fallback_pos: Pos,
) -> PostMacAtom {
) -> Option<MacTreeSeq> {
if val.items.is_empty() {
return PostMac::new(bot(mk_errv(
is("Empty sequence").await,
"() or (\\arg ) left after macro execution. \
This is usually caused by an incomplete call to a macro with bad error detection",
[fallback_pos],
)))
.atom();
return None;
}
// A sorted collection of overlapping but non-nested matches to exclusive
// macros
@@ -289,6 +222,7 @@ async fn resolve_seq(
x_matches.splice(lt_start..lt_start + lt_range.len(), [new_r]);
}
}
let mut any_match = !x_matches.is_empty();
// apply exclusive matches
if !x_matches.is_empty() {
// ranges of indices into x_matches which setwise conflict with each other.
@@ -319,19 +253,19 @@ async fn resolve_seq(
})
.reduce(|l, r| l + r);
if let Some(error) = error {
return PostMac::new(bot(error)).atom();
report(error.clone());
return Some(MacTreeSeq::new([MacTok::Bottom(error).at(fallback_pos)]));
}
// no conflicts, apply all exclusive matches
for (range, mac, rule, state) in x_matches.into_iter().rev() {
// backwards so that the non-overlapping ranges remain valid
let pos = (state.names().flat_map(|r| r.1).cloned().reduce(Pos::add))
.expect("All macro rules must contain at least one locally defined name");
let subex =
mk_body_call(mac, rule, &state, pos.clone(), arg_stk.clone()).await.to_expr().await;
let subex = mk_body_call(mac, rule, &state, pos.clone()).await.to_expr().await;
new_val.splice(range, [MacTok::Value(subex).at(pos)]);
}
};
// Does this glossary refresh actually pay off?
// TODO: Does this glossary refresh actually pay off?
let top_glossary = (new_val.iter())
.flat_map(|t| if let MacTok::Name(t) = t.tok() { Some(t.clone()) } else { None })
.collect::<HashSet<_>>();
@@ -342,41 +276,20 @@ async fn resolve_seq(
continue;
}
let Some((pre, state, suf)) = rule.matcher.apply(&new_val, &|_| true).await else { continue };
any_match = true;
let range = pre.len()..new_val.len() - suf.len();
let pos = (state.names().flat_map(|pair| pair.1).cloned().reduce(Pos::add))
.expect("All macro rules must contain at least one locally defined name");
let subex =
mk_body_call(mac, rule, &state, pos.clone(), arg_stk.clone()).await.to_expr().await;
let subex = mk_body_call(mac, rule, &state, pos.clone()).await.to_expr().await;
std::mem::drop(state);
new_val.splice(range, [MacTok::Value(subex).at(pos)]);
}
}
let mut exprs = stream(async |mut h| {
for mt in new_val {
h.emit(resolve_one(ctx, arg_stk.clone(), &mt).await).await
}
})
.collect::<VecDeque<_>>()
.boxed_local()
.await;
let first = exprs.pop_front().expect(
"We checked first that it isn't empty, and named macros get replaced with their results",
);
PostMac::with(async move |cx| {
stream::iter(exprs).fold(cx.ex(first), async |f, x| call(f, cx.ex(x)).await).await
})
.await
.atom()
any_match.then_some(MacTreeSeq::new(new_val))
}
async fn mk_body_call(
mac: &Macro,
rule: &Rule,
state: &MatchState<'_>,
pos: Pos,
arg_stk: ArgStack,
) -> GExpr {
let mut call_args = vec![new_atom(arg_stk).at(Pos::None)];
async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, pos: Pos) -> GExpr {
let mut call_args = vec![];
for name in rule.ph_names.iter() {
call_args.push(match state.get(name).expect("Missing state entry for placeholder") {
StateEntry::Scalar(scal) => new_atom((**scal).clone()),

View File

@@ -4,8 +4,11 @@ use crate::macros::utils::{build_macro, mactree, mactreev};
pub async fn gen_functional_macro_lib() -> Vec<GenMember> {
prefix("std::fn", [build_macro(Some(4), ["|>"])
.rule(mactreev!("...$" lhs 0 "std::fn::|>" "$" fun "...$" rhs 0), [
async |cx, [lhs, fun, rhs]| cx.recur(mactree!(("push" fun ; "push" lhs ;) "pushv" rhs ;)),
])
.rule(
mactreev!("...$" lhs 0 "std::fn::|>" "$" fun "...$" rhs 0),
async |mut cx, [lhs, fun, rhs]| {
Ok(cx.recur(mactree!("push" fun ; "push" lhs ; "pushv" rhs)).await)
},
)
.finish()])
}

View File

@@ -1,19 +1,17 @@
use futures::StreamExt;
use orchid_base::sym;
use orchid_extension::gen_expr::{call, new_atom};
use orchid_extension::gen_expr::new_atom;
use orchid_extension::tree::{GenMember, fun, prefix};
use orchid_extension::{Expr, TAtom, ToExpr, exec};
use orchid_extension::{Expr, TAtom, exec};
use crate::macros::match_macros::MatcherAtom;
use crate::macros::match_macros::{MatcherAtom, match_one};
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::{OrcOpt, Tpl};
pub async fn gen_option_macro_lib() -> Vec<GenMember> {
prefix("std::option", [
fun(false, "is_some_body", |sub: TAtom<MatcherAtom>, val: OrcOpt<Expr>| {
fun(false, "is_some_body", |sub: Expr, val: OrcOpt<Expr>| {
exec(async move |mut h| {
let Some(sub_val) = val.0 else { return Ok(OrcOpt(None)) };
sub.run_matcher(&mut h, sub_val).await
match_one(&mut h, sub, sub_val).await
})
}),
fun(
@@ -24,22 +22,25 @@ pub async fn gen_option_macro_lib() -> Vec<GenMember> {
},
),
build_macro(None, ["some", "none"])
.rule(mactreev!(pattern::match_rule ( std::option::some "...$" sub_pattern 0)), [
.rule(
mactreev!("pattern::match_rule" ( "std::option::some" "...$" sub_pattern 0)),
async |mut cx, [sub]| {
let sub: TAtom<MatcherAtom> =
cx.exec(cx.recur(mactree!(pattern::match_rule "push" sub;))).await?;
Ok(new_atom(MatcherAtom {
keys: sub.keys().collect().await,
matcher: call(sym!(std::option::is_some_body), sub).await.create().await,
}))
cx.recur_call(mactree!("pattern::match_rule" "push" sub;)).await?;
let sub = sub.own().await;
let matcher = new_atom(MatcherAtom {
keys: sub.keys,
matcher: mactree!("std::option::is_some_body" "push" sub.matcher),
});
Ok(mactree!("Val" matcher))
},
])
.rule(mactreev!(pattern::match_rule(std::option::none)), [async |_cx, []| {
new_atom(MatcherAtom {
)
.rule(mactreev!("pattern::match_rule"("std::option::none")), async |_cx, []| {
Ok(mactree!("Val" new_atom(MatcherAtom {
keys: vec![],
matcher: sym!(std::option::is_none_body).to_expr().await,
})
}])
matcher: mactree!("std::option::is_none_body"),
})))
})
.finish(),
])
}

View File

@@ -1,27 +1,31 @@
use orchid_base::sym;
use orchid_extension::gen_expr::call;
use orchid_extension::TAtom;
use orchid_extension::gen_expr::new_atom;
use orchid_extension::tree::{GenMember, prefix};
use orchid_extension::{Expr, TAtom, ToExpr};
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::std::string::str_atom::IntStrAtom;
use crate::{HomoTpl, MacTree, Tpl};
pub async fn gen_record_macro_lib() -> Vec<GenMember> {
prefix("std::record", [build_macro(None, ["r", "_row"])
.rule(mactreev!(std::record::r[ "...$" elements 0 ]), [async |mut cx, [elements]| {
.rule(mactreev!("std::record::r"[ "...$" elements 0 ]), async |mut cx, [elements]| {
let tup: HomoTpl<TAtom<MacTree>> =
cx.exec(cx.recur(mactree!((macros::common::comma_list "push" elements ;)))).await?;
let mut record = sym!(std::record::empty).to_gen().await;
cx.recur_call(mactree!(("macros::common::comma_list" "push" elements))).await?;
let mut record = mactree!("std::record::empty");
for item_exprh in tup.0 {
let Tpl((key, value)): Tpl<(TAtom<IntStrAtom>, Expr)> =
cx.exec(cx.recur(mactree!(std::record::_row "push" item_exprh.own().await ;))).await?;
record = call(sym!(std::record::set), (record, key, value)).await;
let Tpl((key, value)): Tpl<(TAtom<MacTree>, TAtom<MacTree>)> =
cx.recur_call(mactree!("std::record::_row" "push" item_exprh.own().await)).await?;
record = mactree!("std::record::set"
"push" record;
"push" key.own().await;
"push" value.own().await);
}
Ok(record)
}])
.rule(mactreev!(std::record::_row ( "$" name "...$" value 1 )), [async |cx, [name, value]| {
Ok(Tpl((cx.recur(name), cx.recur(value))))
}])
})
.rule(
mactreev!("std::record::_row" ( "$" name "...$" value 1 )),
async |mut cx, [name, value]| {
Ok(mactree!("Val" Tpl((new_atom(cx.recur(name).await), new_atom(cx.recur(value).await)))))
},
)
.finish()])
}

View File

@@ -1,85 +1,93 @@
use async_fn_stream::stream;
use futures::{StreamExt, stream};
use orchid_base::{OrcRes, sym};
use orchid_extension::gen_expr::{GExpr, call, new_atom};
use orchid_extension::tree::{GenMember, fun, prefix};
use orchid_extension::{Expr, TAtom, ToExpr, exec};
use crate::macros::match_macros::MatcherAtom;
use crate::macros::match_macros::{MatcherAtom, match_one};
use crate::macros::utils::{RuleCtx, build_macro, mactree, mactreev};
use crate::{HomoTpl, MacTree, OrcOpt};
pub async fn gen_tuple_macro_lib() -> Vec<GenMember> {
prefix("std::tuple", [
build_macro(None, ["t"])
.rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [async |mut cx, [elements]| {
let tup: HomoTpl<TAtom<MacTree>> = cx
.exec(cx.recur(
mactree!((macros::common::comma_list "push" elements ;)),
))
.await?;
let val = stream::iter(&tup.0[..])
.fold(sym!(std::tuple::empty).to_gen().await, async |head, new| {
call(sym!(std::tuple::cat), (
head,
call(sym!(std::tuple::one), call(
sym!(macros::resolve),
new.clone(),
)),
)).await
})
.await;
Ok(val)
}])
.rule(
mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0 macros::common::, macros::common::..])),
[async |cx, [elements]| parse_tpl(cx, elements, Some(mactree!(macros::common::_))).await],
)
.rule(
mactreev!(pattern::match_rule(
std::tuple::t[ "...$" elements 1 macros::common::, macros::common::.. "...$" tail 0]
)),
[async |cx, [elements, tail]| parse_tpl(cx, elements, Some(tail)).await],
)
.rule(mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0])), [
async |cx, [elements]| parse_tpl(cx, elements, None).await,
])
.finish(),
fun(false, "matcher_body", tuple_matcher_body),
])
build_macro(None, ["t"])
.rule(mactreev!("std::tuple::t" [ "...$" elements 0 ]), async |mut cx, [elements]| {
let tup: HomoTpl<TAtom<MacTree>> =
cx.recur_call(mactree!("macros::common::comma_list" "push" elements)).await?;
let val = stream(async |mut h| {
for item in &tup.0[..] {
h.emit(cx.recur(item.own().await).await).await
}
})
.fold(mactree!("std::tuple::empty"), async |head, new| {
mactree!(
"std::tuple::cat"
"push" head;
("std::tuple::one"
"push" new)
)
})
.await;
Ok(val)
})
.rule(
mactreev!("pattern::match_rule"(
"std::tuple::t"[ "...$" elements 0 "macros::common::," "macros::common::.."]
)),
async |cx, [elements]| parse_tpl(cx, elements, Some(mactree!("macros::common::_"))).await,
)
.rule(
mactreev!("pattern::match_rule"(
"std::tuple::t"[ "...$" elements 1 "macros::common::," "macros::common::.." "...$" tail 0]
)),
async |cx, [elements, tail]| parse_tpl(cx, elements, Some(tail)).await,
)
.rule(
mactreev!("pattern::match_rule"("std::tuple::t"[ "...$" elements 0])),
async |cx, [elements]| parse_tpl(cx, elements, None).await,
)
.finish(),
fun(false, "matcher_body", tuple_matcher_body),
])
}
async fn parse_tpl(
mut cx: RuleCtx<'_>,
elements: MacTree,
tail_matcher: Option<MacTree>,
) -> OrcRes<GExpr> {
) -> OrcRes<MacTree> {
let tup: HomoTpl<TAtom<MacTree>> =
cx.exec(cx.recur(mactree!((macros::common::comma_list "push" elements ;)))).await?;
let mut subs = Vec::with_capacity(tup.0.len());
cx.recur_call(mactree!("macros::common::comma_list" "push" elements)).await?;
let mut keys = Vec::new();
let mut sub_matchers = mactree!("std::tuple::empty");
for mac_a in &tup.0[..] {
let sub: TAtom<MatcherAtom> =
cx.exec(cx.recur(mactree!(pattern::match_rule ("push" mac_a.own().await ;)))).await?;
subs.push(sub);
cx.recur_call(mactree!("pattern::match_rule" ("push" mac_a.own().await))).await?;
let owned = sub.own().await;
keys.extend(owned.keys);
sub_matchers =
mactree!("std::tuple::cat" "push" sub_matchers; ("std::tuple::one" "push" owned.matcher));
}
let tail_matcher: Option<TAtom<MatcherAtom>> = match tail_matcher {
Some(mac) => Some(cx.exec(cx.recur(mactree!(pattern::match_rule "push" mac ;))).await?),
None => None,
let tail_matcher = match tail_matcher {
Some(mac) => {
let atm: TAtom<MatcherAtom> =
cx.recur_call(mactree!("pattern::match_rule" "push" mac)).await?;
let owned = atm.own().await;
keys.extend(owned.keys);
mactree!("std::option::some" "push" owned.matcher)
},
None => mactree!("std::option::none"),
};
Ok(new_atom(MatcherAtom {
keys: stream::iter(&subs[..])
.flat_map(|t| t.keys())
.chain(stream::iter(&tail_matcher).flat_map(|mat| mat.keys()))
.collect()
.await,
matcher: call(sym!(std::tuple::matcher_body), (HomoTpl(subs), OrcOpt(tail_matcher)))
.to_expr()
.await,
}))
Ok(mactree!("Val" new_atom(MatcherAtom {
keys,
matcher: mactree!("std::tuple::matcher_body" "push" sub_matchers; "push" tail_matcher),
})))
}
fn tuple_matcher_body(
children: HomoTpl<TAtom<MatcherAtom>>,
tail: OrcOpt<TAtom<MatcherAtom>>,
children: HomoTpl<Expr>,
tail: OrcOpt<Expr>,
value: HomoTpl<Expr>,
) -> impl Future<Output = GExpr> {
exec(async move |mut h| -> OrcRes<OrcOpt<HomoTpl<Expr>>> {
@@ -88,7 +96,7 @@ fn tuple_matcher_body(
}
let mut binds = Vec::new();
for (sub_mat, sub_val) in children.0.iter().zip(&value.0) {
match sub_mat.run_matcher(&mut h, sub_val.clone()).await? {
match match_one(&mut h, sub_mat.clone(), sub_val.clone()).await? {
OrcOpt(None) => return Ok(OrcOpt(None)),
OrcOpt(Some(subres)) => binds.extend(subres.0),
}
@@ -102,7 +110,7 @@ fn tuple_matcher_body(
call(sym!(std::tuple::cat), (prefix, new.clone())).await
})
.await;
match tail_mat.run_matcher(&mut h, tail_tpl).await? {
match match_one(&mut h, tail_mat, tail_tpl).await? {
OrcOpt(Some(tail_binds)) => binds.extend(tail_binds.0),
OrcOpt(None) => return Ok(OrcOpt(None)),
}

View File

@@ -8,14 +8,16 @@ use itertools::{Itertools, chain};
use never::Never;
use orchid_base::{NameLike, OrcRes, Sym, VPath, is};
use orchid_extension::gen_expr::{GExpr, new_atom};
use orchid_extension::tree::{GenMember, MemKind, cnst, lazy};
use orchid_extension::tree::{GenMember, MemKind, lazy};
use orchid_extension::{
Atomic, ExecHandle, OwnedAtom, OwnedVariant, TAtom, ToExpr, ToExprFuture, TryFromExpr, exec,
Atomic, ExecHandle, OwnedAtom, OwnedVariant, TAtom, ToExpr, TryFromExpr, exec,
};
use substack::Substack;
use crate::macros::lower::lower;
use crate::macros::macro_value::{Macro, MacroData, Rule};
use crate::macros::mactree::MacTreeSeq;
use crate::macros::resolve::{ArgStack, resolve};
use crate::macros::resolve::resolve;
use crate::macros::rule::matcher::Matcher;
use crate::{MacTok, MacTree};
@@ -24,7 +26,6 @@ pub type Args = Vec<MacTree>;
#[derive(Clone)]
pub struct MacroBodyArgCollector {
argc: usize,
arg_stack: Option<ArgStack>,
args: Args,
cb: Rc<dyn for<'a> Fn(RuleCtx<'a>, Args) -> LocalBoxFuture<'a, GExpr>>,
}
@@ -42,20 +43,13 @@ impl OwnedAtom for MacroBodyArgCollector {
self.clone().call(arg).await
}
async fn call(mut self, arg: orchid_extension::Expr) -> GExpr {
if self.arg_stack.is_none() {
let atom = (TAtom::<ArgStack>::downcast(arg.handle()).await).unwrap_or_else(|_| {
panic!("The argstack is an intermediary value, the argument types are known in advance")
});
self.arg_stack = Some(atom.own().await);
} else {
let atom = (TAtom::<MacTree>::downcast(arg.handle()).await).unwrap_or_else(|_| {
panic!("This is an intermediary value, the argument types are known in advance")
});
self.args.push(atom.own().await);
}
let atom = (TAtom::<MacTree>::downcast(arg.handle()).await).unwrap_or_else(|_| {
panic!("This is an intermediary value, the argument types are known in advance")
});
self.args.push(atom.own().await);
if self.argc == self.args.len() {
exec(async move |handle| {
let rule_ctx = RuleCtx { arg_stk: self.arg_stack.unwrap(), handle };
let rule_ctx = RuleCtx { handle };
(self.cb)(rule_ctx, self.args).await
})
.await
@@ -68,15 +62,26 @@ impl OwnedAtom for MacroBodyArgCollector {
fn body_name(name: &str, counter: usize) -> String { format!("({name})::{counter}") }
pub struct RuleCtx<'a> {
arg_stk: ArgStack,
handle: ExecHandle<'a>,
}
impl RuleCtx<'_> {
pub fn recur(&self, mt: MacTree) -> impl ToExpr + use<> {
/// Recursively resolve a subexpression
pub async fn recur(&mut self, mt: MacTree) -> MacTree {
eprintln!("Recursion in macro");
let arg_stk = self.arg_stk.clone();
ToExprFuture(resolve(mt, arg_stk))
resolve(&mut self.handle, mt).await
}
/// Recursively resolve a value from a delegate macro which is expected to
/// match only keywords and return any subexpressions in a datastructure
///
/// If this is used with syntax that matches an expression macro, names bound
/// in the enclosing scope will not be correctly matched and the conversion at
/// the end will fail.
pub async fn recur_call<T: TryFromExpr>(&mut self, mt: MacTree) -> OrcRes<T> {
let resolved = self.recur(mt).await;
let lowered = lower(&resolved, Substack::Bottom).await;
self.exec(lowered).await
}
/// Normalize a value, run an expression to completion.
pub async fn exec<T: TryFromExpr>(&mut self, val: impl ToExpr) -> OrcRes<T> {
self.handle.exec(val).await
}
@@ -100,28 +105,33 @@ pub(crate) struct MacroBuilder {
body_consts: Vec<GenMember>,
}
impl MacroBuilder {
pub(crate) fn rule<const N: usize, R: ToExpr>(
pub(crate) fn rule<const N: usize>(
mut self,
pat: MacTreeSeq,
body: [impl AsyncFn(RuleCtx, [MacTree; N]) -> R + 'static; 1],
body: impl AsyncFn(RuleCtx, [MacTree; N]) -> OrcRes<MacTree> + 'static,
) -> Self {
let [body] = body;
let body = Rc::new(body);
let name = &body_name(self.own_kws[0], self.body_consts.len());
self.body_consts.extend(cnst(
true,
name,
new_atom(MacroBodyArgCollector {
argc: N,
arg_stack: None,
args: Vec::new(),
cb: Rc::new(move |rec, argv| {
let arr = argv.into_iter().collect_array::<N>().expect("argc should enforce the length");
let body = body.clone();
Box::pin(async move { body(rec, arr).await.to_gen().await })
}),
}),
));
self.body_consts.extend(lazy(true, name, async |_| {
MemKind::Const(if N == 0 {
exec(async move |handle| {
let empty = std::iter::empty().collect_array::<N>().unwrap();
Ok(new_atom(body(RuleCtx { handle }, empty).await?))
})
.await
} else {
new_atom(MacroBodyArgCollector {
argc: N,
args: Vec::new(),
cb: Rc::new(move |rec, argv| {
let arr =
argv.into_iter().collect_array::<N>().expect("argc should enforce the length");
let body = body.clone();
Box::pin(async move { body(rec, arr).await.map(new_atom).to_gen().await })
}),
})
})
}));
self.patterns.push(pat);
self
}
@@ -199,17 +209,26 @@ macro_rules! mactreev_impl {
}).at(orchid_base::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "Val" $arg:expr) => {
$crate::macros::utils::mactreev_impl!(@RECUR $ret "Val" $arg ;)
};
(@RECUR $ret:ident "Val" $arg:expr ; $($tail:tt)*) => {
$ret.push(
$crate::macros::mactree::MacTok::Value($arg)
$crate::macros::mactree::MacTok::Value(orchid_extension::ToExpr::to_expr($arg).await)
.at(orchid_base::Pos::Inherit)
);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "push" $arg:expr) => {
$crate::macros::utils::mactreev_impl!(@RECUR $ret "push" $arg ;)
};
(@RECUR $ret:ident "push" $arg:expr ; $($tail:tt)*) => {
$ret.push($arg);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "pushv" $arg:expr) => {
$crate::macros::utils::mactreev_impl!(@RECUR $ret "pushv" $arg ;)
};
(@RECUR $ret:ident "pushv" $arg:expr ; $($tail:tt)*) => {
let $crate::macros::mactree::MacTok::S(_, body) = $arg.tok() else {
panic!("pushv used with non-vec value")
@@ -226,9 +245,15 @@ macro_rules! mactreev_impl {
).at(orchid_base::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "l" $argh:tt $(:: $arg:tt)+ ($($body:tt)*) $($tail:tt)*) => {
(@RECUR $ret:ident "l" $arg:literal ($($body:tt)*) $($tail:tt)*) => {
assert!(
$arg.contains("::"),
"{} was treated as a name, but it doesn't have a namespace prefix",
$arg
);
let sym = orchid_base::Sym::parse($arg).await.expect("Empty string in sym literal in Rust");
$ret.push(MacTok::Lambda(
MacTok::Name(sym!($argh $(:: $arg)+).await).at(orchid_base::Pos::Inherit),
MacTok::Name(sym).at(orchid_base::Pos::Inherit),
$crate::macros::utils::mactreev!($($body)*)
).at(orchid_base::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
@@ -239,12 +264,9 @@ macro_rules! mactreev_impl {
"{} was treated as a name, but it doesn't have a namespace prefix",
$name
);
let sym = orchid_base::Sym::parse(
$name
).await.expect("Empty string in sym literal in Rust");
let sym = orchid_base::Sym::parse($name).await.expect("Empty string in sym literal in Rust");
$ret.push(
$crate::macros::mactree::MacTok::Name(sym)
.at(orchid_base::Pos::Inherit)
$crate::macros::mactree::MacTok::Name(sym).at(orchid_base::Pos::Inherit)
);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
@@ -278,20 +300,6 @@ macro_rules! mactreev_impl {
);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident $ns:ident :: $nhead:tt $($tail:tt)*) => {
$crate::macros::utils::mactreev_impl!(@NAME_MUNCHER $ret ($ns :: $nhead) $($tail)*)
};
(@NAME_MUNCHER $ret:ident ($($munched:tt)*) :: $name:tt $($tail:tt)*) => {
$crate::macros::utils::mactreev_impl!(@NAME_MUNCHER $ret ($($munched)* :: $name) $($tail)*)
};
(@NAME_MUNCHER $ret:ident ($($munched:tt)*) $($tail:tt)*) => {
let sym = orchid_base::sym!($($munched)*);
$ret.push(
$crate::macros::mactree::MacTok::Name(sym)
.at(orchid_base::Pos::Inherit)
);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
() => { Vec::new() };
}
macro_rules! mactreev {