use std::marker::PhantomData;
#[derive(Debug)]
pub struct Token<'a, T> {
kind: T,
text: &'a str,
}
impl<'a, T> Token<'a, T> {
pub fn new(kind: T, text: &'a str) -> Token<'a, T> {
Token { kind, text }
}
pub fn kind(&self) -> &T {
&self.kind
}
pub fn text(&self) -> &'a str {
self.text
}
}
pub struct LexerInput<'a> {
pos: usize,
bytes: &'a [u8],
}
impl<'a> LexerInput<'a> {
fn new(input: &'a str) -> LexerInput<'a> {
LexerInput {
pos: 0,
bytes: input.as_bytes(),
}
}
pub fn pos(&self) -> usize {
self.pos
}
pub fn peek(&self) -> Option<u8> {
if self.pos < self.bytes.len() {
Some(self.bytes[self.pos])
} else {
None
}
}
pub fn next(&mut self) -> Option<u8> {
if let Some(b) = self.peek() {
self.pos += 1;
Some(b)
} else {
None
}
}
pub fn accept(&mut self, b: u8) -> bool {
if let Some(actual) = self.peek() {
if actual == b {
self.next();
return true;
}
}
false
}
pub fn next_while<P>(&mut self, predicate: P)
where P: Fn(&u8) -> bool {
while let Some(byte) = self.peek() {
if predicate(&byte) {
self.next().unwrap();
} else {
return;
}
}
}
}
pub type MatcherResult<T> = Result<T, String>;
pub trait MatcherFn<T> {
fn try_match(&mut self, first_char: u8, input: &mut LexerInput) -> MatcherResult<T>;
}
impl<T, F> MatcherFn<T> for F
where F: Fn(u8, &mut LexerInput) -> MatcherResult<T> {
fn try_match(&mut self, first_char: u8, input: &mut LexerInput) -> MatcherResult<T> {
(*self)(first_char, input)
}
}
pub struct Lexer<'a, F, T>
where F: MatcherFn<T> {
input: LexerInput<'a>,
matcher: F,
skip_whitespace: bool,
matcher_t: PhantomData<T>,
}
impl<'a, M, T> Lexer<'a, M, T>
where M: MatcherFn<T> {
pub fn new(input: &'a str, matcher: M, skip_whitespace: bool) -> Lexer<'a, M, T> {
Lexer {
input: LexerInput::new(input),
matcher,
skip_whitespace,
matcher_t: PhantomData::default(),
}
}
}
impl<'a, M, T> Iterator for Lexer<'a, M, T>
where M: MatcherFn<T> {
type Item = MatcherResult<Token<'a, T>>;
fn next(&mut self) -> Option<Self::Item> {
if self.skip_whitespace {
self.input.next_while(u8::is_ascii_whitespace);
}
let start = self.input.pos;
let byte = match self.input.next() {
Some(b) => b,
None => return None,
};
let kind = match self.matcher.try_match(byte, &mut self.input) {
Ok(k) => k,
Err(e) => return Some(Err(e)),
};
let end = self.input.pos;
let text = match ::std::str::from_utf8(&self.input.bytes[start..end]) {
Ok(t) => t,
Err(e) => return Some(Err(format!("Error: Invalid UTF-8; error = {:?}", e)))
};
Some(Ok(Token::new(kind, text)))
}
}