New macro system and stdlib additions

This commit is contained in:
2025-11-21 14:25:03 +01:00
parent b77653f841
commit 603efef28e
230 changed files with 3033 additions and 16640 deletions

View File

@@ -4,12 +4,13 @@ use never::Never;
use orchid_base::format::fmt;
use orchid_extension::atom::{Atomic, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::expr::Expr;
use orchid_extension::gen_expr::GExpr;
use crate::macros::mactree::{MacTok, MacTree, map_mactree};
use crate::macros::mactree::{MacTok, MacTree};
#[derive(Clone)]
pub struct InstantiateTplCall {
@@ -35,24 +36,24 @@ impl OwnedAtom for InstantiateTplCall {
self.clone().call(arg).await
}
async fn call(mut self, arg: Expr) -> GExpr {
exec("macros::instantiate_tpl", async move |mut h| {
exec(async move |mut h| {
match h.exec::<TAtom<MacTree>>(arg.clone()).await {
Err(_) => panic!("Expected a macro param, found {}", fmt(&arg, arg.ctx().i()).await),
Ok(t) => self.argv.push(own(t).await),
Err(_) => panic!("Expected a macro param, found {}", fmt(&arg, &i()).await),
Ok(t) => self.argv.push(own(&t).await),
};
if self.argv.len() < self.argc {
return self.to_expr().await;
return self.to_gen().await;
}
let mut args = self.argv.into_iter();
let ret = map_mactree(&self.tpl, &mut false, &mut |mt| match mt.tok() {
let ret = self.tpl.map(&mut false, &mut |mt| match mt.tok() {
MacTok::Slot => Some(args.next().expect("Not enough arguments to fill all slots")),
_ => None,
});
assert!(args.next().is_none(), "Too many arguments for all slots");
ret.to_expr().await
ret.to_gen().await
})
.await
.to_expr()
.to_gen()
.await
}
}

View File

@@ -11,11 +11,12 @@ use orchid_base::parse::{
use orchid_base::sym;
use orchid_base::tree::Paren;
use orchid_extension::atom::TAtom;
use orchid_extension::context::i;
use orchid_extension::conv::TryFromExpr;
use orchid_extension::gen_expr::{atom, call, sym_ref};
use orchid_extension::parser::{ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser};
use crate::macros::mactree::{MacTok, MacTree, glossary_v, map_mactree_v};
use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq};
use crate::macros::ph_lexer::PhAtom;
#[derive(Default)]
@@ -40,21 +41,18 @@ impl Parser for LetLine {
let aliased = parse_tokv(tail, &ctx).await;
Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |ctx| {
let rep = Reporter::new();
let dealiased = dealias_mac_v(aliased, &ctx, &rep).await;
let macro_input = MacTok::S(Paren::Round, dealiased).at(sr.pos());
let macro_input =
MacTok::S(Paren::Round, dealias_mac_v(&aliased, &ctx, &rep).await).at(sr.pos());
if let Some(e) = rep.errv() {
return Err(e);
}
Ok(call(sym_ref(sym!(macros::lower; ctx.i()).await), [call(
sym_ref(sym!(macros::resolve; ctx.i()).await),
[atom(macro_input)],
)]))
Ok(call(sym_ref(sym!(macros::resolve; i())), [atom(macro_input)]))
})])
}
}
pub async fn dealias_mac_v(aliased: Vec<MacTree>, ctx: &ConstCtx, rep: &Reporter) -> Vec<MacTree> {
let keys = glossary_v(&aliased).collect_vec();
pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx, rep: &Reporter) -> 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)));
while let Some((canonical, local)) = stream.next().await {
@@ -65,13 +63,13 @@ pub async fn dealias_mac_v(aliased: Vec<MacTree>, ctx: &ConstCtx, rep: &Reporter
},
}
}
map_mactree_v(&aliased, &mut false, &mut |tree| match &*tree.tok {
aliased.map(&mut false, &mut |tree| match &*tree.tok {
MacTok::Name(n) => names.get(n).map(|new_n| MacTok::Name(new_n.clone()).at(tree.pos())),
_ => None,
})
}
pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> Vec<MacTree> {
pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> MacTreeSeq {
if let Some((idx, arg)) = line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) {
let (head, lambda) = line.split_at(idx as u32);
let (_, body) = lambda.pop_front().unwrap();
@@ -86,14 +84,14 @@ pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> Vec<MacTree>
.await,
),
};
all
MacTreeSeq::new(all)
} else {
parse_tokv_no_lambdas(&line, ctx).await
MacTreeSeq::new(parse_tokv_no_lambdas(&line, ctx).await)
}
}
async fn parse_tokv_no_lambdas(line: &[PTokTree], ctx: &impl ParseCtx) -> Vec<MacTree> {
stream::iter(line).filter_map(|tt| parse_tok(tt, ctx)).collect().await
stream::iter(line).filter_map(|tt| parse_tok(tt, ctx)).collect::<Vec<_>>().await
}
pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> Option<MacTree> {
@@ -115,7 +113,7 @@ pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> Option<MacTree>
},
PTok::Handle(expr) => match TAtom::<PhAtom>::try_from_expr(expr.clone()).await {
Err(_) => MacTok::Value(expr.clone()),
Ok(ta) => MacTok::Ph(ta.value.to_full(ta.ctx()).await),
Ok(ta) => MacTok::Ph(ta.value.to_full().await),
},
PTok::NewExpr(never) => match *never {},
PTok::LambdaHead(_) => panic!("Lambda-head handled in the sequence parser"),

View File

