use std::cell::RefCell; use std::rc::Rc; use async_once_cell::OnceCell; use futures::{StreamExt, stream}; use itertools::Itertools; use orchid_base::error::{OrcRes, Reporter, mk_errv}; use orchid_base::parse::{ Comment, ParseCtx, Parsed, Snippet, expect_end, expect_tok, line_items, token_errv, try_pop_no_fluff, }; 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, 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( 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: prio_or_body, tail } = try_pop_no_fluff(&ctx, line).await?; let bad_first_item_err = || { token_errv(&ctx, 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(&ctx, 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(&ctx, tail).await?; let Token::S(Paren::Round, block) = &body.tok else { return Err( token_errv(&ctx, prio_or_body, "Expected () block", |s| { format!("Expected a () block, found {s}") }) .await, ); }; expect_end(&ctx, tail).await?; (Some(prio), block) }, }, _ => return Err(bad_first_item_err().await), }; let lines = line_items(&ctx, 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(&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.push((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.first().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 sr = line.tail.sr(); let name = ctx.i().i(&format!("rule::{}::{}", macro_name, idx)).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(); 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, &ctx).await; for (ph, ph_pos) in placeholders.iter().rev() { let name = ctx.module().suffix([ph.name.clone()], ctx.i()).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 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!(macros::resolve; i())), [macro_input.to_gen().await])) })) } let mac_cell = Rc::new(OnceCell::new()); let rules = Rc::new(RefCell::new(Some(rules))); for (kw, sr) in &*keywords { clone!(mac_cell, 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::iter(rules) .then(|(body_name, placeholders, pattern_rel)| { let cctx = &cctx; let rep = &rep; async move { 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(matcher) => Some(Rule { body_name, matcher, pattern, placeholders }), Err(e) => { rep.report(e); None }, } } }) .flat_map(stream::iter) .collect::>() .await; Macro(Rc::new(MacroData { module, prio: prio.map(|i| i.0 as u64), rules })) }) .await; atom(mac.clone()) })) } Ok(lines) } }