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, line: PSnippet<'a>, ) -> OrcRes> { 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::::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::>(), ) .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) } }