use std::ops::Range;
pub trait Input<'src> {
type Token: 'src;
type Pos: Clone + Into<usize> + 'src;
fn start_pos(&self) -> Self::Pos;
fn read_token(&mut self, pos: &mut Self::Pos) -> Option<Self::Token>;
fn try_consume_prefix_bytes(&mut self, pos: &mut Self::Pos, prefix: &[u8]) -> Option<bool> {
let _ = (pos, prefix);
None
}
}
pub trait SliceableInput<'src>: Input<'src> {
type Slice: 'src;
fn slice(&self, range: Range<Self::Pos>) -> Self::Slice;
}
impl<'src> Input<'src> for &'src str {
type Token = char;
type Pos = usize;
#[inline]
fn start_pos(&self) -> Self::Pos {
0
}
#[inline]
fn read_token(&mut self, pos: &mut Self::Pos) -> Option<Self::Token> {
let bytes = self.as_bytes();
let i = *pos;
let b = *bytes.get(i)?;
if b.is_ascii() {
*pos += 1;
return Some(char::from(b));
}
let ch = self.get(i..)?.chars().next()?;
*pos += ch.len_utf8();
Some(ch)
}
#[inline]
fn try_consume_prefix_bytes(&mut self, pos: &mut Self::Pos, prefix: &[u8]) -> Option<bool> {
if prefix.is_empty() {
return Some(true);
}
if !prefix.iter().all(|b| b.is_ascii()) {
return None;
}
let i = *pos;
let bytes = self.as_bytes();
if bytes.get(i..i + prefix.len())? == prefix {
*pos += prefix.len();
Some(true)
} else {
Some(false)
}
}
}
impl<'src> SliceableInput<'src> for &'src str {
type Slice = &'src str;
fn slice(&self, range: Range<Self::Pos>) -> Self::Slice {
&self[range.start..range.end]
}
}
impl<'src, T> Input<'src> for &'src [T] {
type Token = &'src T;
type Pos = usize;
#[inline]
fn start_pos(&self) -> Self::Pos {
0
}
#[inline]
fn read_token(&mut self, pos: &mut Self::Pos) -> Option<Self::Token> {
if *pos < self.len() {
let token = &self[*pos];
*pos += 1;
Some(token)
} else {
None
}
}
}
impl<'src, T> SliceableInput<'src> for &'src [T] {
type Slice = &'src [T];
fn slice(&self, range: Range<Self::Pos>) -> Self::Slice {
&self[range.start..range.end]
}
}
pub(crate) struct InputStream<'src, I: Input<'src>> {
input: I,
pos: I::Pos,
}
impl<'src, I: Input<'src>> InputStream<'src, I> {
#[inline]
pub(crate) fn new(input: I) -> Self {
let pos = input.start_pos();
Self { input, pos }
}
#[inline]
pub(crate) fn next(&mut self) -> Option<I::Token> {
self.input.read_token(&mut self.pos)
}
#[inline]
pub(crate) fn get_pos(&self) -> I::Pos {
self.pos.clone()
}
#[inline]
pub(crate) fn set_pos(&mut self, pos: I::Pos) {
self.pos = pos;
}
#[inline]
pub(crate) fn try_consume_prefix_bytes(&mut self, prefix: &[u8]) -> Option<bool> {
self.input.try_consume_prefix_bytes(&mut self.pos, prefix)
}
#[inline]
pub(crate) fn slice(&self, range: Range<I::Pos>) -> I::Slice
where
I: SliceableInput<'src>,
{
self.input.slice(range)
}
}
impl<'src> InputStream<'src, &'src [u8]> {
#[inline]
pub(crate) fn try_consume_byte_prefix(&mut self, prefix: &[u8]) -> bool {
if prefix.is_empty() {
return true;
}
let i = self.pos;
let buf = self.input;
if buf.get(i..i + prefix.len()) == Some(prefix) {
self.pos = i + prefix.len();
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::{Input, InputStream};
#[test]
fn str_try_consume_prefix_advances_pos() {
let mut stream = InputStream::new("hello");
assert_eq!(stream.try_consume_prefix_bytes(b"hel"), Some(true));
assert_eq!(stream.get_pos(), 3);
assert_eq!(stream.try_consume_prefix_bytes(b"lo"), Some(true));
assert_eq!(stream.get_pos(), 5);
}
#[test]
fn str_try_consume_prefix_mismatch_returns_false() {
let mut stream = InputStream::new("hello");
assert_eq!(stream.try_consume_prefix_bytes(b"hi"), Some(false));
assert_eq!(stream.get_pos(), 0);
}
#[test]
fn str_try_consume_empty_prefix_is_true() {
let mut stream = InputStream::new("x");
assert_eq!(stream.try_consume_prefix_bytes(b""), Some(true));
assert_eq!(stream.get_pos(), 0);
}
#[test]
fn byte_stream_try_consume_byte_prefix() {
let buf: &[u8] = b"nulltail";
let mut stream = InputStream::new(buf);
assert!(stream.try_consume_byte_prefix(b"null"));
assert_eq!(stream.get_pos(), 4);
assert!(!stream.try_consume_byte_prefix(b"nomatch"));
assert_eq!(stream.get_pos(), 4);
}
#[test]
fn str_try_consume_non_ascii_prefix_returns_none() {
let mut stream = InputStream::new("x");
assert_eq!(stream.try_consume_prefix_bytes(&[0xC3, 0xA9]), None);
assert_eq!(stream.get_pos(), 0);
}
#[test]
fn str_input_try_consume_via_trait() {
let mut s: &str = "abc";
let mut pos = 0usize;
assert_eq!(
Input::try_consume_prefix_bytes(&mut s, &mut pos, b"ab"),
Some(true)
);
assert_eq!(pos, 2);
}
}