use crate::matcher::{Matcher, MatcherResult};
use crate::token::Token;
use std::collections::HashMap;
#[derive(Clone, Debug)]
pub struct Target {
pub matching: bool,
pub target: Box<Vec<char>>,
}
#[derive(Clone, Debug)]
pub struct KeywordMatcher {
pub index: usize,
pub precedence: u8,
pub running: bool,
pub found: Option<usize>,
pub targets: Box<Vec<Target>>,
pub token_type: u16,
}
impl Matcher for KeywordMatcher {
fn reset(&mut self, _ctx: &mut Box<HashMap<String, i32>>) {
for t in self.targets.iter_mut() {
t.matching = true
}
self.found = None;
self.index = 0;
self.running = true;
}
fn find_match(
&mut self,
oc: Option<char>,
_value: &[char],
_ctx: &mut Box<HashMap<String, i32>>,
) -> MatcherResult {
match oc {
None => {
self.running = false;
for (i, target) in self.targets.iter().enumerate() {
if target.matching && target.target.get(self.index).is_none() {
self.found = Some(i);
break; }
}
self.generate_keyword_token()
}
Some(c) => {
self.running = false;
if !self.targets.iter().any(|t| t.matching) {
return self.generate_keyword_token();
}
let mut found_potential_match = false;
for (i, target) in self.targets.iter_mut().enumerate() {
if !target.matching {
continue; }
match target.target.get(self.index) {
None => {
target.matching = false;
if self.index > 0 && !c.is_alphanumeric() {
self.found = Some(i);
found_potential_match = true;
}
}
Some(m) => {
if *m == c {
self.running = true; } else {
target.matching = false;
}
}
}
}
self.index += 1;
if !self.running && !found_potential_match {
self.generate_keyword_token()
} else if self.running {
MatcherResult::Running()
} else {
self.generate_keyword_token()
}
}
}
}
fn is_running(&self) -> bool {
self.running
}
fn precedence(&self) -> u8 {
self.precedence
}
}
impl KeywordMatcher {
pub fn build_matcher_keyword(
matches: Vec<&str>,
token_type: u16,
precedence: u8,
) -> KeywordMatcher {
let mut targets = Vec::with_capacity(matches.len());
for m in matches {
let mut chars = Vec::with_capacity(m.len());
chars.extend(m.chars());
targets.push(Target {
matching: true,
target: Box::new(chars),
});
}
KeywordMatcher {
index: 0,
precedence,
found: None,
running: true,
token_type,
targets: Box::new(targets),
}
}
#[inline(always)]
fn generate_keyword_token(&mut self) -> MatcherResult {
match self.found {
None => MatcherResult::Failed(),
Some(_) => {
let i = self.found.unwrap();
let target = &self.targets.get(i).unwrap().target;
let token_value: String = target.clone().into_iter().collect();
let len = token_value.len();
MatcherResult::Matched(Token {
value: token_value,
token_type: self.token_type,
len,
line: 0,
column: len,
precedence: self.precedence,
})
}
}
}
}
#[cfg(test)]
mod tests {
use crate::input::InputString;
use crate::matcher::keyword::KeywordMatcher;
use crate::matcher::whitespace::WhitespaceMatcher;
use crate::token::TOKEN_TYPE_KEYWORD;
use crate::{LexxError, Lexxer, Lexxor};
#[test]
fn matcher_exact_matches_word() {
let mut lexxor: Box<dyn Lexxer> = Box::new(Lexxor::<512>::new(
Box::new(InputString::new(String::from("The"))),
vec![Box::new(KeywordMatcher::build_matcher_keyword(
vec!["The"],
TOKEN_TYPE_KEYWORD,
0,
))],
));
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(_) => {
unreachable!("Should not have failed parsing file");
}
LexxError::Error(_) => {
unreachable!("Should not have failed parsing file");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "The");
assert_eq!(t.token_type, TOKEN_TYPE_KEYWORD)
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
}
#[test]
fn matcher_exact_matches_multiple_words() {
use crate::token::TOKEN_TYPE_WHITESPACE;
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from("The quick brown fox qquick"))),
vec![
Box::new(KeywordMatcher::build_matcher_keyword(
vec!["brown", "The", "fox", "quick", "qquick"],
TOKEN_TYPE_KEYWORD,
0,
)),
Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
}),
],
);
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(_) => {
unreachable!("Should not have failed parsing file");
}
LexxError::Error(_) => {
unreachable!("Should not have failed parsing file");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "The")
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(_) => {
unreachable!("Should not have failed parsing file");
}
LexxError::Error(_) => {
unreachable!("Should not have failed parsing file");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "quick")
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(_) => {
unreachable!("Should not have failed parsing file");
}
LexxError::Error(_) => {
unreachable!("Should not have failed parsing file");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "brown")
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(_) => {
unreachable!("Should not have failed parsing file");
}
LexxError::Error(_) => {
unreachable!("Should not have failed parsing file");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "fox")
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(_) => {
unreachable!("Should not have failed parsing file");
}
LexxError::Error(_) => {
unreachable!("Should not have failed parsing file");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "qquick");
assert_eq!(t.line, 1);
assert_eq!(t.column, 21);
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
}
#[test]
fn matcher_exact_matches_multiple_words_and_lines() {
use crate::token::TOKEN_TYPE_WHITESPACE;
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from(
"The quick\rbrown\nfox jumped\rover the lazy dog",
))),
vec![
Box::new(KeywordMatcher::build_matcher_keyword(
vec![
"brown", "The", "fox", "quick", "dog", "over", "jumped", "lazy", "the",
],
TOKEN_TYPE_KEYWORD,
0,
)),
Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
}),
],
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "The"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "quick"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "brown"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "fox"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "jumped"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "over"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "the"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "lazy"));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE)
);
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(_) => {
unreachable!("Should not have failed parsing file");
}
LexxError::Error(_) => {
unreachable!("Should not have failed parsing file");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "dog");
assert_eq!(t.line, 2);
assert_eq!(t.column, 25);
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
}
#[test]
fn matcher_exact_does_not_match_partial_word() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from("Then"))),
vec![Box::new(KeywordMatcher::build_matcher_keyword(
vec!["The"],
TOKEN_TYPE_KEYWORD,
0,
))],
);
match lexxor.next_token() {
Err(e) => match e {
LexxError::TokenNotFound(e) => {
assert_eq!(e, "Could not resolve token at 1, 1: 'Some('n')'.");
}
LexxError::Error(_) => {
unreachable!("Should not throw error");
}
},
Ok(Some(t)) => {
assert_eq!(t.value, "The");
unreachable!("should not have matched 'The'");
}
Ok(None) => {
unreachable!("Should not hit None");
}
}
}
}