use std::fmt;
use std::hash::{Hash, Hasher};
use std::ptr;
use std::str;
use position;
#[derive(Clone)]
pub struct Span<'i> {
input: &'i str,
start: usize,
end: usize,
}
impl<'i> Span<'i> {
pub(crate) unsafe fn new_unchecked(input: &str, start: usize, end: usize) -> Span {
debug_assert!(input.get(start..end).is_some());
Span { input, start, end }
}
#[allow(clippy::new_ret_no_self)]
pub fn new(input: &str, start: usize, end: usize) -> Option<Span> {
if input.get(start..end).is_some() {
Some(Span { input, start, end })
} else {
None
}
}
#[inline]
pub fn start(&self) -> usize {
self.start
}
#[inline]
pub fn end(&self) -> usize {
self.end
}
#[inline]
pub fn start_pos(&self) -> position::Position<'i> {
unsafe { position::Position::new_unchecked(self.input, self.start) }
}
#[inline]
pub fn end_pos(&self) -> position::Position<'i> {
unsafe { position::Position::new_unchecked(self.input, self.end) }
}
#[inline]
pub fn split(self) -> (position::Position<'i>, position::Position<'i>) {
let pos1 = unsafe { position::Position::new_unchecked(self.input, self.start) };
let pos2 = unsafe { position::Position::new_unchecked(self.input, self.end) };
(pos1, pos2)
}
#[inline]
pub fn as_str(&self) -> &'i str {
&self.input[self.start..self.end]
}
#[inline]
pub fn lines(&self) -> Lines {
Lines {
span: self,
pos: self.start,
}
}
}
impl<'i> fmt::Debug for Span<'i> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Span")
.field("str", &self.as_str())
.field("start", &self.start)
.field("end", &self.end)
.finish()
}
}
impl<'i> PartialEq for Span<'i> {
fn eq(&self, other: &Span<'i>) -> bool {
ptr::eq(self.input, other.input) && self.start == other.start && self.end == other.end
}
}
impl<'i> Eq for Span<'i> {}
impl<'i> Hash for Span<'i> {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.input as *const str).hash(state);
self.start.hash(state);
self.end.hash(state);
}
}
pub struct Lines<'i> {
span: &'i Span<'i>,
pos: usize,
}
impl<'i> Iterator for Lines<'i> {
type Item = &'i str;
fn next(&mut self) -> Option<&'i str> {
if self.pos > self.span.end {
return None;
}
let pos = position::Position::new(self.span.input, self.pos)?;
if pos.at_end() {
return None;
}
let line = pos.line_of();
self.pos = pos.find_line_end();
Some(line)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split() {
let input = "a";
let start = position::Position::from_start(input);
let mut end = start.clone();
assert!(end.skip(1));
let span = start.clone().span(&end.clone());
assert_eq!(span.split(), (start, end));
}
#[test]
fn lines_mid() {
let input = "abc\ndef\nghi";
let span = Span::new(input, 1, 7).unwrap();
let lines: Vec<_> = span.lines().collect();
println!("{:?}", lines);
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "abc\n".to_owned());
assert_eq!(lines[1], "def\n".to_owned());
}
#[test]
fn lines_eof() {
let input = "abc\ndef\nghi";
let span = Span::new(input, 5, 11).unwrap();
assert!(span.end_pos().at_end());
let lines: Vec<_> = span.lines().collect();
println!("{:?}", lines);
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "def\n".to_owned());
assert_eq!(lines[1], "ghi".to_owned());
}
}