use std::fmt::{Display,Formatter,Error};
use super::lexeme::{Lexeme,LexemeKind};
use super::detect::character::detect_character;
use super::detect::comment::detect_comment;
use super::detect::identifier::detect_identifier;
use super::detect::number::detect_number;
use super::detect::punctuation::detect_punctuation;
use super::detect::string::detect_string;
use super::detect::whitespace::detect_whitespace;
pub struct LexemizeResult {
pub lexemes: Vec<Lexeme>,
}
impl Display for LexemizeResult {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
let mut out = format!("Lexemes, incl <EOI>: {}\n", self.lexemes.len());
for lexeme in &self.lexemes {
out.push_str(&lexeme.to_string());
out.push_str("\n");
}
write!(fmt, "{}", out)
}
}
pub const DETECTORS: [fn (&str, usize) -> (LexemeKind, usize); 7] = [
detect_character,
detect_comment,
detect_string,
detect_identifier,
detect_number,
detect_punctuation,
detect_whitespace,
];
pub fn lexemize(
orig: &'static str
) -> LexemizeResult {
let len = orig.len();
let mut chr = 0;
let mut unident_chr = 0;
let mut lexemes: Vec<Lexeme> = vec![];
'outer: while chr < len {
if orig.is_char_boundary(chr) {
for detector in DETECTORS.iter() {
let (kind, next_chr) = detector(orig, chr);
if kind != LexemeKind::Undetected {
if unident_chr != chr {
lexemes.push(Lexeme {
kind: LexemeKind::Unidentifiable,
chr: unident_chr,
snippet: &orig[unident_chr..chr],
});
}
lexemes.push(Lexeme {
kind,
chr,
snippet: &orig[chr..next_chr],
});
chr = next_chr;
unident_chr = next_chr;
continue 'outer;
}
}
}
chr += 1;
}
if unident_chr != chr {
lexemes.push(Lexeme {
kind: LexemeKind::Unidentifiable,
chr: unident_chr,
snippet: &orig[unident_chr..chr],
});
}
lexemes.push(Lexeme {
kind: LexemeKind::WhitespaceTrimmable,
chr,
snippet: "<EOI>",
});
LexemizeResult {
lexemes,
}
}
fn _detect(
detector: fn (&str, usize) -> usize,
kind: LexemeKind,
orig: &'static str,
chr: usize,
unident_chr: usize,
lexemes: &mut Vec<Lexeme>,
) -> usize {
let next_chr = detector(orig, chr);
if next_chr == chr { return chr }
if unident_chr != chr {
lexemes.push(Lexeme {
kind: LexemeKind::Unidentifiable,
chr: unident_chr,
snippet: &orig[unident_chr..chr],
});
}
lexemes.push(Lexeme {
kind,
chr,
snippet: &orig[chr..next_chr],
});
next_chr
}
#[cfg(test)]
mod tests {
use super::{LexemizeResult,lexemize};
use super::super::lexeme::{Lexeme,LexemeKind};
#[test]
fn lexemize_result_to_string_as_expected() {
let result = LexemizeResult {
lexemes: vec![
Lexeme {
kind: LexemeKind::CommentMultiline,
chr: 0,
snippet: "/* This is a comment */",
},
Lexeme {
kind: LexemeKind::NumberDecimal,
chr: 23,
snippet: "44.4",
},
Lexeme {
kind: LexemeKind::WhitespaceTrimmable,
chr: 27,
snippet: "<EOI>",
},
],
};
assert_eq!(result.to_string(),
"Lexemes, incl <EOI>: 3\n\
CommentMultiline 0 /* This is a comment */\n\
NumberDecimal 23 44.4\n\
WhitespaceTrimmable 27 <EOI>\n"
);
}
#[test]
fn lexemize_all_lexemes() {
assert_eq!(lexemize("").to_string(),
"Lexemes, incl <EOI>: 1\n\
WhitespaceTrimmable 0 <EOI>\n");
assert_eq!(lexemize("'A'/*B*/C 1!\"D\"\n").to_string(),
"Lexemes, incl <EOI>: 9\n\
CharacterPlain 0 \'A\'\n\
CommentMultiline 3 /*B*/\n\
IdentifierFreeword 8 C\n\
WhitespaceTrimmable 9 \n\
NumberDecimal 10 1\n\
Punctuation 11 !\n\
StringPlain 12 \"D\"\n\
WhitespaceTrimmable 15 <NL>\n\
WhitespaceTrimmable 16 <EOI>\n");
assert_eq!(lexemize("'€'/*€*/€1!\"€\"\n").to_string(),
"Lexemes, incl <EOI>: 8\n\
CharacterPlain 0 \'€\'\n\
CommentMultiline 5 /*€*/\n\
Unidentifiable 12 €\n\
NumberDecimal 15 1\n\
Punctuation 16 !\n\
StringPlain 17 \"€\"\n\
WhitespaceTrimmable 22 <NL>\n\
WhitespaceTrimmable 23 <EOI>\n");
assert_eq!(lexemize("println!(\"Hello, World!\");\n").to_string(),
"Lexemes, incl <EOI>: 8\n\
IdentifierFreeword 0 println\n\
Punctuation 7 !\n\
Punctuation 8 (\n\
StringPlain 9 \"Hello, World!\"\n\
Punctuation 24 )\n\
Punctuation 25 ;\n\
WhitespaceTrimmable 26 <NL>\n\
WhitespaceTrimmable 27 <EOI>\n");
}
#[test]
fn lexemize_characters() {
assert_eq!(lexemize("'Z''\\t''\\x3F''\\u{3F}'").to_string(),
"Lexemes, incl <EOI>: 5\n\
CharacterPlain 0 \'Z\'\n\
CharacterPlain 3 \'\\t\'\n\
CharacterHex 7 \'\\x3F\'\n\
CharacterUnicode 13 \'\\u{3F}\'\n\
WhitespaceTrimmable 21 <EOI>\n"
);
}
#[test]
fn lexemize_comments() {
assert_eq!(lexemize("/**A/*A'*/*///B\n//C").to_string(),
"Lexemes, incl <EOI>: 5\n\
CommentMultiline 0 /**A/*A'*/*/\n\
CommentInline 12 //B\n\
WhitespaceTrimmable 15 <NL>\n\
CommentInline 16 //C\n\
WhitespaceTrimmable 19 <EOI>\n"
);
}
#[test]
fn lexemize_identifiers() {
assert_eq!(lexemize("u32;_D,__12 as foo!").to_string(),
"Lexemes, incl <EOI>: 11\n\
IdentifierStdType 0 u32\n\
Punctuation 3 ;\n\
IdentifierFreeword 4 _D\n\
Punctuation 6 ,\n\
IdentifierFreeword 7 __12\n\
WhitespaceTrimmable 11 \n\
IdentifierKeyword 12 as\n\
WhitespaceTrimmable 14 \n\
IdentifierFreeword 15 foo\n\
Punctuation 18 !\n\
WhitespaceTrimmable 19 <EOI>\n"
);
}
#[test]
fn lexemize_numbers() {
assert_eq!(lexemize("0b1001_0011 1_2.3_4E+_5_ 0x__01aB__ 0o1_7").to_string(),
"Lexemes, incl <EOI>: 8\n\
NumberBinary 0 0b1001_0011\n\
WhitespaceTrimmable 11 \n\
NumberDecimal 12 1_2.3_4E+_5_\n\
WhitespaceTrimmable 24 \n\
NumberHex 25 0x__01aB__\n\
WhitespaceTrimmable 35 \n\
NumberOctal 36 0o1_7\n\
WhitespaceTrimmable 41 <EOI>\n"
);
}
#[test]
fn lexemize_punctuations() {
assert_eq!(lexemize(";*=>>=").to_string(),
"Lexemes, incl <EOI>: 4\n\
Punctuation 0 ;\n\
Punctuation 1 *=\n\
Punctuation 3 >>=\n\
WhitespaceTrimmable 6 <EOI>\n"
);
}
#[test]
fn lexemize_strings() {
assert_eq!(lexemize("\"\"\"ok\"r##\"\\\"\"##").to_string(),
"Lexemes, incl <EOI>: 4\n\
StringPlain 0 \"\"\n\
StringPlain 2 \"ok\"\n\
StringRaw 6 r##\"\\\"\"##\n\
WhitespaceTrimmable 15 <EOI>\n"
);
}
#[test]
fn lexemize_unidentifiable() {
assert_eq!(lexemize("~¶ €").to_string(),
"Lexemes, incl <EOI>: 4\n\
Unidentifiable 0 ~¶\n\
WhitespaceTrimmable 3 \n\
Unidentifiable 4 €\n\
WhitespaceTrimmable 7 <EOI>\n"
);
assert_eq!(lexemize("~`\\").to_string(),
"Lexemes, incl <EOI>: 2\n\
Unidentifiable 0 ~`\\\n\
WhitespaceTrimmable 3 <EOI>\n"
);
assert_eq!(lexemize("颀±").to_string(),
"Lexemes, incl <EOI>: 2\n\
Unidentifiable 0 颀±\n\
WhitespaceTrimmable 9 <EOI>\n"
);
}
#[test]
fn lexemize_whitespace() {
assert_eq!(lexemize("\t\ta \n\nb\r ").to_string(),
"Lexemes, incl <EOI>: 6\n\
WhitespaceTrimmable 0 \t\t\n\
IdentifierFreeword 2 a\n\
WhitespaceTrimmable 3 <NL><NL>\n\
IdentifierFreeword 6 b\n\
WhitespaceTrimmable 7 \r \n\
WhitespaceTrimmable 9 <EOI>\n"
);
}
}