use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use crate::lex::ast::range::{Position as CorePosition, Range as CoreRange};
use lex_extension::wire::{Position as WirePosition, Range as WireRange};
#[derive(Default)]
pub(crate) struct OriginInterner {
cache: HashMap<String, Arc<PathBuf>>,
}
impl OriginInterner {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn intern(&mut self, s: &str) -> Arc<PathBuf> {
if let Some(arc) = self.cache.get(s) {
return Arc::clone(arc);
}
let arc = Arc::new(PathBuf::from(s));
self.cache.insert(s.to_string(), Arc::clone(&arc));
arc
}
}
pub(crate) fn position_to_wire(p: &CorePosition) -> WirePosition {
let line = u32::try_from(p.line).unwrap_or_else(|_| {
debug_assert!(false, "position line {} exceeds u32::MAX", p.line);
u32::MAX
});
let column = u32::try_from(p.column).unwrap_or_else(|_| {
debug_assert!(false, "position column {} exceeds u32::MAX", p.column);
u32::MAX
});
WirePosition::new(line, column)
}
pub(crate) fn position_from_wire(p: &WirePosition) -> CorePosition {
CorePosition::new(p.line() as usize, p.column() as usize)
}
pub(crate) fn range_to_wire(r: &CoreRange) -> WireRange {
WireRange::new(position_to_wire(&r.start), position_to_wire(&r.end))
}
pub(crate) fn range_from_wire(r: &WireRange) -> CoreRange {
CoreRange::new(
0..0,
position_from_wire(&r.start),
position_from_wire(&r.end),
)
}
pub(crate) fn range_from_wire_with_origin(
r: &WireRange,
origin: Option<&str>,
interner: &mut OriginInterner,
) -> CoreRange {
let mut range = range_from_wire(r);
if let Some(s) = origin {
range.origin_path = Some(interner.intern(s));
}
range
}
#[allow(dead_code)] pub(crate) fn origin_string(r: &CoreRange) -> Option<String> {
r.origin_path
.as_ref()
.map(|p| p.to_string_lossy().into_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn position_round_trip() {
let core = CorePosition::new(12, 34);
let wire = position_to_wire(&core);
assert_eq!(wire, WirePosition::new(12, 34));
let back = position_from_wire(&wire);
assert_eq!(back.line, 12);
assert_eq!(back.column, 34);
}
#[test]
fn range_round_trip() {
let core = CoreRange::new(10..20, CorePosition::new(1, 2), CorePosition::new(1, 12));
let wire = range_to_wire(&core);
let back = range_from_wire(&wire);
assert_eq!(back.start.line, 1);
assert_eq!(back.start.column, 2);
assert_eq!(back.end.line, 1);
assert_eq!(back.end.column, 12);
}
#[test]
fn interner_shares_arc_for_repeated_origins() {
let mut interner = OriginInterner::new();
let r1 = WireRange::new(WirePosition::new(0, 0), WirePosition::new(0, 0));
let r2 = WireRange::new(WirePosition::new(1, 0), WirePosition::new(1, 0));
let a = range_from_wire_with_origin(&r1, Some("/repo/file.lex"), &mut interner);
let b = range_from_wire_with_origin(&r2, Some("/repo/file.lex"), &mut interner);
let a_arc = a.origin_path.expect("a has origin");
let b_arc = b.origin_path.expect("b has origin");
assert!(
Arc::ptr_eq(&a_arc, &b_arc),
"interner must share Arc<PathBuf> for identical origin strings"
);
}
#[test]
fn interner_keeps_distinct_arcs_for_different_origins() {
let mut interner = OriginInterner::new();
let r = WireRange::new(WirePosition::new(0, 0), WirePosition::new(0, 0));
let a = range_from_wire_with_origin(&r, Some("/repo/a.lex"), &mut interner);
let b = range_from_wire_with_origin(&r, Some("/repo/b.lex"), &mut interner);
assert!(
!Arc::ptr_eq(
a.origin_path.as_ref().unwrap(),
b.origin_path.as_ref().unwrap()
),
"different origin strings must keep separate Arcs"
);
}
#[test]
fn interner_treats_none_origin_as_unstamped() {
let mut interner = OriginInterner::new();
let r = WireRange::new(WirePosition::new(0, 0), WirePosition::new(0, 0));
let a = range_from_wire_with_origin(&r, None, &mut interner);
assert!(a.origin_path.is_none());
}
}