use crate::parser::position::{Position, Span as ParserSpan};
use nom_locate::LocatedSpan;
use std::cell::Cell;
thread_local! {
static TRACK_POSITIONS: Cell<bool> = const { Cell::new(true) };
static PARSE_MATH: Cell<bool> = const { Cell::new(true) };
static PARSE_DIAGRAMS: Cell<bool> = const { Cell::new(true) };
}
pub(crate) struct ParseOptionsGuard {
prev_track: bool,
prev_math: bool,
prev_diagrams: bool,
}
impl ParseOptionsGuard {
pub(crate) fn new(track: bool, math: bool, diagrams: bool) -> Self {
let prev_track = TRACK_POSITIONS.with(|c| c.replace(track));
let prev_math = PARSE_MATH.with(|c| c.replace(math));
let prev_diagrams = PARSE_DIAGRAMS.with(|c| c.replace(diagrams));
Self {
prev_track,
prev_math,
prev_diagrams,
}
}
}
impl Drop for ParseOptionsGuard {
fn drop(&mut self) {
TRACK_POSITIONS.with(|c| c.set(self.prev_track));
PARSE_MATH.with(|c| c.set(self.prev_math));
PARSE_DIAGRAMS.with(|c| c.set(self.prev_diagrams));
}
}
#[inline]
pub(crate) fn parse_math_enabled() -> bool {
PARSE_MATH.with(|c| c.get())
}
#[inline]
pub(crate) fn parse_diagrams_enabled() -> bool {
PARSE_DIAGRAMS.with(|c| c.get())
}
#[inline]
pub fn opt_span(span: GrammarSpan) -> Option<ParserSpan> {
if !TRACK_POSITIONS.with(|c| c.get()) {
return None;
}
Some(to_parser_span(span))
}
#[inline]
pub fn opt_span_range(start: GrammarSpan, end: GrammarSpan) -> Option<ParserSpan> {
if !TRACK_POSITIONS.with(|c| c.get()) {
return None;
}
Some(to_parser_span_range(start, end))
}
#[inline]
pub fn opt_span_range_inclusive(start: GrammarSpan, end: GrammarSpan) -> Option<ParserSpan> {
if !TRACK_POSITIONS.with(|c| c.get()) {
return None;
}
Some(to_parser_span_range_inclusive(start, end))
}
pub type GrammarSpan<'a> = LocatedSpan<&'a str>;
pub fn to_parser_span(span: GrammarSpan) -> ParserSpan {
let start_line = span.location_line() as usize; let frag = span.fragment().as_bytes();
let mut newline_count = 0usize;
let mut last_nl: Option<usize> = None;
for (i, &b) in frag.iter().enumerate() {
if b == b'\n' {
newline_count += 1;
last_nl = Some(i);
}
}
let end_line = start_line + newline_count;
let end_column = match last_nl {
Some(pos) if pos == frag.len() - 1 => {
1
}
Some(pos) => {
frag.len() - pos - 1 + 1
}
None => {
span.get_column() + frag.len()
}
};
let start = Position::new(start_line, span.get_column(), span.location_offset());
let end = Position::new(
end_line,
end_column,
span.location_offset() + span.fragment().len(),
);
ParserSpan::new(start, end)
}
pub fn to_parser_span_range(start: GrammarSpan, end: GrammarSpan) -> ParserSpan {
let start_pos = Position::new(
start.location_line() as usize,
start.get_column(),
start.location_offset(),
);
let end_pos = Position::new(
end.location_line() as usize,
end.get_column(),
end.location_offset(),
);
ParserSpan::new(start_pos, end_pos)
}
pub fn to_parser_span_range_inclusive(start: GrammarSpan, end: GrammarSpan) -> ParserSpan {
let start_pos = Position::new(
start.location_line() as usize,
start.get_column(),
start.location_offset(),
);
let end_pos = Position::new(
end.location_line() as usize,
end.get_column() + end.fragment().len(),
end.location_offset() + end.fragment().len(),
);
ParserSpan::new(start_pos, end_pos)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_parser_span_ascii() {
let input = GrammarSpan::new("hello");
let span = to_parser_span(input);
assert_eq!(span.start.line, 1);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.column, 6); }
#[test]
fn test_to_parser_span_utf8_and_emoji() {
let input = GrammarSpan::new("Tëst");
let span = to_parser_span(input);
assert_eq!(span.start.column, 1);
assert_eq!(span.end.column, 6);
let input2 = GrammarSpan::new("🎨");
let span2 = to_parser_span(input2);
assert_eq!(span2.start.column, 1);
assert_eq!(span2.end.column, 5);
}
}