@@ -1,97 +1,65 @@
use hashbrown::HashMap;
use itertools::{Itertools, chain};
use orchid_base::error::Reporter;
use orchid_base::{clone, sym};
use orchid_base::sym;
use orchid_extension::atom::TAtom;
use orchid_extension::atom_owned::own;
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::func_atom::Lambda;
use orchid_extension::gen_expr::{call, sym_ref};
use orchid_extension::reflection::{ReflMemKind, refl};
use orchid_extension::tree::{GenMember, MemKind, fun, lazy, prefix};
use substack::Substack;
use orchid_extension::tree::{GenMember, fun, prefix};
use crate::MacTok;
use crate::macros::macro_value::{Macro, Matcher};
use crate::macros::mactree::{LowerCtx, MacTree, Ph};
use crate::macros::resolve::{ResolveCtx, resolve};
use crate::macros::utils::{mactree, mactreev, mk_macro};
use crate::macros::mactree::MacTree;
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
pub fn gen_macro_lib() -> Vec<GenMember> {
pub async fn gen_macro_lib() -> Vec<GenMember> {
prefix("macros", [
fun(true, "lower", |tpl: TAtom<MacTree>| async move {
let ctx = LowerCtx { sys: tpl.untyped.ctx().clone(), rep: &Reporter::new() };
let res = own(tpl).await.lower(ctx, Substack::Bottom).await;
if let Some(e) = Reporter::new().errv() { Err(e) } else { Ok(res) }
}),
fun(true, "recur", async |tpl: TAtom<MacTree>| {
call(sym_ref(sym!(macros::lower; tpl.i()).await), [call(
sym_ref(sym!(macros::resolve; tpl.i()).await),
[tpl.to_expr().await],
)])
}),
fun(true, "resolve", |tpl: TAtom<MacTree>| async move {
exec("macros::resolve", async move |mut h| {
let ctx = tpl.ctx().clone();
let root = refl(&ctx);
let tpl = own(tpl.clone()).await;
let mut macros = HashMap::new();
for n in tpl.glossary() {
if let Ok(ReflMemKind::Const) = root.get_by_path(n).await.map(|m| m.kind()) {
let Ok(mac) = h.exec::<TAtom<Macro>>(sym_ref(n.clone())).await else { continue };
let mac = own(mac).await;
macros.entry(mac.canonical_name(&ctx).await).or_insert(mac);
}
}
let mut named = HashMap::new();
let mut priod = Vec::new();
for (_, mac) in macros.iter() {
for rule in mac.0.rules.iter() {
if rule.glossary.is_subset(tpl.glossary()) {
match &rule.pattern {
Matcher::Named(m) =>
named.entry(m.head()).or_insert(Vec::new()).push((m, mac, rule)),
Matcher::Priod(p) => priod.push((mac.0.prio, (p, mac, rule))),
}
}
}
}
let priod = priod.into_iter().sorted_unstable_by_key(|(p, _)| *p).map(|(_, r)| r).collect();
let mut rctx = ResolveCtx { h, ctx: ctx.clone(), named, priod };
let resolve_res = resolve(&mut rctx, &tpl).await;
std::mem::drop(rctx);
match resolve_res {
Some(out_tree) => out_tree.to_expr().await,
None => tpl.to_expr().await,
}
})
.await
}),
fun(true, "resolve", async |tpl: TAtom<MacTree>| resolve(own(&tpl).await).await),
// TODO test whether any of this worked
lazy(true, "common", async |_, ctx| {
let add_macro = {
clone!(ctx);
mk_macro(Some(1), ["+"], [(
mactreev!(ctx.i(); "...$" lhs 0 macros::common::+ "...$" rhs 1),
Lambda::new("std::number::add", async move |lhs: TAtom<MacTree>, rhs: TAtom<MacTree>| {
mactree!(ctx.i(); std::number::add
(macros::recur "'" lhs.ex();)
(macros::recur "'" rhs.ex();)
)
}),
)])
};
let mul_macro = mk_macro(Some(2), ["*"], [(
mactreev!(ctx.i(); "...$" lhs 0 macros::common::* "...$" rhs 1),
Lambda::new("std::number::mul", async |lhs: TAtom<MacTree>, rhs: TAtom<MacTree>| {
mactree!(lhs.ctx().i(); std::number::mul
(macros::recur "'" lhs.ex();)
(macros::recur "'" rhs.ex();)
)
}),
)]);
MemKind::Mod { members: chain!(add_macro, mul_macro).collect_vec() }
}),
prefix("common", [
build_macro(None, ["..", "_"]).finish(),
build_macro(Some(1), ["+"])
.rule(mactreev!("...$" lhs 0 macros::common::+ "...$" rhs 1), [async |[lhs, rhs]| {
call(sym_ref(sym!(std::number::add; i())), [resolve(lhs).await, resolve(rhs).await])
}])
.finish(),
build_macro(Some(2), ["*"])
.rule(mactreev!("...$" lhs 0 macros::common::* "...$" rhs 1), [async |[lhs, rhs]| {
call(sym_ref(sym!(std::number::mul; i())), [resolve(lhs).await, resolve(rhs).await])
}])
.finish(),
build_macro(None, ["comma_list", ","])
.rule(
mactreev!(macros::common::comma_list ( "...$" head 0 macros::common::, "...$" tail 1)),
[async |[head, tail]| {
call(sym_ref(sym!(std::tuple::cat; i())), [
call(sym_ref(sym!(std::tuple::one; i())), [head.to_gen().await]),
resolve(mactree!(macros::common::comma_list "push" tail ;)).await,
])
}],
)
.rule(mactreev!(macros::common::comma_list ( "...$" final_tail 0 )), [async |[tail]| {
call(sym_ref(sym!(std::tuple::one; i())), [tail.to_gen().await])
}])
.rule(mactreev!(macros::common::comma_list()), [async |[]| {
sym_ref(sym!(std::tuple::empty; i()))
}])
.finish(),
build_macro(None, ["semi_list", ";"])
.rule(
mactreev!(macros::common::semi_list ( "...$" head 0 macros::common::; "...$" tail 1)),
[async |[head, tail]| {
call(sym_ref(sym!(std::tuple::cat; i())), [
call(sym_ref(sym!(std::tuple::one; i())), [resolve(head).await]),
resolve(mactree!(macros::common::semi_list "push" tail ;)).await,
])
}],
)
.rule(mactreev!(macros::common::semi_list ( "...$" final_tail 0 )), [async |[tail]| {
call(sym_ref(sym!(std::tuple::one; i())), [resolve(tail).await])
}])
.rule(mactreev!(macros::common::semi_list()), [async |[]| {
sym_ref(sym!(std::tuple::empty; i()))
}])
.finish(),
]),
])
}

View File

@@ -12,14 +12,15 @@ use orchid_base::parse::{
use orchid_base::tree::{Paren, Token};
use orchid_base::{clone, sym};
use orchid_extension::atom::TAtom;
use orchid_extension::context::i;
use orchid_extension::conv::{ToExpr, TryFromExpr};
use orchid_extension::gen_expr::{atom, call, sym_ref};
use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser};
use crate::macros::let_line::{dealias_mac_v, parse_tokv};
use crate::macros::macro_value::{Macro, MacroData, Matcher, Rule};
use crate::macros::mactree::{glossary_v, map_mactree_v};
use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher};
use crate::macros::macro_value::{Macro, MacroData, Rule};
use crate::macros::mactree::MacTreeSeq;
use crate::macros::rule::matcher::Matcher;
use crate::{Int, MacTok};
#[derive(Default)]
@@ -114,7 +115,7 @@ impl Parser for MacroLine {
};
let pattern = parse_tokv(pattern, &ctx).await;
let mut placeholders = Vec::new();
map_mactree_v(&pattern, &mut false, &mut |tok| {
pattern.map(&mut false, &mut |tok| {
if let MacTok::Ph(ph) = tok.tok() {
placeholders.push((ph.clone(), tok.pos()))
}
@@ -123,23 +124,21 @@ impl Parser for MacroLine {
let mut body_mactree = parse_tokv(body, &ctx).await;
for (ph, ph_pos) in placeholders.iter().rev() {
let name = ctx.module().suffix([ph.name.clone()], ctx.i()).await;
body_mactree = vec![
MacTok::Lambda(MacTok::Name(name).at(ph_pos.clone()), body_mactree).at(ph_pos.clone()),
]
body_mactree =
MacTreeSeq::new([
MacTok::Lambda(MacTok::Name(name).at(ph_pos.clone()), body_mactree).at(ph_pos.clone())
])
}
let body_sr = body.sr();
rules.push((name.clone(), placeholders, pattern));
lines.push(ParsedLine::cnst(&sr, &line.output, true, name, async move |ctx| {
let rep = Reporter::new();
let body = dealias_mac_v(body_mactree, &ctx, &rep).await;
let body = dealias_mac_v(&body_mactree, &ctx, &rep).await;
let macro_input = MacTok::S(Paren::Round, body).at(body_sr.pos());
if let Some(e) = rep.errv() {
return Err(e);
}
Ok(call(sym_ref(sym!(macros::lower; ctx.i()).await), [call(
sym_ref(sym!(macros::resolve; ctx.i()).await),
[macro_input.to_expr().await],
)]))
Ok(call(sym_ref(sym!(macros::resolve; i())), [macro_input.to_gen().await]))
}))
}
let mac_cell = Rc::new(OnceCell::new());
@@ -152,20 +151,15 @@ impl Parser for MacroLine {
let rep = Reporter::new();
let rules = rules.borrow_mut().take().expect("once cell initializer runs");
let rules = stream::iter(rules)
.then(|(body_name, placeholders, pattern_macv)| {
.then(|(body_name, placeholders, pattern_rel)| {
let cctx = &cctx;
let rep = &rep;
let prio = &prio;
async move {
let pattern_abs = dealias_mac_v(pattern_macv, cctx, rep).await;
let glossary = glossary_v(&pattern_abs).collect();
let pattern_res = match prio {
None => NamedMatcher::new(&pattern_abs, cctx.i()).await.map(Matcher::Named),
Some(_) => PriodMatcher::new(&pattern_abs, cctx.i()).await.map(Matcher::Priod),
};
let pattern = dealias_mac_v(&pattern_rel, cctx, rep).await;
let pattern_res = Matcher::new(pattern.clone()).await;
let placeholders = placeholders.into_iter().map(|(ph, _)| ph.name).collect_vec();
match pattern_res {
Ok(pattern) => Some(Rule { body_name, pattern, glossary, placeholders }),
Ok(matcher) => Some(Rule { body_name, matcher, pattern, placeholders }),
Err(e) => {
rep.report(e);
None

View File

@@ -1,15 +1,16 @@
use orchid_base::interner::Interner;
use never::Never;
use orchid_base::name::Sym;
use orchid_base::reqnot::Receipt;
use orchid_base::sym;
use orchid_extension::atom::{AtomDynfo, AtomicFeatures};
use orchid_extension::context::i;
use orchid_extension::entrypoint::ExtReq;
use orchid_extension::lexer::LexerObj;
use orchid_extension::other_system::SystemHandle;
use orchid_extension::parser::ParserObj;
use orchid_extension::system::{System, SystemCard};
use orchid_extension::system_ctor::SystemCtor;
use orchid_extension::tree::GenMember;
use orchid_extension::tree::{GenMember, merge_trivial};
use crate::macros::instantiate_tpl::InstantiateTplCall;
use crate::macros::let_line::LetLine;
@@ -17,8 +18,10 @@ use crate::macros::macro_lib::gen_macro_lib;
use crate::macros::macro_line::MacroLine;
use crate::macros::macro_value::Macro;
use crate::macros::mactree_lexer::MacTreeLexer;
use crate::macros::match_macros::gen_match_macro_lib;
use crate::macros::ph_lexer::{PhAtom, PhLexer};
use crate::macros::requests::MacroReq;
use crate::macros::std_macros::gen_std_macro_lib;
use crate::macros::utils::MacroBodyArgCollector;
use crate::{MacTree, StdSystem};
#[derive(Default)]
@@ -32,26 +35,30 @@ impl SystemCtor for MacroSystem {
}
impl SystemCard for MacroSystem {
type Ctor = Self;
type Req = MacroReq;
type Req = Never;
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomDynfo>>> {
[
Some(InstantiateTplCall::dynfo()),
Some(MacTree::dynfo()),
Some(Macro::dynfo()),
Some(PhAtom::dynfo()),
Some(MacroBodyArgCollector::dynfo()),
]
}
}
impl System for MacroSystem {
async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { todo!("Handle {req:?}") }
async fn prelude(i: &Interner) -> Vec<Sym> {
async fn request(_: ExtReq<'_>, req: Never) -> Receipt<'_> { match req {} }
async fn prelude() -> Vec<Sym> {
vec![
sym!(macros::resolve; i).await,
sym!(macros::common::+; i).await,
sym!(macros::common::*; i).await,
sym!(macros::common::+; i()),
sym!(macros::common::*; i()),
sym!(macros::common::,; i()),
sym!(std::tuple::t; i()),
]
}
fn lexers() -> Vec<LexerObj> { vec![&MacTreeLexer, &PhLexer] }
fn parsers() -> Vec<ParserObj> { vec![&LetLine, &MacroLine] }
fn env() -> Vec<GenMember> { gen_macro_lib() }
async fn env() -> Vec<GenMember> {
merge_trivial([gen_macro_lib().await, gen_std_macro_lib().await, gen_match_macro_lib().await])
}
}

View File

@@ -1,15 +1,15 @@
use std::borrow::Cow;
use std::rc::Rc;
use hashbrown::HashSet;
use never::Never;
use orchid_base::interner::Tok;
use orchid_base::name::Sym;
use orchid_extension::atom::Atomic;
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
use orchid_extension::system::SysCtx;
use orchid_extension::context::i;
use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher};
use crate::macros::mactree::MacTreeSeq;
use crate::macros::rule::matcher::Matcher;
#[derive(Debug)]
pub struct MacroData {
@@ -21,23 +21,18 @@ pub struct MacroData {
#[derive(Clone, Debug)]
pub struct Macro(pub Rc<MacroData>);
impl Macro {
pub async fn canonical_name(&self, ctx: &SysCtx) -> Sym {
self.0.module.suffix([self.0.rules[0].body_name.clone()], ctx.i()).await
pub async fn canonical_name(&self) -> Sym {
self.0.module.suffix([self.0.rules[0].body_name.clone()], &i()).await
}
}
#[derive(Debug)]
pub struct Rule {
pub pattern: Matcher,
pub glossary: HashSet<Sym>,
pub pattern: MacTreeSeq,
pub matcher: Matcher,
pub placeholders: Vec<Tok<String>>,
pub body_name: Tok<String>,
}
#[derive(Debug)]
pub enum Matcher {
Named(NamedMatcher),
Priod(PriodMatcher),
}
impl Atomic for Macro {
type Data = ();
type Variant = OwnedVariant;

View File

@@ -5,10 +5,9 @@ use std::rc::Rc;
use futures::FutureExt;
use futures::future::join_all;
use hashbrown::HashSet;
use itertools::Itertools;
use orchid_api_derive::Coding;
use orchid_base::error::{OrcErrv, Reporter, mk_errv};
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants, fmt};
use orchid_base::error::OrcErrv;
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
use orchid_base::interner::Tok;
use orchid_base::location::Pos;
use orchid_base::name::Sym;
@@ -16,16 +15,89 @@ use orchid_base::tl_cache;
use orchid_base::tree::{Paren, indent};
use orchid_extension::atom::Atomic;
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
use orchid_extension::conv::ToExpr;
use orchid_extension::expr::Expr;
use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref};
use orchid_extension::system::SysCtx;
use substack::Substack;
#[derive(Clone)]
pub struct LowerCtx<'a> {
pub sys: SysCtx,
pub rep: &'a Reporter,
fn union_rc_sets(seq: impl IntoIterator<Item = Rc<HashSet<Sym>>>) -> Rc<HashSet<Sym>> {
let mut acc = Rc::<HashSet<Sym>>::default();
for right in seq {
if acc.is_empty() {
acc = right;
continue;
}
if right.is_empty() {
continue;
}
acc = match (Rc::try_unwrap(acc), Rc::try_unwrap(right)) {
(Ok(mut left), Ok(right)) => {
left.extend(right);
Rc::new(left)
},
(Ok(mut owned), Err(borrowed)) | (Err(borrowed), Ok(mut owned)) => {
owned.extend(borrowed.iter().cloned());
Rc::new(owned)
},
(Err(left), Err(right)) => Rc::new(left.union(&right).cloned().collect()),
}
}
acc
}
#[derive(Debug, Clone)]
pub struct MacTreeSeq {
pub items: Rc<Vec<MacTree>>,
pub top_glossary: Rc<HashSet<Sym>>,
pub glossary: Rc<HashSet<Sym>>,
}
impl MacTreeSeq {
pub fn new(i: impl IntoIterator<Item = MacTree>) -> Self {
let mut items = Vec::new();
let mut top_glossary = HashSet::new();
let mut glossary = HashSet::new();
for item in i {
glossary.extend(item.glossary().iter().cloned());
if let MacTok::Name(n) = item.tok() {
top_glossary.insert(n.clone());
}
items.push(item);
}
Self { items: Rc::new(items), top_glossary: Rc::new(top_glossary), glossary: Rc::new(glossary) }
}
pub fn map<F: FnMut(MacTree) -> Option<MacTree>>(&self, changed: &mut bool, map: &mut F) -> Self {
Self::new(self.items.iter().map(|tree| ro(changed, |changed| tree.map(changed, map))))
}
pub fn glossary(&self) -> &HashSet<Sym> { &self.glossary }
pub fn concat(self, other: Self) -> Self {
if self.items.is_empty() {
return other;
} else if other.items.is_empty() {
return self;
}
let items = match (Rc::try_unwrap(self.items), Rc::try_unwrap(other.items)) {
(Ok(mut left), Ok(mut right)) => {
left.append(&mut right);
left
},
(Ok(mut left), Err(right)) => {
left.extend_from_slice(&right[..]);
left
},
(Err(left), Ok(mut right)) => {
right.splice(0..0, left.iter().cloned());
right
},
(Err(left), Err(right)) => left.iter().chain(&right[..]).cloned().collect(),
};
Self {
items: Rc::new(items),
top_glossary: union_rc_sets([self.top_glossary, other.top_glossary]),
glossary: union_rc_sets([self.glossary, other.glossary]),
}
}
}
impl Format for MacTreeSeq {
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
mtreev_fmt(&self.items[..], c).await
}
}
#[derive(Debug, Clone)]
@@ -38,66 +110,21 @@ impl MacTree {
pub fn tok(&self) -> &MacTok { &self.tok }
pub fn pos(&self) -> Pos { self.pos.clone() }
pub fn glossary(&self) -> &HashSet<Sym> { &self.glossary }
pub async fn lower(&self, ctx: LowerCtx<'_>, args: Substack<'_, Sym>) -> GExpr {
let expr = match self.tok() {
MacTok::Bottom(e) => bot(e.clone()),
MacTok::Lambda(arg, body) => {
let MacTok::Name(name) = &*arg.tok else {
return bot(mk_errv(
ctx.sys.i().i("Syntax error after macros").await,
"This token ends up as a binding, consider replacing it with a name",
[arg.pos()],
));
};
let arg_pos = args.len() as u64;
let args = args.push(name.clone());
let body = match &body[..] {
[] => bot(mk_errv(
ctx.sys.i().i("Empty lambda body").await,
"Lambdas must evaluate to an expression",
[self.pos()],
)),
[f, argv @ ..] => call(
f.lower(ctx.clone(), args.clone()).boxed_local().await,
lower_v(argv, ctx, args).await,
),
};
lambda(arg_pos, body)
pub fn map<F: FnMut(Self) -> Option<Self>>(&self, changed: &mut bool, map: &mut F) -> Self {
let tok = match map(self.clone()) {
Some(new_tok) => {
*changed = true;
return new_tok;
},
MacTok::Name(name) => match args.iter().enumerate().find(|(_, n)| *n == name) {
None => sym_ref(name.clone()),
Some((i, _)) => arg((args.len() - i - 1) as u64),
None => match &*self.tok {
MacTok::Lambda(arg, body) =>
MacTok::Lambda(ro(changed, |changed| arg.map(changed, map)), body.map(changed, map)),
MacTok::Name(_) | MacTok::Value(_) => return self.clone(),
MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) => return self.clone(),
MacTok::S(p, body) => MacTok::S(*p, body.map(changed, map)),
},
MacTok::Ph(ph) => {
return bot(mk_errv(
ctx.sys.i().i("Placeholder in value").await,
format!("Placeholder {ph} is only supported in macro patterns"),
[self.pos()],
));
},
MacTok::S(Paren::Round, body) => match &body[..] {
[fun, argv @ ..] => call(
fun.lower(ctx.clone(), args.clone()).boxed_local().await,
lower_v(argv, ctx, args).await,
),
[] =>
return bot(mk_errv(
ctx.sys.i().i("Empty ()").await,
"Empty () is not a meaningful expression",
[self.pos()],
)),
},
MacTok::S(..) => {
return bot(mk_errv(
ctx.sys.i().i("[] or {} after macros").await,
format!("{} didn't match any macro", fmt(self, ctx.sys.i()).await),
[self.pos()],
));
},
MacTok::Slot => panic!("Uninstantiated template should never be exposed"),
MacTok::Value(v) => v.clone().to_expr().await,
};
expr.at(self.pos())
if *changed { tok.at(self.pos()) } else { self.clone() }
}
}
impl Atomic for MacTree {
@@ -119,35 +146,31 @@ impl Format for MacTree {
}
}
pub async fn lower_v(v: &[MacTree], ctx: LowerCtx<'_>, args: Substack<'_, Sym>) -> Vec<GExpr> {
join_all(v.iter().map(|t| t.lower(ctx.clone(), args.clone())).collect::<Vec<_>>()).await
}
#[derive(Debug, Clone)]
pub enum MacTok {
S(Paren, Vec<MacTree>),
S(Paren, MacTreeSeq),
Name(Sym),
/// Only permitted in arguments to `instantiate_tpl`
Slot,
Value(Expr),
Lambda(MacTree, Vec<MacTree>),
Lambda(MacTree, MacTreeSeq),
/// Only permitted in "pattern" values produced by macro blocks, which are
/// never accessed as variables by usercode
Ph(Ph),
Bottom(OrcErrv),
}
impl MacTok {
pub fn build_glossary(&self) -> HashSet<Sym> {
pub fn build_glossary(&self) -> Rc<HashSet<Sym>> {
match self {
MacTok::Bottom(_) | MacTok::Ph(_) | MacTok::Slot | MacTok::Value(_) => HashSet::new(),
MacTok::Name(sym) => HashSet::from([sym.clone()]),
MacTok::S(_, body) => body.iter().flat_map(|mt| &*mt.glossary).cloned().collect(),
MacTok::Bottom(_) | MacTok::Ph(_) | MacTok::Slot | MacTok::Value(_) => Rc::default(),
MacTok::Name(sym) => Rc::new(HashSet::from([sym.clone()])),
MacTok::S(_, body) => union_rc_sets(body.items.iter().map(|mt| mt.glossary.clone())),
MacTok::Lambda(arg, body) =>
body.iter().chain([arg]).flat_map(|mt| &*mt.glossary).cloned().collect(),
union_rc_sets(body.items.iter().chain([arg]).map(|mt| mt.glossary.clone())),
}
}
pub fn at(self, pos: impl Into<Pos>) -> MacTree {
MacTree { pos: pos.into(), glossary: Rc::new(self.build_glossary()), tok: Rc::new(self) }
MacTree { pos: pos.into(), glossary: self.build_glossary(), tok: Rc::new(self) }
}
}
impl Format for MacTok {
@@ -157,7 +180,7 @@ impl Format for MacTok {
Self::Lambda(arg, b) => tl_cache!(Rc<Variants>: Rc::new(Variants::default()
.unbounded("\\{0} {1l}")
.bounded("(\\{0} {1b})")))
.units([arg.print(c).boxed_local().await, mtreev_fmt(b, c).await]),
.units([arg.print(c).boxed_local().await, b.print(c).await]),
Self::Name(n) => format!("{n}").into(),
Self::Ph(ph) => format!("{ph}").into(),
Self::S(p, body) => match *p {
@@ -165,7 +188,7 @@ impl Format for MacTok {
Paren::Curly => tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("{{0b}}"))),
Paren::Square => tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("[{0b}]"))),
}
.units([mtreev_fmt(body, c).await]),
.units([body.print(c).await]),
Self::Slot => "$SLOT".into(),
Self::Bottom(err) if err.len() == 1 => format!("Bottom({}) ", err.one().unwrap()).into(),
Self::Bottom(err) => format!("Botttom(\n{}) ", indent(&err.to_string())).into(),
@@ -177,7 +200,7 @@ pub async fn mtreev_fmt<'b>(
v: impl IntoIterator<Item = &'b MacTree>,
c: &(impl FmtCtx + ?Sized),
) -> FmtUnit {
FmtUnit::sequence(" ", None, join_all(v.into_iter().map(|t| t.print(c))).await)
FmtUnit::sequence("", " ", "", None, join_all(v.into_iter().map(|t| t.print(c))).await)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -203,36 +226,6 @@ pub enum PhKind {
Vector { at_least_one: bool, priority: u8 },
}
pub fn map_mactree<F: FnMut(MacTree) -> Option<MacTree>>(
src: &MacTree,
changed: &mut bool,
map: &mut F,
) -> MacTree {
let tok = match map(src.clone()) {
Some(new_tok) => {
*changed = true;
return new_tok;
},
None => match &*src.tok {
MacTok::Lambda(arg, body) => MacTok::Lambda(
ro(changed, |changed| map_mactree(arg, changed, map)),
map_mactree_v(body, changed, map),
),
MacTok::Name(_) | MacTok::Value(_) => return src.clone(),
MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) => return src.clone(),
MacTok::S(p, body) => MacTok::S(*p, map_mactree_v(body, changed, map)),
},
};
if *changed { tok.at(src.pos()) } else { src.clone() }
}
pub fn map_mactree_v<F: FnMut(MacTree) -> Option<MacTree>>(
src: &[MacTree],
changed: &mut bool,
map: &mut F,
) -> Vec<MacTree> {
src.iter().map(|tree| ro(changed, |changed| map_mactree(tree, changed, map))).collect_vec()
}
/// reverse "or". Inside, the flag is always false, but raising it will raise
/// the outside flag too.
fn ro<T>(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T {
@@ -241,7 +234,3 @@ fn ro<T>(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T {
*flag |= new_flag;
val
}
pub fn glossary_v(src: &[MacTree]) -> impl Iterator<Item = Sym> {
src.iter().flat_map(|mt| mt.glossary()).cloned()
}

View File

@@ -12,21 +12,21 @@ use orchid_extension::tree::{GenTok, GenTokTree, x_tok};
use crate::macros::instantiate_tpl::InstantiateTplCall;
use crate::macros::let_line::parse_tok;
use crate::macros::mactree::{MacTok, MacTree};
use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq};
#[derive(Default)]
pub struct MacTreeLexer;
impl Lexer for MacTreeLexer {
const CHAR_FILTER: &'static [RangeInclusive<char>] = &['\''..='\''];
async fn lex<'a>(tail: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> {
async fn lex<'a>(tail: &'a str, lctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> {
let Some(tail2) = tail.strip_prefix('\'') else {
return Err(err_not_applicable(ctx.i()).await);
return Err(err_not_applicable().await);
};
let tail3 = tail2.trim_start();
let mut args = Vec::new();
return match mac_tree(tail3, &mut args, ctx).await {
return match mac_tree(tail3, &mut args, lctx).await {
Ok((tail4, mactree)) => {
let range = ctx.pos_tt(tail, tail4);
let range = lctx.pos_tt(tail, tail4);
let tok = match &args[..] {
[] => x_tok(mactree).await,
_ => {
@@ -38,7 +38,7 @@ impl Lexer for MacTreeLexer {
};
Ok((tail4, tok.at(range)))
},
Err(e) => Ok((tail2, GenTok::Bottom(e).at(ctx.pos_lt(1, tail2)))),
Err(e) => Ok((tail2, GenTok::Bottom(e).at(lctx.pos_lt(1, tail2)))),
};
async fn mac_tree<'a>(
tail: &'a str,
@@ -51,7 +51,8 @@ impl Lexer for MacTreeLexer {
return loop {
let tail2 = body_tail.trim_start();
if let Some(tail3) = tail2.strip_prefix(*rp) {
break Ok((tail3, MacTok::S(*paren, items).at(ctx.pos_tt(tail, tail3).pos())));
let tok = MacTok::S(*paren, MacTreeSeq::new(items));
break Ok((tail3, tok.at(ctx.pos_tt(tail, tail3).pos())));
} else if tail2.is_empty() {
return Err(mk_errv(
ctx.i().i("Unclosed block").await,
@@ -83,7 +84,7 @@ impl Lexer for MacTreeLexer {
body.push(body_tok);
tail3 = tail5;
}
Ok((tail3, MacTok::Lambda(param, body).at(ctx.pos_tt(tail, tail3).pos())))
Ok((tail3, MacTok::Lambda(param, MacTreeSeq::new(body)).at(ctx.pos_tt(tail, tail3).pos())))
} else {
let (tail2, sub) = ctx.recurse(tail).await?;
let parsed = parse_tok(&sub, ctx).await.expect("Unexpected invalid token");

View File

@@ -0,0 +1,178 @@
use std::borrow::Cow;
use async_fn_stream::stream;
use futures::future::join_all;
use futures::{Stream, StreamExt, stream};
use never::Never;
use orchid_api::ExprTicket;
use orchid_api_derive::Coding;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::format::fmt;
use orchid_base::name::Sym;
use orchid_base::sym;
use orchid_extension::atom::{Atomic, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::{ExecHandle, exec};
use orchid_extension::expr::{Expr, ExprHandle};
use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref};
use orchid_extension::tree::{GenMember, fun, prefix};
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::std::reflection::sym_atom::SymAtom;
use crate::std::tuple::Tuple;
use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple, api};
#[derive(Clone, Coding)]
pub struct MatcherData {
keys: Vec<api::TStrv>,
matcher: 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.to_gen().await, [val.to_gen().await]))
.await
}
pub fn keys(&self) -> impl Stream<Item = Sym> {
stream(async |mut h| {
for tk in &self.keys {
h.emit(Sym::from_api(*tk, &i()).await).await
}
})
}
}
#[derive(Clone)]
pub struct MatcherAtom {
/// The names that subresults may be bound to
pub(super) keys: Vec<Sym>,
/// Takes the value-to-be-matched, returns an `option (tuple T1..TN)` of the
/// subresults to be bound to the names returned by [Self::keys]
pub(super) matcher: Expr,
}
impl Atomic for MatcherAtom {
type Data = MatcherData;
type Variant = OwnedVariant;
}
impl OwnedAtom for MatcherAtom {
type Refs = Never;
async fn val(&self) -> std::borrow::Cow<'_, Self::Data> {
Cow::Owned(MatcherData {
keys: self.keys.iter().map(|t| t.to_api()).collect(),
matcher: self.matcher.handle().ticket(),
})
}
}
pub async fn gen_match_macro_lib() -> Vec<GenMember> {
prefix("pattern", [
fun(
true,
"match_one",
async |mat: TAtom<MatcherAtom>, value: Expr, then: Expr, default: Expr| {
exec(async move |mut h| match mat.run_matcher(&mut h, value).await? {
OrcOpt(Some(values)) =>
Ok(call(then.to_gen().await, join_all(values.0.into_iter().map(|x| x.to_gen())).await)),
OrcOpt(None) => Ok(default.to_gen().await),
})
.await
},
),
fun(true, "matcher", async |names: HomoTpl<TAtom<SymAtom>>, matcher: Expr| MatcherAtom {
keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0, &i()).await)).await,
matcher,
}),
build_macro(None, ["match", "match_rule", "_row", "=>"])
.rule(mactreev!("pattern::match" { "..$" rules 0 }), [async |[rules]| {
exec(async move |mut h| {
let rule_lines = h
.exec::<TAtom<Tuple>>(call(sym_ref(sym!(macros::resolve; i())), [
mactree!(macros::common::semi_list "push" rules.clone();).to_gen().await,
]))
.await?;
let mut rule_atoms = Vec::<(TAtom<MatcherAtom>, Expr)>::new();
for line_exprh in rule_lines.iter() {
let line_mac = h
.exec::<TAtom<MacTree>>(Expr::from_handle(ExprHandle::from_ticket(*line_exprh).await))
.await?;
let Tpl((matcher, body)) = h
.exec(call(sym_ref(sym!(macros::resolve; i())), [
mactree!(pattern::_row "push" own(&line_mac).await ;).to_gen().await,
]))
.await?;
rule_atoms.push((matcher, body));
}
let base_case = lambda(0, [bot(mk_errv(
i().i("No branches match").await,
"None of the pattern provided matches this value",
[rules.pos()],
))]);
let match_expr = stream::iter(rule_atoms.into_iter().rev())
.fold(base_case, async |tail, (mat, body)| {
lambda(0, [call(sym_ref(sym!(pattern::match_one; i())), [
mat.to_gen().await,
arg(0),
body.to_gen().await,
call(tail, [arg(0)]),
])])
})
.await;
Ok(match_expr)
})
.await
}])
.rule(mactreev!(pattern::match_rule (( "...$" pattern 0 ))), [async |[pattern]| {
resolve(mactree!(pattern::match_rule "push" pattern; )).await
}])
.rule(mactreev!(pattern::_row ( "...$" pattern 0 pattern::=> "...$" value 1 )), [
async |[pattern, mut value]| {
exec(async move |mut h| -> OrcRes<Tpl<(TAtom<MatcherAtom>, GExpr)>> {
let Ok(pat) = h
.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve; i())), [
mactree!(pattern::match_rule "push" pattern.clone();).to_gen().await,
]))
.await
else {
return Err(mk_errv(
i().i("Invalid pattern").await,
format!("Could not parse {} as a match pattern", fmt(&pattern, &i()).await),
[pattern.pos()],
));
};
value = (pat.keys())
.fold(value, async |value, name| mactree!("l_" name; ( "push" value ; )))
.await;
Ok(Tpl((pat, resolve(value).await)))
})
.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 |[name]| {
let MacTok::Name(name) = name.tok() else {
return Err(mk_errv(
i().i("pattern 'ref' requires a name to bind to").await,
format!(
"'ref' was interpreted as a binding matcher, \
but it was followed by {} instead of a name",
fmt(&name, &i()).await
),
[name.pos()],
));
};
Ok(MatcherAtom {
keys: vec![name.clone()],
matcher: sym_ref(sym!(pattern::ref_body; i())).to_expr().await,
})
}])
.finish(),
])
}

View File

@@ -6,10 +6,11 @@ pub mod macro_system;
mod macro_value;
pub mod mactree;
mod mactree_lexer;
pub mod match_macros;
mod ph_lexer;
mod requests;
mod resolve;
mod rule;
pub mod std_macros;
mod utils;
use mactree::{MacTok, MacTree};

View File

@@ -4,8 +4,8 @@ use orchid_base::format::FmtUnit;
use orchid_base::parse::{name_char, name_start};
use orchid_extension::atom::Atomic;
use orchid_extension::atom_thin::{ThinAtom, ThinVariant};
use orchid_extension::context::i;
use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable};
use orchid_extension::system::SysCtx;
use orchid_extension::tree::{GenTokTree, x_tok};
use crate::macros::mactree::{Ph, PhKind};
@@ -13,17 +13,15 @@ use crate::macros::mactree::{Ph, PhKind};
#[derive(Clone, Coding)]
pub struct PhAtom(orchid_api::TStr, PhKind);
impl PhAtom {
pub async fn to_full(&self, ctx: &SysCtx) -> Ph {
Ph { kind: self.1, name: ctx.i().ex(self.0).await }
}
pub async fn to_full(&self) -> Ph { Ph { kind: self.1, name: i().ex(self.0).await } }
}
impl Atomic for PhAtom {
type Data = Self;
type Variant = ThinVariant;
}
impl ThinAtom for PhAtom {
async fn print(&self, ctx: SysCtx) -> FmtUnit {
Ph { name: ctx.i().ex(self.0).await, kind: self.1 }.to_string().into()
async fn print(&self) -> FmtUnit {
Ph { name: i().ex(self.0).await, kind: self.1 }.to_string().into()
}
}
@@ -54,7 +52,7 @@ impl Lexer for PhLexer {
(prio_num, tail)
} else {
return Err(mk_errv(
ctx.ctx.i().i("Invalid priority, must be 0-255").await,
i().i("Invalid priority, must be 0-255").await,
format!("{prio} is not a valid placeholder priority"),
[ctx.pos_lt(prio.len(), tail)],
));
@@ -70,10 +68,10 @@ impl Lexer for PhLexer {
let (name, priority, tail) = name_and_prio(tail, ctx).await?;
(tail, name, PhKind::Vector { at_least_one: true, priority })
} else {
return Err(err_not_applicable(ctx.ctx.i()).await);
return Err(err_not_applicable().await);
}
};
let ph_atom = PhAtom(ctx.ctx.i().i::<String>(name).await.to_api(), phkind);
let ph_atom = PhAtom(i().i::<String>(name).await.to_api(), phkind);
Ok((tail, x_tok(ph_atom).await.at(ctx.pos_tt(line, tail))))
}
}

View File

@@ -1,67 +0,0 @@
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use crate::api;
use crate::macros::mactree::PhKind;
/* TODO:
Create handlers and wrappers for these, probably expose MacTree to other crates.
Define new extension binary to test the request functionality.
*/
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extendable]
pub enum MacroReq {
CreateMacro(CreateMacro),
CreateQuote(CreateQuote),
}
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(MacroReq)]
pub struct CreateMacro {
pub module: api::TStrv,
pub prio: Option<u64>,
pub rules: Vec<CreateRule>,
}
impl Request for CreateMacro {
type Response = api::ExprTicket;
}
#[derive(Clone, Debug, Coding)]
pub struct CreateRule {
pub pattern: Vec<api::TokenTree>,
pub body_name: api::TStr,
}
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(MacroReq)]
pub struct CreateQuote(MsgMacTree);
impl Request for CreateQuote {
type Response = api::ExprTicket;
}
#[derive(Clone, Debug, Coding)]
pub struct MsgMacTree {
pub tok: MsgMacTok,
pub location: api::Location,
}
#[derive(Clone, Debug, Coding)]
pub enum MsgMacTok {
S(api::Paren, Vec<MsgMacTree>),
Name(api::TStrv),
/// Only permitted in arguments to `instantiate_tpl`
Slot,
Value(api::ExprTicket),
Lambda(Box<MsgMacTree>, Vec<MsgMacTree>),
/// Only permitted in "pattern" values produced by macro blocks, which are
/// never accessed as variables by usercode
Ph(MsgPh),
Bottom(Vec<api::OrcError>),
}
#[derive(Clone, Debug, Coding)]
pub struct MsgPh {
kind: PhKind,
name: api::TStr,
}

View File

@@ -1,91 +1,275 @@
use futures::FutureExt;
use hashbrown::HashMap;
use std::ops::{Add, Range};
use async_fn_stream::stream;
use futures::{FutureExt, StreamExt};
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use orchid_base::error::mk_errv;
use orchid_base::format::fmt;
use orchid_base::location::Pos;
use orchid_base::name::Sym;
use orchid_base::sym;
use orchid_base::tree::Paren;
use orchid_extension::atom::TAtom;
use orchid_extension::atom_owned::own;
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::ExecHandle;
use orchid_extension::gen_expr::{GExpr, call, sym_ref};
use orchid_extension::system::SysCtx;
use orchid_extension::coroutine_exec::{ExecHandle, exec};
use orchid_extension::gen_expr::{GExpr, bot, call, lambda, sym_ref};
use orchid_extension::reflection::{ReflMemKind, refl};
use subslice_offset::SubsliceOffset;
use substack::Substack;
use crate::macros::macro_value::{Macro, Rule};
use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher};
use crate::macros::mactree::MacTreeSeq;
use crate::macros::rule::state::{MatchState, StateEntry};
use crate::{MacTok, MacTree};
pub struct ResolveCtx<'a> {
pub ctx: SysCtx,
pub h: ExecHandle<'a>,
pub named: HashMap<Sym, Vec<(&'a NamedMatcher, &'a Macro, &'a Rule)>>,
pub priod: Vec<(&'a PriodMatcher, &'a Macro, &'a Rule)>,
pub async fn resolve(tpl: MacTree) -> GExpr {
exec(async move |mut h| {
let root = refl();
let mut macros = HashMap::new();
for n in tpl.glossary() {
if let Ok(ReflMemKind::Const) = root.get_by_path(n).await.map(|m| m.kind()) {
let Ok(mac) = h.exec::<TAtom<Macro>>(sym_ref(n.clone())).await else { continue };
let mac = own(&mac).await;
macros.entry(mac.canonical_name().await).or_insert(mac);
}
}
let mut exclusive = Vec::new();
let mut prios = Vec::<u64>::new();
let mut priod = Vec::<FilteredMacroRecord>::new();
for (_, mac) in macros.iter() {
let mut record = FilteredMacroRecord { mac, rules: Vec::new() };
for (rule_i, rule) in mac.0.rules.iter().enumerate() {
if rule.pattern.glossary.is_subset(tpl.glossary()) {
record.rules.push(rule_i);
}
}
if !record.rules.is_empty() {
match mac.0.prio {
None => exclusive.push(record),
Some(prio) => {
let i = prios.partition_point(|p| *p > prio);
prios.insert(i, prio);
priod.insert(i, record);
},
}
}
}
let mut rctx = ResolveCtx { h, exclusive, priod };
resolve_one(&mut rctx, Substack::Bottom, &tpl).await
})
.await
}
pub async fn resolve(ctx: &mut ResolveCtx<'_>, value: &MacTree) -> Option<MacTree> {
/// Rules belonging to one macro that passed a particular filter
pub struct FilteredMacroRecord<'a> {
mac: &'a Macro,
/// The rules in increasing order of index
rules: Vec<usize>,
}
struct ResolveCtx<'a> {
pub h: ExecHandle<'a>,
/// If these overlap, that's a compile-time error
pub exclusive: Vec<FilteredMacroRecord<'a>>,
/// If these overlap, the priorities decide the order. In case of a tie, the
/// order is unspecified
pub priod: Vec<FilteredMacroRecord<'a>>,
}
async fn resolve_one(
ctx: &mut ResolveCtx<'_>,
arg_stk: Substack<'_, Sym>,
value: &MacTree,
) -> GExpr {
match value.tok() {
MacTok::Ph(_) | MacTok::Slot => panic!("Forbidden element in value mactree"),
MacTok::Bottom(_) | MacTok::Value(_) | MacTok::Name(_) => None,
MacTok::Lambda(arg, body) =>
Some(MacTok::Lambda(arg.clone(), resolve_seq(ctx, body).await?).at(value.pos())),
MacTok::S(ptyp, body) => Some(MacTok::S(*ptyp, resolve_seq(ctx, body).await?).at(value.pos())),
MacTok::Bottom(err) => bot(err.clone()),
MacTok::Value(v) => v.clone().to_gen().await,
MacTok::Name(n) => sym_ref(n.clone()),
MacTok::Lambda(arg, body) => {
let MacTok::Name(name) = &*arg.tok else {
return bot(mk_errv(
i().i("Syntax error after macros").await,
"This token ends up as a binding, consider replacing it with a name",
[arg.pos()],
));
};
let arg_pos = arg_stk.len() as u64;
let arg_stk = arg_stk.push(name.clone());
lambda(arg_pos, [resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await])
},
MacTok::S(Paren::Round, body) => resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await,
MacTok::S(..) => bot(mk_errv(
i().i("Leftover [] or {} not matched by macro").await,
format!("{} was not matched by any macro", fmt(value, &i()).await),
[value.pos()],
)),
}
}
pub async fn resolve_seq(ctx: &mut ResolveCtx<'_>, val: &[MacTree]) -> Option<Vec<MacTree>> {
let mut any_changed = false;
let mut i = 0;
let mut val = val.to_vec();
'all_named: while i < val.len() {
'one_named: {
let MacTok::Name(key) = val[i].tok() else { break 'one_named };
let Some(options) = ctx.named.get(key) else { break 'one_named };
let matches = (options.iter())
.filter_map(|r| Some((r.1, r.2, r.0.apply(&val[i..], |_| false)?)))
.collect_vec();
match matches.len() {
0 => break 'one_named,
1 => {
any_changed = true;
let (mac, rule, (state, tail)) = matches.into_iter().exactly_one().unwrap();
let end = val.len() - tail.len();
let body_call = mk_body_call(mac, rule, &state, &ctx.ctx).await;
std::mem::drop(state);
val.splice(i..end, [MacTok::Value(ctx.h.register(body_call).await).at(Pos::None)]);
i = end;
},
2.. => todo!("Named macros conflict!"),
type XMatches<'a> = Vec<(Range<usize>, &'a Macro, &'a Rule, MatchState<'a>)>;
/// find the subsection of the slice that satisfies both the lower and upper
/// limit.
fn subsection<T>(
slice: &[T],
lower_limit: impl FnMut(&T) -> bool,
mut upper_limit: impl FnMut(&T) -> bool,
) -> Range<usize> {
let start = slice.partition_point(lower_limit);
let len = slice[start..].partition_point(|t| !upper_limit(t));
start..start + len
}
async fn resolve_seq(
ctx: &mut ResolveCtx<'_>,
arg_stk: Substack<'_, Sym>,
val: MacTreeSeq,
fallback_pos: Pos,
) -> GExpr {
if val.items.is_empty() {
return bot(mk_errv(
i().i("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],
));
}
// A sorted collection of overlapping but non-nested matches to exclusive
// macros
let mut x_matches: XMatches = Vec::new();
let top_glossary = val.top_glossary.clone();
let mut new_val = val.items.to_vec();
'x_macros: for x in &ctx.exclusive {
let mut rules_iter = x.rules.iter();
let ((before, state, after), rule) = 'rules: loop {
let Some(ridx) = rules_iter.next() else { continue 'x_macros };
let rule = &x.mac.0.rules[*ridx];
if rule.pattern.top_glossary.is_subset(&top_glossary)
&& let Some(record) = rule.matcher.apply(&val.items[..], &|_| true).await
{
break 'rules (record, rule);
};
};
let new_r = (before.len()..new_val.len() - after.len(), x.mac, rule, state);
// elements that overlap with us
let overlap =
subsection(&x_matches[..], |r| new_r.0.start < r.0.end, |r| r.0.start < new_r.0.end);
let overlapping = &x_matches[overlap.clone()];
// elements that fully contain us
let geq_range =
subsection(overlapping, |r| r.0.start <= new_r.0.start, |r| new_r.0.end <= r.0.end);
let geq = &overlapping[geq_range.clone()];
// if any of these is equal to us, all of them must be, otherwise the larger
// ranges would have overridden the smaller ones
if let Some(example) = geq.first() {
// if they are equal to us, record the conflict.
if example.0 == new_r.0 {
let idx = (x_matches.subslice_offset(geq))
.expect("this slice is statically derived from x_matches");
x_matches.insert(idx, new_r);
}
continue 'all_named;
// either way, we matched so no further rules can run.
continue 'x_macros;
}
i += 1;
}
for (matcher, mac, rule) in &ctx.priod {
let Some(state) = matcher.apply(&val, |_| false) else { continue };
return Some(vec![
MacTok::Value(ctx.h.register(mk_body_call(mac, rule, &state, &ctx.ctx).await).await)
.at(Pos::None),
]);
}
for expr in val.iter_mut() {
if let Some(new) = resolve(ctx, expr).boxed_local().await {
*expr = new;
any_changed = true;
// elements we fully contain. Equal ranges have been handled above
let lt_range =
subsection(overlapping, |r| new_r.0.start <= r.0.start, |r| r.0.end <= new_r.0.end);
let lt = &overlapping[lt_range.clone()];
if lt.is_empty() {
// an empty range
let i = x_matches.partition_point(|r| r.0.start < new_r.0.start);
x_matches.insert(i, new_r);
} else {
let lt_start =
x_matches.subslice_offset(overlapping).expect("Slice statically derived from x_matches");
x_matches.splice(lt_start..lt_start + lt_range.len(), [new_r]);
}
}
if any_changed { Some(val) } else { None }
// apply exclusive matches
if !x_matches.is_empty() {
// ranges of indices into x_matches which setwise conflict with each other.
// Pairwise conflict reporting is excess noise, but a single conflict error
// doesn't reveal where within the parenthesized block to look, so it's easiest
// to group them setwise even if these sets may associate macros which don't
// directly conflict.
let conflict_sets = (0..x_matches.len()).map(|x| x..x + 1).coalesce(|lran, rran| {
// each index was mapped to a range that contains only itself. Now we check if
// the last match in the first range overlaps the first match in the second
// range, and combine them if this is the case.
if x_matches[rran.start].0.start < x_matches[lran.end].0.end {
Ok(lran.start..rran.end)
} else {
Err((lran, rran))
}
});
let mac_conflict_tk = i().i("Macro conflict").await;
let error = conflict_sets
.filter(|r| 1 < r.len())
.map(|set| {
mk_errv(
mac_conflict_tk.clone(),
"Multiple partially overlapping syntax elements detected. \n\
Try parenthesizing whichever side is supposed to be the subexpression.",
x_matches[set].iter().flat_map(|rec| rec.3.names()).flat_map(|name| name.1).cloned(),
)
})
.reduce(|l, r| l + r);
if let Some(error) = error {
return bot(error);
}
// 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 = ctx.h.register(mk_body_call(mac, rule, &state, pos.clone()).await).await;
new_val.splice(range, [MacTok::Value(subex).at(pos)]);
}
};
// 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<_>>();
for FilteredMacroRecord { mac, rules } in &ctx.priod {
for ridx in rules {
let rule = &mac.0.rules[*ridx];
if !rule.pattern.top_glossary.is_subset(&top_glossary) {
continue;
}
let Some((pre, state, suf)) = rule.matcher.apply(&new_val, &|_| true).await else { continue };
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 = ctx.h.register(mk_body_call(mac, rule, &state, pos.clone()).await).await;
std::mem::drop(state);
new_val.splice(range, [MacTok::Value(subex).at(pos)]);
}
}
let exprs = stream(async |mut h| {
for mt in new_val {
h.emit(resolve_one(ctx, arg_stk.clone(), &mt).await).await
}
})
.collect::<Vec<_>>()
.boxed_local()
.await;
exprs.into_iter().reduce(|f, x| call(f, [x])).expect(
"We checked first that it isn't empty, and named macros get replaced with their results",
)
}
async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, ctx: &SysCtx) -> GExpr {
async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, pos: Pos) -> GExpr {
let mut call_args = vec![];
for name in rule.placeholders.iter() {
call_args.push(match state.get(name).expect("Missing state entry for placeholder") {
StateEntry::Scalar(scal) => (**scal).clone().to_expr().await,
StateEntry::Vec(vec) => MacTok::S(Paren::Round, vec.to_vec()).at(Pos::None).to_expr().await,
StateEntry::Scalar(scal) => (**scal).clone().to_gen().await,
StateEntry::Vec(vec) =>
MacTok::S(Paren::Round, MacTreeSeq::new(vec.iter().cloned())).at(Pos::None).to_gen().await,
});
}
call(sym_ref(sym!(macros::lower; ctx.i()).await), [call(
sym_ref(mac.0.module.suffix([rule.body_name.clone()], ctx.i()).await),
call_args,
)])
call(sym_ref(mac.0.module.suffix([rule.body_name.clone()], &i()).await), call_args)
.at(pos.clone())
}

View File

@@ -2,9 +2,10 @@ use futures::FutureExt;
use futures::future::join_all;
use itertools::Itertools;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::interner::{Interner, Tok};
use orchid_base::interner::Tok;
use orchid_base::join_ok;
use orchid_base::side::Side;
use orchid_extension::context::i;
use super::shared::{AnyMatcher, ScalMatcher, VecMatcher};
use super::vec_attrs::vec_attrs;
@@ -31,29 +32,29 @@ fn scal_cnt<'a>(iter: impl Iterator<Item = &'a MacTree>) -> usize {
iter.take_while(|expr| vec_attrs(expr).is_none()).count()
}
pub async fn mk_any(pattern: &[MacTree], i: &Interner) -> OrcRes<AnyMatcher> {
pub async fn mk_any(pattern: &[MacTree]) -> OrcRes<AnyMatcher> {
let left_split = scal_cnt(pattern.iter());
if pattern.len() <= left_split {
return Ok(AnyMatcher::Scalar(mk_scalv(pattern, i).await?));
return Ok(AnyMatcher::Scalar(mk_scalv(pattern).await?));
}
let (left, not_left) = pattern.split_at(left_split);
let right_split = not_left.len() - scal_cnt(pattern.iter().rev());
let (mid, right) = not_left.split_at(right_split);
join_ok! {
left = mk_scalv(left, i).await;
mid = mk_vec(mid, i).await;
right = mk_scalv(right, i).await;
left = mk_scalv(left).await;
mid = mk_vec(mid).await;
right = mk_scalv(right).await;
}
Ok(AnyMatcher::Vec { left, mid, right })
}
/// Pattern MUST NOT contain vectorial placeholders
async fn mk_scalv(pattern: &[MacTree], i: &Interner) -> OrcRes<Vec<ScalMatcher>> {
join_all(pattern.iter().map(|pat| mk_scalar(pat, i))).await.into_iter().collect()
async fn mk_scalv(pattern: &[MacTree]) -> OrcRes<Vec<ScalMatcher>> {
join_all(pattern.iter().map(mk_scalar)).await.into_iter().collect()
}
/// Pattern MUST start and end with a vectorial placeholder
pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes<VecMatcher> {
pub async fn mk_vec(pattern: &[MacTree]) -> OrcRes<VecMatcher> {
debug_assert!(!pattern.is_empty(), "pattern cannot be empty");
debug_assert!(pattern.first().map(vec_attrs).is_some(), "pattern must start with a vectorial");
debug_assert!(pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial");
@@ -68,8 +69,8 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes<VecMatcher> {
(&[], &[]) => Ok(VecMatcher::Placeh { key, nonzero }),
(&[], _) => {
join_ok! {
sep = mk_scalv(r_sep, i).await;
right = mk_vec(r_side, i).boxed_local().await;
sep = mk_scalv(r_sep).await;
right = mk_vec(r_side).boxed_local().await;
}
Ok(VecMatcher::Scan {
direction: Side::Left,
@@ -80,8 +81,8 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes<VecMatcher> {
},
(_, &[]) => {
join_ok! {
left = mk_vec(l_side, i).boxed_local().await;
sep = mk_scalv(l_sep, i).await;
left = mk_vec(l_side).boxed_local().await;
sep = mk_scalv(l_sep).await;
}
Ok(VecMatcher::Scan {
direction: Side::Right,
@@ -95,10 +96,10 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes<VecMatcher> {
l_side.iter().chain(r_side.iter()).filter_map(vec_attrs).collect::<Vec<_>>();
key_order.sort_by_key(|(_, prio, _)| -(*prio as i64));
join_ok! {
left = mk_vec(l_side, i).boxed_local().await;
left_sep = mk_scalv(l_sep, i).await;
right_sep = mk_scalv(r_sep, i).await;
right = mk_vec(r_side, i).boxed_local().await;
left = mk_vec(l_side).boxed_local().await;
left_sep = mk_scalv(l_sep).await;
right_sep = mk_scalv(r_sep).await;
right = mk_vec(r_side).boxed_local().await;
}
Ok(VecMatcher::Middle {
left: Box::new(left),
@@ -113,7 +114,7 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes<VecMatcher> {
}
/// Pattern MUST NOT be a vectorial placeholder
async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes<ScalMatcher> {
async fn mk_scalar(pattern: &MacTree) -> OrcRes<ScalMatcher> {
Ok(match &*pattern.tok {
MacTok::Name(n) => ScalMatcher::Name(n.clone()),
MacTok::Ph(Ph { name, kind }) => match kind {
@@ -122,10 +123,10 @@ async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes<ScalMatcher> {
},
PhKind::Scalar => ScalMatcher::Placeh { key: name.clone() },
},
MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body, i).boxed_local().await?)),
MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(&body.items).boxed_local().await?)),
MacTok::Lambda(..) =>
return Err(mk_errv(
i.i("Lambda in matcher").await,
i().i("Lambda in matcher").await,
"Lambdas can't be matched for, only generated in templates",
[pattern.pos()],
)),
@@ -136,50 +137,52 @@ async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes<ScalMatcher> {
#[cfg(test)]
mod test {
use orchid_base::interner::Interner;
use orchid_base::location::SrcRange;
use orchid_base::sym;
use orchid_base::tokens::Paren;
use orchid_extension::context::{i, mock_ctx, with_ctx};
use test_executors::spin_on;
use super::mk_any;
use crate::macros::MacTok;
use crate::macros::mactree::{Ph, PhKind};
use crate::macros::mactree::{MacTreeSeq, Ph, PhKind};
#[test]
fn test_scan() {
spin_on(async {
let i = Interner::new_master();
let ex = |tok: MacTok| async { tok.at(SrcRange::mock(&i).await.pos()) };
spin_on(with_ctx(mock_ctx(), async {
let ex = |tok: MacTok| async { tok.at(SrcRange::mock(&i()).await.pos()) };
let pattern = vec![
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: i.i("::prefix").await,
name: i().i("::prefix").await,
}))
.await,
ex(MacTok::Name(sym!(prelude::do; i).await)).await,
ex(MacTok::S(Paren::Round, vec![
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: i.i("expr").await,
}))
.await,
ex(MacTok::Name(sym!(prelude::; ; i).await)).await,
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 1, at_least_one: false },
name: i.i("rest").await,
}))
.await,
]))
ex(MacTok::Name(sym!(prelude::do; i()))).await,
ex(MacTok::S(
Paren::Round,
MacTreeSeq::new([
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: i().i("expr").await,
}))
.await,
ex(MacTok::Name(sym!(prelude::; ; i()))).await,
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 1, at_least_one: false },
name: i().i("rest").await,
}))
.await,
]),
))
.await,
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: i.i("::suffix").await,
name: i().i("::suffix").await,
}))
.await,
];
let matcher = mk_any(&pattern, &i).await.expect("This matcher isn't broken");
let matcher = mk_any(&pattern).await.expect("This matcher isn't broken");
println!("{matcher}");
})
}))
}
}

View File

@@ -1,87 +1,61 @@
use std::fmt;
use std::rc::Rc;
use itertools::Itertools;
use orchid_base::error::OrcRes;
use orchid_base::interner::{Interner, Tok};
use orchid_base::location::Pos;
use orchid_base::name::Sym;
use orchid_extension::context::i;
use super::any_match::any_match;
use super::build::{mk_any, mk_vec};
use super::shared::{AnyMatcher, VecMatcher};
use super::build::mk_any;
use super::shared::AnyMatcher;
use super::state::{MatchState, StateEntry};
use super::vec_attrs::vec_attrs;
use super::vec_match::vec_match;
use crate::macros::mactree::{Ph, PhKind};
use crate::macros::mactree::{MacTreeSeq, Ph, PhKind};
use crate::macros::{MacTok, MacTree};
pub struct NamedMatcher {
pub struct Matcher {
inner: AnyMatcher,
head: Sym,
after_tok: Tok<String>,
}
impl NamedMatcher {
pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes<Self> {
let head = match pattern.first().map(|tree| tree.tok()) {
Some(MacTok::Name(name)) => name.clone(),
_ => panic!("Named matchers must begin with a name"),
};
let after_tok = i.i("::after").await;
let inner = match pattern.last().and_then(vec_attrs).is_some() {
true => mk_any(pattern, i).await?,
false => {
let kind = PhKind::Vector { priority: 0, at_least_one: false };
let suffix = [MacTok::Ph(Ph { name: after_tok.clone(), kind }).at(Pos::None)];
mk_any(&pattern.iter().cloned().chain(suffix).collect_vec(), i).await?
},
};
Ok(Self { after_tok, inner, head })
}
pub fn head(&self) -> Sym { self.head.clone() }
/// Also returns the tail, if any, which should be matched further
/// Note that due to how priod works below, the main usable information from
/// the tail is its length
pub fn apply<'a>(
&self,
seq: &'a [MacTree],
save_loc: impl Fn(Sym) -> bool,
) -> Option<(MatchState<'a>, &'a [MacTree])> {
let mut state = any_match(&self.inner, seq, &save_loc)?;
match state.remove(self.after_tok.clone()) {
Some(StateEntry::Scalar(_)) => panic!("{} can never be a scalar entry!", self.after_tok),
Some(StateEntry::Vec(v)) => Some((state, v)),
None => Some((state, &[][..])),
}
}
}
impl fmt::Display for NamedMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) }
}
impl fmt::Debug for NamedMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "NamedMatcher({self})") }
}
pub struct PriodMatcher(VecMatcher);
impl PriodMatcher {
pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes<Self> {
assert!(
pattern.first().and_then(vec_attrs).is_some() && pattern.last().and_then(vec_attrs).is_some(),
"Prioritized matchers must start and end with a vectorial",
);
Ok(Self(mk_vec(pattern, i).await?))
impl Matcher {
pub async fn new(pattern: MacTreeSeq) -> OrcRes<Self> {
let mut pattern = Rc::unwrap_or_clone(pattern.items);
let kind = PhKind::Vector { at_least_one: false, priority: 0 };
let first = pattern.first().expect("Empty pattern is not allowed");
if vec_attrs(first).is_none() {
let pos = first.pos();
pattern.insert(0, MacTok::Ph(Ph { name: i().i("::before").await, kind }).at(pos));
}
let last = pattern.last().expect("first returned Some above");
if vec_attrs(last).is_none() {
let pos = last.pos();
pattern.insert(0, MacTok::Ph(Ph { name: i().i("::after").await, kind }).at(pos));
}
Ok(Matcher { inner: mk_any(&pattern).await? })
}
/// tokens before the offset always match the prefix
pub fn apply<'a>(
/// Also returns the head and tail, which should be matched by overarching
/// matchers attempted later.
pub async fn apply<'a>(
&self,
seq: &'a [MacTree],
save_loc: impl Fn(Sym) -> bool,
) -> Option<MatchState<'a>> {
vec_match(&self.0, seq, &save_loc)
save_loc: &dyn Fn(Sym) -> bool,
) -> Option<(&'a [MacTree], MatchState<'a>, &'a [MacTree])> {
let mut result = any_match(&self.inner, seq, &save_loc)?;
async fn remove_frame<'a>(result: &mut MatchState<'a>, key: &str) -> &'a [MacTree] {
match result.remove(i().i(key).await) {
Some(StateEntry::Scalar(_)) => panic!("{key} is defined in the constructor as a Vec"),
Some(StateEntry::Vec(v)) => v,
None => &[],
}
}
let before = remove_frame(&mut result, "::before").await;
let after = remove_frame(&mut result, "::after").await;
Some((before, result, after))
}
}
impl fmt::Display for PriodMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }
impl fmt::Display for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) }
}
impl fmt::Debug for PriodMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PriodMatcher({self})") }
impl fmt::Debug for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "NamedMatcher({self})") }
}

