initial rust scanner
This commit is contained in:
parent
818816d16d
commit
3cf8ef02d1
22
rust/rox/Cargo.lock
generated
22
rust/rox/Cargo.lock
generated
@ -247,6 +247,8 @@ name = "rox"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"lazy_static",
|
||||||
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
@ -283,6 +285,26 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.8"
|
version = "1.1.8"
|
||||||
|
@ -7,5 +7,7 @@ authors = ["Sebastian Hugentobler <shu@vanwa.ch>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.28", features = ["derive"] }
|
clap = { version = "4.5.28", features = ["derive"] }
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
thiserror = "2.0.11"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
25
rust/rox/src/keywords.rs
Normal file
25
rust/rox/src/keywords.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use crate::token::TokenType::{self, *};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref KEYWORDS: HashMap<std::string::String, TokenType> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert("and".into(), And);
|
||||||
|
m.insert("class".into(), Class);
|
||||||
|
m.insert("else".into(), Else);
|
||||||
|
m.insert("false".into(), False);
|
||||||
|
m.insert("for".into(), For);
|
||||||
|
m.insert("fun".into(), Fun);
|
||||||
|
m.insert("if".into(), If);
|
||||||
|
m.insert("nil".into(), Nil);
|
||||||
|
m.insert("or".into(), Or);
|
||||||
|
m.insert("print".into(), Print);
|
||||||
|
m.insert("return".into(), Return);
|
||||||
|
m.insert("super".into(), Super);
|
||||||
|
m.insert("true".into(), True);
|
||||||
|
m.insert("var".into(), Var);
|
||||||
|
m.insert("while".into(), While);
|
||||||
|
m
|
||||||
|
};
|
||||||
|
}
|
@ -1,6 +1,45 @@
|
|||||||
use std::path::Path;
|
use std::{
|
||||||
|
fs::{self},
|
||||||
|
io::{self, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
|
pub mod keywords;
|
||||||
|
pub mod scanner;
|
||||||
|
pub mod token;
|
||||||
|
pub mod tokenizer {
|
||||||
|
pub mod comment;
|
||||||
|
pub mod identifier;
|
||||||
|
pub mod interface;
|
||||||
|
pub mod lookahead;
|
||||||
|
pub mod newline;
|
||||||
|
pub mod number;
|
||||||
|
pub mod single_char;
|
||||||
|
pub mod string;
|
||||||
|
pub mod whitespace;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compile(source: &Path) {}
|
pub fn compile(source: &Path) -> Result<(), io::Error> {
|
||||||
pub fn repl() {}
|
let input = fs::read_to_string(source)?;
|
||||||
|
let _tokens = scanner::tokenize(&input);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repl() {
|
||||||
|
loop {
|
||||||
|
print!("> ");
|
||||||
|
let _ = io::stdout().flush();
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
match io::stdin().read_line(&mut input) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => error!("{}", e),
|
||||||
|
}
|
||||||
|
let input = input.trim().to_string();
|
||||||
|
let _tokens = scanner::tokenize(&input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
use std::process::exit;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rox::cli::{Cli, Commands};
|
use rox::cli::{Cli, Commands};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if std::env::var_os("RUST_LOG").is_none() {
|
if std::env::var_os("RUST_LOG").is_none() {
|
||||||
@ -11,7 +14,21 @@ fn main() {
|
|||||||
|
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
Commands::Compile(compile_config) => {
|
Commands::Compile(compile_config) => {
|
||||||
rox::compile(&compile_config.source);
|
if !&compile_config.source.exists() {
|
||||||
|
error!(
|
||||||
|
"{} does not exist",
|
||||||
|
&compile_config.source.to_string_lossy()
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = rox::compile(&compile_config.source) {
|
||||||
|
error!(
|
||||||
|
"failed to compile {}: {}",
|
||||||
|
&compile_config.source.to_string_lossy(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Commands::Repl => {
|
Commands::Repl => {
|
||||||
rox::repl();
|
rox::repl();
|
||||||
|
60
rust/rox/src/scanner.rs
Normal file
60
rust/rox/src/scanner.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use crate::{
|
||||||
|
token::{Token, TokenType::Eof},
|
||||||
|
tokenizer::{
|
||||||
|
comment::Comment, identifier::Identifier, interface::Tokenizer, lookahead::Lookahead,
|
||||||
|
newline::Newline, number::Number, single_char::SingleChar, string::String,
|
||||||
|
whitespace::Whitespace,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TOKENIZERS: Vec<Box<dyn Tokenizer>> = vec![
|
||||||
|
Box::new(SingleChar),
|
||||||
|
Box::new(Whitespace),
|
||||||
|
Box::new(Newline),
|
||||||
|
Box::new(Lookahead),
|
||||||
|
Box::new(Comment),
|
||||||
|
Box::new(String),
|
||||||
|
Box::new(Number),
|
||||||
|
Box::new(Identifier),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tokenize(source: &str) -> Vec<Token> {
|
||||||
|
let mut tokens: Vec<Token> = Vec::new();
|
||||||
|
let mut source_chars = source.char_indices().peekable();
|
||||||
|
|
||||||
|
let mut line = 1;
|
||||||
|
while let Some(c) = source_chars.next() {
|
||||||
|
let mut tokenizer_idx = 0;
|
||||||
|
let mut tokenizer_result = None;
|
||||||
|
while tokenizer_idx < TOKENIZERS.len() && tokenizer_result.is_none() {
|
||||||
|
tokenizer_result = TOKENIZERS[tokenizer_idx].run(c, &mut source_chars, source, line);
|
||||||
|
tokenizer_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
match tokenizer_result {
|
||||||
|
Some((line_advance, token)) => {
|
||||||
|
line += line_advance;
|
||||||
|
|
||||||
|
if let Some(token) = token {
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("unexpected character: {}", c.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens.push(Token {
|
||||||
|
token_type: Eof,
|
||||||
|
lexeme: "".to_string(),
|
||||||
|
literal: None,
|
||||||
|
line,
|
||||||
|
});
|
||||||
|
debug!("{:?}", tokens);
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
76
rust/rox/src/token.rs
Normal file
76
rust/rox/src/token.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum TokenType {
|
||||||
|
// Single-character tokens.
|
||||||
|
LeftParen,
|
||||||
|
RightParen,
|
||||||
|
LeftBrace,
|
||||||
|
RightBrace,
|
||||||
|
Comma,
|
||||||
|
Dot,
|
||||||
|
Minus,
|
||||||
|
Plus,
|
||||||
|
Semicolon,
|
||||||
|
Slash,
|
||||||
|
Star,
|
||||||
|
|
||||||
|
// One or two character tokens.
|
||||||
|
Bang,
|
||||||
|
BangEqual,
|
||||||
|
Equal,
|
||||||
|
EqualEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterEqual,
|
||||||
|
Less,
|
||||||
|
LessEqual,
|
||||||
|
|
||||||
|
// Literals.
|
||||||
|
Identifier,
|
||||||
|
String,
|
||||||
|
Number,
|
||||||
|
|
||||||
|
// Keywords.
|
||||||
|
And,
|
||||||
|
Class,
|
||||||
|
Else,
|
||||||
|
False,
|
||||||
|
Fun,
|
||||||
|
For,
|
||||||
|
If,
|
||||||
|
Nil,
|
||||||
|
Or,
|
||||||
|
Print,
|
||||||
|
Return,
|
||||||
|
Super,
|
||||||
|
This,
|
||||||
|
True,
|
||||||
|
Var,
|
||||||
|
While,
|
||||||
|
|
||||||
|
Eof,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Literal {
|
||||||
|
String(String),
|
||||||
|
Number(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Token {
|
||||||
|
pub token_type: TokenType,
|
||||||
|
pub lexeme: String,
|
||||||
|
pub literal: Option<Literal>,
|
||||||
|
pub line: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Token {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}: {:?} {} {:?}",
|
||||||
|
self.line, self.token_type, self.lexeme, self.literal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
39
rust/rox/src/tokenizer/comment.rs
Normal file
39
rust/rox/src/tokenizer/comment.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::token::TokenType::Slash;
|
||||||
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
|
use crate::token::Token;
|
||||||
|
|
||||||
|
use super::interface::Tokenizer;
|
||||||
|
|
||||||
|
pub struct Comment;
|
||||||
|
impl Tokenizer for Comment {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
source: &str,
|
||||||
|
line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
match c.1 {
|
||||||
|
'/' => {
|
||||||
|
let (line_advance, token) = if chars.next_if(|(_, peek)| *peek == '/').is_some() {
|
||||||
|
while chars.next_if(|(_, peek)| *peek != '\n').is_some() {}
|
||||||
|
chars.next();
|
||||||
|
(1, None)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
Some(Token {
|
||||||
|
token_type: Slash,
|
||||||
|
lexeme: source[c.0..=c.0].to_string(),
|
||||||
|
literal: None,
|
||||||
|
line,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Some((line_advance, token))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
rust/rox/src/tokenizer/identifier.rs
Normal file
42
rust/rox/src/tokenizer/identifier.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use crate::{
|
||||||
|
keywords::KEYWORDS,
|
||||||
|
token::{Token, TokenType},
|
||||||
|
};
|
||||||
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
|
use super::interface::Tokenizer;
|
||||||
|
|
||||||
|
pub struct Identifier;
|
||||||
|
impl Tokenizer for Identifier {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
source: &str,
|
||||||
|
line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
if c.1.is_alphabetic() || c.1 == '_' {
|
||||||
|
let mut end_idx = c.0;
|
||||||
|
while let Some((idx, _)) = chars.next_if(|(_, x)| x.is_alphanumeric() || c.1 == '_') {
|
||||||
|
end_idx = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lexeme = source[c.0..=end_idx].to_string();
|
||||||
|
let token_type = match KEYWORDS.get(&lexeme) {
|
||||||
|
Some(token_type) => *token_type,
|
||||||
|
None => TokenType::Identifier,
|
||||||
|
};
|
||||||
|
Some((
|
||||||
|
0,
|
||||||
|
Some(Token {
|
||||||
|
token_type,
|
||||||
|
lexeme,
|
||||||
|
literal: None,
|
||||||
|
line,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
rust/rox/src/tokenizer/interface.rs
Normal file
13
rust/rox/src/tokenizer/interface.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
|
use crate::token::Token;
|
||||||
|
|
||||||
|
pub trait Tokenizer: Send + Sync {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
source: &str,
|
||||||
|
line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)>;
|
||||||
|
}
|
91
rust/rox/src/tokenizer/lookahead.rs
Normal file
91
rust/rox/src/tokenizer/lookahead.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use super::interface::Tokenizer;
|
||||||
|
use crate::token::TokenType::*;
|
||||||
|
use crate::token::{Token, TokenType};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::{collections::HashMap, iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
|
struct LookaheadEntry {
|
||||||
|
default_token: TokenType,
|
||||||
|
lookahead_map: HashMap<char, TokenType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LOOKAHEAD_TOKENS: HashMap<char, LookaheadEntry> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
|
||||||
|
let mut bang_map = HashMap::new();
|
||||||
|
bang_map.insert('=', BangEqual);
|
||||||
|
m.insert(
|
||||||
|
'!',
|
||||||
|
LookaheadEntry {
|
||||||
|
default_token: Bang,
|
||||||
|
lookahead_map: bang_map,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut equal_map = HashMap::new();
|
||||||
|
equal_map.insert('=', EqualEqual);
|
||||||
|
m.insert(
|
||||||
|
'=',
|
||||||
|
LookaheadEntry {
|
||||||
|
default_token: Equal,
|
||||||
|
lookahead_map: equal_map,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut less_map = HashMap::new();
|
||||||
|
less_map.insert('=', LessEqual);
|
||||||
|
m.insert(
|
||||||
|
'<',
|
||||||
|
LookaheadEntry {
|
||||||
|
default_token: Less,
|
||||||
|
lookahead_map: less_map,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut greater_map = HashMap::new();
|
||||||
|
greater_map.insert('=', GreaterEqual);
|
||||||
|
m.insert(
|
||||||
|
'>',
|
||||||
|
LookaheadEntry {
|
||||||
|
default_token: Greater,
|
||||||
|
lookahead_map: greater_map,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
m
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Lookahead;
|
||||||
|
impl Tokenizer for Lookahead {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
source: &str,
|
||||||
|
line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
LOOKAHEAD_TOKENS.get(&c.1).map(|entry| {
|
||||||
|
let (lexeme, token_type) = if let Some(&(_, peeked)) = chars.peek() {
|
||||||
|
if let Some(&token_type) = entry.lookahead_map.get(&peeked) {
|
||||||
|
chars.next();
|
||||||
|
(source[c.0..=c.0 + 1].to_string(), token_type)
|
||||||
|
} else {
|
||||||
|
(source[c.0..=c.0].to_string(), entry.default_token)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(source[c.0..=c.0].to_string(), entry.default_token)
|
||||||
|
};
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
Some(Token {
|
||||||
|
token_type,
|
||||||
|
lexeme,
|
||||||
|
literal: None,
|
||||||
|
line,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
19
rust/rox/src/tokenizer/newline.rs
Normal file
19
rust/rox/src/tokenizer/newline.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use super::interface::Tokenizer;
|
||||||
|
use crate::token::Token;
|
||||||
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
|
pub struct Newline;
|
||||||
|
impl Tokenizer for Newline {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
_chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
_source: &str,
|
||||||
|
_line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
match c.1 {
|
||||||
|
'\n' => Some((1, None)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
rust/rox/src/tokenizer/number.rs
Normal file
45
rust/rox/src/tokenizer/number.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use crate::token::{Literal, Token, TokenType};
|
||||||
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use super::interface::Tokenizer;
|
||||||
|
|
||||||
|
pub struct Number;
|
||||||
|
impl Tokenizer for Number {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
source: &str,
|
||||||
|
line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
if c.1.is_ascii_digit() {
|
||||||
|
let mut end_idx = c.0;
|
||||||
|
while let Some((idx, _)) = chars.next_if(|(_, x)| x.is_ascii_digit()) {
|
||||||
|
end_idx = idx;
|
||||||
|
}
|
||||||
|
chars.next_if(|(_, x)| *x == '.');
|
||||||
|
while let Some((idx, _)) = chars.next_if(|(_, x)| x.is_ascii_digit()) {
|
||||||
|
end_idx = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lexeme = source[c.0..=end_idx].to_string();
|
||||||
|
let token = match lexeme.parse::<f64>() {
|
||||||
|
Ok(literal) => Some(Token {
|
||||||
|
token_type: TokenType::Number,
|
||||||
|
lexeme,
|
||||||
|
literal: Some(Literal::Number(literal)),
|
||||||
|
line,
|
||||||
|
}),
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to parse number: {e}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((0, token))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
rust/rox/src/tokenizer/single_char.rs
Normal file
49
rust/rox/src/tokenizer/single_char.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use super::interface::Tokenizer;
|
||||||
|
use crate::token::{
|
||||||
|
Token,
|
||||||
|
TokenType::{self, *},
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::{collections::HashMap, iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref SINGLE_CHAR_TOKENS: HashMap<char, TokenType> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert('(', LeftParen);
|
||||||
|
m.insert(')', RightParen);
|
||||||
|
m.insert('{', LeftBrace);
|
||||||
|
m.insert('}', RightBrace);
|
||||||
|
m.insert(',', Comma);
|
||||||
|
m.insert('.', Dot);
|
||||||
|
m.insert('-', Minus);
|
||||||
|
m.insert('+', Plus);
|
||||||
|
m.insert(';', Semicolon);
|
||||||
|
m.insert('*', Star);
|
||||||
|
m
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SingleChar;
|
||||||
|
impl Tokenizer for SingleChar {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
_chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
source: &str,
|
||||||
|
line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
let lexeme = source[c.0..=c.0].to_string();
|
||||||
|
|
||||||
|
SINGLE_CHAR_TOKENS.get(&c.1).map(|token_type| {
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
Some(Token {
|
||||||
|
token_type: *token_type,
|
||||||
|
lexeme,
|
||||||
|
literal: None,
|
||||||
|
line,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
48
rust/rox/src/tokenizer/string.rs
Normal file
48
rust/rox/src/tokenizer/string.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use super::interface::Tokenizer;
|
||||||
|
use crate::token::{Literal, Token, TokenType};
|
||||||
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
pub struct String;
|
||||||
|
impl Tokenizer for String {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
source: &str,
|
||||||
|
line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
match c.1 {
|
||||||
|
'"' => {
|
||||||
|
let mut lines = 0;
|
||||||
|
while let Some((_, c)) = chars.next_if(|(_, peek)| *peek != '"') {
|
||||||
|
if c == '\n' {
|
||||||
|
lines += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match chars.next_if(|(_, x)| *x == '"') {
|
||||||
|
Some((end_idx, _)) => {
|
||||||
|
let lexeme = source[c.0..=end_idx].to_string();
|
||||||
|
let literal = source[c.0 + 1..end_idx].to_string();
|
||||||
|
|
||||||
|
Some((
|
||||||
|
lines,
|
||||||
|
Some(Token {
|
||||||
|
token_type: TokenType::String,
|
||||||
|
lexeme,
|
||||||
|
literal: Some(Literal::String(literal)),
|
||||||
|
line,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("unterminated string");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
rust/rox/src/tokenizer/whitespace.rs
Normal file
19
rust/rox/src/tokenizer/whitespace.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use super::interface::Tokenizer;
|
||||||
|
use crate::token::Token;
|
||||||
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
|
pub struct Whitespace;
|
||||||
|
impl Tokenizer for Whitespace {
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
c: (usize, char),
|
||||||
|
_chars: &mut Peekable<CharIndices<'_>>,
|
||||||
|
_source: &str,
|
||||||
|
_line: usize,
|
||||||
|
) -> Option<(usize, Option<Token>)> {
|
||||||
|
match c.1 {
|
||||||
|
' ' | '\r' | '\t' => Some((0, None)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user