use crate::matcher::{Matcher, MatcherResult};
pub use crate::token::{TOKEN_TYPE_WHITESPACE, Token};
use std::collections::HashMap;
#[derive(Clone, Debug, Copy)]
pub struct WhitespaceMatcher {
pub index: usize,
pub column: usize,
pub line: usize,
pub precedence: u8,
pub running: bool,
}
impl Matcher for WhitespaceMatcher {
fn reset(&mut self, _ctx: &mut Box<HashMap<String, i32>>) {
self.index = 0;
self.line = 0;
self.column = 0;
self.running = true;
}
fn find_match(
&mut self,
oc: Option<char>,
value: &[char],
_ctx: &mut Box<HashMap<String, i32>>,
) -> MatcherResult {
match oc {
Some(c) if c.is_whitespace() => {
self.index += 1;
self.column += 1;
if c == '\r' {
self.column = 0;
} else if c == '\n' {
self.column = 1;
self.line += 1;
}
MatcherResult::Running()
}
_ => {
self.running = false;
self.generate_whitspace_token(value)
}
}
}
fn is_running(&self) -> bool {
self.running
}
fn precedence(&self) -> u8 {
self.precedence
}
}
impl WhitespaceMatcher {
#[inline(always)]
fn generate_whitspace_token(&mut self, value: &[char]) -> MatcherResult {
if self.index > 0 {
MatcherResult::Matched(Token {
value: value[0..self.index].iter().collect(),
token_type: TOKEN_TYPE_WHITESPACE,
len: self.index,
line: self.line,
column: self.column,
precedence: self.precedence,
})
} else {
MatcherResult::Failed()
}
}
}
#[cfg(test)]
mod tests {
use crate::input::InputString;
use crate::matcher::Matcher;
use crate::matcher::MatcherResult;
use crate::matcher::whitespace::WhitespaceMatcher;
use crate::matcher::word::WordMatcher;
use crate::token::TOKEN_TYPE_WHITESPACE;
use crate::{Lexxer, Lexxor};
use std::collections::HashMap;
#[test]
fn test_basic_whitespace_matching() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from(" "))),
vec![Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
})],
);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, " ");
assert!(matches!(lexxor.next_token(), Ok(None)));
}
#[test]
fn test_mixed_whitespace_types() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from(" \t\r\n "))),
vec![Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
})],
);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, " \t\r\n ");
assert!(matches!(lexxor.next_token(), Ok(None)));
}
#[test]
fn test_line_counting() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from("a\nb\r\nc\rd"))),
vec![
Box::new(WordMatcher {
index: 0,
precedence: 0,
running: true,
}),
Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
}),
],
);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.value, "a");
assert_eq!(token.line, 1);
assert_eq!(token.column, 1);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, "\n");
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.value, "b");
assert_eq!(token.line, 2);
assert_eq!(token.column, 1);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, "\r\n");
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.value, "c");
assert_eq!(token.line, 3);
assert_eq!(token.column, 1);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, "\r");
}
#[test]
fn test_whitespace_with_non_whitespace() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from(" abc "))),
vec![
Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
}),
Box::new(WordMatcher {
index: 0,
precedence: 0,
running: true,
}),
],
);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, " ");
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.value, "abc");
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, " ");
}
#[test]
fn test_unicode_whitespace() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from(" \u{00A0}\u{2002}\u{3000} "))),
vec![Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
})],
);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, " \u{00A0}\u{2002}\u{3000} ");
}
#[test]
fn test_empty_input() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from(""))),
vec![Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
})],
);
assert!(matches!(lexxor.next_token(), Ok(None)));
}
#[test]
fn test_reset_functionality() {
let mut matcher = WhitespaceMatcher {
index: 10, column: 5,
line: 3,
precedence: 0,
running: false,
};
let mut ctx = Box::new(HashMap::new());
matcher.reset(&mut ctx);
assert_eq!(matcher.index, 0);
assert_eq!(matcher.column, 0);
assert_eq!(matcher.line, 0);
assert!(matcher.running);
}
#[test]
fn test_whitespace_at_end_of_input() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from("abc "))),
vec![
Box::new(WordMatcher {
index: 0,
precedence: 0,
running: true,
}),
Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
}),
],
);
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.value, "abc");
let token = lexxor.next_token().unwrap().unwrap();
assert_eq!(token.token_type, TOKEN_TYPE_WHITESPACE);
assert_eq!(token.value, " ");
assert!(matches!(lexxor.next_token(), Ok(None)));
}
#[test]
fn test_direct_matcher_methods() {
let mut matcher = WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 2, running: true,
};
let mut ctx = Box::new(HashMap::new());
let value: Vec<char> = vec![' ', ' ', '\n'];
assert!(matcher.is_running());
assert_eq!(matcher.precedence(), 2);
assert!(matches!(
matcher.find_match(Some(' '), &value, &mut ctx),
MatcherResult::Running()
));
assert_eq!(matcher.index, 1);
assert_eq!(matcher.column, 1);
assert!(matches!(
matcher.find_match(Some('\n'), &value, &mut ctx),
MatcherResult::Running()
));
assert_eq!(matcher.index, 2);
assert_eq!(matcher.column, 1); assert_eq!(matcher.line, 1);
assert!(matches!(
matcher.find_match(Some('a'), &value, &mut ctx),
MatcherResult::Matched(_)
));
assert!(!matcher.is_running()); }
}