View File

@@ -19,7 +19,7 @@ pub fn scal_match<'a>(
(ScalMatcher::Placeh { key }, _) =>
Some(MatchState::from_ph(key.clone(), StateEntry::Scalar(expr))),
(ScalMatcher::S(c1, b_mat), MacTok::S(c2, body)) if c1 == c2 =>
any_match(b_mat, &body[..], save_loc),
any_match(b_mat, &body.items, save_loc),
_ => None,
}
}

View File

@@ -54,6 +54,9 @@ impl<'a> MatchState<'a> {
pub fn from_name(name: Sym, location: Pos) -> Self {
Self { name_posv: HashMap::from([(name, vec![location])]), placeholders: HashMap::new() }
}
pub fn names(&self) -> impl Iterator<Item = (Sym, &[Pos])> {
self.name_posv.iter().map(|(sym, vec)| (sym.clone(), &vec[..]))
}
pub fn get(&self, key: &Tok<String>) -> Option<&StateEntry<'a>> { self.placeholders.get(key) }
pub fn remove(&mut self, name: Tok<String>) -> Option<StateEntry<'a>> {
self.placeholders.remove(&name)

View File

@@ -0,0 +1,177 @@
use futures::{StreamExt, stream};
use orchid_base::error::OrcRes;
use orchid_base::sym;
use orchid_extension::atom::TAtom;
use orchid_extension::atom_owned::own;
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::expr::Expr;
use orchid_extension::gen_expr::{GExpr, call, sym_ref};
use orchid_extension::tree::{GenMember, fun, prefix};
use crate::macros::match_macros::MatcherAtom;
use crate::macros::resolve::resolve;
use crate::macros::utils::{build_macro, mactree, mactreev};
use crate::{HomoTpl, MacTree, OrcOpt, Tpl};
pub async fn gen_std_macro_lib() -> Vec<GenMember> {
prefix("std", [
prefix("option", [
fun(false, "is_some_body", |sub: TAtom<MatcherAtom>, val: OrcOpt<Expr>| {
exec(async move |mut h| {
let Some(sub_val) = val.0 else { return Ok(OrcOpt(None)) };
h.exec::<OrcOpt<Expr>>(call(sub.to_gen().await, [sub_val.to_gen().await])).await
})
}),
fun(false, "is_none_body", async |val: OrcOpt<Expr>| {
if val.0.is_none() { OrcOpt(Some(Tpl(()))) } else { OrcOpt(None) }
}),
build_macro(None, ["of", "empty"])
.rule(mactreev!(pattern::match_rule ( std::option::of "...$" sub_pattern 0)), [
|[sub]: [_; _]| {
exec(async move |mut h| {
let sub = h
.exec::<TAtom<MatcherAtom>>(
resolve(mactree!(pattern::match_rule "push" sub;)).await,
)
.await?;
Ok(MatcherAtom {
keys: sub.keys().collect().await,
matcher: h
.register(call(sym_ref(sym!(std::option::is_some_body; i())), [sub
.to_gen()
.await]))
.await,
})
})
},
])
.rule(mactreev!(pattern::match_rule(std::option::empty)), [|[]: [_; _]| {
exec(async |mut h| {
Ok(MatcherAtom {
keys: vec![],
matcher: h.register(sym_ref(sym!(std::option::is_none_body; i()))).await,
})
})
}])
.finish(),
]),
prefix("tuple", [
build_macro(None, ["t"])
.rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [|[elements]: [_; _]| {
exec(async move |mut h| {
let tup = h
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve; i())), [
mactree!((macros::common::comma_list "push" elements ;)).to_gen().await,
]))
.await?;
let val = stream::iter(&tup.0[..])
.fold(sym_ref(sym!(std::tuple::empty; i())), async |head, new| {
call(sym_ref(sym!(std::tuple::cat; i())), [
head,
call(sym_ref(sym!(std::tuple::one; i())), [call(
sym_ref(sym!(macros::resolve; i())),
[new.clone().to_gen().await],
)]),
])
})
.await;
Ok(val)
})
}])
.rule(
mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0 macros::common::..])),
[async |[elements]: [_; _]| parse_tpl(elements, Some(mactree!(macros::common::_))).await],
)
.rule(
mactreev!(pattern::match_rule(
std::tuple::t[ "...$" elements 1 macros::common::.. "...$" tail 0]
)),
[async |[elements, tail]: [_; _]| parse_tpl(elements, Some(tail)).await],
)
.rule(mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0])), [
|[elements]: [_; _]| parse_tpl(elements, None),
])
.finish(),
fun(false, "matcher_body", tuple_matcher_body),
]),
])
}
fn parse_tpl(elements: MacTree, tail_matcher: Option<MacTree>) -> impl Future<Output = GExpr> {
exec(async move |mut h| -> OrcRes<MatcherAtom> {
let tup = h
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve; i())), [
mactree!((macros::common::comma_list "push" elements ;)).to_gen().await,
]))
.await?;
let mut subs = Vec::with_capacity(tup.0.len());
for mac_a in &tup.0[..] {
let mac = own(mac_a).await;
let sub = h
.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve; i())), [
mactree!(pattern::match_rule "push" mac ;).to_gen().await,
]))
.await?;
subs.push(sub);
}
let tail_matcher = match tail_matcher {
Some(mac) => Some(
h.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve; i())), [
mactree!(pattern::match_rule "push" mac ;).to_gen().await,
]))
.await?,
),
None => None,
};
Ok(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_ref(sym!(std::tuple::matcher_body; i())), [
HomoTpl(subs).to_gen().await,
OrcOpt(tail_matcher).to_gen().await,
])
.to_expr()
.await,
})
})
}
fn tuple_matcher_body(
children: HomoTpl<TAtom<MatcherAtom>>,
tail: OrcOpt<TAtom<MatcherAtom>>,
value: HomoTpl<Expr>,
) -> impl Future<Output = GExpr> {
exec(async move |mut h| -> OrcRes<OrcOpt<GExpr>> {
if value.0.len() < children.0.len() {
return Ok(OrcOpt(None));
}
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? {
OrcOpt(None) => return Ok(OrcOpt(None)),
OrcOpt(Some(subres)) => binds.extend(subres.0),
}
}
match tail.0 {
None if children.0.len() < value.0.len() => return Ok(OrcOpt(None)),
None => (),
Some(tail_mat) => {
let tail_tpl = stream::iter(&value.0[children.0.len()..])
.fold(sym_ref(sym!(std::tuple::empty; i())), async |prefix, new| {
call(sym_ref(sym!(std::tuple::cat; i())), [prefix, new.clone().to_gen().await])
})
.await;
match tail_mat.run_matcher(&mut h, tail_tpl).await? {
OrcOpt(Some(tail_binds)) => binds.extend(tail_binds.0),
OrcOpt(None) => return Ok(OrcOpt(None)),
}
},
};
todo!()
})
}

