forked from Orchid/orchid
Macro system done in theory
too afraid to begin debugging, resting for a moment
This commit is contained in:
230
orchid-std/src/macros/macro_line.rs
Normal file
230
orchid-std/src/macros/macro_line.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
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(()) }
|
||||
}
|
||||
Reference in New Issue
Block a user