tokens
This commit is contained in:
commit
b55135245d
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lang"
|
||||||
|
version = "0.1.0"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "lang"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
18
src/main.rs
Normal file
18
src/main.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
mod token;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
repl();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repl() {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
loop {
|
||||||
|
stdin.read_line(&mut buffer).unwrap();
|
||||||
|
let tokens = token::tokenize(&buffer);
|
||||||
|
for tok in tokens {
|
||||||
|
println!("{} : {:?}", &buffer[tok.start..tok.end], tok.ttype);
|
||||||
|
}
|
||||||
|
buffer = String::new();
|
||||||
|
}
|
||||||
|
}
|
210
src/token.rs
Normal file
210
src/token.rs
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TokenType {
|
||||||
|
// Symbols
|
||||||
|
LeftParen,
|
||||||
|
RightParen,
|
||||||
|
LeftSquare,
|
||||||
|
RightSquare,
|
||||||
|
LeftBrace,
|
||||||
|
RightBrace,
|
||||||
|
Comma,
|
||||||
|
Dot,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Star,
|
||||||
|
Slash,
|
||||||
|
Semicolon,
|
||||||
|
|
||||||
|
Bang,
|
||||||
|
BangEqual,
|
||||||
|
Equal,
|
||||||
|
DoubleEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterEqual,
|
||||||
|
Less,
|
||||||
|
LessEqual,
|
||||||
|
|
||||||
|
// Literals
|
||||||
|
String,
|
||||||
|
Character,
|
||||||
|
Number(f64),
|
||||||
|
|
||||||
|
// Words
|
||||||
|
Ident,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Self_,
|
||||||
|
Struct,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
Fn,
|
||||||
|
If,
|
||||||
|
Else,
|
||||||
|
Nil,
|
||||||
|
Print,
|
||||||
|
Return,
|
||||||
|
Super,
|
||||||
|
Let,
|
||||||
|
While,
|
||||||
|
For,
|
||||||
|
|
||||||
|
// Special
|
||||||
|
Unrecognized,
|
||||||
|
TooLong,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type, index
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Token {
|
||||||
|
pub ttype: TokenType,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tokenize(input: &str) -> Vec<Token> {
|
||||||
|
let input_str = input;
|
||||||
|
let mut input = input.char_indices().peekable();
|
||||||
|
let mut tokens = vec![];
|
||||||
|
'outer: loop {
|
||||||
|
// Find next non-whitespace line
|
||||||
|
let (start, c) = 'ws: loop {
|
||||||
|
match input.next() {
|
||||||
|
// Stop at end of input
|
||||||
|
None => break 'outer,
|
||||||
|
Some((index, character)) if !character.is_whitespace() => {
|
||||||
|
break 'ws (index, character)
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut end = start + 1;
|
||||||
|
let mut advance = || {};
|
||||||
|
let ttype = match c {
|
||||||
|
// Match single character tokens
|
||||||
|
'(' => TokenType::LeftParen,
|
||||||
|
')' => TokenType::RightParen,
|
||||||
|
'[' => TokenType::LeftSquare,
|
||||||
|
']' => TokenType::RightSquare,
|
||||||
|
'{' => TokenType::LeftBrace,
|
||||||
|
'}' => TokenType::RightBrace,
|
||||||
|
',' => TokenType::Comma,
|
||||||
|
'.' => TokenType::Dot,
|
||||||
|
'+' => TokenType::Plus,
|
||||||
|
'-' => TokenType::Minus,
|
||||||
|
'*' => TokenType::Star,
|
||||||
|
'/' => TokenType::Slash,
|
||||||
|
';' => TokenType::Semicolon,
|
||||||
|
// Match multicharacter tokens
|
||||||
|
'!' => match input.peek() {
|
||||||
|
Some((_, '=')) => {
|
||||||
|
input.next();
|
||||||
|
end += 1;
|
||||||
|
TokenType::BangEqual
|
||||||
|
},
|
||||||
|
_ => TokenType::Bang,
|
||||||
|
},
|
||||||
|
'=' => match input.peek() {
|
||||||
|
Some((_, '=')) => {
|
||||||
|
input.next();
|
||||||
|
end += 1;
|
||||||
|
TokenType::DoubleEqual
|
||||||
|
},
|
||||||
|
_ => TokenType::Equal,
|
||||||
|
},
|
||||||
|
'<' => match input.peek() {
|
||||||
|
Some((_, '=')) => {
|
||||||
|
input.next();
|
||||||
|
end += 1;
|
||||||
|
TokenType::GreaterEqual
|
||||||
|
},
|
||||||
|
_ => TokenType::Greater,
|
||||||
|
},
|
||||||
|
'>' => match input.peek() {
|
||||||
|
Some((_, '=')) => {
|
||||||
|
input.next();
|
||||||
|
end += 1;
|
||||||
|
TokenType::LessEqual
|
||||||
|
},
|
||||||
|
_ => TokenType::Less,
|
||||||
|
},
|
||||||
|
// Match keywords, identifiers, and literals
|
||||||
|
c if c.is_alphanumeric() => 'case: {
|
||||||
|
// Scan full word
|
||||||
|
while let Some((new_end, next)) = input.peek() {
|
||||||
|
if next.is_alphanumeric() || *next == '_' {
|
||||||
|
let _ = input.next();
|
||||||
|
} else {
|
||||||
|
end = *new_end;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let word = &input_str[start..end];
|
||||||
|
// Attempt to parse hex literal
|
||||||
|
if let Some(s) =
|
||||||
|
word.strip_prefix("0x").or_else(|| word.strip_prefix("0X"))
|
||||||
|
{
|
||||||
|
if let Ok(n) = u64::from_str_radix(s, 16) {
|
||||||
|
break 'case TokenType::Number(n as f64);
|
||||||
|
} else {
|
||||||
|
break 'case TokenType::Unrecognized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Attempt to parse binary literal
|
||||||
|
if let Some(s) =
|
||||||
|
word.strip_prefix("0b").or_else(|| word.strip_prefix("0B"))
|
||||||
|
{
|
||||||
|
if let Ok(n) = u64::from_str_radix(s, 2) {
|
||||||
|
break 'case TokenType::Number(n as f64);
|
||||||
|
} else {
|
||||||
|
break 'case TokenType::Unrecognized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Attempt to parse decimal literal
|
||||||
|
if let Ok(f) = word.parse::<f64>() {
|
||||||
|
break 'case TokenType::Number(f);
|
||||||
|
}
|
||||||
|
// Parse keyword or ident
|
||||||
|
match word {
|
||||||
|
"and" => TokenType::And,
|
||||||
|
"or" => TokenType::Or,
|
||||||
|
"self" => TokenType::Self_,
|
||||||
|
"struct" => TokenType::Struct,
|
||||||
|
"true" => TokenType::True,
|
||||||
|
"false" => TokenType::False,
|
||||||
|
"fn" => TokenType::Fn,
|
||||||
|
"if" => TokenType::If,
|
||||||
|
"else" => TokenType::Else,
|
||||||
|
"nil" => TokenType::Nil,
|
||||||
|
"print" => TokenType::Print,
|
||||||
|
"return" => TokenType::Return,
|
||||||
|
"super" => TokenType::Super,
|
||||||
|
"let" => TokenType::Let,
|
||||||
|
"while" => TokenType::While,
|
||||||
|
"for" => TokenType::For,
|
||||||
|
_ => TokenType::Ident,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Parse string
|
||||||
|
'"' => {
|
||||||
|
while let Some((new_end, next)) = input.next() {
|
||||||
|
match next {
|
||||||
|
'"' => {
|
||||||
|
end = new_end + 1;
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
// Skip escapes and deal with them later
|
||||||
|
'\\' => {
|
||||||
|
let _ = input.next();
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TokenType::String
|
||||||
|
},
|
||||||
|
// Parse character
|
||||||
|
_ => TokenType::Unrecognized,
|
||||||
|
};
|
||||||
|
tokens.push(Token { ttype, start, end });
|
||||||
|
}
|
||||||
|
tokens
|
||||||
|
}
|
Loading…
Reference in a new issue