diff --git a/Cargo.lock b/Cargo.lock index 65cd111..6485a35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,6 +199,7 @@ dependencies = [ "axum", "chrono", "dotenv", + "log", "log4rs", "reqwest", "serde", diff --git a/Cargo.toml b/Cargo.toml index 928a4a9..03ba4c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ tracing = "0.1.41" tracing-subscriber = "0.3.20" anyhow = "1.0" tower-livereload = "0.9.6" +log = "0.4.28" diff --git a/src/chunk.rs b/src/chunk.rs index 889e762..144e46b 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -3,7 +3,7 @@ use crate::value::Value; use crate::vm::{ OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CONSTANT, OP_DIVIDE, OP_FALSE, OP_MULTIPLY, OP_NEGATE, OP_RETURN, OP_SUBTRACT, OP_TRUE, OP_NOT, OP_SHL, OP_SHR, OP_LESS, OP_LESS_EQUAL, - OP_GREATER, OP_GREATER_EQUAL, OP_EQUAL, OP_PRINT, OP_POP, + OP_GREATER, OP_GREATER_EQUAL, OP_EQUAL, OP_PRINT, OP_POP, OP_DEFINE, OP_GET }; pub struct Chunk { @@ -73,6 +73,8 @@ impl Chunk { OP_EQUAL => self.simple_inst("EQ", offset), OP_PRINT => self.simple_inst("PRT", offset), OP_POP => self.simple_inst("POP", offset), + OP_DEFINE => self.constant_inst("DEF", offset), + OP_GET => self.constant_inst("GET", offset), _ => { println!("Unknown instruction"); offset + 1 diff --git a/src/compiler.rs b/src/compiler.rs index e90c732..acb4b40 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -2,7 +2,11 @@ use crate::chunk::Chunk; use crate::scanner::scan; use crate::tokens::{Token, TokenType}; use crate::value::Value; -use crate::vm::{OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CONSTANT, OP_DIVIDE, OP_EQUAL, OP_FALSE, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, OP_TRUE}; +use crate::vm::{ + OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CONSTANT, OP_DEFINE, OP_DIVIDE, OP_EQUAL, OP_FALSE, + OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT, + OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, OP_TRUE, +}; use anyhow::anyhow; use std::collections::HashMap; use std::sync::LazyLock; @@ -13,6 +17,7 @@ pub fn compile(source: &str) -> anyhow::Result { debug!("Scanned tokens: {:?}", tokens); let mut compiler = Compiler { + source: source.lines().map(|s| s.to_string()).collect(), chunk: Chunk::new("main"), previous_token: &tokens[0], current_token: &tokens[0], @@ -25,6 +30,7 @@ pub fn compile(source: &str) -> anyhow::Result { } struct Compiler<'a> { + source: Vec, chunk: Chunk, tokens: &'a Vec, current: usize, @@ -36,8 +42,6 @@ struct Compiler<'a> { impl<'a> Compiler<'a> { fn compile(mut self) -> anyhow::Result { - - while !self.match_token(TokenType::Eof) { self.declaration()?; } @@ -50,7 +54,41 @@ impl<'a> Compiler<'a> { } fn declaration(&mut self) -> anyhow::Result<()> { - self.statement() + if self.match_token(TokenType::Let) { + self.let_declaration() + } else { + self.statement() + } + } + + fn let_declaration(&mut self) -> anyhow::Result<()> { + let index = self.parse_variable("Expect variable name")?; + if self.match_token(TokenType::Equal) { + self.expression()?; + self.consume(TokenType::Eol, "Expect end of line")?; + self.define_variable(index)?; + } else { + return Err(anyhow!( + "You cannot declare a variable without initializing it." + )); + } + Ok(()) + } + + fn parse_variable(&mut self, message: &str) -> anyhow::Result { + self.consume(TokenType::Identifier, message)?; + self.identifier_constant(self.previous_token) + } + + fn identifier_constant(&mut self, token: &Token) -> anyhow::Result { + let name = token.lexeme.clone(); + let index = self.chunk.add_constant(Value::String(name)); + Ok(index) + } + + fn define_variable(&mut self, index: usize) -> anyhow::Result<()> { + self.emit_bytes(OP_DEFINE, index as u16); + Ok(()) } fn statement(&mut self) -> anyhow::Result<()> { @@ -61,7 +99,8 @@ impl<'a> Compiler<'a> { } } - fn expression_statement(&mut self) -> anyhow::Result<()>{ + fn expression_statement(&mut self) -> anyhow::Result<()> { + debug!("expression statement"); self.expression()?; self.emit_byte(OP_POP); Ok(()) @@ -69,7 +108,7 @@ impl<'a> Compiler<'a> { fn print_statement(&mut self) -> anyhow::Result<()> { self.expression()?; - // self.consume(TokenType::Semicolon, "Expect ';' after value.")?; + self.consume(TokenType::Eol, "No further expressions expected. Please continue on a new line after the first.\n")?; self.emit_byte(OP_PRINT); Ok(()) } @@ -97,7 +136,12 @@ impl<'a> Compiler<'a> { if token_type == self.current_token.token_type { self.advance() } else { - Err(anyhow!("{}", message)) + Err(anyhow!( + r#"{} at line {}: "{}""#, + message, + self.current_token.line + 1, + self.source[self.current_token.line] + )) } } @@ -116,6 +160,7 @@ impl<'a> Compiler<'a> { fn expression(&mut self) -> anyhow::Result<()> { self.parse_precedence(PREC_ASSIGNMENT)?; + Ok(()) } @@ -133,7 +178,7 @@ impl<'a> Compiler<'a> { } } } else { - return Err(anyhow!("Expect expression.")); + return Err(anyhow!("Expect expression.")); } Ok(()) } @@ -182,14 +227,19 @@ fn number(s: &mut Compiler) -> anyhow::Result<()> { fn literal(s: &mut Compiler) -> anyhow::Result<()> { match s.previous_token.token_type { - TokenType::False => s.emit_byte(OP_FALSE), - TokenType::True => s.emit_byte(OP_TRUE), + TokenType::False => s.emit_constant(Value::Bool(false)), + TokenType::True => s.emit_constant(Value::Bool(true)), TokenType::String => s.emit_constant(Value::String(s.previous_token.lexeme.clone())), _ => {} } Ok(()) } +fn skip(s: &mut Compiler) -> anyhow::Result<()> { + // s.advance() + Ok(()) +} + fn grouping(s: &mut Compiler) -> anyhow::Result<()> { s.expression()?; s.consume(TokenType::RightParen, "Expect ')' after expression.") @@ -237,6 +287,12 @@ fn binary(s: &mut Compiler) -> anyhow::Result<()> { Ok(()) } +fn variable(s: &mut Compiler) -> anyhow::Result<()> { + let index = s.identifier_constant(s.previous_token)?; + s.emit_bytes(OP_GET, index as u16); + Ok(()) +} + fn get_rule(operator_type: &TokenType) -> &'static Rule { debug!("{:?}", operator_type); RULES.get(operator_type).unwrap() @@ -244,32 +300,28 @@ fn get_rule(operator_type: &TokenType) -> &'static Rule { static RULES: LazyLock> = LazyLock::new(|| { let mut rules: HashMap = HashMap::new(); - rules.insert( - TokenType::LeftParen, - Rule::new(Some(binary), None, PREC_NONE), - ); - rules.insert(TokenType::RightParen, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::LeftBrace, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::RightBrace, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::LeftBracket, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::RightBracket, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::Comma, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::Dot, Rule::new(None, None, PREC_NONE)); - rules.insert( - TokenType::Minus, - Rule::new(Some(unary), Some(binary), PREC_TERM), - ); - rules.insert(TokenType::Plus, Rule::new(None, Some(binary), PREC_TERM)); - rules.insert(TokenType::Colon, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::Slash, Rule::new(None, Some(binary), PREC_FACTOR)); - rules.insert(TokenType::Star, Rule::new(None, Some(binary), PREC_FACTOR)); rules.insert(TokenType::Bang, Rule::new(Some(unary), None, PREC_UNARY)); rules.insert(TokenType::BangEqual, Rule::new(None, None, PREC_EQUALITY)); + rules.insert(TokenType::BitOr, Rule::new(None, Some(binary), PREC_BITOR)); + rules.insert( + TokenType::BitXor, + Rule::new(None, Some(binary), PREC_BITXOR), + ); + rules.insert(TokenType::Colon, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::Comma, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::DateType, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::Dot, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::Else, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::Eof, Rule::new(Some(skip), None, PREC_NONE)); + rules.insert(TokenType::Eol, Rule::new(Some(skip), None, PREC_NONE)); rules.insert(TokenType::Equal, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::False, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::Fn, Rule::new(None, None, PREC_NONE)); rules.insert( TokenType::EqualEqual, Rule::new(None, Some(binary), PREC_EQUALITY), ); + rules.insert(TokenType::False, Rule::new(Some(literal), None, PREC_NONE)); rules.insert( TokenType::Greater, Rule::new(None, Some(binary), PREC_COMPARISON), @@ -282,6 +334,19 @@ static RULES: LazyLock> = LazyLock::new(|| { TokenType::GreaterGreater, Rule::new(None, Some(binary), PREC_BITSHIFT), ); + rules.insert(TokenType::I32Type, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::I64Type, Rule::new(None, None, PREC_NONE)); + rules.insert( + TokenType::Identifier, + Rule::new(Some(variable), None, PREC_NONE), + ); + rules.insert(TokenType::Indent, Rule::new(Some(skip), None, PREC_NONE)); + rules.insert(TokenType::LeftBrace, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::LeftBracket, Rule::new(None, None, PREC_NONE)); + rules.insert( + TokenType::LeftParen, + Rule::new(Some(binary), None, PREC_NONE), + ); rules.insert( TokenType::Less, Rule::new(None, Some(binary), PREC_COMPARISON), @@ -294,41 +359,35 @@ static RULES: LazyLock> = LazyLock::new(|| { TokenType::LessLess, Rule::new(None, Some(binary), PREC_BITSHIFT), ); - rules.insert(TokenType::Identifier, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::String, Rule::new(Some(literal), None, PREC_NONE)); - rules.insert(TokenType::Number, Rule::new(Some(number), None, PREC_NONE)); rules.insert( TokenType::LogicalAnd, Rule::new(None, Some(binary), PREC_AND), ); rules.insert(TokenType::LogicalOr, Rule::new(None, Some(binary), PREC_OR)); + rules.insert( + TokenType::Minus, + Rule::new(Some(unary), Some(binary), PREC_TERM), + ); + rules.insert(TokenType::Number, Rule::new(Some(number), None, PREC_NONE)); + rules.insert(TokenType::Plus, Rule::new(None, Some(binary), PREC_TERM)); + rules.insert(TokenType::Print, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::Return, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::RightParen, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::RightBrace, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::RightBracket, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::Slash, Rule::new(None, Some(binary), PREC_FACTOR)); + rules.insert(TokenType::Star, Rule::new(None, Some(binary), PREC_FACTOR)); + rules.insert(TokenType::String, Rule::new(Some(literal), None, PREC_NONE)); rules.insert( TokenType::BitAnd, Rule::new(None, Some(binary), PREC_BITAND), ); - rules.insert(TokenType::BitOr, Rule::new(None, Some(binary), PREC_BITOR)); - rules.insert( - TokenType::BitXor, - Rule::new(None, Some(binary), PREC_BITXOR), - ); - rules.insert(TokenType::Fn, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::StringType, Rule::new(None, None, PREC_NONE)); rules.insert(TokenType::Struct, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::Else, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::False, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::True, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::While, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::Print, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::Return, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::Eof, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::True, Rule::new(Some(literal), None, PREC_NONE)); rules.insert(TokenType::U32Type, Rule::new(None, None, PREC_NONE)); rules.insert(TokenType::U64Type, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::I32Type, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::I64Type, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::DateType, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::StringType, Rule::new(None, None, PREC_NONE)); - rules.insert(TokenType::False, Rule::new(Some(literal), None, PREC_NONE)); - rules.insert(TokenType::True, Rule::new(Some(literal), None, PREC_NONE)); - rules.insert(TokenType::Indent, Rule::new(None, None, PREC_NONE)); + rules.insert(TokenType::While, Rule::new(None, None, PREC_NONE)); rules }); diff --git a/src/keywords.rs b/src/keywords.rs index dae20cf..95730e2 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -2,19 +2,28 @@ use crate::tokens::TokenType; pub(crate) fn get_keyword(lexeme: &str) -> Option { match lexeme { - "fn" => Some(TokenType::Fn), - "struct" => Some(TokenType::Struct), - "u32" => Some(TokenType::U32Type), - "string" => Some(TokenType::StringType), - "date" => Some(TokenType::DateType), - "print" => Some(TokenType::Print), "and" => Some(TokenType::LogicalAnd), + "bool" => Some(TokenType::BoolType), + "char" => Some(TokenType::CharType), + "date" => Some(TokenType::DateType), "else" => Some(TokenType::Else), "false" => Some(TokenType::False), - "true" => Some(TokenType::True), + "fn" => Some(TokenType::Fn), "for" => Some(TokenType::For), "if" => Some(TokenType::If), + "i32" => Some(TokenType::I32Type), + "i64" => Some(TokenType::I64Type), + "let" => Some(TokenType::Let), + "list" => Some(TokenType::ListType), + "map" => Some(TokenType::MapType), "or" => Some(TokenType::LogicalOr), + "object" => Some(TokenType::ObjectType), + "print" => Some(TokenType::Print), + "struct" => Some(TokenType::Struct), + "string" => Some(TokenType::StringType), + "true" => Some(TokenType::True), + "u32" => Some(TokenType::U32Type), + "u64" => Some(TokenType::U64Type), "while" => Some(TokenType::While), _ => None, diff --git a/src/main.rs b/src/main.rs index 7d5ab10..86baf7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,23 @@ - fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); - let chunk = crudlang::compiler::compile("\"hello \" + 42")?; - chunk.disassemble(); + let chunk = crudlang::compiler::compile( + r#"let a = "hello " + 42 + print a print a + print a"#, + ); + match chunk { + Err(e) => { + println!("{}", e); + return Ok(()); + } + Ok(chunk) => { + chunk.disassemble(); + + let result = crudlang::vm::interpret(chunk)?; + println!("{}", result); + } + } - let result = crudlang::vm::interpret(chunk); - println!("{:?}", result); Ok(()) } - diff --git a/src/scanner.rs b/src/scanner.rs index 0d3efd3..2908f78 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -25,6 +25,7 @@ impl Scanner { self.start = self.current; self.scan_token(); } + self.add_token(TokenType::Eol); self.add_token(TokenType::Eof); self.tokens } @@ -103,6 +104,7 @@ impl Scanner { '\n' => { self.line += 1; self.new_line = true; + self.add_token(TokenType::Eol); } '&' => { let t = if self.match_next('&') { diff --git a/src/tokens.rs b/src/tokens.rs index 1bd429e..f251d72 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -22,60 +22,66 @@ enum Value { #[derive(Debug, PartialEq, Clone, Copy, Hash)] pub(crate) enum TokenType { - Error, - LeftParen, - RightParen, - LeftBrace, - RightBrace, - LeftBracket, - RightBracket, - Colon, - Semicolon, - Comma, - Dot, - Star, - Slash, - Plus, - Minus, - Not, - Hash, Bang, BangEqual, - EqualEqual, - Equal, - Greater, - Less, - GreaterEqual, - GreaterGreater, - LessLess, - LessEqual, - Indent, - Identifier, - String, - Number, - LogicalAnd, - LogicalOr, BitAnd, BitOr, BitXor, - Fn, - Struct, + BoolType, + CharType, + Colon, + Comma, + DateType, + Dot, Else, - False, - True, - Null, - If, - While, - For, - Return, - Print, Eof, - U32Type, - U64Type, + Eol, + Equal, + EqualEqual, + Error, + False, + Fn, + For, + Greater, + GreaterEqual, + GreaterGreater, + Hash, I32Type, I64Type, - DateType, + If, + Indent, + Identifier, + LeftBrace, + LeftBracket, + LeftParen, + Less, + LessEqual, + LessLess, + Let, + ListType, + MapType, + LogicalAnd, + LogicalOr, + Minus, + Not, + Number, + ObjectType, + Plus, + Print, + Return, + RightParen, + RightBrace, + RightBracket, + Semicolon, + Slash, + Star, + String, StringType, + Struct, + True, + U32Type, + U64Type, + While, } impl Eq for TokenType { diff --git a/src/vm.rs b/src/vm.rs index d8ef60b..9e10fb5 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,13 +1,15 @@ use crate::chunk::Chunk; use crate::value::Value; use anyhow::anyhow; +use std::collections::HashMap; use tracing::debug; -pub fn interpret(chunk: Chunk) -> Result { +pub fn interpret(chunk: Chunk) -> anyhow::Result { let mut vm = Vm { chunk, ip: 0, stack: vec![], + local_vars: HashMap::new(), }; vm.run() } @@ -16,26 +18,21 @@ pub struct Vm { chunk: Chunk, ip: usize, stack: Vec, + local_vars: HashMap, } impl Vm { - fn run(&mut self) -> Result { + fn run(&mut self) -> anyhow::Result { loop { - debug!("["); - for value in self.stack.iter() { - debug!("{:?} ", value); - } - debug!("]"); + debug!("{:?}", self.stack); let opcode = self.chunk.code[self.ip]; self.ip += 1; match opcode { - OP_CONSTANT => { + OP_CONSTANT | OP_FALSE | OP_TRUE => { let value = &self.chunk.constants[self.chunk.code[self.ip] as usize]; self.ip += 1; self.push(value.clone()); } - OP_FALSE => self.push(Value::Bool(false)), - OP_TRUE => self.push(Value::Bool(true)), OP_ADD => binary_op(self, |a, b| a + b), OP_SUBTRACT => binary_op(self, |a, b| a - b), OP_MULTIPLY => binary_op(self, |a, b| a * b), @@ -60,10 +57,11 @@ impl Vm { OP_BITXOR => binary_op(self, |a, b| a ^ b), OP_NEGATE => unary_op(self, |a| -a), OP_RETURN => { + debug!("return {:?}", self.stack); return if self.stack.is_empty() { - Result::Ok(Value::Void) + Ok(Value::Void) } else { - return Result::Ok(self.pop()); + Ok(self.pop()) }; } OP_SHL => binary_op(self, |a, b| a << b), @@ -74,14 +72,32 @@ impl Vm { OP_LESS => binary_op(self, |a, b| Ok(Value::Bool(a < b))), OP_LESS_EQUAL => binary_op(self, |a, b| Ok(Value::Bool(a <= b))), OP_PRINT => { + debug!("print {:?}", self.stack); let v = self.pop(); println!("{}", v); } + OP_DEFINE => { + let name = self.read_constant(); + let value = self.pop(); + self.local_vars.insert(name, value); + } + OP_GET => { + let name = self.read_constant(); + let value = self.local_vars.get(&name).unwrap(); + self.push(value.clone()); // not happy + debug!("after get {:?}", self.stack); + } _ => {} } } } + fn read_constant(&mut self) -> String { + let name = self.chunk.constants[self.chunk.code[self.ip] as usize].to_string(); + self.ip += 1; + name + } + fn reset_stack(&mut self) { self.stack.clear(); } @@ -117,13 +133,6 @@ fn unary_op(stack: &mut Vm, op: impl Fn(&Value) -> anyhow::Result + Copy) } } -#[derive(Debug)] -pub enum Result { - Ok(Value), - CompileError, - Error, -} - pub const OP_CONSTANT: u16 = 1; pub const OP_ADD: u16 = 2; pub const OP_SUBTRACT: u16 = 3; @@ -149,3 +158,5 @@ pub const OP_BITXOR: u16 = 22; pub const OP_SHR: u16 = 23; pub const OP_SHL: u16 = 24; pub const OP_POP: u16 = 25; +pub const OP_DEFINE: u16 = 26; +pub const OP_GET: u16 = 27;