use std::ops::{Add, Range}; use futures::FutureExt; use hashbrown::{HashMap, HashSet}; use itertools::Itertools; use orchid_base::{NameLike, Paren, Pos, VPath, fmt, is, log, mk_errv, report}; use orchid_extension::gen_expr::{call_v, new_atom}; use orchid_extension::{ExecHandle, ReflMemKind, TAtom, refl}; use subslice_offset::SubsliceOffset; use crate::macros::macro_value::{Macro, Rule}; use crate::macros::mactree::MacTreeSeq; use crate::macros::rule::state::{MatchState, StateEntry}; use crate::{MacTok, MacTree}; pub async fn resolve(h: &mut ExecHandle<'_>, val: MacTree) -> MacTree { writeln!(log("debug"), "Macro-resolving {}", fmt(&val).await).await; let root = refl(); let mut macros = HashMap::new(); for n in val.glossary() { let (foot, body) = n.split_last_seg(); let new_name = VPath::new(body.iter().cloned()) .name_with_suffix(is(&format!("__macro__{foot}")).await) .to_sym() .await; if let Ok(ReflMemKind::Const) = root.get_by_path(&new_name).await.map(|m| m.kind()) { let Ok(mac) = h.exec::>(new_name).await else { continue }; let mac = mac.own().await; macros.entry(mac.0.canonical_name.clone()).or_insert(mac); } } let mut exclusive = Vec::new(); let mut prios = Vec::::new(); let mut priod = Vec::::new(); for (_, mac) in macros.iter() { let mut record = FilteredMacroRecord { mac, rules: Vec::new() }; for (rule_i, rule) in mac.0.rules.iter().enumerate() { if rule.pattern.glossary.is_subset(val.glossary()) { record.rules.push(rule_i); } } if !record.rules.is_empty() { match mac.0.prio { None => exclusive.push(record), Some(prio) => { let i = prios.partition_point(|p| *p > prio); prios.insert(i, prio); priod.insert(i, record); }, } } } let mut rctx = ResolveCtx { exclusive, priod, h: &mut *h }; let gex = resolve_one(&mut rctx, &val).await.unwrap_or(val.clone()); writeln!(log("debug"), "Macro-resolution over {} yielded {}", fmt(&val).await, fmt(&gex).await) .await; gex } /// Rules belonging to one macro that passed a particular filter pub struct FilteredMacroRecord<'a> { mac: &'a Macro, /// The rules in increasing order of index rules: Vec, } struct ResolveCtx<'a, 'b> { /// If these overlap, that's a compile-time error pub exclusive: Vec>, /// If these overlap, the priorities decide the order. In case of a tie, the /// order is unspecified pub priod: Vec>, pub h: &'a mut ExecHandle<'b>, } async fn resolve_one(ctx: &mut ResolveCtx<'_, '_>, value: &MacTree) -> Option { match value.tok() { MacTok::Ph(_) | MacTok::Slot => panic!("Forbidden element in value mactree"), MacTok::Bottom(_) | MacTok::Value(_) | MacTok::Name(_) | MacTok::Resolved(_) => None, MacTok::Lambda(arg, body) => { let new_arg = resolve_one(ctx, arg).boxed_local().await; let new_body = resolve_seq(ctx, body, value.pos()).boxed_local().await; if new_arg.is_none() && new_body.is_none() { return None; } let tok = MacTok::Lambda( new_arg.unwrap_or_else(|| arg.clone()), new_body.unwrap_or_else(|| body.clone()), ); Some(tok.at(value.pos())) }, MacTok::S(pty, body) => (resolve_seq(ctx, body, value.pos()).boxed_local().await) .map(|body| MacTok::S(*pty, body).at(value.pos())), } } type XMatches<'a> = Vec<(Range, &'a Macro, &'a Rule, MatchState<'a>)>; /// find the subsection of the slice that satisfies both the lower and upper /// limit. fn subsection( slice: &[T], lower_limit: impl FnMut(&T) -> bool, mut upper_limit: impl FnMut(&T) -> bool, ) -> Range { let start = slice.partition_point(lower_limit); let len = slice[start..].partition_point(|t| !upper_limit(t)); start..start + len } async fn resolve_seq( ctx: &mut ResolveCtx<'_, '_>, val: &MacTreeSeq, fallback_pos: Pos, ) -> Option { if val.items.is_empty() { return None; } // A sorted collection of overlapping but non-nested matches to exclusive // macros let mut x_matches: XMatches = Vec::new(); let top_glossary = val.top_glossary.clone(); let mut new_val = val.items.to_vec(); 'x_macros: for x in &ctx.exclusive { let mut rules_iter = x.rules.iter(); let ((before, state, after), rule) = 'rules: loop { let Some(ridx) = rules_iter.next() else { continue 'x_macros }; let rule = &x.mac.0.rules[*ridx]; if rule.pattern.top_glossary.is_subset(&top_glossary) && let Some(record) = rule.matcher.apply(&val.items[..], &|_| true).await { break 'rules (record, rule); }; }; let new_r = (before.len()..new_val.len() - after.len(), x.mac, rule, state); // elements that overlap with us let overlap = subsection(&x_matches[..], |r| new_r.0.start < r.0.end, |r| r.0.start < new_r.0.end); let overlapping = &x_matches[overlap.clone()]; // elements that fully contain us let geq_range = subsection(overlapping, |r| r.0.start <= new_r.0.start, |r| new_r.0.end <= r.0.end); let geq = &overlapping[geq_range.clone()]; // if any of these is equal to us, all of them must be, otherwise the larger // ranges would have overridden the smaller ones if let Some(example) = geq.first() { // if they are equal to us, record the conflict. if example.0 == new_r.0 { let idx = (x_matches.subslice_offset(geq)) .expect("this slice is statically derived from x_matches"); x_matches.insert(idx, new_r); } // either way, we matched so no further rules can run. continue 'x_macros; } // elements we fully contain. Equal ranges have been handled above let lt_range = subsection(overlapping, |r| new_r.0.start <= r.0.start, |r| r.0.end <= new_r.0.end); let lt = &overlapping[lt_range.clone()]; if lt.is_empty() { // an empty range let i = x_matches.partition_point(|r| r.0.start < new_r.0.start); x_matches.insert(i, new_r); } else { let lt_start = x_matches.subslice_offset(overlapping).expect("Slice statically derived from x_matches"); x_matches.splice(lt_start..lt_start + lt_range.len(), [new_r]); } } let mut any_match = !x_matches.is_empty(); // apply exclusive matches if !x_matches.is_empty() { // ranges of indices into x_matches which setwise conflict with each other. // Pairwise conflict reporting is excess noise, but a single conflict error // doesn't reveal where within the parenthesized block to look, so it's easiest // to group them setwise even if these sets may associate macros which don't // directly conflict. let conflict_sets = (0..x_matches.len()).map(|x| x..x + 1).coalesce(|lran, rran| { // each index was mapped to a range that contains only itself. Now we check if // the last match in the first range overlaps the first match in the second // range, and combine them if this is the case. if x_matches[rran.start].0.start < x_matches[lran.end].0.end { Ok(lran.start..rran.end) } else { Err((lran, rran)) } }); let mac_conflict_tk = is("Macro conflict").await; let error = conflict_sets .filter(|r| 1 < r.len()) .map(|set| { mk_errv( mac_conflict_tk.clone(), "Multiple partially overlapping syntax elements detected. \n\ Try parenthesizing whichever side is supposed to be the subexpression.", x_matches[set].iter().flat_map(|rec| rec.3.names()).flat_map(|name| name.1).cloned(), ) }) .reduce(|l, r| l + r); if let Some(error) = error { report(error.clone()); return Some(MacTreeSeq::new([MacTok::Bottom(error).at(fallback_pos)])); } // no conflicts, apply all exclusive matches for (range, mac, rule, state) in x_matches.into_iter().rev() { // backwards so that the non-overlapping ranges remain valid let pos = (state.names().flat_map(|r| r.1).cloned().reduce(Pos::add)) .expect("All macro rules must contain at least one locally defined name"); let subex = call_body(ctx.h, mac, rule, &state, pos.clone()).await; new_val.splice(range, [subex]); } }; // TODO: Does this glossary refresh actually pay off? let top_glossary = (new_val.iter()) .flat_map(|t| if let MacTok::Name(t) = t.tok() { Some(t.clone()) } else { None }) .collect::>(); for FilteredMacroRecord { mac, rules } in &ctx.priod { for ridx in rules { let rule = &mac.0.rules[*ridx]; if !rule.pattern.top_glossary.is_subset(&top_glossary) { continue; } let Some((pre, state, suf)) = rule.matcher.apply(&new_val, &|_| true).await else { continue }; any_match = true; let range = pre.len()..new_val.len() - suf.len(); let pos = (state.names().flat_map(|pair| pair.1).cloned().reduce(Pos::add)) .expect("All macro rules must contain at least one locally defined name"); let subex = call_body(ctx.h, mac, rule, &state, pos.clone()).await; std::mem::drop(state); new_val.splice(range, [subex]); } } for item in new_val.iter_mut() { let Some(new) = resolve_one(ctx, item).await else { continue }; *item = new; any_match = true; } any_match.then_some(MacTreeSeq::new(new_val)) } async fn call_body( h: &mut ExecHandle<'_>, mac: &Macro, rule: &Rule, state: &MatchState<'_>, pos: Pos, ) -> MacTree { let mut call_args = vec![]; for name in rule.ph_names.iter() { call_args.push(match state.get(name).expect("Missing state entry for placeholder") { StateEntry::Scalar(scal) => new_atom((**scal).clone()), StateEntry::Vec(vec) => new_atom(MacTok::S(Paren::Round, MacTreeSeq::new(vec.iter().cloned())).at(Pos::None)), }); } let f_name = mac.0.module.suffix([rule.body.clone()]).await; match h.exec::>(call_v(f_name, call_args)).await { Err(e) => MacTok::Bottom(e).at(pos), Ok(mt) => MacTok::Resolved(mt.own().await).at(mt.pos()), } }