#![deny(
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unstable_features,
unused_import_braces,
unused_qualifications
)]
pub mod input;
pub mod matcher;
pub mod rolling_char_buffer;
pub mod token;
use arrayvec::ArrayVec;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use crate::input::LexxorInput;
use crate::matcher::Matcher;
use crate::matcher::MatcherResult::{Failed, Matched, Running};
use crate::rolling_char_buffer::{RollingCharBuffer, RollingCharBufferError};
use token::Token;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum LexxError {
TokenNotFound(String),
Error(String),
}
impl fmt::Display for LexxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
LexxError::TokenNotFound(ref s) => {
write!(f, "a parser could not be found for: {:?}", s)
}
LexxError::Error(ref s) => {
write!(f, "an error occurred: {:?}", s)
}
}
}
}
impl Error for LexxError {
#[allow(deprecated)]
fn description(&self) -> &str {
match *self {
LexxError::TokenNotFound(..) => "no token could be found",
LexxError::Error(..) => "an error occurred",
}
}
}
#[derive(Debug)]
pub struct Lexxor<const CAP: usize> {
matchers: Vec<Box<dyn Matcher>>,
input: Box<dyn LexxorInput>,
cache: Box<RollingCharBuffer<CAP>>,
value: Box<ArrayVec<char, CAP>>,
pub lexxor_result: Option<Result<Option<Token>, LexxError>>,
pub found_token: Option<Token>,
pub line: usize,
pub column: usize,
pub ctx: Box<HashMap<String, i32>>,
}
impl<const CAP: usize> Lexxor<CAP> {
pub fn new(input: Box<dyn LexxorInput>, matchers: Vec<Box<dyn Matcher>>) -> Self {
let cache = Box::new(RollingCharBuffer::<CAP>::new());
Lexxor {
matchers,
input,
cache,
value: Box::new(ArrayVec::<char, CAP>::new()),
lexxor_result: None,
found_token: None,
line: 1,
column: 1,
ctx: Box::new(HashMap::new()),
}
}
fn get_token(&mut self) -> Result<Option<Token>, LexxError> {
let mut precedence = 0;
self.value.clear();
for m in &mut self.matchers {
m.reset(&mut self.ctx);
}
self.found_token = None;
loop {
let c = if self.cache.is_empty() {
self.input.next()?
} else {
Some(self.cache.read().unwrap())
};
let mut found_token: Option<Token> = None;
let mut running = false;
if let Some(ch) = c {
self.value.push(ch);
}
for m in &mut self.matchers {
if m.is_running() {
match m.find_match(c, &self.value[..], &mut self.ctx) {
Running() => running = true,
Matched(token) => {
if let Some(ref _ft) = found_token {
if precedence <= token.precedence {
precedence = token.precedence;
found_token = Some(token);
}
} else {
precedence = token.precedence;
found_token = Some(token);
}
}
Failed() => {}
}
}
}
if let Some(token) = found_token {
match &self.found_token {
Some(ft) if ft.precedence <= token.precedence => self.found_token = Some(token),
None => self.found_token = Some(token),
_ => {}
}
}
if !running {
if let Some(mut token) = self.found_token.take() {
if self.value.len() > token.len
&& let Err(e) = self.cache.prepend(&self.value[token.len..])
{
panic!("Ran out of buffer space: {}", e)
}
let l = self.line;
let c = self.column;
if token.line > 0 {
self.line += token.line;
self.column = token.column;
} else {
self.column += token.column;
}
token.line = l;
token.column = c;
return Ok(Some(token));
} else {
return if c.is_none() {
Ok(None)
} else {
Err(LexxError::TokenNotFound(format!(
"Could not resolve token at {}, {}: '{:?}'.",
self.line, self.column, c
)))
};
}
}
if c.is_none() {
return Ok(None);
}
}
}
}
impl<const CAP: usize> Lexxer for Lexxor<CAP> {
fn next_token(&mut self) -> Result<Option<Token>, LexxError> {
if self.lexxor_result.is_some() {
let lr = self.lexxor_result.clone().unwrap();
self.lexxor_result = None;
return lr;
}
self.get_token()
}
fn look_ahead(&mut self) -> Result<Option<Token>, LexxError> {
if self.lexxor_result.is_some() {
self.lexxor_result.clone().unwrap()
} else {
self.lexxor_result = Some(self.get_token());
self.lexxor_result.clone().unwrap()
}
}
fn rewind(&mut self, token: Token) -> Result<usize, RollingCharBufferError> {
self.line = token.line;
self.column = token.column;
if let Some(Ok(Some(ref lookahead_token))) = self.lexxor_result {
self.cache
.prepend(&lookahead_token.value.chars().collect::<Vec<char>>())?;
}
self.lexxor_result = None;
self.cache
.prepend(&token.value.chars().collect::<Vec<char>>())
}
fn set_input(&mut self, input: Box<dyn LexxorInput>) {
self.input = input;
self.line = 1;
self.column = 1;
self.cache.clear();
self.lexxor_result = None;
}
}
pub trait Lexxer {
fn next_token(&mut self) -> Result<Option<Token>, LexxError>;
fn look_ahead(&mut self) -> Result<Option<Token>, LexxError>;
fn rewind(&mut self, token: Token) -> Result<usize, RollingCharBufferError>;
fn set_input(&mut self, input: Box<dyn LexxorInput>);
}
impl Iterator for dyn Lexxer {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
self.next_token()
.unwrap_or_else(|e| panic!("{}", e.to_string()))
}
}
impl<const CAP: usize> Iterator for Lexxor<CAP> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
self.next_token()
.unwrap_or_else(|e| panic!("{}", e.to_string()))
}
}
#[cfg(test)]
mod tests {
use crate::input::InputString;
use crate::matcher::exact::ExactMatcher;
use crate::matcher::whitespace::WhitespaceMatcher;
use crate::matcher::word::WordMatcher;
use crate::token::{TOKEN_TYPE_EXACT, TOKEN_TYPE_WHITESPACE};
use crate::{Lexxer, Lexxor, Token};
#[test]
fn lexxor_test_precedence() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from("fox"))),
vec![
Box::new(ExactMatcher::build_exact_matcher(
vec!["fox"],
TOKEN_TYPE_EXACT,
1,
)),
Box::new(WordMatcher {
index: 0,
precedence: 0,
running: true,
}),
],
);
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "fox" && t.token_type == TOKEN_TYPE_EXACT)
);
lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from("fox"))),
vec![
Box::new(WordMatcher {
index: 0,
precedence: 0,
running: true,
}),
Box::new(ExactMatcher::build_exact_matcher(
vec!["fox"],
TOKEN_TYPE_EXACT,
1,
)),
],
);
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "fox" && t.token_type == TOKEN_TYPE_EXACT)
);
}
#[test]
fn lexxor_test_look_ahead() {
let mut lexxor = Lexxor::<512>::new(
Box::new(InputString::new(String::from("The lazy dog"))),
vec![
Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
}),
Box::new(WordMatcher {
index: 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.look_ahead(), Ok(Some(t)) if t.value == "lazy" && t.line == 1 && t.column == 5 && t.len == 4)
);
assert!(
matches!(lexxor.look_ahead(), Ok(Some(t)) if t.value == "lazy" && t.line == 1 && t.column == 5 && t.len == 4)
);
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "lazy" && t.line == 1 && t.column == 5 && t.len == 4)
);
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 == "dog" && t.line == 1 && t.column == 10 && t.len == 3)
);
}
#[test]
fn lexxor_test_rewind() {
let mut lexxor = Lexxor::<20>::new(
Box::new(InputString::new(String::from("The lazy dog"))),
vec![
Box::new(WhitespaceMatcher {
index: 0,
column: 0,
line: 0,
precedence: 0,
running: true,
}),
Box::new(WordMatcher {
index: 0,
precedence: 0,
running: true,
}),
],
);
let mut the: Option<Token> = None;
if let Ok(t) = lexxor.next_token() {
assert_eq!(t.as_ref().unwrap().value, "The");
the = t;
}
let mut whitespace: Option<Token> = None;
if let Ok(t) = lexxor.next_token() {
assert_eq!(t.as_ref().unwrap().token_type, TOKEN_TYPE_WHITESPACE);
whitespace = t;
}
let mut lazy: Option<Token> = None;
if let Ok(t) = lexxor.next_token() {
assert_eq!(t.as_ref().unwrap().value, "lazy");
lazy = t;
}
if let Some(t) = lazy {
assert_eq!(lexxor.rewind(t), Ok(15))
}
if let Some(t) = whitespace {
assert_eq!(lexxor.rewind(t), Ok(14))
}
if let Some(t) = the {
assert_eq!(lexxor.rewind(t), Ok(11))
}
assert!(matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "The"));
let w = lexxor.next_token();
assert!(matches!(w, Ok(Some(t)) if t.token_type == TOKEN_TYPE_WHITESPACE));
assert!(
matches!(lexxor.next_token(), Ok(Some(t)) if t.value == "lazy" && t.line == 1 && t.column == 5 && t.len == 4)
);
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 == "dog" && t.line == 1 && t.column == 10 && t.len == 3)
);
}
}