use core::{slice, ops::Range};
const fn subslice<T>(slice: &[T], range: Range<usize>) -> &[T] {
assert!(range.start <= range.end, "Invalid range.");
assert!(range.end <= slice.len(), "Range out of bounds.");
let range_len = range.end - range.start;
unsafe {
slice::from_raw_parts(slice.as_ptr().add(range.start), range_len)
}
}
const fn substr(s: &str, range: Range<usize>) -> &str {
match str::from_utf8(subslice(s.as_bytes(), range)) {
Ok(result) => result,
Err(_) => panic!("Invalid UTF-8 slice."),
}
}
macro_rules! const_cmp_body {
($lhs:expr, $rhs:expr) => {
{
let lhs = $lhs;
let rhs = $rhs;
let (min_len, short_circuit_result) = if lhs.len() == rhs.len() {
(lhs.len(), core::cmp::Ordering::Equal)
} else if lhs.len() < rhs.len() {
(lhs.len(), core::cmp::Ordering::Less)
} else {
(rhs.len(), core::cmp::Ordering::Greater)
};
let mut index = 0usize;
while index < min_len {
if lhs[index] < rhs[index] {
return core::cmp::Ordering::Less;
} else if lhs[index] > rhs[index] {
return core::cmp::Ordering::Greater;
}
index += 1;
}
short_circuit_result
}
};
}
const fn cmp_str(lhs: &str, rhs: &str) -> core::cmp::Ordering {
const_cmp_body!(lhs.as_bytes(), rhs.as_bytes())
}
use std::{cell::Cell, collections::HashSet, mem::MaybeUninit, sync::atomic::AtomicU64};
#[inline]
fn next_id() -> u64 {
static ID_COUNTER: AtomicU64 = AtomicU64::new(0);
ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::AcqRel)
}
macro_rules! const_min {
($lhs:expr, $rhs:expr) => {
{
let lhs = $lhs;
let rhs = $rhs;
if lhs <= rhs {
lhs
} else {
rhs
}
}
};
}
pub const fn next_char_with_len(s: &str) -> (char, u32) {
let bytes = s.as_bytes();
let first = bytes[0];
match first.leading_ones() {
0 => {
let codepoint = first & 0b01111111;
let chr = unsafe {
char::from_u32_unchecked(codepoint as u32)
};
(chr, 1)
}
2 => {
let mut codepoint = ((first & 0b00011111) as u32) << 6;
let second = bytes[1];
codepoint |= (second & 0b00111111) as u32;
let chr = unsafe {
char::from_u32_unchecked(codepoint)
};
(chr, 2)
}
3 => {
let mut codepoint = ((first & 0b00001111) as u32) << 6;
let second = bytes[1];
codepoint = (codepoint | (second & 0b00111111) as u32) << 6;
let third = bytes[2];
codepoint = codepoint | (third & 0b00111111) as u32;
let chr = unsafe {
char::from_u32_unchecked(codepoint)
};
(chr, 3)
}
4 => {
let mut codepoint = ((first & 0b00000111) as u32) << 6;
let second = bytes[1];
codepoint = (codepoint | (second & 0b00111111) as u32) << 6;
let third = bytes[2];
codepoint = (codepoint | (third & 0b00111111) as u32) << 6;
let fourth = bytes[3];
codepoint = codepoint | (fourth & 0b00111111) as u32;
let chr = unsafe {
char::from_u32_unchecked(codepoint)
};
(chr, 4)
}
_ => unreachable!(),
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MatchStatus {
Continue = 0,
ContinueSuccess = 1,
Success = 2,
Failure = 3,
End = 4,
EndSuccess = 5,
}
impl MatchStatus {
#[must_use]
#[inline]
pub const fn text(self) -> &'static str {
match self {
Self::Continue => "Continue",
Self::ContinueSuccess => "ContinueSuccess",
Self::Success => "Success",
Self::Failure => "Failure",
Self::End => "End",
Self::EndSuccess => "EndSuccess",
}
}
#[inline]
pub const fn is_success(self, include_continue: bool) -> bool {
match self {
Self::Success => true,
Self::ContinueSuccess => include_continue,
_ => false,
}
}
#[inline]
pub const fn is_failure(self) -> bool {
matches!(self, Self::Failure)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
enum ValidState {
#[default]
Init,
Valid,
Invalid,
}
impl ValidState {
pub const fn validate(&mut self, status: MatchStatus) -> Self {
*self = match status {
MatchStatus::Continue => Self::Invalid,
MatchStatus::ContinueSuccess => Self::Valid,
MatchStatus::Success => Self::Valid,
MatchStatus::Failure => Self::Invalid,
MatchStatus::End => return *self,
MatchStatus::EndSuccess => Self::Valid,
};
*self
}
}
impl std::fmt::Display for MatchStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.text())
}
}
pub struct Spec<'a, 'b: 'a> {
parser: &'a Parser<'b>,
cursor: usize,
matched: char,
len: u32,
}
impl<'a, 'b: 'a> Spec<'a, 'b> {
const fn new(parser: &'a Parser<'b>, matched: char, len: u32) -> Self {
Self {
parser,
cursor: parser.cursor(),
matched,
len,
}
}
#[inline(always)]
pub const fn matched(&self) -> char {
self.matched
}
#[inline(always)]
pub const fn len(&self) -> u32 {
self.len
}
#[inline(always)]
pub const fn unchecked_consume(self) -> (char, u32) {
if self.parser.cursor() != self.cursor {
panic!("parser's cursor does not match spec's cursor.");
}
self.parser.advance_cursor(self.len as usize);
(self.matched, self.len)
}
#[inline]
pub const fn try_consume(self) -> Result<(char, u32), (char, u32)> {
if self.parser.cursor() == self.cursor {
Ok(self.unchecked_consume())
} else {
Err((self.matched, self.len))
}
}
}
#[derive(Debug, Clone)]
pub struct Parser<'a> {
id: u64,
fork_depth: Cell<u64>,
source: &'a str,
start: usize,
cursor: Cell<usize>,
}
impl<'a> std::cmp::PartialEq<Parser<'a>> for Parser<'a> {
fn eq(&self, other: &Parser<'a>) -> bool {
std::ptr::eq(self.source, other.source)
&& self.start == other.start
&& self.cursor == other.cursor
}
fn ne(&self, other: &Parser<'a>) -> bool {
!std::ptr::eq(self.source, other.source)
|| self.start != other.start
|| self.cursor != other.cursor
}
}
impl<'a> std::cmp::Eq for Parser<'a> {}
impl<'a> Parser<'a> {
#[inline]
pub(crate) const fn with_id(source: &'a str, id: u64) -> Self {
Self {
source,
start: 0,
cursor: Cell::new(0),
id,
fork_depth: Cell::new(0),
}
}
#[inline(always)]
pub fn new(source: &'a str) -> Self {
Self::with_id(source, next_id())
}
#[inline]
pub const fn cursor(&self) -> usize {
self.cursor.get()
}
#[inline]
pub const fn fork_depth(&self) -> u64 {
self.fork_depth.get()
}
#[inline]
pub const fn speculate(&self) -> Option<Spec<'_, '_>> {
if self.at_end() {
return None;
}
let (next, len) = next_char_with_len(self.substr_at_cursor());
Some(Spec::new(self, next, len))
}
#[inline]
pub const fn peek_with_len(&self) -> Option<(char, u32)> {
if self.at_end() {
return None;
}
let result = next_char_with_len(self.substr_at_cursor());
Some(result)
}
#[inline]
pub const fn peek(&self) -> Option<char> {
match self.peek_with_len() {
Some((chr, _)) => Some(chr),
None => None,
}
}
#[inline]
pub const fn peek2_with_len(&self) -> Option<(char, u32)> {
if self.at_end() {
return None;
}
let (_, len) = next_char_with_len(self.substr_at_cursor());
if self.at_end() {
return None;
}
let (resc, rlen) = next_char_with_len(substr(self.source, self.cursor() + len as usize..self.source.len()));
Some((resc, len + rlen))
}
#[inline]
pub const fn peek2(&self) -> Option<char> {
match self.peek2_with_len() {
Some((chr, _)) => Some(chr),
None => None,
}
}
#[inline]
pub const fn peek_with_len_at(&self, index: usize) -> Result<(char, usize), usize> {
let mut i = 0;
let fork = self.fork();
let mut total_len = 0usize;
let mut current_char = MaybeUninit::<char>::uninit();
loop {
match fork.next_with_len() {
Some((chr, len)) => {
total_len += len as usize;
current_char.write(chr);
}
None => return Err(i),
}
if i >= index {
break;
}
i += 1;
}
let current_char = unsafe { current_char.assume_init() };
Ok((current_char, total_len))
}
#[inline]
pub const fn peek_at(&self, index: usize) -> Result<char, usize> {
match self.peek_with_len_at(index) {
Ok((chr, _)) => Ok(chr),
Err(remaining_chars) => Err(remaining_chars),
}
}
#[inline]
pub const fn next_with_len(&self) -> Option<(char, u32)> {
if self.at_end() {
return None;
}
let (next, len) = next_char_with_len(self.substr_at_cursor());
self.advance_cursor(len as usize);
Some((next, len))
}
#[inline]
pub const fn next(&self) -> Option<char> {
match self.next_with_len() {
Some((chr, _)) => Some(chr),
None => None,
}
}
#[inline(always)]
pub const fn same_source(&self, other: &Self) -> bool {
matches!(cmp_str(self.source, other.source), std::cmp::Ordering::Equal)
}
#[inline(always)]
pub const fn advance1(&self) -> bool {
self.next().is_some()
}
#[inline(always)]
pub const fn advance2(&self) -> usize {
if self.advance1() {
1 + (self.advance1() as usize)
} else {
0
}
}
#[inline]
pub const fn advance(&self, len: usize) -> usize {
let mut index = 0usize;
while index < len {
if !self.advance1() {
return index;
}
index += 1;
}
len
}
#[inline]
pub const fn advance_cursor(&self, count: usize) -> usize {
let advance_to = const_min!(self.cursor() + count, self.source.len());
self.cursor.replace(advance_to);
advance_to
}
pub const fn match_char(&self, expect: char) -> bool {
match self.peek_with_len() {
Some((next, len)) if next == expect => {
self.advance_cursor(len as usize);
true
}
_ => false,
}
}
pub const fn peek_match_char(&self, expect: char) -> bool {
match self.peek() {
Some(next) if next == expect => true,
_ => false,
}
}
#[inline(always)]
const fn primitive_match_multi_char_with_index<const ADVANCE: bool>(&self, chars: &[char]) -> Option<(char, usize)> {
match self.peek_with_len() {
Some((next, len)) => {
let mut index = 0usize;
while index < chars.len() {
if chars[index] == next {
if const { ADVANCE } {
self.advance_cursor(len as usize);
}
return Some((chars[index], index));
}
index += 1;
}
None
}
None => None,
}
}
pub const fn match_multi_char_with_index(&self, chars: &[char]) -> Option<(char, usize)> {
self.primitive_match_multi_char_with_index::<true>(chars)
}
#[inline]
pub const fn multi_match_char(&self, chars: &[char]) -> Option<char> {
match self.match_multi_char_with_index(chars) {
Some((matched, _index)) => Some(matched),
_ => None,
}
}
pub const fn peek_multi_char_with_index(&self, chars: &[char]) -> Option<(char, usize)> {
self.primitive_match_multi_char_with_index::<false>(chars)
}
#[inline]
pub const fn peek_multi_char(&self, chars: &[char]) -> Option<char> {
match self.peek_multi_char_with_index(chars) {
Some((matched, _index)) => Some(matched),
_ => None,
}
}
#[inline(always)]
fn primitive_parse_with_fork<const ADVANCE: bool, F: FnOnce(&Parser<'a>) -> bool>(&self, f: F) -> Option<(&str, Range<usize>)> {
let fork = self.fork();
if f(&fork) {
if const { ADVANCE } {
self.merge(&fork);
}
Some((fork.span_substr(), fork.span()))
} else {
None
}
}
pub fn parse_with_fork<F: FnOnce(&Parser<'a>) -> bool>(&self, f: F) -> Option<(&str, Range<usize>)> {
self.primitive_parse_with_fork::<true, F>(f)
}
pub fn peek_with_fork<F: FnOnce(&Parser<'a>) -> bool>(&self, f: F) -> Option<(&str, Range<usize>)> {
self.primitive_parse_with_fork::<false, F>(f)
}
#[inline(always)]
fn primitive_match_char_fn<const ADVANCE: bool, F: FnOnce(char) -> bool>(&self, matcher: F) -> Option<char> {
let (next, len) = self.peek_with_len()?;
if matcher(next) {
if const { ADVANCE } {
self.advance_cursor(len as usize);
}
Some(next)
} else {
None
}
}
#[inline]
pub fn match_char_fn<F: FnOnce(char) -> bool>(&self, matcher: F) -> Option<char> {
self.primitive_match_char_fn::<true, _>(matcher)
}
#[inline]
pub fn peek_char_fn<F: FnOnce(char) -> bool>(&self, matcher: F) -> Option<char> {
self.primitive_match_char_fn::<false, _>(matcher)
}
#[inline]
pub fn match_char_repeated<F: FnMut(char) -> bool>(&self, mut matcher: F) -> Option<(&'a str, Range<usize>)> {
self.match_str_fn(move |chr| if matcher(chr) {
MatchStatus::ContinueSuccess
} else {
MatchStatus::End
})
}
#[inline]
pub fn peek_char_repeated<F: FnMut(char) -> bool>(&self, matcher: F) -> Option<(&'a str, Range<usize>)> {
let fork = self.fork();
fork.match_char_repeated(matcher)
}
pub fn match_str_fn<F: FnMut(char) -> MatchStatus>(&self, mut matcher: F) -> Option<(&'a str, Range<usize>)> {
let mut validation = ValidState::Init;
let fork = self.fork();
loop {
let Some((peek, peek_len)) = fork.peek_with_len() else {
match validation {
ValidState::Valid => {
self.merge(&fork);
return Some((fork.span_substr(), fork.span()));
},
_ => return None,
}
};
match matcher(peek) {
cont @ (MatchStatus::Continue | MatchStatus::ContinueSuccess) => {
validation.validate(cont);
fork.advance_cursor(peek_len as usize);
},
MatchStatus::Success => {
fork.advance_cursor(peek_len as usize);
self.merge(&fork);
return Some((fork.span_substr(), fork.span()));
},
MatchStatus::Failure => return None,
MatchStatus::End => {
match validation {
ValidState::Valid => {
self.merge(&fork);
return Some((fork.span_substr(), fork.span()));
},
_ => return None,
}
}
MatchStatus::EndSuccess => {
self.merge(&fork);
return Some((fork.span_substr(), fork.span()));
}
}
}
}
#[inline(always)]
pub fn peek_str_fn<F: FnMut(char) -> MatchStatus>(&self, matcher: F) -> Option<(&'a str, Range<usize>)> {
let fork = self.fork();
fork.match_str_fn(matcher)
}
pub fn match_newline(&self) -> Option<(&'a str, Range<usize>)> {
#[derive(Clone, Copy, PartialEq, Eq)]
enum Stages {
Begin,
ReadCr,
}
let mut stage = Stages::Begin;
self.match_str_fn(move |chr| {
match chr {
'\n' => {
MatchStatus::Success
}
'\r' => match stage {
Stages::Begin => {
stage = Stages::ReadCr;
MatchStatus::ContinueSuccess
}
Stages::ReadCr => {
MatchStatus::EndSuccess
}
}
_ => match stage {
Stages::Begin => MatchStatus::Failure,
Stages::ReadCr => MatchStatus::EndSuccess,
}
}
})
}
pub fn peek_newline(&self) -> Option<(&'a str, Range<usize>)> {
let fork = self.fork();
fork.match_newline()
}
#[inline(always)]
pub fn eat_whitespace(&self) -> Option<(&'a str, Range<usize>)> {
self.match_str_fn(whitespace)
}
#[inline(always)]
pub fn match_ascii_ident(&self) -> Option<(&'a str, Range<usize>)> {
let mut ass: [MaybeUninit<bool>; 8] = unsafe { MaybeUninit::uninit().assume_init() };
ass[0].write(true);
self.match_str_fn(ascii_ident())
}
#[inline]
pub const fn match_exact(&self, exact: &str) -> bool {
let match_end = self.cursor() + exact.len();
if match_end > self.source.len() {
return false;
}
let sub = substr(self.source, self.cursor()..match_end);
match cmp_str(sub, exact) {
::std::cmp::Ordering::Equal => {
self.advance_cursor(exact.len());
true
}
_ => false,
}
}
#[inline(always)]
pub const fn peek_exact(&self, exact: &str) -> bool {
let fork = self.fork();
fork.match_exact(exact)
}
#[inline]
pub fn match_until<F: FnMut(char) -> bool>(&self, until: F) -> (&'a str, Range<usize>) {
self.match_str_fn(parse_until(until)).unwrap()
}
#[inline]
pub fn match_while<F: FnMut(char) -> bool>(&self, while_: F) -> (&'a str, Range<usize>) {
self.match_str_fn(parse_while(while_)).unwrap()
}
#[inline(always)]
pub fn peek_until<F: FnMut(char) -> bool>(&self, until: F) -> (&'a str, Range<usize>) {
let fork = self.fork();
fork.match_until(until)
}
#[inline(always)]
pub fn peek_while<F: FnMut(char) -> bool>(&self, while_: F) -> (&'a str, Range<usize>) {
let fork = self.fork();
fork.match_while(while_)
}
#[inline]
pub const fn match_exact_char(&self, exact: char) -> bool {
if let Some(peek) = self.peek()
&& peek == exact {
self.advance_cursor(peek.len_utf8());
true
} else {
false
}
}
#[inline]
pub fn peek_exact_char(&self, exact: char) -> bool {
let fork = self.fork();
fork.match_exact_char(exact)
}
#[inline(always)]
pub const fn fork(&self) -> Parser<'a> {
Parser {
source: self.source,
cursor: Cell::new(self.cursor()),
start: self.cursor(),
id: self.id,
fork_depth: Cell::new(self.fork_depth() + 1),
}
}
#[inline(always)]
pub const fn merge(&self, fork: &Parser<'_>) {
assert!(self.id == fork.id, "Fork does not have the same id as the merge target.");
assert!(self.fork_depth() <= fork.fork_depth(), "Fork is further down the chain from target.");
assert!(self.cursor() <= fork.cursor(), "self.cursor is past fork.cursor.");
self.cursor.replace(fork.cursor());
self.fork_depth.replace(fork.fork_depth());
}
#[inline(always)]
pub const fn substr_to_start(&self) -> &'a str {
substr(self.source, 0..self.start)
}
#[inline(always)]
pub const fn substr_to_cursor(&self) -> &'a str {
substr(self.source, 0..self.cursor())
}
#[inline(always)]
pub const fn span_substr(&self) -> &'a str {
substr(self.source, self.span())
}
#[inline(always)]
pub const fn substr_at_cursor(&self) -> &'a str {
substr(self.source, self.cursor()..self.source.len())
}
#[inline(always)]
pub const fn source(&self) -> &'a str {
self.source
}
#[inline(always)]
pub const fn start(&self) -> usize {
self.start
}
#[inline(always)]
pub const fn at_end(&self) -> bool {
self.cursor() == self.source.len()
}
#[inline(always)]
pub const fn span(&self) -> std::ops::Range<usize> {
self.start..self.cursor()
}
}
#[must_use]
#[inline(always)]
pub fn parse_while<F: FnMut(char) -> bool>(mut matcher: F) -> impl FnMut(char) -> MatchStatus {
move |c| {
if matcher(c) {
MatchStatus::ContinueSuccess
} else {
MatchStatus::EndSuccess
}
}
}
#[must_use]
#[inline(always)]
pub fn parse_until<F: FnMut(char) -> bool>(mut until: F) -> impl FnMut(char) -> MatchStatus {
move |c| {
if until(c) {
MatchStatus::EndSuccess
} else {
MatchStatus::ContinueSuccess
}
}
}
#[must_use]
#[inline(always)]
pub fn match_exact(to_match: &str) -> impl FnMut(char) -> MatchStatus {
let mut chars = to_match.chars();
move |c| {
match chars.next() {
Some(next) => if next == c {
MatchStatus::Continue
} else {
MatchStatus::Failure
},
None => MatchStatus::EndSuccess,
}
}
}
#[must_use]
#[inline(always)]
pub fn match_char(to_match: char) -> impl Fn(char) -> MatchStatus {
move |c| {
if c == to_match {
MatchStatus::Success
} else {
MatchStatus::Failure
}
}
}
#[must_use]
#[inline]
pub fn match_any_char(chars: &str) -> impl Fn(char) -> MatchStatus {
move |c| {
if chars.contains(c) {
MatchStatus::Success
} else {
MatchStatus::Failure
}
}
}
#[must_use]
#[inline]
pub fn match_from_set(chars: &HashSet<char>) -> impl Fn(char) -> MatchStatus {
move |c| {
if chars.contains(&c) {
MatchStatus::Success
} else {
MatchStatus::Failure
}
}
}
#[inline(always)]
pub fn whitespace(c: char) -> MatchStatus {
if c.is_whitespace() {
MatchStatus::ContinueSuccess
} else {
MatchStatus::End
}
}
#[inline]
pub fn ascii_ident() -> impl FnMut(char) -> MatchStatus {
let mut first = true;
move |c| {
match c {
'_'
| 'a'..='z'
| 'A'..='Z' => {
first = false;
MatchStatus::ContinueSuccess
}
'0'..='9' => if first {
MatchStatus::Failure
} else {
MatchStatus::ContinueSuccess
}
_ => MatchStatus::End
}
}
}
pub fn singleline_str_literal_matcher() -> impl FnMut(char) -> MatchStatus {
let mut first = true;
let mut skip1 = false;
move |c| {
if first {
if c == '"' {
first = false;
return MatchStatus::Continue;
} else {
return MatchStatus::Failure;
}
}
if skip1 {
skip1 = false;
return MatchStatus::Continue;
}
match c {
'\\' => {
skip1 = true;
MatchStatus::Continue
}
'\n' => MatchStatus::Failure,
'"' => MatchStatus::Success,
_ => MatchStatus::Continue,
}
}
}
pub struct Repeated {
chr: char,
count: u32,
}
impl Repeated {
pub const fn new(chr: char, count: u32) -> Self {
Self {
chr,
count,
}
}
}
pub fn match_between_repeated(left: Repeated, right: Repeated) -> impl FnMut(char) -> MatchStatus {
struct CharCounter {
chr: char,
count: u32,
success: u32,
}
impl CharCounter {
#[inline(always)]
fn new(chr: char, success: u32) -> Self {
Self {
chr,
count: 0,
success,
}
}
#[inline(always)]
fn done(&self) -> bool {
self.count == self.success
}
#[inline(always)]
fn count(&mut self, chr: char) -> bool {
self.count = if chr == self.chr {
self.count + 1
} else {
0
};
self.done()
}
}
let mut left_counter = CharCounter::new(left.chr, left.count);
let mut right_counter = CharCounter::new(right.chr, right.count);
let mut first_chr = true;
move |chr| -> MatchStatus {
if first_chr || !left_counter.done() {
first_chr = false;
if left_counter.count(chr) || left_counter.count > 0 {
MatchStatus::Continue
} else {
MatchStatus::Failure
}
} else {
if right_counter.count(chr) {
MatchStatus::Success
} else {
MatchStatus::Continue
}
}
}
}
#[must_use]
#[inline(always)]
pub fn match_singleline_str_literal(source: &str) -> Option<(&str, Range<usize>)> {
let parser = Parser::new(source);
parser.match_str_fn(singleline_str_literal_matcher())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parser_test() {
let source = " \t\t\n1234";
let parser = Parser::new(source);
let peeked = parser.peek_str_fn(parse_while(|c| c.is_whitespace()));
assert!(matches!(peeked, Some((" \t\t\n", _))));
let source = "Hello, world!";
let parser = Parser::new(source);
let peeked = parser.peek_str_fn(parse_until(|c| c == '!'));
assert!(matches!(peeked, Some(("Hello, world", _))));
let parser = Parser::new("foo");
assert!(parser.peek_str_fn(match_char('f')).is_some());
assert!(parser.peek_str_fn(match_char('x')).is_none());
assert!(parser.peek_exact_char('f'));
let source = r#""Hello, \"world\"!", this is a test."#;
let expected = r#""Hello, \"world\"!""#;
assert_eq!(match_singleline_str_literal(source), Some((expected, 0..expected.len())));
assert_eq!(match_singleline_str_literal("not a string literal"), None);
assert_eq!(match_singleline_str_literal("\"not a complete string literal"), None);
assert_eq!(match_singleline_str_literal("not a string literal\""), None);
let parser = Parser::new("hello, world");
let hello = "hello";
assert_eq!(
parser.peek_str_fn(match_exact(hello)),
Some((hello, 0..hello.len()))
);
let parser = Parser::new("```this is a test.``` end");
let inbetween = "```this is a test.```";
assert_eq!(
parser.peek_str_fn(match_between_repeated(
Repeated::new('`', 3),
Repeated::new('`', 3),
)),
Some((inbetween, 0..inbetween.len()))
);
}
}