use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::str;
use std::sync::Arc;
use span;
#[derive(Clone)]
pub struct Position {
input: Arc<str>,
pos: usize,
}
impl Position {
pub(crate) unsafe fn new_unchecked(input: Arc<str>, pos: usize) -> Position {
debug_assert!(input.get(pos..).is_some());
Position { input, pos }
}
#[allow(clippy::new_ret_no_self)]
pub fn new(input: Arc<str>, pos: usize) -> Option<Position> {
match input.get(pos..) {
Some(..) => Some(Position { input, pos }),
None => None,
}
}
#[inline]
pub fn from_start(input: Arc<str>) -> Position {
Position { input, pos: 0 }
}
#[inline]
pub fn pos(&self) -> usize {
self.pos
}
#[inline]
pub fn span(&self, other: &Position) -> span::Span {
if Arc::ptr_eq(&self.input, &other.input)
{
unsafe { span::Span::new_unchecked(self.input.clone(), self.pos, other.pos) }
} else {
panic!("span created from positions from different inputs")
}
}
#[inline]
pub fn line_col(&self) -> (usize, usize) {
if self.pos > self.input.len() {
panic!("position out of bounds");
}
let mut pos = self.pos;
let slice = &self.input[..pos];
let mut chars = slice.chars().peekable();
let mut line_col = (1, 1);
while pos != 0 {
match chars.next() {
Some('\r') => {
if let Some(&'\n') = chars.peek() {
chars.next();
if pos == 1 {
pos -= 1;
} else {
pos -= 2;
}
line_col = (line_col.0 + 1, 1);
} else {
pos -= 1;
line_col = (line_col.0, line_col.1 + 1);
}
}
Some('\n') => {
pos -= 1;
line_col = (line_col.0 + 1, 1);
}
Some(c) => {
pos -= c.len_utf8();
line_col = (line_col.0, line_col.1 + 1);
}
None => unreachable!(),
}
}
line_col
}
#[inline]
pub fn line_of(&self) -> &str {
if self.pos > self.input.len() {
panic!("position out of bounds");
};
&self.input[self.find_line_start()..self.find_line_end()]
}
pub(crate) fn find_line_start(&self) -> usize {
if self.input.is_empty() {
return 0;
};
let start = self
.input
.char_indices()
.rev()
.skip_while(|&(i, _)| i >= self.pos)
.find(|&(_, c)| c == '\n');
match start {
Some((i, _)) => i + 1,
None => 0,
}
}
pub(crate) fn find_line_end(&self) -> usize {
if self.input.is_empty() {
0
} else if self.pos == self.input.len() - 1 {
self.input.len()
} else {
let end = self
.input
.char_indices()
.skip_while(|&(i, _)| i < self.pos)
.find(|&(_, c)| c == '\n');
match end {
Some((i, _)) => i + 1,
None => self.input.len(),
}
}
}
#[inline]
pub(crate) fn at_start(&self) -> bool {
self.pos == 0
}
#[inline]
pub(crate) fn at_end(&self) -> bool {
self.pos == self.input.len()
}
#[inline]
pub(crate) fn skip(&mut self, n: usize) -> bool {
let skipped = {
let mut len = 0;
let mut chars = (&self.input[self.pos..]).chars();
for _ in 0..n {
if let Some(c) = chars.next() {
len += c.len_utf8();
} else {
return false;
}
}
len
};
self.pos += skipped;
true
}
#[inline]
pub(crate) fn skip_back(&mut self, n: usize) -> bool {
let skipped = {
let mut len = 0;
let mut chars = (&self.input[..self.pos]).chars().rev();
for _ in 0..n {
if let Some(c) = chars.next() {
len += c.len_utf8();
} else {
return false;
}
}
len
};
self.pos -= skipped;
true
}
#[inline]
pub(crate) fn skip_until(&mut self, strings: &[&str]) -> bool {
for from in self.pos..self.input.len() {
let bytes = if let Some(string) = self.input.get(from..) {
string.as_bytes()
} else {
continue;
};
for slice in strings.iter() {
let to = slice.len();
if Some(slice.as_bytes()) == bytes.get(0..to) {
self.pos = from;
return true;
}
}
}
self.pos = self.input.len();
false
}
#[inline]
pub(crate) fn match_char_by<F>(&mut self, f: F) -> bool
where
F: FnOnce(char) -> bool,
{
if let Some(c) = (&self.input[self.pos..]).chars().next() {
if f(c) {
self.pos += c.len_utf8();
true
} else {
false
}
} else {
false
}
}
#[inline]
pub(crate) fn match_string(&mut self, string: &str) -> bool {
let to = self.pos + string.len();
if Some(string.as_bytes()) == self.input.as_bytes().get(self.pos..to) {
self.pos = to;
true
} else {
false
}
}
#[inline]
pub(crate) fn match_insensitive(&mut self, string: &str) -> bool {
let matched = {
let slice = &self.input[self.pos..];
if let Some(slice) = slice.get(0..string.len()) {
slice.eq_ignore_ascii_case(string)
} else {
false
}
};
if matched {
self.pos += string.len();
true
} else {
false
}
}
#[inline]
pub(crate) fn match_range(&mut self, range: Range<char>) -> bool {
if let Some(c) = (&self.input[self.pos..]).chars().next() {
if range.start <= c && c <= range.end {
self.pos += c.len_utf8();
return true;
}
}
false
}
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Position").field("pos", &self.pos).finish()
}
}
impl PartialEq for Position {
fn eq(&self, other: &Position) -> bool {
Arc::ptr_eq(&self.input, &other.input) && self.pos == other.pos
}
}
impl Eq for Position {}
impl PartialOrd for Position {
fn partial_cmp(&self, other: &Position) -> Option<Ordering> {
if Arc::ptr_eq(&self.input, &other.input) {
self.pos.partial_cmp(&other.pos)
} else {
None
}
}
}
impl Ord for Position {
fn cmp(&self, other: &Position) -> Ordering {
self.partial_cmp(other)
.expect("cannot compare positions from different strs")
}
}
impl Hash for Position {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.input).hash(state);
self.pos.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty() {
let input: Arc<str> = Arc::from("");
assert_eq!(
Position::new(input.clone(), 0).unwrap().match_string(""),
true
);
assert_eq!(
!Position::new(input.clone(), 0).unwrap().match_string("a"),
true
);
}
#[test]
fn parts() {
let input: Arc<str> = Arc::from("asdasdf");
assert_eq!(
Position::new(input.clone(), 0).unwrap().match_string("asd"),
true
);
assert_eq!(
Position::new(input.clone(), 3)
.unwrap()
.match_string("asdf"),
true
);
}
#[test]
fn line_col() {
let input: Arc<str> = Arc::from("a\rb\nc\r\nd嗨");
assert_eq!(Position::new(input.clone(), 0).unwrap().line_col(), (1, 1));
assert_eq!(Position::new(input.clone(), 1).unwrap().line_col(), (1, 2));
assert_eq!(Position::new(input.clone(), 2).unwrap().line_col(), (1, 3));
assert_eq!(Position::new(input.clone(), 3).unwrap().line_col(), (1, 4));
assert_eq!(Position::new(input.clone(), 4).unwrap().line_col(), (2, 1));
assert_eq!(Position::new(input.clone(), 5).unwrap().line_col(), (2, 2));
assert_eq!(Position::new(input.clone(), 6).unwrap().line_col(), (2, 3));
assert_eq!(Position::new(input.clone(), 7).unwrap().line_col(), (3, 1));
assert_eq!(Position::new(input.clone(), 8).unwrap().line_col(), (3, 2));
assert_eq!(Position::new(input.clone(), 11).unwrap().line_col(), (3, 3));
}
#[test]
fn line_of() {
let input: Arc<str> = Arc::from("a\rb\nc\r\nd嗨");
assert_eq!(Position::new(input.clone(), 0).unwrap().line_of(), "a\rb\n");
assert_eq!(Position::new(input.clone(), 1).unwrap().line_of(), "a\rb\n");
assert_eq!(Position::new(input.clone(), 2).unwrap().line_of(), "a\rb\n");
assert_eq!(Position::new(input.clone(), 3).unwrap().line_of(), "a\rb\n");
assert_eq!(Position::new(input.clone(), 4).unwrap().line_of(), "c\r\n");
assert_eq!(Position::new(input.clone(), 5).unwrap().line_of(), "c\r\n");
assert_eq!(Position::new(input.clone(), 6).unwrap().line_of(), "c\r\n");
assert_eq!(Position::new(input.clone(), 7).unwrap().line_of(), "d嗨");
assert_eq!(Position::new(input.clone(), 8).unwrap().line_of(), "d嗨");
assert_eq!(Position::new(input.clone(), 11).unwrap().line_of(), "d嗨");
}
#[test]
fn line_of_empty() {
let input: Arc<str> = Arc::from("");
assert_eq!(Position::new(input, 0).unwrap().line_of(), "");
}
#[test]
fn line_of_new_line() {
let input: Arc<str> = Arc::from("\n");
assert_eq!(Position::new(input, 0).unwrap().line_of(), "\n");
}
#[test]
fn line_of_between_new_line() {
let input: Arc<str> = Arc::from("\n\n");
assert_eq!(Position::new(input, 1).unwrap().line_of(), "\n");
}
fn measure_skip(input: &Arc<str>, pos: usize, n: usize) -> Option<usize> {
let mut p = Position::new(input.clone(), pos).unwrap();
if p.skip(n) {
Some(p.pos - pos)
} else {
None
}
}
#[test]
fn skip_empty() {
let input: Arc<str> = Arc::from("");
assert_eq!(measure_skip(&input, 0, 0), Some(0));
assert_eq!(measure_skip(&input, 0, 1), None);
}
#[test]
fn skip() {
let input: Arc<str> = Arc::from("d嗨");
assert_eq!(measure_skip(&input, 0, 0), Some(0));
assert_eq!(measure_skip(&input, 0, 1), Some(1));
assert_eq!(measure_skip(&input, 1, 1), Some(3));
}
#[test]
fn skip_until() {
let input: Arc<str> = Arc::from("ab ac");
let pos = Position::from_start(input);
let mut test_pos = pos.clone();
test_pos.skip_until(&["a", "b"]);
assert_eq!(test_pos.pos(), 0);
test_pos = pos.clone();
test_pos.skip_until(&["b"]);
assert_eq!(test_pos.pos(), 1);
test_pos = pos.clone();
test_pos.skip_until(&["ab"]);
assert_eq!(test_pos.pos(), 0);
test_pos = pos.clone();
test_pos.skip_until(&["ac", "z"]);
assert_eq!(test_pos.pos(), 3);
test_pos = pos.clone();
assert!(!test_pos.skip_until(&["z"]));
assert_eq!(test_pos.pos(), 5);
}
#[test]
fn match_range() {
let input: Arc<str> = Arc::from("b");
assert_eq!(
Position::new(input.clone(), 0)
.unwrap()
.match_range('a'..'c'),
true
);
assert_eq!(
Position::new(input.clone(), 0)
.unwrap()
.match_range('b'..'b'),
true
);
assert_eq!(
!Position::new(input.clone(), 0)
.unwrap()
.match_range('a'..'a'),
true
);
assert_eq!(
!Position::new(input.clone(), 0)
.unwrap()
.match_range('c'..'c'),
true
);
assert_eq!(
Position::new(input.clone(), 0)
.unwrap()
.match_range('a'..'嗨'),
true
);
}
#[test]
fn match_insensitive() {
let input: Arc<str> = Arc::from("AsdASdF");
assert_eq!(
Position::new(input.clone(), 0)
.unwrap()
.match_insensitive("asd"),
true
);
assert_eq!(
Position::new(input.clone(), 3)
.unwrap()
.match_insensitive("asdf"),
true
);
}
#[test]
fn cmp() {
let input: Arc<str> = Arc::from("a");
let start = Position::from_start(input);
let mut end = start.clone();
assert!(end.skip(1));
let result = start.cmp(&end);
assert_eq!(result, Ordering::Less);
}
#[test]
#[should_panic]
fn cmp_panic() {
let input1 = Arc::from("a");
let input2 = Arc::from("b");
let pos1 = Position::from_start(input1);
let pos2 = Position::from_start(input2);
let _ = pos1.cmp(&pos2);
}
#[test]
#[cfg(feature = "std")]
fn hash() {
use std::collections::HashSet;
let input: Arc<str> = Arc::from("a");
let start = Position::from_start(input);
let mut positions = HashSet::new();
positions.insert(start);
}
}