use std::ops::RangeBounds; use nom::{bytes::complete::tag, Compare, InputTake, Slice}; fn main() { let input = "this is my test input".to_owned(); let wrapped_input = WrappedInput::new(input.as_str()); let output = tag::<_, _, (_, nom::error::ErrorKind)>("this")(wrapped_input).unwrap(); println!("{:#?}", output); } #[derive(Debug)] struct WrappedInput<'s> { full_source: &'s str, start: usize, end: usize, //exclusive preceding_line_break: Option, } impl<'s> WrappedInput<'s> { /// Returns a wrapped string that keeps track of values we need for parsing org-mode. /// /// Only call this on the full original string. Calling this on a substring can result in invalid values. pub fn new(input: &'s str) -> Self { WrappedInput { full_source: input, start: 0, end: input.len(), preceding_line_break: None, } } /// Get the text since the line break preceding the start of this WrappedInput. pub fn text_since_line_break(&self) -> &'s str { let start = self.preceding_line_break.unwrap_or(0); &self.full_source[start..self.start] } } impl<'s> InputTake for WrappedInput<'s> { fn take(&self, count: usize) -> Self { self.slice(..count) } fn take_split(&self, count: usize) -> (Self, Self) { (self.slice(count..), self.slice(..count)) } } impl<'s, O: Into<&'s str>> Compare for WrappedInput<'s> { fn compare(&self, t: O) -> nom::CompareResult { (&self.full_source[self.start..self.end]).compare(t.into()) } fn compare_no_case(&self, t: O) -> nom::CompareResult { (&self.full_source[self.start..self.end]).compare_no_case(t.into()) } } impl<'s> From<&'s str> for WrappedInput<'s> { fn from(value: &'s str) -> Self { WrappedInput::new(value) } } impl<'s> From<&WrappedInput<'s>> for &'s str { fn from(value: &WrappedInput<'s>) -> Self { &value.full_source[value.start..value.end] } } impl<'s, R> Slice for WrappedInput<'s> where R: RangeBounds, { fn slice(&self, range: R) -> Self { let new_start = match range.start_bound() { std::ops::Bound::Included(idx) => self.start + idx, std::ops::Bound::Excluded(idx) => self.start + idx - 1, std::ops::Bound::Unbounded => self.start, }; let new_end = match range.end_bound() { std::ops::Bound::Included(idx) => self.start + idx + 1, std::ops::Bound::Excluded(idx) => self.start + idx, std::ops::Bound::Unbounded => self.end, }; if new_start < self.start { panic!("Attempted to extend before the start of the WrappedInput.") } if new_end > self.end { panic!("Attempted to extend past the end of the WrappedInput.") } let skipped_text = &self.full_source[self.start..new_start]; let last_line_feed = skipped_text .rfind('\n') .map(|idx| idx + self.preceding_line_break.unwrap_or(0) + 1); // TODO: calculate updated values for WrappedInput WrappedInput { full_source: self.full_source, start: new_start, end: new_end, preceding_line_break: last_line_feed, } } } impl<'s> std::fmt::Display for WrappedInput<'s> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Into::<&str>::into(self).fmt(f) } } #[cfg(test)] mod tests { use super::*; #[test] fn range() { let input = WrappedInput::new("foo bar baz"); let output = input.slice(4..7); assert_eq!(output.to_string(), "bar"); } #[test] fn range_to() { let input = WrappedInput::new("foo bar baz"); let output = input.slice(..7); assert_eq!(output.to_string(), "foo bar"); } #[test] fn range_from() { let input = WrappedInput::new("foo bar baz"); let output = input.slice(4..); assert_eq!(output.to_string(), "bar baz"); } #[test] fn full_range() { let input = WrappedInput::new("foo bar baz"); let output = input.slice(..); assert_eq!(output.to_string(), "foo bar baz"); } #[test] fn nested_range() { let input = WrappedInput::new("lorem foo bar baz ipsum"); let first_cut = input.slice(6..17); let output = first_cut.slice(4..7); assert_eq!(first_cut.to_string(), "foo bar baz"); assert_eq!(output.to_string(), "bar"); } #[test] #[should_panic] fn out_of_bounds() { let input = WrappedInput::new("lorem foo bar baz ipsum"); input.slice(6..30); } #[test] #[should_panic] fn out_of_nested_bounds() { let input = WrappedInput::new("lorem foo bar baz ipsum"); let first_cut = input.slice(6..17); first_cut.slice(4..14); } #[test] fn line_break() { let input = WrappedInput::new("lorem\nfoo\nbar\nbaz\nipsum"); assert_eq!(input.slice(5..).preceding_line_break, None); assert_eq!(input.slice(6..).preceding_line_break, Some(6)); assert_eq!(input.slice(6..).slice(10..).preceding_line_break, Some(14)); } #[test] fn text_since_line_break() { let input = WrappedInput::new("lorem\nfoo\nbar\nbaz\nipsum"); assert_eq!(input.text_since_line_break(), ""); assert_eq!(input.slice(5..).text_since_line_break(), "lorem"); assert_eq!(input.slice(6..).text_since_line_break(), ""); assert_eq!(input.slice(6..).slice(10..).text_since_line_break(), "ba"); } }