231 lines
7.4 KiB
Rust
231 lines
7.4 KiB
Rust
use std::borrow::Cow;
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use async_once_cell::OnceCell;
|
|
use async_std::stream;
|
|
use futures::StreamExt;
|
|
use hashbrown::{HashMap, HashSet};
|
|
use itertools::Itertools;
|
|
use never::Never;
|
|
use orchid_api::Paren;
|
|
use orchid_base::error::{OrcRes, Reporter, mk_errv};
|
|
use orchid_base::interner::Tok;
|
|
use orchid_base::location::Pos;
|
|
use orchid_base::name::Sym;
|
|
use orchid_base::parse::{
|
|
Comment, ParseCtx, Parsed, Snippet, expect_end, expect_tok, line_items, token_errv,
|
|
try_pop_no_fluff,
|
|
};
|
|
use orchid_base::tree::Token;
|
|
use orchid_base::{clone, sym};
|
|
use orchid_extension::atom::{Atomic, TypAtom};
|
|
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
|
|
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::mactree::{glossary_v, map_mactree_v};
|
|
use crate::macros::recur_state::{RecurState, RulePath};
|
|
use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher};
|
|
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(
|
|
ctx.i().i("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, tail } = try_pop_no_fluff(&ctx, line).await?;
|
|
let bad_first_item_err = || {
|
|
token_errv(&ctx, output, "Expected priority or block", |s| {
|
|
format!("Expected a priority number or a () block, found {s}")
|
|
})
|
|
};
|
|
let (prio, body) = match &output.tok {
|
|
Token::S(Paren::Round, body) => (None, body),
|
|
Token::Handle(expr) => match TypAtom::<Int>::try_from_expr(expr.clone()).await {
|
|
Err(e) => {
|
|
return Err(e + bad_first_item_err().await);
|
|
},
|
|
Ok(prio) => {
|
|
let Token::S(Paren::Round, block) = &output.tok else {
|
|
return Err(
|
|
token_errv(&ctx, output, "Expected () block", |s| {
|
|
format!("Expected a () block, found {s}")
|
|
})
|
|
.await,
|
|
);
|
|
};
|
|
(Some(prio), block)
|
|
},
|
|
},
|
|
_ => return Err(bad_first_item_err().await),
|
|
};
|
|
expect_end(&ctx, tail).await?;
|
|
let lines = line_items(&ctx, Snippet::new(output, body)).await;
|
|
let Some((kw_line, rule_lines)) = lines.split_first() else { return Ok(Vec::new()) };
|
|
let mut keywords = HashMap::new();
|
|
let Parsed { tail: kw_tail, .. } =
|
|
expect_tok(&ctx, kw_line.tail, ctx.i().i("keywords").await).await?;
|
|
for kw_tok in kw_tail.iter().filter(|kw| !kw.is_fluff()) {
|
|
match kw_tok.as_name() {
|
|
Some(kw) => {
|
|
keywords.insert(kw, kw_tok.sr());
|
|
},
|
|
None => ctx.rep().report(
|
|
token_errv(&ctx, 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.keys().next().cloned() else {
|
|
return Err(mk_errv(
|
|
ctx.i().i("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 path = RulePath { module: module.clone(), main_kw: macro_name.clone(), rule: idx };
|
|
let sr = line.tail.sr();
|
|
let name = ctx.i().i(&path.name()).await;
|
|
let Parsed { tail, .. } = expect_tok(&ctx, line.tail, ctx.i().i("rule").await).await?;
|
|
let arrow_token = ctx.i().i("=>").await;
|
|
let Some((pattern, body)) = tail.split_once(|tok| tok.is_kw(arrow_token.clone())) else {
|
|
ctx.rep().report(mk_errv(
|
|
ctx.i().i("Missing => in rule").await,
|
|
"Rule lines are of the form `rule ...pattern => ...body`",
|
|
[line.tail.sr()],
|
|
));
|
|
continue;
|
|
};
|
|
let pattern = parse_tokv(pattern, &ctx).await;
|
|
let mut placeholders = Vec::new();
|
|
map_mactree_v(&pattern, &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, &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()),
|
|
]
|
|
}
|
|
let body_sr = body.sr();
|
|
rules.push((name.clone(), placeholders, rules.len() as u32, sr.pos(), 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 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!(macro::resolve_recur; ctx.i()).await),
|
|
atom(RecurState::base(path)),
|
|
macro_input.to_expr().await,
|
|
]))
|
|
}))
|
|
}
|
|
let mac_cell = Rc::new(OnceCell::new());
|
|
let keywords = Rc::new(keywords);
|
|
let rules = Rc::new(RefCell::new(Some(rules)));
|
|
for (kw, sr) in &*keywords {
|
|
clone!(mac_cell, keywords, rules, module, prio);
|
|
lines.push(ParsedLine::cnst(&sr.clone(), &comments, true, kw.clone(), async move |cctx| {
|
|
let mac = mac_cell
|
|
.get_or_init(async {
|
|
let rep = Reporter::new();
|
|
let rules = rules.borrow_mut().take().expect("once cell initializer runs");
|
|
let rules = stream::from_iter(rules)
|
|
.then(|(body_name, placeholders, index, pos, pattern_macv)| {
|
|
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 placeholders = placeholders.into_iter().map(|(ph, _)| ph.name).collect_vec();
|
|
match pattern_res {
|
|
Ok(pattern) =>
|
|
Some(Rule { index, pos, body_name, pattern, glossary, placeholders }),
|
|
Err(e) => {
|
|
rep.report(e);
|
|
None
|
|
},
|
|
}
|
|
}
|
|
})
|
|
.flat_map(stream::from_iter)
|
|
.collect::<Vec<_>>()
|
|
.await;
|
|
let own_kws = keywords.keys().cloned().collect_vec();
|
|
Macro(Rc::new(MacroData { module, prio: prio.map(|i| i.0 as u64), rules, own_kws }))
|
|
})
|
|
.await;
|
|
atom(mac.clone())
|
|
}))
|
|
}
|
|
Ok(lines)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct MacroData {
|
|
pub module: Sym,
|
|
pub prio: Option<u64>,
|
|
pub rules: Vec<Rule>,
|
|
pub own_kws: Vec<Tok<String>>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Macro(pub Rc<MacroData>);
|
|
#[derive(Debug)]
|
|
pub struct Rule {
|
|
pub index: u32,
|
|
pub pos: Pos,
|
|
pub pattern: Matcher,
|
|
pub glossary: HashSet<Sym>,
|
|
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;
|
|
}
|
|
impl OwnedAtom for Macro {
|
|
type Refs = Never;
|
|
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
|
}
|