111 lines
3.5 KiB
Rust
111 lines
3.5 KiB
Rust
use std::{collections::HashMap, ops::Range};
|
|
|
|
pub fn marked_text_by(
|
|
marked_text: &str,
|
|
markers: Vec<char>,
|
|
) -> (String, HashMap<char, Vec<usize>>) {
|
|
let mut extracted_markers: HashMap<char, Vec<usize>> = Default::default();
|
|
let mut unmarked_text = String::new();
|
|
|
|
for char in marked_text.chars() {
|
|
if markers.contains(&char) {
|
|
let char_offsets = extracted_markers.entry(char).or_insert(Vec::new());
|
|
char_offsets.push(unmarked_text.len());
|
|
} else {
|
|
unmarked_text.push(char);
|
|
}
|
|
}
|
|
|
|
(unmarked_text, extracted_markers)
|
|
}
|
|
|
|
pub fn marked_text(marked_text: &str) -> (String, Vec<usize>) {
|
|
let (unmarked_text, mut markers) = marked_text_by(marked_text, vec!['|']);
|
|
(unmarked_text, markers.remove(&'|').unwrap_or_default())
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Hash)]
|
|
pub enum TextRangeMarker {
|
|
Empty(char),
|
|
Range(char, char),
|
|
}
|
|
|
|
impl TextRangeMarker {
|
|
fn markers(&self) -> Vec<char> {
|
|
match self {
|
|
Self::Empty(m) => vec![*m],
|
|
Self::Range(l, r) => vec![*l, *r],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<char> for TextRangeMarker {
|
|
fn from(marker: char) -> Self {
|
|
Self::Empty(marker)
|
|
}
|
|
}
|
|
|
|
impl From<(char, char)> for TextRangeMarker {
|
|
fn from((left_marker, right_marker): (char, char)) -> Self {
|
|
Self::Range(left_marker, right_marker)
|
|
}
|
|
}
|
|
|
|
pub fn marked_text_ranges_by(
|
|
marked_text: &str,
|
|
markers: Vec<TextRangeMarker>,
|
|
) -> (String, HashMap<TextRangeMarker, Vec<Range<usize>>>) {
|
|
let all_markers = markers.iter().flat_map(|m| m.markers()).collect();
|
|
|
|
let (unmarked_text, mut marker_offsets) = marked_text_by(marked_text, all_markers);
|
|
let range_lookup = markers
|
|
.into_iter()
|
|
.map(|marker| match marker {
|
|
TextRangeMarker::Empty(empty_marker_char) => {
|
|
let ranges = marker_offsets
|
|
.remove(&empty_marker_char)
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.map(|empty_index| empty_index..empty_index)
|
|
.collect::<Vec<Range<usize>>>();
|
|
(marker, ranges)
|
|
}
|
|
TextRangeMarker::Range(start_marker, end_marker) => {
|
|
let starts = marker_offsets.remove(&start_marker).unwrap_or_default();
|
|
let ends = marker_offsets.remove(&end_marker).unwrap_or_default();
|
|
assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
|
|
|
|
let ranges = starts
|
|
.into_iter()
|
|
.zip(ends)
|
|
.map(|(start, end)| {
|
|
assert!(end >= start, "marked ranges must be disjoint");
|
|
start..end
|
|
})
|
|
.collect::<Vec<Range<usize>>>();
|
|
(marker, ranges)
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
(unmarked_text, range_lookup)
|
|
}
|
|
|
|
// Returns ranges delimited by (), [], and <> ranges. Ranges using the same markers
|
|
// must not be overlapping. May also include | for empty ranges
|
|
pub fn marked_text_ranges(full_marked_text: &str) -> (String, Vec<Range<usize>>) {
|
|
let (unmarked, range_lookup) = marked_text_ranges_by(
|
|
&full_marked_text,
|
|
vec![
|
|
'|'.into(),
|
|
('[', ']').into(),
|
|
('(', ')').into(),
|
|
('<', '>').into(),
|
|
],
|
|
);
|
|
let mut combined_ranges: Vec<_> = range_lookup.into_values().flatten().collect();
|
|
|
|
combined_ranges.sort_by_key(|range| range.start);
|
|
(unmarked, combined_ranges)
|
|
}
|