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

@@ -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()),