use std::ops::Range;
use ahash::AHashSet;
use crate::slice_helpers::zero_slice;
use crate::templaters::base::TemplatedFile;
#[derive(Debug, Clone, Eq)]
pub struct PositionMarker {
pub source_slice: Range<usize>,
pub templated_slice: Range<usize>,
pub templated_file: TemplatedFile,
pub working_line_no: usize,
pub working_line_pos: usize,
}
impl Default for PositionMarker {
fn default() -> Self {
PositionMarker {
source_slice: 0..0,
templated_slice: 0..0,
templated_file: "".to_string().into(),
working_line_no: 0,
working_line_pos: 0,
}
}
}
impl PositionMarker {
pub fn new(
source_slice: Range<usize>,
templated_slice: Range<usize>,
templated_file: TemplatedFile,
working_line_no: Option<usize>,
working_line_pos: Option<usize>,
) -> Self {
match (working_line_no, working_line_pos) {
(Some(working_line_no), Some(working_line_pos)) => PositionMarker {
source_slice,
templated_slice,
templated_file,
working_line_no,
working_line_pos,
},
_ => {
let (working_line_no, working_line_pos) =
templated_file.get_line_pos_of_char_pos(templated_slice.start, false);
PositionMarker {
source_slice,
templated_slice,
templated_file,
working_line_no,
working_line_pos,
}
}
}
}
#[track_caller]
pub fn source_str(&self) -> &str {
&self.templated_file.source_str[self.source_slice.clone()]
}
pub fn line_no(&self) -> usize {
self.source_position().0
}
pub fn line_pos(&self) -> usize {
self.source_position().1
}
#[track_caller]
pub fn from_child_markers<'a>(
markers: impl Iterator<Item = &'a PositionMarker>,
) -> PositionMarker {
let mut source_start = usize::MAX;
let mut source_end = usize::MIN;
let mut template_start = usize::MAX;
let mut template_end = usize::MIN;
let mut templated_files = AHashSet::new();
for marker in markers {
source_start = source_start.min(marker.source_slice.start);
source_end = source_end.max(marker.source_slice.end);
template_start = template_start.min(marker.templated_slice.start);
template_end = template_end.max(marker.templated_slice.end);
templated_files.insert(marker.templated_file.clone());
}
if templated_files.len() != 1 {
panic!("Attempted to make a parent marker from multiple files.");
}
let templated_file = templated_files.into_iter().next().unwrap();
PositionMarker::new(
source_start..source_end,
template_start..template_end,
templated_file,
None,
None,
)
}
pub fn source_position(&self) -> (usize, usize) {
self.templated_file
.get_line_pos_of_char_pos(self.templated_slice.start, true)
}
pub fn templated_position(&self) -> (usize, usize) {
self.templated_file
.get_line_pos_of_char_pos(self.templated_slice.start, false)
}
pub fn working_loc_after(&self, raw: &str) -> (usize, usize) {
Self::infer_next_position(raw, self.working_line_no, self.working_line_pos)
}
pub fn infer_next_position(raw: &str, line_no: usize, line_pos: usize) -> (usize, usize) {
if raw.is_empty() {
return (line_no, line_pos);
}
let split: Vec<&str> = raw.split('\n').collect();
(
line_no + (split.len() - 1),
if split.len() == 1 {
line_pos + raw.len()
} else {
split.last().unwrap().len() + 1
},
)
}
pub fn working_loc(&self) -> (usize, usize) {
(self.working_line_no, self.working_line_pos)
}
pub fn from_point(
source_point: usize,
templated_point: usize,
templated_file: TemplatedFile,
working_line_no: Option<usize>,
working_line_pos: Option<usize>,
) -> Self {
Self::new(
zero_slice(source_point),
zero_slice(templated_point),
templated_file,
working_line_no,
working_line_pos,
)
}
pub fn start_point_marker(&self) -> PositionMarker {
PositionMarker::from_point(
self.source_slice.start,
self.templated_slice.start,
self.templated_file.clone(),
Some(self.working_line_no),
Some(self.working_line_pos),
)
}
pub fn end_point_marker(&self) -> PositionMarker {
PositionMarker::from_point(
self.source_slice.end,
self.templated_slice.end,
self.templated_file.clone(),
None,
None,
)
}
pub fn is_literal(&self) -> bool {
self.templated_file
.is_source_slice_literal(&self.source_slice)
}
pub fn from_points(
start_point_marker: &PositionMarker,
end_point_marker: &PositionMarker,
) -> PositionMarker {
Self {
source_slice: start_point_marker.source_slice.start..end_point_marker.source_slice.end,
templated_slice: start_point_marker.templated_slice.start
..end_point_marker.templated_slice.end,
templated_file: start_point_marker.templated_file.clone(),
working_line_no: start_point_marker.working_line_no,
working_line_pos: start_point_marker.working_line_pos,
}
}
pub(crate) fn with_working_position(self, line_no: usize, line_pos: usize) -> PositionMarker {
Self {
working_line_no: line_no,
working_line_pos: line_pos,
..self
}
}
pub(crate) fn is_point(&self) -> bool {
self.source_slice.is_empty() && self.templated_slice.is_empty()
}
}
impl PartialEq for PositionMarker {
fn eq(&self, other: &Self) -> bool {
self.working_loc() == other.working_loc()
}
}
impl PartialOrd for PositionMarker {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.working_loc().cmp(&other.working_loc()))
}
}
#[cfg(test)]
mod tests {
use std::ops::Range;
use crate::parser::markers::PositionMarker;
use crate::templaters::base::TemplatedFile;
#[test]
fn test_markers_infer_next_position() {
struct Test {
raw: String,
start: Range<usize>,
end: Range<usize>,
}
let tests: Vec<Test> = vec![
Test {
raw: "fsaljk".to_string(),
start: 0..0,
end: 0..6,
},
Test {
raw: "".to_string(),
start: 2..2,
end: 2..2,
},
Test {
raw: "\n".to_string(),
start: 2..2,
end: 3..1,
},
Test {
raw: "boo\n".to_string(),
start: 2..2,
end: 3..1,
},
Test {
raw: "boo\nfoo".to_string(),
start: 2..2,
end: 3..4,
},
Test {
raw: "\nfoo".to_string(),
start: 2..2,
end: 3..4,
},
];
for t in tests {
assert_eq!(
(t.end.start, t.end.end),
PositionMarker::infer_next_position(&t.raw, t.start.start, t.start.end)
);
}
}
#[test]
fn test_markers_setting_position_raw() {
let template: TemplatedFile = "foobar".into();
assert_eq!(template.get_line_pos_of_char_pos(2, true), (1, 3));
assert_eq!(template.get_line_pos_of_char_pos(2, false), (1, 3));
let pos = PositionMarker::new(2..5, 2..5, template, None, None);
assert_eq!(pos.working_loc(), (1, 3));
}
#[test]
fn test_markers_setting_position_working() {
let templ: TemplatedFile = "foobar".into();
let pos = PositionMarker::new(2..5, 2..5, templ, Some(4), Some(4));
assert_eq!(pos.working_loc(), (4, 4))
}
#[test]
fn test_markers_comparison() {
let templ: TemplatedFile = "abc".into();
let a_pos = PositionMarker::new(0..1, 0..1, templ.clone(), None, None);
let b_pos = PositionMarker::new(1..2, 1..2, templ.clone(), None, None);
let c_pos = PositionMarker::new(2..3, 2..3, templ.clone(), None, None);
let all_pos = [&a_pos, &b_pos, &c_pos];
assert!(all_pos.iter().all(|p| p == p));
assert!(a_pos != b_pos && a_pos != c_pos && b_pos != c_pos);
assert!(a_pos < b_pos && b_pos < c_pos);
assert!(!(c_pos < a_pos));
assert!(c_pos > a_pos && c_pos > b_pos);
assert!(!(a_pos > c_pos));
assert!(all_pos.iter().all(|p| a_pos <= **p));
assert!(all_pos.iter().all(|p| c_pos >= **p));
}
}