tokenizer.rs en Rust
Un tokenizador de copia cero: cada token es un slice del código fuente original.
#[derive(Debug, PartialEq)]
enum Token<'a> {
Word(&'a str),
Number(&'a str),
Symbol(char),
}
struct Lexer<'a> {
source: &'a str,
pos: usize,
}
impl<'a> Lexer<'a> {
fn new(source: &'a str) -> Self {
Lexer { source, pos: 0 }
}
fn rest(&self) -> &'a str {
&self.source[self.pos..]
}
fn take_while<F: Fn(char) -> bool>(&mut self, test: F) -> &'a str {
let start = self.pos;
for c in self.rest().chars() {
if !test(c) {
break;
}
self.pos += c.len_utf8();
}
&self.source[start..self.pos]
}
}
impl<'a> Iterator for Lexer<'a> {
type Item = Token<'a>;
fn next(&mut self) -> Option<Token<'a>> {
self.take_while(char::is_whitespace);
let c = self.rest().chars().next()?;
if c.is_ascii_digit() {
return Some(Token::Number(
self.take_while(|c| c.is_ascii_digit()),
));
}
if c.is_alphabetic() || c == '_' {
return Some(Token::Word(
self.take_while(|c| c.is_alphanumeric() || c == '_'),
));
}
self.pos += c.len_utf8();
Some(Token::Symbol(c))
}
}
fn main() {
let source = "let total = count * 42;";
let tokens: Vec<Token> = Lexer::new(source).collect();
for token in &tokens {
println!("{:?}", token);
}
let words = tokens
.iter()
.filter(|t| matches!(t, Token::Word(_)))
.count();
println!("{words} words, {} tokens", tokens.len());
assert_eq!(tokens[0], Token::Word("let"));
}
Cómo funciona
- El enum
Tokenpresta de la entrada, así que nada se copia. - El lexer camina bytes y corta en las fronteras que encuentra.
- El lifetime del struct ata cada token a esa fuente.
Palabras clave y builtins usados aquí
FnIteratorOptionSomeVecboolbreakcharenumfnforifimplinletmutreturnselfstrstructtypeusize
El intento, en números
- Líneas
- 70
- Caracteres a escribir
- 1272
- Tokens
- 462
- Ritmo de tres estrellas
- 115 tpm
Al ritmo de tres estrellas de 115 tokens por minuto, este intento toma unos 241 segundos.
Paso 1 de 1 en Bis; paso 11 de 11 en Lifetimes y mutabilidad interior.