forked from Orchid/orchid
171 lines
5.6 KiB
Rust
171 lines
5.6 KiB
Rust
use std::rc::Rc;
|
|
|
|
use async_fn_stream::stream;
|
|
use async_once_cell::OnceCell;
|
|
use futures::StreamExt;
|
|
use itertools::Itertools;
|
|
use orchid_base::{
|
|
Comment, OrcRes, Paren, Parsed, Snippet, Token, clone, expect_end, expect_tok, is, line_items,
|
|
mk_errv, report, sym, token_errv, try_pop_no_fluff, with_reporter,
|
|
};
|
|
use orchid_extension::TAtom;
|
|
use orchid_extension::{ToExpr, TryFromExpr};
|
|
use orchid_extension::gen_expr::{call, new_atom};
|
|
use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser};
|
|
|
|
use crate::macros::let_line::{dealias_mac_v, parse_tokv};
|
|
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)]
|
|
pub struct MacroLine;
|
|
impl Parser for MacroLine {
|
|
const LINE_HEAD: &'static str = "macro";
|
|
async fn parse<'a>(
|
|
ctx: ParsCtx<'a>,
|
|
exported: bool,
|
|
comments: Vec<Comment>,
|
|
line: PSnippet<'a>,
|
|
) -> OrcRes<Vec<ParsedLine>> {
|
|
if exported {
|
|
return Err(mk_errv(
|
|
is("macros are always exported").await,
|
|
"The export keyword is forbidden here to avoid confusion\n\
|
|
because macros are exported by default",
|
|
[line.sr()],
|
|
));
|
|
}
|
|
let module = ctx.module();
|
|
let Parsed { output: prio_or_body, tail } = try_pop_no_fluff(line).await?;
|
|
let bad_first_item_err = || {
|
|
token_errv(prio_or_body, "Expected priority or block", |s| {
|
|
format!("Expected a priority number or a () block, found {s}")
|
|
})
|
|
};
|
|
let (prio, body) = match &prio_or_body.tok {
|
|
Token::S(Paren::Round, body) => {
|
|
expect_end(tail).await?;
|
|
(None, body)
|
|
},
|
|
Token::Handle(expr) => match TAtom::<Int>::try_from_expr(expr.clone()).await {
|
|
Err(e) => {
|
|
return Err(e + bad_first_item_err().await);
|
|
},
|
|
Ok(prio) => {
|
|
let Parsed { output: body, tail } = try_pop_no_fluff(tail).await?;
|
|
let Token::S(Paren::Round, block) = &body.tok else {
|
|
return Err(
|
|
token_errv(prio_or_body, "Expected () block", |s| {
|
|
format!("Expected a () block, found {s}")
|
|
})
|
|
.await,
|
|
);
|
|
};
|
|
expect_end(tail).await?;
|
|
(Some(prio), block)
|
|
},
|
|
},
|
|
_ => return Err(bad_first_item_err().await),
|
|
};
|
|
let lines = line_items(Snippet::new(prio_or_body, body)).await;
|
|
let Some((kw_line, rule_lines)) = lines.split_first() else { return Ok(Vec::new()) };
|
|
let mut keywords = Vec::new();
|
|
let Parsed { tail: kw_tail, .. } = expect_tok(kw_line.tail, is("keywords").await).await?;
|
|
for kw_tok in kw_tail.iter().filter(|kw| !kw.is_fluff()) {
|
|
match kw_tok.as_name() {
|
|
Some(kw) => {
|
|
keywords.push((kw, kw_tok.sr()));
|
|
},
|
|
None => report(
|
|
token_errv(kw_tok, "invalid macro keywords list", |tok| {
|
|
format!("The keywords list must be a sequence of names; received {tok}")
|
|
})
|
|
.await,
|
|
),
|
|
}
|
|
}
|
|
let Some((macro_name, _)) = keywords.first().cloned() else {
|
|
return Err(mk_errv(
|
|
is("macro with no keywords").await,
|
|
"Macros must define at least one macro of their own.",
|
|
[kw_line.tail.sr()],
|
|
));
|
|
};
|
|
let mut rules = Vec::new();
|
|
let mut lines = Vec::new();
|
|
for (idx, line) in rule_lines.iter().enumerate().map(|(n, v)| (n as u32, v)) {
|
|
let sr = line.tail.sr();
|
|
let name = is(&format!("rule::{}::{}", macro_name, idx)).await;
|
|
let Parsed { tail, .. } = expect_tok(line.tail, is("rule").await).await?;
|
|
let arrow_token = is("=>").await;
|
|
let Some((pattern, body)) = tail.split_once(|tok| tok.is_kw(arrow_token.clone())) else {
|
|
report(mk_errv(
|
|
is("Missing => in rule").await,
|
|
"Rule lines are of the form `rule ...pattern => ...body`",
|
|
[line.tail.sr()],
|
|
));
|
|
continue;
|
|
};
|
|
let pattern = parse_tokv(pattern).await;
|
|
let mut placeholders = Vec::new();
|
|
pattern.map(&mut false, &mut |tok| {
|
|
if let MacTok::Ph(ph) = tok.tok() {
|
|
placeholders.push((ph.clone(), tok.pos()))
|
|
}
|
|
None
|
|
});
|
|
let mut body_mactree = parse_tokv(body).await;
|
|
for (ph, ph_pos) in placeholders.iter().rev() {
|
|
let name = ctx.module().suffix([ph.name.clone()]).await;
|
|
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 macro_input =
|
|
MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&body_mactree, &ctx)).await?)
|
|
.at(body_sr.pos());
|
|
Ok(call(sym!(macros::resolve), new_atom(macro_input)))
|
|
}))
|
|
}
|
|
let mac_cell = Rc::new(OnceCell::new());
|
|
let rules = Rc::new(rules);
|
|
for (kw, sr) in &*keywords {
|
|
clone!(mac_cell, rules, module, macro_name, prio);
|
|
let kw_key = is(&format!("__macro__{kw}")).await;
|
|
lines.push(ParsedLine::cnst(&sr.clone(), &comments, true, kw_key, async move |cctx| {
|
|
let mac_future = async {
|
|
let rules = with_reporter(
|
|
stream(async |mut h| {
|
|
for (body, ph_names, pattern_rel) in rules.iter() {
|
|
let pattern = dealias_mac_v(pattern_rel, &cctx).await;
|
|
let ph_names = ph_names.iter().map(|(ph, _)| ph.name.clone()).collect_vec();
|
|
match Matcher::new(pattern.clone()).await {
|
|
Ok(matcher) =>
|
|
h.emit(Rule { body: body.clone(), matcher, pattern, ph_names }).await,
|
|
Err(e) => report(e),
|
|
}
|
|
}
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
.await?;
|
|
Ok(Macro(Rc::new(MacroData {
|
|
canonical_name: module.suffix([macro_name]).await,
|
|
module,
|
|
prio: prio.map(|i| i.0 as u64),
|
|
rules,
|
|
})))
|
|
};
|
|
mac_cell.get_or_init(mac_future).await.clone().map(new_atom).to_gen().await
|
|
}))
|
|
}
|
|
Ok(lines)
|
|
}
|
|
}
|