use std::collections::HashMap;
use rucc_base::Symbol;
use rucc_diag::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TraceId(u32);
impl TraceId {
pub const NONE: TraceId = TraceId(0);
#[inline]
pub const fn is_none(self) -> bool {
self.0 == 0
}
#[inline]
pub const fn raw(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Step {
pub macro_name: Symbol,
pub at: Span,
pub outer: TraceId,
}
#[derive(Debug, Default)]
pub struct Traces {
steps: Vec<Step>,
map: HashMap<Step, TraceId>,
}
impl Traces {
pub fn new() -> Traces {
Traces::default()
}
pub fn push(&mut self, macro_name: Symbol, at: Span, outer: TraceId) -> TraceId {
let step = Step { macro_name, at, outer };
if let Some(&found) = self.map.get(&step) {
return found;
}
let Ok(next) = u32::try_from(self.steps.len() + 1) else {
return outer;
};
let id = TraceId(next);
self.steps.push(step);
self.map.insert(step, id);
id
}
#[must_use]
pub fn step(&self, id: TraceId) -> Option<Step> {
self.steps.get((id.0 as usize).checked_sub(1)?).copied()
}
#[must_use]
pub fn chain(&self, id: TraceId) -> Vec<Step> {
let mut out = Vec::new();
let mut at = id;
while let Some(step) = self.step(at) {
out.push(step);
at = step.outer;
if out.len() > 256 {
break;
}
}
out.reverse();
out
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use super::*;
fn names() -> (Interner, Symbol, Symbol) {
let mut interner = Interner::new();
let cat = interner.intern("CAT");
let outer = interner.intern("OUTER");
(interner, cat, outer)
}
fn span(lo: u32) -> Span {
Span::new(lo, lo + 1)
}
#[test]
fn a_token_the_user_wrote_has_no_chain() {
let traces = Traces::new();
assert!(TraceId::NONE.is_none());
assert_eq!(traces.step(TraceId::NONE), None);
assert!(traces.chain(TraceId::NONE).is_empty());
}
#[test]
fn the_chain_reads_from_the_outermost_macro_inwards() {
let (_interner, cat_name, outer_name) = names();
let mut traces = Traces::new();
let outer = traces.push(outer_name, span(40), TraceId::NONE);
let cat = traces.push(cat_name, span(20), outer);
let chain = traces.chain(cat);
assert_eq!(chain.len(), 2);
assert_eq!(chain[0].macro_name, outer_name);
assert_eq!(chain[0].at, span(40));
assert_eq!(chain[1].macro_name, cat_name);
assert_eq!(chain[1].at, span(20));
}
#[test]
fn the_same_step_twice_is_stored_once() {
let (_interner, cat_name, _) = names();
let mut traces = Traces::new();
let first = traces.push(cat_name, span(20), TraceId::NONE);
let second = traces.push(cat_name, span(20), TraceId::NONE);
assert_eq!(first, second);
assert_eq!(traces.steps.len(), 1);
let third = traces.push(cat_name, span(20), first);
assert_ne!(third, first);
assert_eq!(traces.chain(third).len(), 2);
}
#[test]
fn two_macros_of_the_same_name_at_different_places_are_different_steps() {
let (_interner, cat_name, _) = names();
let mut traces = Traces::new();
let here = traces.push(cat_name, span(20), TraceId::NONE);
let there = traces.push(cat_name, span(90), TraceId::NONE);
assert_ne!(here, there);
}
}