#![allow(clippy::inline_always)]
#[derive(Clone, Copy, Debug)]
pub struct CanonicalScanner<'a> {
remaining: &'a str,
}
#[derive(Clone, Copy, Debug)]
pub struct CanonicalBytesScanner<'a> {
remaining: &'a [u8],
}
impl<'a> CanonicalScanner<'a> {
#[must_use]
#[inline(always)]
pub const fn new(input: &'a str) -> Self {
Self { remaining: input }
}
#[inline(always)]
pub fn literal(&mut self, expected: &str) -> Option<()> {
self.remaining = self.remaining.strip_prefix(expected)?;
Some(())
}
#[inline(always)]
pub fn plain_string(&mut self) -> Option<&'a str> {
let input = self.remaining.strip_prefix('"')?;
let end = memchr::memchr(b'"', input.as_bytes())?;
let value = &input[..end];
if value
.as_bytes()
.iter()
.any(|byte| *byte == b'\\' || *byte < 0x20)
{
return None;
}
self.remaining = &input[end + 1..];
Some(value)
}
#[inline(always)]
pub fn unsigned(&mut self) -> Option<u64> {
let bytes = self.remaining.as_bytes();
let first = *bytes.first()?;
if !first.is_ascii_digit() {
return None;
}
if first == b'0' && bytes.get(1).is_some_and(u8::is_ascii_digit) {
return None;
}
let mut value = 0_u64;
let mut length = 0;
for &digit in bytes {
if !digit.is_ascii_digit() {
break;
}
value = value
.checked_mul(10)?
.checked_add(u64::from(digit - b'0'))?;
length += 1;
}
self.remaining = &self.remaining[length..];
Some(value)
}
#[inline(always)]
pub fn boolean(&mut self) -> Option<bool> {
if let Some(remaining) = self.remaining.strip_prefix("true") {
self.remaining = remaining;
Some(true)
} else if let Some(remaining) = self.remaining.strip_prefix("false") {
self.remaining = remaining;
Some(false)
} else {
None
}
}
#[must_use]
#[inline(always)]
pub const fn remaining(&self) -> &'a str {
self.remaining
}
#[must_use]
#[inline(always)]
pub const fn is_finished(&self) -> bool {
self.remaining.is_empty()
}
}
impl<'a> CanonicalBytesScanner<'a> {
#[must_use]
#[inline(always)]
pub const fn new(input: &'a [u8]) -> Self {
Self { remaining: input }
}
#[inline(always)]
pub fn literal(&mut self, expected: &str) -> Option<()> {
self.remaining = self.remaining.strip_prefix(expected.as_bytes())?;
Some(())
}
#[inline(always)]
pub fn plain_string(&mut self) -> Option<&'a str> {
let input = self.remaining.strip_prefix(b"\"")?;
let end = memchr::memchr(b'"', input)?;
let value = &input[..end];
if value.iter().any(|byte| *byte == b'\\' || *byte < 0x20) {
return None;
}
let value = std::str::from_utf8(value).ok()?;
self.remaining = &input[end + 1..];
Some(value)
}
#[inline(always)]
pub fn plain_ascii_string(&mut self) -> Option<&'a [u8]> {
let input = self.remaining.strip_prefix(b"\"")?;
let end = memchr::memchr(b'"', input)?;
let value = &input[..end];
if value
.iter()
.any(|byte| !byte.is_ascii() || *byte == b'\\' || *byte < 0x20)
{
return None;
}
self.remaining = &input[end + 1..];
Some(value)
}
#[inline(always)]
pub fn unsigned(&mut self) -> Option<u64> {
let first = *self.remaining.first()?;
if !first.is_ascii_digit() {
return None;
}
if first == b'0' && self.remaining.get(1).is_some_and(u8::is_ascii_digit) {
return None;
}
let mut value = 0_u64;
let mut length = 0;
for &digit in self.remaining {
if !digit.is_ascii_digit() {
break;
}
value = value
.checked_mul(10)?
.checked_add(u64::from(digit - b'0'))?;
length += 1;
}
self.remaining = &self.remaining[length..];
Some(value)
}
#[inline(always)]
pub fn boolean(&mut self) -> Option<bool> {
if let Some(remaining) = self.remaining.strip_prefix(b"true") {
self.remaining = remaining;
Some(true)
} else if let Some(remaining) = self.remaining.strip_prefix(b"false") {
self.remaining = remaining;
Some(false)
} else {
None
}
}
#[must_use]
#[inline(always)]
pub const fn remaining(&self) -> &'a [u8] {
self.remaining
}
#[must_use]
#[inline(always)]
pub const fn is_finished(&self) -> bool {
self.remaining.is_empty()
}
}