use crate::error::Location;
use crate::prelude::*;
#[cfg(not(feature = "std"))]
use alloc::rc::Rc;
use core::cell::OnceCell;
#[cfg(feature = "std")]
use core::cell::RefCell;
#[cfg(feature = "std")]
use std::rc::Rc;
use crate::prelude::FxHashMap;
#[cfg(feature = "std")]
use crate::value::Value;
#[cfg(feature = "std")]
#[derive(Debug, Clone)]
pub(crate) enum SpanTree {
Leaf(usize, usize),
Sequence {
start: usize,
end: usize,
items: Vec<Self>,
},
Mapping {
start: usize,
end: usize,
entries: Vec<((usize, usize), Self)>,
},
Alias(Box<Self>),
}
#[derive(Debug)]
pub struct SpanContext {
pub spans: FxHashMap<usize, (usize, usize)>,
pub source: Arc<str>,
lines: SharedLineIndex,
}
pub(crate) type SharedLineIndex = Rc<OnceCell<LineIndex>>;
impl SpanContext {
#[cfg(feature = "std")]
pub(crate) fn new(spans: FxHashMap<usize, (usize, usize)>, source: Arc<str>) -> Self {
Self::with_lines(spans, source, SharedLineIndex::default())
}
#[cfg(feature = "std")]
pub(crate) fn with_lines(
spans: FxHashMap<usize, (usize, usize)>,
source: Arc<str>,
lines: SharedLineIndex,
) -> Self {
Self {
spans,
source,
lines,
}
}
pub(crate) fn location(&self, index: usize) -> Location {
self.lines
.get_or_init(|| LineIndex::new(&self.source))
.location(self.source.as_bytes(), index)
}
}
#[derive(Debug)]
pub(crate) struct LineIndex {
line_starts: Vec<usize>,
chars_at: Vec<usize>,
}
impl LineIndex {
const STRIDE: usize = 128;
pub(crate) fn new(source: &str) -> Self {
let bytes = source.as_bytes();
let mut line_starts = vec![0];
line_starts.extend(
bytes
.iter()
.enumerate()
.filter(|&(_, &b)| b == b'\n')
.map(|(i, _)| i + 1),
);
let mut chars_at = Vec::with_capacity(bytes.len() / Self::STRIDE + 2);
let mut chars = 0;
chars_at.push(0);
for chunk in bytes.chunks(Self::STRIDE) {
chars += count_chars(chunk);
chars_at.push(chars);
}
Self {
line_starts,
chars_at,
}
}
fn chars_before(&self, bytes: &[u8], pos: usize) -> usize {
let pos = pos.min(bytes.len());
let block = pos / Self::STRIDE;
self.chars_at[block] + count_chars(&bytes[block * Self::STRIDE..pos])
}
pub(crate) fn location(&self, bytes: &[u8], index: usize) -> Location {
let line = self.line_starts.partition_point(|&start| start <= index);
let line_start = self.line_starts[line - 1];
let end = index.min(bytes.len());
let column = if end - line_start <= Self::STRIDE {
1 + count_chars(&bytes[line_start..end])
} else {
1 + self.chars_before(bytes, end) - self.chars_before(bytes, line_start)
};
Location::new(line, column, index)
}
}
fn count_chars(bytes: &[u8]) -> usize {
bytes.iter().filter(|&&b| b & 0xC0 != 0x80).count()
}
#[cfg(feature = "std")]
mod tls {
use super::{RefCell, SpanContext};
use core::cell::Cell;
thread_local! {
pub(super) static SPAN_CONTEXT: RefCell<Option<SpanContext>> = const { RefCell::new(None) };
pub(super) static ERROR_NODE: Cell<Option<usize>> = const { Cell::new(None) };
}
}
#[cfg(feature = "std")]
pub(crate) fn record_error_node(addr: usize) {
tls::ERROR_NODE.with(|cell| cell.set(Some(addr)));
}
#[cfg(feature = "std")]
pub(crate) fn take_error_node() -> Option<usize> {
tls::ERROR_NODE.with(core::cell::Cell::take)
}
#[cfg(feature = "std")]
pub(crate) struct SpanContextGuard {
ctx: SpanContext,
}
#[cfg(feature = "std")]
impl SpanContextGuard {
pub(crate) fn as_ref(&self) -> &SpanContext {
&self.ctx
}
}
#[cfg(feature = "std")]
impl Drop for SpanContextGuard {
fn drop(&mut self) {
tls::SPAN_CONTEXT.with(|cell| {
*cell.borrow_mut() = None;
});
tls::ERROR_NODE.with(|cell| cell.set(None));
}
}
#[cfg(feature = "std")]
pub(crate) fn set_span_context(ctx: SpanContext) -> SpanContextGuard {
let thread_local_ctx = SpanContext::with_lines(
FxHashMap::default(),
Arc::clone(&ctx.source),
Rc::clone(&ctx.lines),
);
tls::SPAN_CONTEXT.with(|cell| {
*cell.borrow_mut() = Some(thread_local_ctx);
});
SpanContextGuard { ctx }
}
#[cfg(feature = "std")]
pub(crate) fn build_span_map(value: &Value, tree: &SpanTree) -> FxHashMap<usize, (usize, usize)> {
let mut map = FxHashMap::default();
walk(value, tree, &mut map);
map
}
#[cfg(feature = "std")]
fn walk(value: &Value, tree: &SpanTree, map: &mut FxHashMap<usize, (usize, usize)>) {
let p: *const Value = value;
let ptr = p as usize;
match tree {
SpanTree::Leaf(start, end) => {
let _ = map.insert(ptr, (*start, *end));
}
SpanTree::Sequence { start, end, items } => {
let _ = map.insert(ptr, (*start, *end));
if let Value::Sequence(seq) = value {
for (v, t) in seq.iter().zip(items.iter()) {
walk(v, t, map);
}
}
}
SpanTree::Mapping {
start,
end,
entries,
} => {
let _ = map.insert(ptr, (*start, *end));
if let Value::Mapping(mapping) = value {
for ((_, v), (_, vt)) in mapping.iter().zip(entries.iter()) {
walk(v, vt, map);
}
}
}
SpanTree::Alias(inner) => walk(value, inner, map),
}
}
#[cfg(all(test, feature = "std"))]
mod line_index_tests {
use super::*;
#[test]
fn line_index_matches_from_index_everywhere() {
let filler = "x".repeat(LineIndex::STRIDE + 7);
let sources = [
String::new(),
"a".to_string(),
"a: 1\nb: 2\n".to_string(),
"\n\n\nx".to_string(),
"é: ü\n - 日本\r\nend 🦀\n".to_string(),
format!("k: {filler}é\n{filler}\n🦀{filler}"),
];
for source in &sources {
let index = LineIndex::new(source);
for i in 0..=source.len() + 2 {
assert_eq!(
index.location(source.as_bytes(), i),
Location::from_index(source, i),
"source {source:?} index {i}"
);
}
}
}
#[test]
fn contexts_share_one_index() {
let source: Arc<str> = "a: 1\n".into();
let lines = SharedLineIndex::default();
let first = SpanContext::with_lines(FxHashMap::default(), source.clone(), lines.clone());
let second = SpanContext::with_lines(FxHashMap::default(), source, lines.clone());
assert_eq!(first.location(3).line(), 1);
assert!(lines.get().is_some());
assert_eq!(second.location(5).line(), 2);
}
}