#![no_std]
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#![warn(missing_docs)]
use core::fmt::{self, Display};
#[cfg(feature = "yap")]
pub use yap;
#[cfg(feature = "yap")]
pub mod yap_support;
pub type Span = core::ops::Range<usize>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Position {
pub line: u32,
pub column: u32,
}
impl Position {
#[inline]
pub const fn starting() -> Self {
Position { line: 1, column: 1 }
}
pub fn update_from_char(&mut self, ch: char) -> Self {
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
*self
}
pub fn update_from_str(&mut self, s: &str) -> Self {
let mut last_line_border = None;
let added_lines = s.bytes().enumerate().filter(|(i, b)| {
if *b == b'\n' {
last_line_border = Some(*i);
true
} else {
false
}
}).count() as u32;
self.line += added_lines;
match last_line_border {
Some(i) => {
self.column = 1 + s[i + 1..].chars().count() as u32;
}
None => {
self.column += s.chars().count() as u32;
}
}
*self
}
}
impl Display for Position {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[line {}, col {}]", self.line, self.column)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Offset(pub usize);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tokens<'s> {
full_input: &'s str,
remaining_input: &'s str,
span: Span,
pos: Position,
offset: usize,
}
impl<'s> Tokens<'s> {
#[inline]
pub fn new(input: &'s str) -> Self {
Self {
full_input: input,
remaining_input: input,
span: 0..0,
pos: Position::starting(),
offset: 0,
}
}
#[inline]
pub fn input(&self) -> &str {
self.full_input
}
#[inline]
pub fn remainder(&self) -> &str {
self.remaining_input
}
#[inline]
pub fn span(&self) -> Span {
self.span.clone()
}
#[inline]
pub fn position(&self) -> Position {
self.pos
}
#[inline]
pub fn offset(&self) -> Offset {
Offset(self.offset)
}
pub fn set_offset(&mut self, offset: Offset) -> bool {
let offset = offset.0;
if self.full_input.is_char_boundary(offset) {
self.remaining_input = &self.full_input[offset..];
self.span = offset..offset;
self.pos = Position::starting().update_from_str(&self.full_input[..offset]);
self.offset = offset;
true
} else {
false
}
}
#[inline]
pub fn is_at_start(&self) -> bool {
self.offset == 0
}
#[inline]
pub fn is_at_end(&self) -> bool {
self.remaining_input.is_empty()
}
#[inline]
pub fn peek(&self) -> Option<char> {
self.remaining_input.chars().next()
}
#[inline]
pub fn consume_all(&mut self) -> &str {
self.split(self.remaining_input.len())
}
pub fn token(&mut self, token: impl AsRef<str>) -> bool {
let token = token.as_ref();
self.remaining_input
.get(..token.len())
.filter(|s| *s == token)
.map(|s| self.split(s.len()))
.is_some()
}
pub fn tokens(&mut self, tokens: impl IntoIterator<Item = impl AsRef<str>>) -> Option<&str> {
for token in tokens.into_iter() {
if self.token(token) {
return Some(&self.full_input[self.span.clone()]);
}
}
None
}
pub fn char(&mut self) -> Option<char> {
(!self.remaining_input.is_empty()).then(|| self.split_next_char())
}
pub fn char_if(&mut self, f: impl FnOnce(char) -> bool) -> Option<char> {
self.remaining_input
.chars()
.next()
.filter(|ch| f(*ch))
.map(|_| self.split_next_char())
}
pub fn bytes(&mut self, n: usize) -> Option<&str> {
self.remaining_input
.is_char_boundary(n)
.then(|| self.split(n))
}
pub fn bytes_if(&mut self, n: usize, f: impl FnOnce(&str) -> bool) -> Option<&str> {
self.remaining_input
.get(..n)
.filter(|s| f(s))
.map(|s| self.split(s.len()))
}
pub fn limit_bytes(&mut self, n: usize) -> bool {
if self.remaining_input.is_char_boundary(n) {
self.remaining_input = &self.remaining_input[..n];
true
} else {
false
}
}
pub fn split_bytes(self, n: usize) -> Option<(Tokens<'s>, Tokens<'s>)> {
let mut first = self.clone();
let mut second = self;
if second.bytes(n).is_some() {
first.limit_bytes(n);
Some((first, second))
} else {
None
}
}
pub fn chars(&mut self, n: usize) -> Option<&str> {
self.remaining_input
.char_indices()
.nth(n.checked_sub(1)?)
.map(|(i, ch)| self.split(i + ch.len_utf8()))
}
pub fn chars_if(&mut self, n: usize, f: impl FnOnce(&str) -> bool) -> Option<&str> {
self.remaining_input
.char_indices()
.nth(n.checked_sub(1)?)
.map(|(i, ch)| &self.remaining_input[..i + ch.len_utf8()])
.filter(|s| f(s))
.map(|s| self.split(s.len()))
}
pub fn limit_chars(&mut self, n: usize) -> bool {
if let Some((i, _)) = self.remaining_input.char_indices().nth(n) {
self.remaining_input = &self.remaining_input[..i];
true
} else {
false
}
}
pub fn split_chars(self, n: usize) -> Option<(Tokens<'s>, Tokens<'s>)> {
let mut first = self.clone();
let mut second = self;
if second.chars(n).is_some() {
first.limit_chars(n);
Some((first, second))
} else {
None
}
}
pub fn take_while(&mut self, mut f: impl FnMut(char) -> bool) -> &str {
self.remaining_input
.char_indices()
.take_while(|(_, ch)| f(*ch))
.last()
.map(|(i, ch)| self.split(i + ch.len_utf8()))
.unwrap_or("")
}
pub fn limit_while(&mut self, mut f: impl FnMut(char) -> bool) {
if let Some((i, ch)) = self
.remaining_input
.char_indices()
.take_while(|(_, ch)| f(*ch))
.last()
{
self.remaining_input = &self.remaining_input[..i + ch.len_utf8()];
}
}
pub fn split_while(self, f: impl FnMut(char) -> bool) -> (Tokens<'s>, Tokens<'s>) {
let mut first = self.clone();
let mut second = self;
let n = second.take_while(f).len();
first.limit_bytes(n);
(first, second)
}
fn split(&mut self, i: usize) -> &str {
let (result, remainder) = self.remaining_input.split_at(i);
self.remaining_input = remainder;
self.pos.update_from_str(result);
self.offset += i;
self.span = self.span.end..self.offset;
result
}
fn split_next_char(&mut self) -> char {
let ch = self.remaining_input.chars().next().unwrap();
self.remaining_input = &self.remaining_input[ch.len_utf8()..];
self.offset += ch.len_utf8();
self.span = self.span.end..self.offset;
self.pos.update_from_char(ch);
ch
}
}
pub trait AsTokens {
fn as_tokens(&self) -> Tokens<'_>;
}
impl<T> AsTokens for T
where
T: AsRef<str>,
{
#[inline]
fn as_tokens(&self) -> Tokens {
Tokens::new(self.as_ref())
}
}
impl<'s> AsTokens for Tokens<'s> {
#[inline]
fn as_tokens(&self) -> Tokens<'_> {
self.clone()
}
}