From b44f3c18321947b715bbf1eb45b91061241d2e5a Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Sat, 11 Apr 2026 11:00:22 +0000 Subject: [PATCH] Updated macro system to process and return mactree and then lower it separately to gexpr --- orchid-extension/src/parser.rs | 21 ++- orchid-std/src/macros/let_line.rs | 13 +- orchid-std/src/macros/lower.rs | 80 ++++++++++ orchid-std/src/macros/macro_lib.rs | 121 ++++++++++----- orchid-std/src/macros/macro_system.rs | 2 - orchid-std/src/macros/match_macros.rs | 148 ++++++++---------- orchid-std/src/macros/mod.rs | 2 +- orchid-std/src/macros/postmac.rs | 53 ------- orchid-std/src/macros/resolve.rs | 157 +++++--------------- orchid-std/src/macros/stdlib/funnctional.rs | 9 +- orchid-std/src/macros/stdlib/option.rs | 39 ++--- orchid-std/src/macros/stdlib/record.rs | 32 ++-- orchid-std/src/macros/stdlib/tuple.rs | 126 ++++++++-------- orchid-std/src/macros/utils.rs | 126 ++++++++-------- 14 files changed, 460 insertions(+), 469 deletions(-) create mode 100644 orchid-std/src/macros/lower.rs delete mode 100644 orchid-std/src/macros/postmac.rs diff --git a/orchid-extension/src/parser.rs b/orchid-extension/src/parser.rs index 5131fac..8935d48 100644 --- a/orchid-extension/src/parser.rs +++ b/orchid-extension/src/parser.rs @@ -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; @@ -118,7 +118,7 @@ impl<'a> ParsCtx<'a> { pub fn module(&self) -> Sym { self.module.clone() } } -type BoxConstCallback = Box LocalBoxFuture<'static, GExpr>>; +type BoxConstCallback = Box FnOnce(ConstCtx<'a>) -> LocalBoxFuture<'a, GExpr>>; task_local! { static CONST_TBL: RefCell, 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, 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(&self, names: [&Sym; N]) -> [OrcRes; N] { self.names(names).collect::>().await.try_into().expect("Lengths must match") } + pub async fn exec(&mut self, val: impl ToExpr) -> OrcRes { + 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 } diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs index 8947632..cde1fdf 100644 --- a/orchid-std/src/macros/let_line.rs +++ b/orchid-std/src/macros/let_line.rs @@ -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(¯o_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))); diff --git a/orchid-std/src/macros/lower.rs b/orchid-std/src/macros/lower.rs new file mode 100644 index 0000000..6c3c209 --- /dev/null +++ b/orchid-std/src/macros/lower.rs @@ -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::>() + .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 +} diff --git a/orchid-std/src/macros/macro_lib.rs b/orchid-std/src/macros/macro_lib.rs index a80e1eb..93680a6 100644 --- a/orchid-std/src/macros/macro_lib.rs +++ b/orchid-std/src/macros/macro_lib.rs @@ -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 { prefix("macros", [ fun(true, "resolve", async |tpl: TAtom| { - 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> = - 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> = - 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(), ]), ]) diff --git a/orchid-std/src/macros/macro_system.rs b/orchid-std/src/macros/macro_system.rs index 7583f4b..9e5b216 100644 --- a/orchid-std/src/macros/macro_system.rs +++ b/orchid-std/src/macros/macro_system.rs @@ -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()), ] } } diff --git a/orchid-std/src/macros/match_macros.rs b/orchid-std/src/macros/match_macros.rs index ceb26b8..67faeec 100644 --- a/orchid-std/src/macros/match_macros.rs +++ b/orchid-std/src/macros/match_macros.rs @@ -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, - 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, + 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>> { + h.exec::>>(call(mat, value)).await } 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| { + 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>, matcher: TAtom| { 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> = - cx.exec(cx.recur(mactree!(macros::common::semi_list "push" rules.clone();))).await?; - let mut rule_atoms = Vec::<(TAtom, Expr)>::new(); + cx.recur_call(mactree!("macros::common::semi_list" "push" rules.clone())).await?; + let mut rule_atoms = Vec::<(TAtom, TAtom)>::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 { [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, _)>> { + 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> = - 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 { [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 { [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(), ]) } diff --git a/orchid-std/src/macros/mod.rs b/orchid-std/src/macros/mod.rs index afe6982..5f977c8 100644 --- a/orchid-std/src/macros/mod.rs +++ b/orchid-std/src/macros/mod.rs @@ -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; diff --git a/orchid-std/src/macros/postmac.rs b/orchid-std/src/macros/postmac.rs deleted file mode 100644 index 02dfc55..0000000 --- a/orchid-std/src/macros/postmac.rs +++ /dev/null @@ -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() { - assert_ne!( - TypeId::of::(), - TypeId::of::(), - "Cannot output GExpr, use PostMac::boxed or PostMac::boxed_clone" - ); -} - -struct PostMacCx<'a>(PhantomData<&'a ()>); -impl PostMacCx<'_> { - pub fn ex(&self, val: PostMac) -> T { val.0 } -} - -#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct PostMac(T); -impl PostMac { - pub fn new(t: T) -> Self { - assert_not_gexpr::(); - Self(t) - } -} -impl + Clone + 'static> PostMac> { - pub fn with(f: impl for<'a> FnOnce(PostMacCx<'a>) -> T + Clone + 'static) -> Self { - assert_not_gexpr::(); - PostMac(ToExprFuture(f(PostMacCx(PhantomData)))) - } -} -impl PostMac { - pub fn atom(self) -> PostMacAtom { PostMac(Box::new(self.0)) } -} -impl From for PostMac { - fn from(value: T) -> Self { Self(value) } -} -pub type PostMacAtom = PostMac>; -impl Atomic for PostMacAtom { - type Data = (); - type Variant = OwnedVariant; - fn reg_methods() -> MethodSetBuilder { MethodSetBuilder::new() } -} -impl OwnedAtom for PostMacAtom { - type Refs = Never; - async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } -} diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index ab654ef..19ab295 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -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, - 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>, } -async fn resolve_one(ctx: &mut ResolveCtx<'_>, arg_stk: ArgStack, value: &MacTree) -> PostMacAtom { +async fn resolve_one(ctx: &mut ResolveCtx<'_>, value: &MacTree) -> Option { 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( async fn resolve_seq( ctx: &mut ResolveCtx<'_>, - arg_stk: ArgStack, - val: MacTreeSeq, + val: &MacTreeSeq, fallback_pos: Pos, -) -> PostMacAtom { +) -> Option { 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::>(); @@ -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::>() - .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()), diff --git a/orchid-std/src/macros/stdlib/funnctional.rs b/orchid-std/src/macros/stdlib/funnctional.rs index 71eaadd..25d0c77 100644 --- a/orchid-std/src/macros/stdlib/funnctional.rs +++ b/orchid-std/src/macros/stdlib/funnctional.rs @@ -4,8 +4,11 @@ use crate::macros::utils::{build_macro, mactree, mactreev}; pub async fn gen_functional_macro_lib() -> Vec { 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()]) } diff --git a/orchid-std/src/macros/stdlib/option.rs b/orchid-std/src/macros/stdlib/option.rs index a06a104..3f5d49d 100644 --- a/orchid-std/src/macros/stdlib/option.rs +++ b/orchid-std/src/macros/stdlib/option.rs @@ -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 { prefix("std::option", [ - fun(false, "is_some_body", |sub: TAtom, val: OrcOpt| { + fun(false, "is_some_body", |sub: Expr, val: OrcOpt| { 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 { }, ), 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 = - 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(), ]) } diff --git a/orchid-std/src/macros/stdlib/record.rs b/orchid-std/src/macros/stdlib/record.rs index 36abbb8..3e4482b 100644 --- a/orchid-std/src/macros/stdlib/record.rs +++ b/orchid-std/src/macros/stdlib/record.rs @@ -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 { 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> = - 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, 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, TAtom)> = + 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()]) } diff --git a/orchid-std/src/macros/stdlib/tuple.rs b/orchid-std/src/macros/stdlib/tuple.rs index 3760fc9..ad81fb0 100644 --- a/orchid-std/src/macros/stdlib/tuple.rs +++ b/orchid-std/src/macros/stdlib/tuple.rs @@ -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 { prefix("std::tuple", [ - build_macro(None, ["t"]) - .rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [async |mut cx, [elements]| { - let tup: HomoTpl> = 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> = + 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, -) -> OrcRes { +) -> OrcRes { let tup: HomoTpl> = - 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 = - 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> = 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 = + 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>, - tail: OrcOpt>, + children: HomoTpl, + tail: OrcOpt, value: HomoTpl, ) -> impl Future { exec(async move |mut h| -> OrcRes>> { @@ -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)), } diff --git a/orchid-std/src/macros/utils.rs b/orchid-std/src/macros/utils.rs index 6111c80..891eda7 100644 --- a/orchid-std/src/macros/utils.rs +++ b/orchid-std/src/macros/utils.rs @@ -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; #[derive(Clone)] pub struct MacroBodyArgCollector { argc: usize, - arg_stack: Option, args: Args, cb: Rc 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::::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::::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::::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(&mut self, mt: MacTree) -> OrcRes { + 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(&mut self, val: impl ToExpr) -> OrcRes { self.handle.exec(val).await } @@ -100,28 +105,33 @@ pub(crate) struct MacroBuilder { body_consts: Vec, } impl MacroBuilder { - pub(crate) fn rule( + pub(crate) fn rule( mut self, pat: MacTreeSeq, - body: [impl AsyncFn(RuleCtx, [MacTree; N]) -> R + 'static; 1], + body: impl AsyncFn(RuleCtx, [MacTree; N]) -> OrcRes + '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::().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::().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::().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 {