#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{cmp::Ordering, fmt, io::Cursor};
pub mod lexical;
#[cfg(feature = "pointer")]
#[cfg_attr(docsrs, doc(cfg(feature = "pointer")))]
pub mod pointer;
pub mod syntax;
#[cfg(doctest)]
use doc_comment::doctest;
#[cfg(doctest)]
doctest!("../README.md");
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Pos {
pub offset: usize,
pub line: usize,
pub col: usize,
}
impl Pos {
#[inline(always)]
pub const fn new(offset: usize, line: usize, col: usize) -> Self {
Self { offset, line, col }
}
}
impl Default for Pos {
fn default() -> Self {
Self {
offset: 0,
line: 1,
col: 1,
}
}
}
impl fmt::Display for Pos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"line {}, column {} (offset: {})",
self.line, self.col, self.offset
)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BufUnderflow {
pub requested: usize,
pub remaining: usize,
}
impl fmt::Display for BufUnderflow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"not enough bytes in buffer ({} requested, but only {} remain)",
self.requested, self.remaining,
)
}
}
impl std::error::Error for BufUnderflow {}
pub trait Buf {
fn advance(&mut self, n: usize);
fn chunk(&self) -> &[u8];
fn remaining(&self) -> usize;
fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), BufUnderflow>;
#[inline]
fn has_remaining(&self) -> bool {
self.remaining() != 0
}
#[inline]
fn copy_to_slice(&mut self, dst: &mut [u8]) {
if let Err(err) = self.try_copy_to_slice(dst) {
panic!("{err}");
}
}
}
pub trait IntoBuf {
type Buf: Buf;
fn into_buf(self) -> Self::Buf;
}
impl Buf for &[u8] {
#[inline]
fn advance(&mut self, n: usize) {
if self.len() < n {
panic!(
"{}",
&BufUnderflow {
requested: n,
remaining: self.len()
}
);
} else {
*self = &self[n..];
}
}
#[inline(always)]
fn chunk(&self) -> &[u8] {
self
}
#[inline(always)]
fn remaining(&self) -> usize {
self.len()
}
#[inline]
fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), BufUnderflow> {
if self.len() < dst.len() {
Err(BufUnderflow {
requested: dst.len(),
remaining: self.len(),
})
} else {
dst.copy_from_slice(&self[..dst.len()]);
*self = &self[dst.len()..];
Ok(())
}
}
}
impl<'a> IntoBuf for &'a str {
type Buf = &'a [u8];
fn into_buf(self) -> Self::Buf {
self.as_bytes()
}
}
#[derive(Debug)]
pub struct StringBuf(Cursor<String>);
impl Buf for StringBuf {
fn advance(&mut self, n: usize) {
let pos = self.0.position() as usize;
let len = self.0.get_ref().len();
if len < pos + n {
panic!(
"{}",
&BufUnderflow {
requested: n,
remaining: len - pos,
}
);
} else {
self.0.set_position((pos + n) as u64);
}
}
#[inline]
fn chunk(&self) -> &[u8] {
let pos = self.0.position() as usize;
let buf = self.0.get_ref().as_bytes();
&buf[pos..]
}
#[inline]
fn remaining(&self) -> usize {
let pos = self.0.position() as usize;
let len = self.0.get_ref().len();
len - pos
}
fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), BufUnderflow> {
let pos = self.0.position() as usize;
let len = self.0.get_ref().len();
if len < pos + dst.len() {
Err(BufUnderflow {
requested: dst.len(),
remaining: len - pos,
})
} else {
dst.copy_from_slice(&self.0.get_ref().as_bytes()[pos..pos + dst.len()]);
self.0.set_position((pos + dst.len()) as u64);
Ok(())
}
}
}
impl IntoBuf for String {
type Buf = StringBuf;
fn into_buf(self) -> Self::Buf {
StringBuf(Cursor::new(self))
}
}
pub trait EqStr: for<'a> PartialEq<&'a str> {}
impl EqStr for &'_ str {}
pub trait OrdStr: EqStr + for<'a> PartialOrd<&'a str> {
fn cmp(&self, other: &str) -> Ordering;
}
impl OrdStr for &'_ str {
#[inline(always)]
fn cmp(&self, other: &str) -> Ordering {
(**self).cmp(other)
}
}
pub fn buf_cmp<A: IntoBuf, B: IntoBuf>(a: A, b: B) -> Ordering {
let mut a = a.into_buf();
let (mut a_chunk, mut a_i) = (a.chunk(), 0);
let mut b = b.into_buf();
let (mut b_chunk, mut b_i) = (b.chunk(), 0);
loop {
if a_i == a_chunk.len() || b_i == b_chunk.len() {
if a_i == a_chunk.len() {
a.advance(a_chunk.len());
a_chunk = a.chunk();
a_i = 0;
}
if b_i == b_chunk.len() {
b.advance(b_chunk.len());
b_chunk = b.chunk();
b_i = 0;
}
if !a.has_remaining() && !b.has_remaining() {
return Ordering::Equal;
} else if !a.has_remaining() {
debug_assert!(a_chunk.is_empty());
return Ordering::Less;
} else if !b.has_remaining() {
debug_assert!(b_chunk.is_empty());
return Ordering::Greater;
}
}
debug_assert!(
a_i < a_chunk.len(),
"a_i ({a_i} >= a_chunk.len() ({})",
a_chunk.len()
);
debug_assert!(
b_i < b_chunk.len(),
"b_i ({b_i} >= b_chunk.len() ({})",
b_chunk.len()
);
let ord = a_chunk[a_i].cmp(&b_chunk[b_i]);
if ord != Ordering::Equal {
return ord;
}
a_i += 1;
b_i += 1;
}
}
#[allow(unused_macros)]
#[cfg(debug_assertions)]
macro_rules! stringify_known_utf8 {
($name:ty, $v:expr) => {
<$name>::from_utf8($v).expect(concat!(
"SAFETY: input ",
stringify!(v),
" must only contain valid UTF-8 characters"
))
};
}
#[allow(unused_macros)]
#[cfg(not(debug_assertions))]
macro_rules! stringify_known_utf8 {
($name:ty, $v:expr) => {
unsafe { <$name>::from_utf8_unchecked($v) }
};
}
#[cfg(any(feature = "pipe", feature = "read"))]
pub(crate) mod buf {
use super::*;
use std::hash::Hasher;
pub fn to_string<T: IntoBuf>(t: T) -> String {
let mut b = t.into_buf();
let mut v = Vec::with_capacity(b.remaining());
while b.has_remaining() {
let chunk = b.chunk();
v.extend(chunk);
b.advance(chunk.len());
}
stringify_known_utf8!(String, v)
}
pub fn display<T: IntoBuf>(t: T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut b = t.into_buf();
let n = b.remaining();
let chunk = b.chunk();
if chunk.len() >= n {
f.write_str(stringify_known_utf8!(str, chunk))
} else {
let mut v = Vec::with_capacity(n);
while b.has_remaining() {
let chunk = b.chunk();
v.extend(chunk);
b.advance(chunk.len());
}
f.write_str(stringify_known_utf8!(str, &v))
}
}
#[cfg(not(test))]
pub const HASH_CHUNK: usize = 1024;
#[cfg(test)]
pub const HASH_CHUNK: usize = 4;
pub fn hash<T: IntoBuf, H: Hasher>(t: T, state: &mut H) {
let mut b = t.into_buf();
let first = b.chunk();
if first.len() <= HASH_CHUNK && first.len() == b.remaining() {
state.write(first);
} else {
let mut chunk = [0; HASH_CHUNK];
while HASH_CHUNK <= b.remaining() {
b.copy_to_slice(&mut chunk);
state.write(&chunk[..]);
}
let n = b.remaining();
if n > 0 {
b.copy_to_slice(&mut chunk[..n]);
state.write(&chunk[..n]);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "read")]
fn test_to_string() {
assert_eq!("foo", to_string("foo"));
}
#[test]
#[cfg(feature = "read")]
fn test_display() {
struct Wrapper(&'static str);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display(self.0.to_string(), f)
}
}
let wrapper = Wrapper("foo");
assert_eq!("foo", format!("{wrapper}"));
}
}
}
pub trait Sink {
fn reserve(&mut self, additional: usize);
fn extend_from_slice(&mut self, other: &[u8]);
fn push(&mut self, value: u8);
}
impl Sink for Vec<u8> {
#[inline(always)]
fn reserve(&mut self, additional: usize) {
Vec::reserve(self, additional)
}
#[inline(always)]
fn extend_from_slice(&mut self, other: &[u8]) {
Vec::extend_from_slice(self, other);
}
#[inline(always)]
fn push(&mut self, value: u8) {
Vec::push(self, value);
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use std::{fmt::Debug, ops::Deref};
#[test]
fn test_pos_new() {
assert_eq!(
Pos {
offset: 1,
line: 2,
col: 3
},
Pos::new(1, 2, 3)
);
}
#[test]
fn test_pos_default() {
assert_eq!(
Pos {
offset: 0,
line: 1,
col: 1
},
Pos::default()
);
}
#[test]
fn test_pos_display() {
assert_eq!(
"line 1, column 1 (offset: 0)",
format!("{}", Pos::default())
);
assert_eq!(
"line 77, column 8 (offset: 103)",
format!("{}", Pos::new(103, 77, 8))
);
}
#[rstest]
#[case("", 1)]
#[case(String::new(), 1)]
#[case("foo", 4)]
#[case("bar".to_string(), 10)]
#[should_panic(expected = "not enough bytes in buffer")]
fn test_buf_advance_panic<T: IntoBuf>(#[case] t: T, #[case] n: usize) {
let mut b = t.into_buf();
b.advance(n);
}
#[rstest]
#[case("foo", 0, "foo")]
#[case("foo", 1, "oo")]
#[case("foo", 2, "o")]
#[case("foo", 3, "")]
#[case("fo", 0, "fo")]
#[case("fo", 1, "o")]
#[case("fo", 2, "")]
#[case("f", 0, "f")]
#[case("f", 1, "")]
#[case("", 0, "")]
fn test_buf_advance_ok(#[case] s: &str, #[case] n: usize, #[case] expect: &str) {
fn exec_test<T: IntoBuf>(t: T, n: usize, expect: &str) {
let mut b = t.into_buf();
b.advance(n);
assert_eq!(expect, str::from_utf8(b.chunk()).unwrap());
assert_eq!(expect.len(), b.remaining());
}
exec_test(s, n, expect);
exec_test(s.to_string(), n, expect);
}
#[rstest]
#[case("")]
#[case("a")]
#[case("foo")]
fn test_buf_chunk(#[case] s: &str) {
fn exec_test<T: IntoBuf>(t: T, s: &str) {
let b = t.into_buf();
assert_eq!(s, str::from_utf8(b.chunk()).unwrap());
}
exec_test(s, s);
exec_test(s.to_string(), s);
}
#[rstest]
#[case("", 0, false)]
#[case("a", 1, true)]
#[case("foo", 3, true)]
fn test_buf_remaining(
#[case] s: &str,
#[case] expect_remaining: usize,
#[case] expect_has_remaining: bool,
) {
fn exec_test<T: IntoBuf>(t: T, expect_remaining: usize, expect_has_remaining: bool) {
let b = t.into_buf();
assert_eq!(expect_remaining, b.remaining());
assert_eq!(expect_has_remaining, b.has_remaining());
}
exec_test(s, expect_remaining, expect_has_remaining);
exec_test(s.to_string(), expect_remaining, expect_has_remaining);
}
#[rstest]
#[case("", b"", "")]
#[case("a", b"", "a")]
#[case("a", b"a", "")]
#[case("bar", b"", "bar")]
#[case("bar", b"b", "ar")]
#[case("bar", b"ba", "r")]
#[case("bar", b"bar", "")]
fn test_buf_try_copy_to_slice_ok<const N: usize>(
#[case] s: &str,
#[case] expect: &[u8; N],
#[case] rem: &str,
) {
fn exec_test<T: IntoBuf, const N: usize>(t: T, expect: &[u8; N], rem: &str) {
let mut b = t.into_buf();
let mut actual = [0; N];
let result = b.try_copy_to_slice(&mut actual);
assert_eq!(Ok(()), result);
assert_eq!(expect, &actual);
assert_eq!(rem.len(), b.remaining());
assert_eq!(rem, str::from_utf8(b.chunk()).unwrap());
}
exec_test(s, expect, rem);
exec_test(s.to_string(), expect, rem);
}
#[rstest]
#[case("", [0; 1])]
#[case("", [0; 2])]
#[case("a", [0; 2])]
#[case("foo", [0; 4])]
#[case("foo", [0; 99])]
fn test_buf_try_copy_to_slice_err<const N: usize>(#[case] s: &str, #[case] dst: [u8; N]) {
fn exec_test<T: IntoBuf + Clone + Debug + Deref<Target = str>, const N: usize>(
t: T,
mut dst: [u8; N],
) {
let u = t.clone();
let mut b = t.into_buf();
let result = b.try_copy_to_slice(&mut dst);
assert_eq!(
Err(BufUnderflow {
remaining: u.len(),
requested: N
}),
result
);
assert_eq!(&*u, str::from_utf8(b.chunk()).unwrap());
}
exec_test(s, dst);
exec_test(s.to_string(), dst);
}
#[rstest]
#[case("", b"", "")]
#[case("a", b"", "a")]
#[case("a", b"a", "")]
#[case("bar", b"", "bar")]
#[case("bar", b"b", "ar")]
#[case("bar", b"ba", "r")]
#[case("bar", b"bar", "")]
fn test_buf_copy_to_slice_ok<const N: usize>(
#[case] s: &str,
#[case] expect: &[u8; N],
#[case] rem: &str,
) {
fn exec_test<T: IntoBuf, const N: usize>(t: T, expect: &[u8; N], rem: &str) {
let mut b = t.into_buf();
let mut actual = [0; N];
b.copy_to_slice(&mut actual);
assert_eq!(expect, &actual);
assert_eq!(rem, str::from_utf8(b.chunk()).unwrap());
}
exec_test(s, expect, rem);
exec_test(s.to_string(), expect, rem);
}
#[test]
fn test_ord_str() {
assert_eq!(Ordering::Less, OrdStr::cmp(&"abc", "abd"));
assert_eq!(Ordering::Equal, OrdStr::cmp(&"abc", "abc"));
assert_eq!(Ordering::Greater, OrdStr::cmp(&"abd", "abc"));
}
}