View File

@@ -1,166 +1,275 @@
use std::borrow::Cow;
use std::rc::Rc;
use async_fn_stream::stream;
use futures::StreamExt;
use futures::future::LocalBoxFuture;
use itertools::{Itertools, chain};
use never::Never;
use orchid_base::name::{NameLike, Sym, VPath};
use orchid_extension::atom::{Atomic, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::gen_expr::sym_ref;
use orchid_extension::gen_expr::{GExpr, sym_ref};
use orchid_extension::tree::{GenMember, MemKind, cnst, lazy};
use crate::macros::macro_value::{Macro, MacroData, Matcher, Rule};
use crate::macros::mactree::map_mactree_v;
use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher};
use crate::macros::macro_value::{Macro, MacroData, Rule};
use crate::macros::mactree::MacTreeSeq;
use crate::macros::rule::matcher::Matcher;
use crate::{MacTok, MacTree};
pub(crate) fn mk_macro<B: ToExpr + Clone + 'static>(
pub type Args = Vec<MacTree>;
#[derive(Clone)]
pub struct MacroBodyArgCollector {
argc: usize,
args: Args,
cb: Rc<dyn Fn(Args) -> LocalBoxFuture<'static, GExpr>>,
}
impl Atomic for MacroBodyArgCollector {
type Data = ();
type Variant = OwnedVariant;
}
impl OwnedAtom for MacroBodyArgCollector {
type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
async fn call_ref(&self, arg: orchid_extension::expr::Expr) -> GExpr {
eprintln!("This is an intermediary value. It should never be copied");
self.clone().call(arg).await
}
async fn call(mut self, arg: orchid_extension::expr::Expr) -> GExpr {
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(own(&atom).await);
if self.argc == self.args.len() {
(self.cb)(self.args).await.to_gen().await
} else {
self.to_gen().await
}
}
}
fn body_name(name: &str, counter: usize) -> String { format!("({name})::{counter}") }
pub(crate) fn build_macro(
prio: Option<u64>,
own_kws: impl IntoIterator<Item = &'static str>,
rules: impl IntoIterator<Item = (Vec<MacTree>, B)>,
) -> Vec<GenMember> {
let own_kws = own_kws.into_iter().collect_vec();
let name = own_kws[0];
let (patterns, bodies) = rules.into_iter().unzip::<_, _, Vec<Vec<MacTree>>, Vec<B>>();
let main_const = lazy(true, name, async move |path, ctx| {
let module = (Sym::new(path.split_last_seg().1.iter().cloned(), ctx.i()).await)
.expect("Default macro in global root");
MemKind::Const(
Macro(Rc::new(MacroData {
module,
prio,
rules: stream(async |mut h| {
for (counter, pat) in patterns.into_iter().enumerate() {
let mut placeholders = Vec::new();
map_mactree_v(&pat, &mut false, &mut |tt| {
if let MacTok::Ph(ph) = &*tt.tok {
placeholders.push(ph.name.clone())
}
None
});
let pattern = match prio {
Some(_) => Matcher::Priod(PriodMatcher::new(&pat, ctx.i()).await.unwrap()),
None => Matcher::Named(NamedMatcher::new(&pat, ctx.i()).await.unwrap()),
};
h.emit(Rule {
glossary: pat.iter().flat_map(|t| t.glossary()).cloned().collect(),
pattern,
placeholders,
body_name: ctx.i().i(&format!("({name})::{counter}")).await,
})
.await;
}
})
.collect()
) -> MacroBuilder {
MacroBuilder {
prio,
own_kws: own_kws.into_iter().collect(),
patterns: Vec::new(),
body_consts: Vec::new(),
}
}
pub(crate) struct MacroBuilder {
prio: Option<u64>,
own_kws: Vec<&'static str>,
patterns: Vec<MacTreeSeq>,
body_consts: Vec<GenMember>,
}
impl MacroBuilder {
pub(crate) fn rule<const N: usize, R: ToExpr>(
mut self,
pat: MacTreeSeq,
body: [impl AsyncFn([MacTree; N]) -> R + 'static; 1],
) -> 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(match N {
0 => lazy(true, name, async move |_| {
let argv = [].into_iter().collect_array().expect("N is 0");
MemKind::Const(body(argv).await.to_gen().await)
}),
1.. => cnst(true, name, MacroBodyArgCollector {
argc: N,
args: Vec::new(),
cb: Rc::new(move |argv| {
let arr = argv.into_iter().collect_array::<N>().expect("argc should enforce the length");
let body = body.clone();
Box::pin(async move { body(arr).await.to_gen().await })
}),
}),
});
self.patterns.push(pat);
self
}
pub(crate) fn finish(self) -> Vec<GenMember> {
let Self { own_kws, prio, patterns, body_consts } = self;
let name = own_kws[0];
let main_const = lazy(true, name, async move |path| {
let module = (Sym::new(path.split_last_seg().1.iter().cloned(), &i()).await)
.expect("Default macro in global root");
MemKind::Const(
Macro(Rc::new(MacroData {
module,
prio,
rules: stream(async |mut h| {
for (counter, pattern) in patterns.into_iter().enumerate() {
let mut placeholders = Vec::new();
pattern.map(&mut false, &mut |tt| {
if let MacTok::Ph(ph) = &*tt.tok {
placeholders.push(ph.name.clone())
}
None
});
h.emit(Rule {
matcher: Matcher::new(pattern.clone()).await.unwrap(),
pattern,
placeholders,
body_name: i().i(&format!("({name})::{counter}")).await,
})
.await;
}
})
.collect()
.await,
}))
.to_gen()
.await,
}))
.to_expr()
.await,
)
});
let kw_consts = own_kws[1..].iter().flat_map(|kw| {
lazy(true, kw, async |path, ctx| {
let name = VPath::new(path.split_last_seg().1.iter().cloned())
.name_with_suffix(ctx.i().i(*kw).await)
.to_sym(ctx.i())
.await;
MemKind::Const(sym_ref(name))
})
});
let body_consts = (bodies.into_iter().enumerate())
.flat_map(|(counter, body)| cnst(false, &format!("({name})::{counter}"), body));
chain!(main_const, kw_consts, body_consts).collect()
)
});
let kw_consts = own_kws[1..].iter().flat_map(|kw| {
lazy(true, kw, async |path| {
let main_const_name = VPath::new(path.split_last_seg().1.iter().cloned())
.name_with_suffix(i().i(name).await)
.to_sym(&i())
.await;
MemKind::Const(sym_ref(main_const_name))
})
});
chain!(main_const, kw_consts, body_consts).collect()
}
}
macro_rules! mactree {
($i:expr; $($body:tt)*) => {
$crate::macros::utils::mactreev!($i; ($($body)*)).remove(0)
($($body:tt)*) => {
$crate::macros::utils::mactreev!(($($body)*)).items[0].clone()
};
}
macro_rules! mactreev {
(@RECUR $i:expr; $ret:ident) => {};
(@RECUR $i:expr; $ret:ident "..$" $name:ident $prio:literal $($tail:tt)*) => {
ret.push(MacTok::Ph(Ph{
name: i.i(stringify!($name)).await,
kind: PhKind::Vector{ at_least_one: false, priority: $prio }
}).at(Pos::Inherit));
mactreev!(@RECUR $i; $ret $($tail)*);
macro_rules! mactreev_impl {
(@RECUR $ret:ident) => {};
(@RECUR $ret:ident "..$" $name:ident $prio:literal $($tail:tt)*) => {
$ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{
name: orchid_extension::context::i().i(stringify!($name)).await,
kind: $crate::macros::mactree::PhKind::Vector{ at_least_one: false, priority: $prio }
}).at(orchid_base::location::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident "...$" $name:ident $prio:literal $($tail:tt)*) => {
$ret.push(MacTok::Ph(Ph{
name: $i.i(stringify!($name)).await,
(@RECUR $ret:ident "...$" $name:ident $prio:literal $($tail:tt)*) => {
$ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{
name: orchid_extension::context::i().i(stringify!($name)).await,
kind: $crate::macros::mactree::PhKind::Vector{ at_least_one: true, priority: $prio }
}).at(orchid_base::location::Pos::Inherit));
mactreev!(@RECUR $i; $ret $($tail)*);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident "$" $name:ident $($tail:tt)*) => {
$ret.push(MacTok::Ph(Ph{
name: $i.i(stringify!(name)).await,
(@RECUR $ret:ident "$" $name:ident $($tail:tt)*) => {
$ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{
name: orchid_extension::context::i().i(stringify!(name)).await,
kind: $crate::macros::mactree::PhKind::Scalar
}).at(orchid_base::location::Pos::Inherit));
mactreev!(@RECUR $i; $ret $($tail)*);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident "'" $arg:expr ; $($tail:tt)*) => {
$ret.push(MacTok::Value($arg).at(orchid_base::location::Pos::Inherit));
mactreev!(@RECUR $i; $ret $($tail)*);
(@RECUR $ret:ident "Val" $arg:expr ; $($tail:tt)*) => {
$ret.push(
$crate::macros::mactree::MacTok::Value($arg)
.at(orchid_base::location::Pos::Inherit)
);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident "" $arg:expr ; $($tail:tt)*) => {
(@RECUR $ret:ident "push" $arg:expr ; $($tail:tt)*) => {
$ret.push($arg);
mactreev!(@RECUR $i; $ret $($tail)*);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident "l_" $arg:expr ; ($($body:tt)*) $($tail:tt)*) => {
(@RECUR $ret:ident "l_" $arg:expr ; ($($body:tt)*) $($tail:tt)*) => {
$ret.push(MacTok::Lambda(
MacTok::Value($arg).at(orchid_base::location::Pos::Inherit),
mactreev!(i; $($body)*)
).at(Pos::Inherit));
mactreev!(@RECUR $i; $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident "l" $argh:tt $(:: $arg:tt)* ($($body:tt)*) $($tail:tt)*) => {
$ret.push(MacTok::Lambda(
MacTok::Name(sym!($argh $(:: $arg)*; $i).await).at(orchid_base::location::Pos::Inherit),
mactreev!(i; $($body)*)
MacTok::Name($arg).at(orchid_base::location::Pos::Inherit),
$crate::macros::utils::mactreev!($($body)*)
).at(orchid_base::location::Pos::Inherit));
mactreev!(@RECUR $i; $ret $($tail)*);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident ( $($body:tt)* ) $($tail:tt)*) => {
(@RECUR $ret:ident "l" $argh:tt $(:: $arg:tt)+ ($($body:tt)*) $($tail:tt)*) => {
$ret.push(MacTok::Lambda(
MacTok::Name(sym!($argh $(:: $arg)+; orchid_extension::context::i()).await).at(orchid_base::location::Pos::Inherit),
$crate::macros::utils::mactreev!($($body)*)
).at(orchid_base::location::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident $name:literal $($tail:tt)*) => {
assert!(
$name.contains("::"),
"{} was treated as a name, but it doesn't have a namespace prefix",
$name
);
let sym = orchid_base::name::Sym::parse(
$name,
&orchid_extension::context::i()
).await.expect("Empty string in sym literal in Rust");
$ret.push(
MacTok::S(orchid_base::tree::Paren::Round, mactreev!($i; $($body)*))
$crate::macros::mactree::MacTok::Name(sym)
.at(orchid_base::location::Pos::Inherit)
);
mactreev!(@RECUR $i; $ret $($tail)*);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident [ $($body:tt)* ] $($tail:tt)*) => {
(@RECUR $ret:ident ( $($body:tt)* ) $($tail:tt)*) => {
$ret.push(
MacTok::S(orchid_base::tree::Paren::Square, mactreev!($i; $($body)*))
$crate::macros::mactree::MacTok::S(
orchid_base::tree::Paren::Round,
$crate::macros::utils::mactreev!($($body)*)
)
.at(orchid_base::location::Pos::Inherit)
);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident [ $($body:tt)* ] $($tail:tt)*) => {
$ret.push(
$crate::macros::mactree::MacTok::S(
orchid_base::tree::Paren::Square,
$crate::macros::utils::mactreev!($($body)*)
)
.at(orchid_base::location::Pos::Inherit)
);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident { $($body:tt)* } $($tail:tt)*) => {
$ret.push(
$crate::macros::mactree::MacTok::S(
orchid_base::tree::Paren::Curly,
$crate::macros::utils::mactreev!($($body)*)
)
.at(orchid_base::location::Pos::Inherit)
);
$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)* ; orchid_extension::context::i());
$ret.push(
$crate::macros::mactree::MacTok::Name(sym)
.at(orchid_base::location::Pos::Inherit)
);
mactreev!(@RECUR $i; $ret $($tail)*);
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident { $($body:tt)* } $($tail:tt)*) => {
$ret.push(
MacTok::S(orchid_base::tree::Paren::Curly, mactreev!($i; $($body)*))
.at(orchid_base::location::Pos::Inherit)
);
mactreev!(@RECUR $i; $ret $($tail)*);
};
(@RECUR $i:expr; $ret:ident $nhead:tt $($tail:tt)*) => {
mactreev!(@NAME_MUNCHER $i; $ret ($nhead) $($tail)*)
};
(@NAME_MUNCHER $i:expr; $ret:ident ($($munched:tt)*) :: $name:tt $($tail:tt)*) => {
mactreev!(@NAME_MUNCHER $i; $ret ($($munched)* :: $name) $($tail)*)
};
(@NAME_MUNCHER $i:expr; $ret:ident ($($munched:tt)*) $($tail:tt)*) => {
let sym = orchid_base::sym!($($munched)* ; $i).await;
$ret.push(MacTok::Name(sym).at(orchid_base::location::Pos::Inherit));
mactreev!(@RECUR $i; $ret $($tail)*);
};
($i:expr; ) => { Vec::new() };
($i:expr; $($tail:tt)*) => {
() => { Vec::new() };
}
macro_rules! mactreev {
($($tail:tt)*) => {
{
let mut ret = Vec::<MacTree>::new();
mactreev!(@RECUR $i; ret $($tail)*);
ret
let mut ret = Vec::<$crate::macros::mactree::MacTree>::new();
ret.extend([]); // silence unneeded mut warning
$crate::macros::utils::mactreev_impl!(@RECUR ret $($tail)*);
$crate::macros::mactree::MacTreeSeq::new(ret)
}
};
}
pub(crate) use {mactree, mactreev};
pub(crate) use {mactree, mactreev, mactreev_impl};