use crate::syntax::SyntaxKind;
use smol_str::SmolStr;
use std::str::Chars;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LexerToken {
pub kind: SyntaxKind,
pub text: SmolStr,
}
#[derive(Debug, Clone)]
pub struct Lexer<'text> {
text: &'text str,
}
impl Lexer<'_> {
pub fn new(text: &str) -> Lexer {
Lexer { text }
}
}
impl<'text> Iterator for Lexer<'text> {
type Item = LexerToken;
fn next(&mut self) -> Option<Self::Item> {
let token = next_token(self.text);
if let Some(ref token) = &token {
self.text = &self.text[token.text.len()..];
}
token
}
}
pub fn lex(text: &str) -> Vec<LexerToken> {
Lexer::new(text).collect()
}
fn next_token(input: &str) -> Option<LexerToken> {
let mut chars = input.chars();
let old_chars = chars.clone();
if let Some(token) = maybe_punctuator(&mut chars) {
return Some(token);
}
while !chars.as_str().is_empty() {
let old_chars = chars.clone();
if maybe_punctuator(&mut chars).is_some() {
chars = old_chars;
break;
}
}
let token_len = old_chars.as_str().len() - chars.as_str().len();
if token_len == 0 {
None
} else {
let text: SmolStr = input[..token_len].into();
Some(LexerToken {
kind: SyntaxKind::TokenText,
text,
})
}
}
fn maybe_punctuator(chars: &mut Chars) -> Option<LexerToken> {
let old_chars = chars.clone();
let first = match chars.next() {
None => return None,
Some(c) => c,
};
let kind: SyntaxKind = match first {
' ' => {
loop {
let saved = chars.clone();
if chars.next() != Some(' ') {
*chars = saved;
break;
}
}
SyntaxKind::TokenSpace
}
'\n' => SyntaxKind::TokenNewline,
'\r' => match chars.next() {
Some('\n') => SyntaxKind::TokenNewline,
_ => return None,
},
':' => SyntaxKind::TokenColon,
'{' => SyntaxKind::TokenOpenBrace,
'}' => SyntaxKind::TokenCloseBrace,
'[' => SyntaxKind::TokenOpenBracket,
']' => SyntaxKind::TokenCloseBracket,
'(' => SyntaxKind::TokenOpenParen,
')' => SyntaxKind::TokenCloseParen,
'_' => SyntaxKind::TokenUnderscore,
'/' => SyntaxKind::TokenSlash,
'\\' => SyntaxKind::TokenBackslash,
'*' => SyntaxKind::TokenStar,
'~' => SyntaxKind::TokenTilde,
_ => return None,
};
let token_len = old_chars.as_str().len() - chars.as_str().len();
let text: SmolStr = old_chars.as_str()[..token_len].into();
Some(LexerToken { kind, text })
}
#[cfg(test)]
mod lexer_tests {
use crate::lexer::{lex, LexerToken};
use ::test_generator::test_resources;
use std::path::{Path, PathBuf};
use std::{fs, io};
#[test_resources("./test-resources/[!.]*/")]
fn test_lex_mkt(path: &str) {
let path: PathBuf = Path::join(Path::new(".."), path);
let _name = path
.components()
.last()
.unwrap()
.as_os_str()
.to_str()
.expect("Failed to retrieve sample name");
let mkt_path = path.join("main.mkt");
let mkt_text: String = ::std::fs::read_to_string(mkt_path).expect("Failed to read input");
let actual_tokens = lex(&mkt_text);
let actual_lex_text = {
let mut actual_lex_text: Vec<u8> = Vec::new();
dump_tokens(&mut actual_lex_text, &actual_tokens).unwrap();
String::from_utf8(actual_lex_text).unwrap()
};
fs::write(path.join("local-main.lex.txt"), &actual_lex_text).unwrap();
let expected_lex_text: String = fs::read_to_string(path.join("main.lex.txt")).expect("Failed to read Lex");
assert_eq!(&actual_lex_text, &expected_lex_text);
}
fn dump_tokens<W: io::Write>(writer: &mut W, tokens: &[LexerToken]) -> Result<(), io::Error> {
for token in tokens {
writeln!(writer, "{:?} {:?}", token.kind, token.text,)?;
}
Ok(())
}
}