├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── bin └── main.rs ├── errors.rs ├── lib.rs ├── literal.rs ├── scanner.rs ├── token.rs └── token_type.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lox" 3 | version = "0.1.0" 4 | authors = ["Paul Woolcock "] 5 | license = "Unlicense" 6 | 7 | [lib] 8 | name = "_lox" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "lox" 13 | path = "src/bin/main.rs" 14 | 15 | [dependencies] 16 | error-chain = "0.10.0" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to / 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crafting Interpreters, except in Rust 2 | 3 | I have really been enjoying following along with [Crafting 4 | Interpreters](http://www.craftinginterpreters.com) by @munificent, but 5 | I enjoy writing rust more than java, so I started adapting the code in 6 | the book. 7 | 8 | - [x] Scanning (branch: 01-scanning) 9 | - [ ] Representing Code 10 | - [ ] Parsing Expressions 11 | - [ ] Evaluating Expressions 12 | - [ ] Statements and State 13 | - [ ] Control Flow 14 | - [ ] Functions 15 | - [ ] Resolving and Binding 16 | - [ ] Classes 17 | - [ ] Inheritance 18 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | extern crate _lox; 2 | 3 | use std::env; 4 | 5 | use _lox::{run_file, run_prompt}; 6 | 7 | fn main() { 8 | let args = env::args().skip(1).collect::>(); 9 | if args.len() > 1 { 10 | eprintln!("Usage: lox [script]"); 11 | } else if args.len() == 1 { 12 | run_file(&args[0]); 13 | } else { 14 | run_prompt(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_doc_comment)] 2 | 3 | error_chain!{ 4 | foreign_links { 5 | ParseInt(::std::num::ParseIntError); 6 | ParseFloat(::std::num::ParseFloatError); 7 | } 8 | 9 | errors { 10 | LoxError(line: usize, msg: String) { 11 | description("lox error") 12 | display("[line: {}] Error: {}", line, msg) 13 | } 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate error_chain; 2 | 3 | use std::fs::File; 4 | use std::io::{stdin, stdout, Read, Write, BufReader, BufRead}; 5 | use std::process; 6 | 7 | pub use errors::*; 8 | use scanner::Scanner; 9 | 10 | mod errors; 11 | mod scanner; 12 | mod token; 13 | mod token_type; 14 | mod literal; 15 | 16 | pub fn run_file(filename: &str) { 17 | let mut src = String::new(); 18 | let mut f = File::open(filename).unwrap(); 19 | if let Err(e) = f.read_to_string(&mut src) { 20 | eprintln!("{}: Error reading from file {}", e, &filename); 21 | process::exit(65); 22 | } 23 | if let Err(e) = run(&src) { 24 | eprintln!("{}", e.description()); 25 | process::exit(65); 26 | } 27 | } 28 | 29 | pub fn run_prompt() { 30 | let handle = stdin(); 31 | let handle = handle.lock(); 32 | let buf_handle = BufReader::new(handle); 33 | let mut lines = buf_handle.lines(); 34 | 35 | loop { 36 | print!("> "); 37 | let _ = stdout().flush().unwrap(); 38 | 39 | let line = if let Some(line) = lines.next() { 40 | line.unwrap() 41 | } else { 42 | break; 43 | }; 44 | 45 | if let Err(e) = run(&line) { 46 | eprintln!("{}", e.description()); 47 | } 48 | } 49 | } 50 | 51 | pub fn run(src: &str) -> Result<()> { 52 | let scanner = Scanner::new(src); 53 | let tokens = scanner.scan(); 54 | 55 | for token in tokens { 56 | println!("{:?}", token); 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | #[test] 65 | fn it_works() { 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/literal.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Literal { 3 | String(String), 4 | Number(Number), 5 | } 6 | 7 | #[derive(Debug)] 8 | pub enum Number { 9 | Int(i64), 10 | Float(f64), 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/scanner.rs: -------------------------------------------------------------------------------- 1 | use errors::{Result, ErrorKind}; 2 | use token::Token; 3 | use token_type::TokenType; 4 | use literal::{Literal, Number}; 5 | 6 | pub struct Scanner { 7 | characters: Vec, 8 | start: usize, 9 | current: usize, 10 | line: usize, 11 | } 12 | 13 | impl Scanner { 14 | pub fn new(source: &str) -> Scanner { 15 | let source: String = source.into(); 16 | let chars = source.chars().collect::>(); 17 | Scanner { 18 | characters: chars, 19 | start: 0, 20 | current: 0, 21 | line: 1, 22 | } 23 | } 24 | 25 | fn is_at_end(&self) -> bool { 26 | self.current >= self.characters.len() 27 | } 28 | 29 | pub fn scan(mut self) -> Result> { 30 | let mut tokens = vec![]; 31 | 32 | while !self.is_at_end() { 33 | self.start = self.current; 34 | let token = self.scan_token()?; 35 | if let Some(token) = token { 36 | tokens.push(token); 37 | } 38 | } 39 | 40 | tokens.push(Token::new(TokenType::Eof, "", None, self.line)); 41 | Ok(tokens) 42 | } 43 | 44 | fn scan_token(&mut self) -> Result> { 45 | let ch = self.next(); 46 | Ok(match ch { 47 | // Single-char tokens 48 | '(' => Some(self.make_token(TokenType::LeftParen, None)), 49 | ')' => Some(self.make_token(TokenType::RightParen, None)), 50 | '{' => Some(self.make_token(TokenType::LeftBrace, None)), 51 | '}' => Some(self.make_token(TokenType::RightBrace, None)), 52 | ',' => Some(self.make_token(TokenType::Comma, None)), 53 | '.' => Some(self.make_token(TokenType::Dot, None)), 54 | '-' => Some(self.make_token(TokenType::Minus, None)), 55 | '+' => Some(self.make_token(TokenType::Plus, None)), 56 | ';' => Some(self.make_token(TokenType::Semicolon, None)), 57 | '*' => Some(self.make_token(TokenType::Star, None)), 58 | 59 | // Single-or-double-char tokens 60 | '!' => { 61 | if self.match_char('=') { 62 | Some(self.make_token(TokenType::BangEqual, None)) 63 | } else { 64 | Some(self.make_token(TokenType::Bang, None)) 65 | } 66 | }, 67 | '=' => { 68 | if self.match_char('=') { 69 | Some(self.make_token(TokenType::EqualEqual, None)) 70 | } else { 71 | Some(self.make_token(TokenType::Equal, None)) 72 | } 73 | }, 74 | '<' => { 75 | if self.match_char('=') { 76 | Some(self.make_token(TokenType::LesserEqual, None)) 77 | } else { 78 | Some(self.make_token(TokenType::Lesser, None)) 79 | } 80 | }, 81 | '>' => { 82 | if self.match_char('=') { 83 | Some(self.make_token(TokenType::GreaterEqual, None)) 84 | } else { 85 | Some(self.make_token(TokenType::Greater, None)) 86 | } 87 | }, 88 | 89 | // Could be single-char token, or a comment 90 | '/' => { 91 | if self.match_char('/') { 92 | while self.peek() != '\n' && !self.is_at_end() { 93 | self.next(); 94 | } 95 | None 96 | } else { 97 | Some(self.make_token(TokenType::Slash, None)) 98 | } 99 | }, 100 | 101 | // Ignore Whitespace 102 | ' ' | '\r' | '\t' => { 103 | None 104 | }, 105 | 106 | '\n' => { 107 | self.line += 1; 108 | self.scan_token()? 109 | }, 110 | 111 | '"' => Some(self.scan_string()?), 112 | 113 | digit if self.is_digit(digit) => { 114 | Some(self.scan_number()?) 115 | }, 116 | 117 | alpha if self.is_alpha(alpha) => { 118 | Some(self.scan_ident()?) 119 | }, 120 | 121 | _ => return Err(ErrorKind::LoxError(self.line, "no matched token".into()).into()) 122 | }) 123 | } 124 | 125 | fn is_digit(&self, ch: char) -> bool { 126 | ch >= '0' && ch <= '9' 127 | } 128 | 129 | fn is_alpha(&self, ch: char) -> bool { 130 | (ch >= 'a' && ch <= 'z') || 131 | (ch >= 'A' && ch <= 'Z') || 132 | ch == '_' 133 | } 134 | 135 | fn is_alphanumeric(&self, ch: char) -> bool { 136 | self.is_alpha(ch) || self.is_digit(ch) 137 | } 138 | 139 | fn scan_ident(&mut self) -> Result { 140 | while self.is_alphanumeric(self.peek()) { 141 | self.next(); 142 | } 143 | 144 | let text = self.characters[self.start..self.current].iter().cloned().collect::(); 145 | if let Some(ttype) = TokenType::keyword(&text) { 146 | Ok(self.make_token(ttype, None)) 147 | } else { 148 | Ok(self.make_token(TokenType::Identifier, None)) 149 | } 150 | } 151 | 152 | fn scan_number(&mut self) -> Result { 153 | let mut is_float = false; 154 | while self.is_digit(self.peek()) { 155 | self.next(); 156 | } 157 | 158 | if self.peek() == '.' && self.is_digit(self.peek_next()) { 159 | is_float = true; 160 | // Consume the '.' 161 | self.next(); 162 | 163 | while self.is_digit(self.peek()) { 164 | self.next(); 165 | } 166 | } 167 | 168 | let lexeme = self.characters[self.start..self.current].iter().cloned().collect::(); 169 | Ok(self.make_token(TokenType::Number, if is_float { 170 | let parsed: f64 = lexeme.parse()?; 171 | let literal = Number::Float(parsed); 172 | Some(Literal::Number(literal)) 173 | } else { 174 | let parsed: i64 = lexeme.parse()?; 175 | let literal = Number::Int(parsed); 176 | Some(Literal::Number(literal)) 177 | })) 178 | } 179 | 180 | fn scan_string(&mut self) -> Result { 181 | while self.peek() != '"' && !self.is_at_end() { 182 | if self.peek() == '\n' { 183 | self.line += 1; 184 | } 185 | self.next(); 186 | } 187 | 188 | if self.is_at_end() { 189 | return Err(ErrorKind::LoxError(self.line, "unterminated string!".into()).into()); 190 | } 191 | 192 | // the closing '"' 193 | self.next(); 194 | 195 | let start = self.start + 1; 196 | let end = self.current - 1; 197 | let val = self.characters[start..end].iter().cloned().collect::(); 198 | let lit = Literal::String(val); 199 | Ok(self.make_token(TokenType::String, Some(lit))) 200 | } 201 | 202 | fn peek(&self) -> char { 203 | if self.is_at_end() { 204 | '\0' 205 | } else { 206 | self.characters[self.current] 207 | } 208 | } 209 | 210 | fn peek_next(&self) -> char { 211 | if self.current + 1 >= self.characters.len() { 212 | '\0' 213 | } else { 214 | self.characters[self.current + 1] 215 | } 216 | } 217 | 218 | fn next(&mut self) -> char { 219 | self.current += 1; 220 | self.characters[self.current - 1] 221 | } 222 | 223 | fn match_char(&mut self, expected: char) -> bool { 224 | if self.is_at_end() { 225 | return false; 226 | } 227 | if self.characters[self.current] != expected { 228 | return false; 229 | } 230 | 231 | self.current += 1; 232 | return true; 233 | } 234 | 235 | fn make_token(&mut self, ttype: TokenType, literal: Option) -> Token { 236 | let lexeme = self.characters[self.start..self.current].iter().cloned().collect::(); 237 | Token::new(ttype, &lexeme, literal, self.line) 238 | } 239 | } 240 | 241 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use token_type::TokenType; 4 | use literal::Literal; 5 | 6 | pub struct Token { 7 | token_type: TokenType, 8 | lexeme: String, 9 | literal: Option, 10 | line: usize, 11 | } 12 | 13 | impl Token { 14 | pub fn new(ttype: TokenType, lexeme: &str, literal: Option, line: usize) -> Token { 15 | Token { 16 | token_type: ttype, 17 | lexeme: lexeme.into(), 18 | literal: literal, 19 | line: line, 20 | } 21 | } 22 | } 23 | 24 | impl fmt::Debug for Token { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | if let Some(ref literal) = self.literal { 27 | write!(f, "{:?} {:?} {:?}", self.token_type, self.lexeme, literal) 28 | } else { 29 | write!(f, "{:?} {:?}", self.token_type, self.lexeme) 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/token_type.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum TokenType { 3 | // single-character tokens 4 | LeftParen, RightParen, LeftBrace, RightBrace, 5 | Comma, Dot, Minus, Plus, Semicolon, Slash, Star, 6 | 7 | // One or two character tokens 8 | Bang, BangEqual, 9 | Equal, EqualEqual, 10 | Greater, GreaterEqual, 11 | Lesser, LesserEqual, 12 | 13 | // Literals 14 | Identifier, String, Number, 15 | 16 | // Keywords 17 | And, Class, Else, False, Fun, For, If, Nil, Or, 18 | Print, Return, Super, This, True, Var, While, 19 | 20 | // Whitespace 21 | Ws, 22 | 23 | Eof, 24 | } 25 | 26 | impl TokenType { 27 | pub fn keyword(s: &str) -> Option { 28 | use self::TokenType::*; 29 | Some(match s { 30 | "and" => And, 31 | "class" => Class, 32 | "else" => Else, 33 | "false" => False, 34 | "fun" => Fun, 35 | "for" => For, 36 | "if" => If, 37 | "nil" => Nil, 38 | "or" => Or, 39 | "print" => Print, 40 | "return" => Return, 41 | "super" => Super, 42 | "this" => This, 43 | "true" => True, 44 | "var" => Var, 45 | "while" => While, 46 | _ => return None, 47 | }) 48 | } 49 | } 50 | --------------------------------------------------------------------------------