use core::str;
use crate::{
span::{Span, Spannable, Spanned},
SourceCodeScanner,
};
pub mod identifier;
pub mod numeric;
pub mod string;
pub mod structure;
impl<'src> SourceCodeScanner<'src> {
#[inline]
pub fn capture_str<F: FnOnce()>(&self, predicate: F) -> Spanned<&'src str> {
let start_pos = self.span();
let range = self.consume_range(predicate);
let end_pos = self.span();
range.spanned(start_pos.up_to(&end_pos))
}
#[inline]
pub fn skip_until_match<F: Fn(char) -> bool>(&self, predicate: F) {
while let Some(char) = self.peek() {
if predicate(char) {
break;
} else {
self.skip();
}
}
}
#[inline]
pub fn skip_until(&self, match_character: char) {
while let Some(char) = self.peek() {
if char == match_character {
break;
} else {
self.skip();
}
}
}
#[inline]
pub fn capture_span<F: FnOnce()>(&self, predicate: F) -> Span {
let start_position = self.span();
predicate();
start_position.up_to(&self.span())
}
#[inline]
pub fn next_span(&self) -> Option<Spanned<char>> {
let span = self.span();
self.next().map(|char| span.wrap(char))
}
#[inline]
pub fn peek_is(&self, char: char) -> bool {
self.peek() == Some(char)
}
#[inline]
pub fn peek_is_map<F: FnOnce(char) -> bool>(&self, predicate: F) -> bool {
self.peek().map_or(false, predicate)
}
#[inline]
pub fn peek_is_not(&self, char: char) -> bool {
self.peek() != Some(char)
}
}
#[macro_export]
macro_rules! map_single_char_token {
($e:expr, $r:expr, $($pat:pat => $value:expr),* $(,)?) => {
match $crate::SourceCodeScanner::peek($e) {
$(core::option::Option::Some($pat) => {
let span = $crate::SourceCodeScanner::span($e);
$crate::SourceCodeScanner::skip($e);
$crate::LexerResult::push_token($r, $crate::token::Token::new($value, span));
continue;
},)*
_ => {},
};
}
}
#[macro_export]
macro_rules! map_double_char_tokens {
($e:expr, $r:expr, $($char:literal => { $($pat:literal => $value:expr,)* _ => $final_value:expr $(,)? }),* $(,)?) => {
match $crate::SourceCodeScanner::peek($e) {
$(
core::option::Option::Some($char) => {
let initial_span = $crate::SourceCodeScanner::span($e);
$crate::SourceCodeScanner::skip($e);
let data = match $crate::SourceCodeScanner::peek($e) {
$(
core::option::Option::Some($pat) => {
$crate::SourceCodeScanner::skip($e);
$value
},
)*
_ => {
$final_value
}
};
let span = $crate::SourceCodeScanner::span($e);
$crate::LexerResult::push_token(
$r,
$crate::token::Token::new(data, $crate::span::Span::up_to(&initial_span, &span))
);
continue;
},
)*
_ => {
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{
span::{Span, Spannable},
SourceCodeScanner,
};
fn ctx(code: &str) -> SourceCodeScanner {
SourceCodeScanner::new(code)
}
#[test]
fn recover_with() {
let code = ctx("1 blabla whatever 2 yea yea 3 ... 4");
unsafe {
code.skip_until_match(|x| x.is_numeric());
assert_eq!(code.next_span(), Some('1'.spanned(Span::new(0, 1))));
code.skip_until_match(|x| x.is_numeric());
assert_eq!(code.next_span(), Some('2'.spanned(Span::new(18, 1))));
code.skip_until_match(|x| x.is_numeric());
assert_eq!(code.next_span(), Some('3'.spanned(Span::new(28, 1))));
code.skip_until_match(|x| x.is_numeric());
assert_eq!(code.next_span(), Some('4'.spanned(Span::new(34, 1))));
assert!(!code.has_next());
code.skip_until_match(|x| x.is_numeric());
assert!(!code.has_next());
}
}
#[test]
fn skip_until() {
let code = ctx("|1 blabla whatever |2 yea yea |3 ... |");
unsafe {
code.skip_until('|');
assert_eq!(code.next_span(), Some('|'.spanned(Span::new(0, 1))));
code.skip_until('|');
assert_eq!(code.next_span(), Some('|'.spanned(Span::new(19, 1))));
code.skip_until('|');
assert_eq!(code.next_span(), Some('|'.spanned(Span::new(30, 1))));
code.skip_until('|');
assert_eq!(code.next_span(), Some('|'.spanned(Span::new(37, 1))));
assert!(!code.has_next());
code.skip_until('|');
assert!(!code.has_next());
}
}
#[test]
fn capture_span() {
let code = ctx("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
code.skip();
code.skip();
let mut out = String::new();
let span = code.capture_span(|| {
out.push(code.next().unwrap());
out.push(code.next().unwrap());
out.push(code.next().unwrap());
out.push(code.next().unwrap());
});
unsafe {
assert_eq!(span, Span::new(2, 4));
}
assert_eq!(out, "CDEF");
}
#[test]
fn capture_str() {
let code = ctx("this is one string|this is another|and another!");
let mut collected = vec![];
while code.has_next() {
let res = code.capture_str(|| {
while code.has_next() {
if code.peek_is('|') {
break;
}
code.skip();
}
});
code.skip();
collected.push(res);
}
assert_eq!(
collected,
unsafe {
vec![
(Span::new(0, 18).wrap("this is one string")),
(Span::new(19, 15).wrap("this is another")),
(Span::new(35, 12).wrap("and another!")),
]
}
);
assert!(!code.has_next());
}
}