From 55a30afd06e6843dc5ca83c939f1f36fb24c8c37 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sat, 1 Nov 2025 09:36:09 +0100 Subject: [PATCH] switched to thiserror for better error handling --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/ast_compiler.rs | 174 +++++++++++++++++++-------------------- src/bytecode_compiler.rs | 15 ++-- src/compiler_tests.rs | 10 +-- src/errors.rs | 61 ++++++++++++++ src/lib.rs | 20 ++++- src/main.rs | 21 +++-- src/scanner.rs | 21 ++--- src/value.rs | 72 ++++++++-------- src/vm.rs | 28 +++---- 11 files changed, 252 insertions(+), 174 deletions(-) create mode 100644 src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index d7cc1f5..0f3f76e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,6 @@ dependencies = [ name = "crudlang" version = "0.1.0" dependencies = [ - "anyhow", "axum", "chrono", "dotenv", @@ -203,6 +202,7 @@ dependencies = [ "log4rs", "reqwest", "serde", + "thiserror", "tokio", "tokio-postgres", "tower", diff --git a/Cargo.toml b/Cargo.toml index e570c6a..7a6534a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ tower-http = { version = "0.6", features = ["fs"] } tower = "0.5.0" tracing = "0.1.41" tracing-subscriber = "0.3.20" -anyhow = "1.0" tower-livereload = "0.9.6" log = "0.4.28" walkdir = "2.5.0" +thiserror = "2.0.17" diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index 2c74aab..f1526ea 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -1,17 +1,17 @@ +use crate::errors::CompilerError::{self, Expected, IncompatibleTypes, ParseError, TooManyParameters, TypeError, UnexpectedIndent, UninitializedVariable}; use crate::tokens::TokenType::{ - Bang, Bool, Char, Colon, Date, Eof, Eol, Equal, F32, F64, False, FloatingPoint, Fn, Greater, - GreaterEqual, GreaterGreater, I32, I64, Identifier, Indent, Integer, LeftBrace, LeftBracket, - LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, - RightBrace, RightBracket, RightParen, SignedInteger, SingleRightArrow, Slash, Star, StringType, - True, U32, U64, UnsignedInteger, + Bang, Bool, Char, Colon, Date, Eof, Eol, Equal, False, FloatingPoint, Fn, Greater, GreaterEqual, GreaterGreater, + Identifier, Indent, Integer, LeftBrace, LeftBracket, LeftParen, Less, LessEqual, LessLess, + Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace, RightBracket, RightParen, SignedInteger, + SingleRightArrow, Slash, Star, StringType, True, UnsignedInteger, F32, F64, + I32, I64, U32, U64, }; use crate::tokens::{Token, TokenType}; use crate::value::Value; -use anyhow::anyhow; use log::debug; use std::collections::HashMap; -pub fn compile(tokens: Vec) -> anyhow::Result> { +pub fn compile(tokens: Vec) -> Result, CompilerError> { let mut compiler = AstCompiler::new(tokens); compiler.compile_tokens() } @@ -49,13 +49,13 @@ impl AstCompiler { self.current = 0; } - fn compile_tokens(&mut self) -> anyhow::Result> { + fn compile_tokens(&mut self) -> Result,CompilerError> { self.collect_functions()?; self.reset(); self.compile() } - fn compile(&mut self) -> anyhow::Result> { + fn compile(&mut self) -> Result, CompilerError> { if !self.had_error { let mut statements = vec![]; while !self.is_at_end() { @@ -68,23 +68,23 @@ impl AstCompiler { } Ok(statements) } else { - Err(anyhow::anyhow!("Compilation failed.")) + Err(CompilerError::Failure) } } - fn collect_functions(&mut self) -> anyhow::Result<()> { + fn collect_functions(&mut self) -> Result<(), CompilerError> { while !self.is_at_end() { if self.match_token(vec![Fn]) { - let name_token = self.consume(Identifier, "Expect function name.")?; - self.consume(LeftParen, "Expect '(' after function name.")?; + let name_token = self.consume(Identifier, Expected("function name."))?; + self.consume(LeftParen, Expected("'(' after function name."))?; let mut parameters = vec![]; while !self.check(RightParen) { if parameters.len() >= 25 { - return Err(anyhow::anyhow!("Too many parameters.")); + return Err(TooManyParameters); } - let parm_name = self.consume(Identifier, "Expect parameter name.")?; + let parm_name = self.consume(Identifier, Expected("a parameter name."))?; - self.consume(Colon, "Expect : after parameter name")?; + self.consume(Colon, Expected(": after parameter name"))?; let var_type = self.peek().token_type; self.vars.push(Expression::Variable { name: parm_name.lexeme.to_string(), @@ -100,15 +100,15 @@ impl AstCompiler { self.advance(); } } - self.consume(RightParen, "Expect ')' after parameters.")?; + self.consume(RightParen, Expected(" ')' after parameters."))?; let return_type = if self.check(SingleRightArrow) { - self.consume(SingleRightArrow, "")?; + self.consume(SingleRightArrow, Expected("->"))?; self.advance().token_type } else { TokenType::Void }; - self.consume(Colon, "Expect colon (:) after function declaration.")?; - self.consume(Eol, "Expect end of line.")?; + self.consume(Colon, Expected("colon (:) after function declaration."))?; + self.consume(Eol, Expected("end of line."))?; let function = Function { name: name_token.clone(), @@ -125,7 +125,7 @@ impl AstCompiler { Ok(()) } - fn indent(&mut self) -> anyhow::Result> { + fn indent(&mut self) -> Result, CompilerError> { let expected_indent = *self.indent.last().unwrap(); // skip empty lines while self.check(Eol) { @@ -138,10 +138,9 @@ impl AstCompiler { indent_on_line += 1; } if indent_on_line > expected_indent { - panic!( - "unexpected indent level {} vs expected {}", + Err(UnexpectedIndent( indent_on_line, expected_indent - ); + )) } else if indent_on_line < expected_indent { self.indent.pop(); return Ok(None); @@ -150,7 +149,7 @@ impl AstCompiler { } } - fn declaration(&mut self) -> anyhow::Result { + fn declaration(&mut self) -> Result { if self.match_token(vec![Fn]) { self.function_declaration() } else if self.match_token(vec![Let]) { @@ -162,10 +161,10 @@ impl AstCompiler { } } - fn object_declaration(&mut self) -> anyhow::Result { - let type_name = self.consume(Identifier, "Expect object name.")?; - self.consume(Colon, "Expect ':' after object name.")?; - self.consume(Eol, "Expect end of line.")?; + fn object_declaration(&mut self) -> Result { + let type_name = self.consume(Identifier, Expected("object name."))?; + self.consume(Colon, Expected("':' after object name."))?; + self.consume(Eol, Expected("end of line."))?; let mut fields = vec![]; @@ -181,13 +180,13 @@ impl AstCompiler { } } if !done { - let field_name = self.consume(Identifier, "Expect an object field name.")?; - self.consume(Colon, "Expect ':' after field name.")?; + let field_name = self.consume(Identifier, Expected("an object field name."))?; + self.consume(Colon, Expected("':' after field name."))?; let field_type = self.peek().token_type; if field_type.is_type() { self.advance(); } else { - Err(anyhow::anyhow!("Expected a type"))? + Err(Expected("a type"))? } fields.push(Parameter { name: field_name, @@ -195,26 +194,26 @@ impl AstCompiler { }); } } - self.consume(Eol, "Expect end of line.")?; + self.consume(Eol, Expected("end of line."))?; Ok(Statement::ObjectStmt { name: type_name, fields, }) } - fn function_declaration(&mut self) -> anyhow::Result { - let name_token = self.consume(Identifier, "Expect function name.")?; - self.consume(LeftParen, "Expect '(' after function name.")?; + fn function_declaration(&mut self) -> Result { + let name_token = self.consume(Identifier, Expected("function name."))?; + self.consume(LeftParen, Expected("'(' after function name."))?; while !self.check(RightParen) { self.advance(); } - self.consume(RightParen, "Expect ')' after parameters.")?; + self.consume(RightParen, Expected("')' after parameters."))?; while !self.check(Colon) { self.advance(); } - self.consume(Colon, "2Expect colon (:) after function declaration.")?; - self.consume(Eol, "Expect end of line.")?; + self.consume(Colon, Expected("colon (:) after function declaration."))?; + self.consume(Eol, Expected("end of line."))?; let current_indent = self.indent.last().unwrap(); self.indent.push(current_indent + 1); @@ -228,8 +227,8 @@ impl AstCompiler { Ok(function_stmt) } - fn let_declaration(&mut self) -> anyhow::Result { - let name_token = self.consume(Identifier, "Expect variable name.")?; + fn let_declaration(&mut self) -> Result { + let name_token = self.consume(Identifier, Expected("variable name."))?; let declared_type = if self.check(Colon) { self.advance(); @@ -240,14 +239,14 @@ impl AstCompiler { if self.match_token(vec![Equal]) { let initializer = self.expression()?; - self.consume(Eol, "Expect end of line after initializer.")?; + self.consume(Eol, Expected("end of line after initializer."))?; let inferred_type = initializer.infer_type(); let var_type = match calculate_type(declared_type, inferred_type) { Ok(var_type) => var_type, Err(e) => { self.had_error = true; - return Err(anyhow!("error at line {}: {}", name_token.line, e)); + return Err(TypeError(name_token.line, Box::new(e))); } }; self.vars.push(Expression::Variable { @@ -261,11 +260,11 @@ impl AstCompiler { initializer, }) } else { - Err(anyhow::anyhow!("Uninitialized variables are not allowed."))? + Err(UninitializedVariable)? } } - fn statement(&mut self) -> anyhow::Result { + fn statement(&mut self) -> Result { if self.match_token(vec![Print]) { self.print_statement() } else { @@ -273,68 +272,68 @@ impl AstCompiler { } } - fn print_statement(&mut self) -> anyhow::Result { + fn print_statement(&mut self) -> Result { let expr = self.expression()?; - self.consume(Eol, "Expect end of line after print statement.")?; + self.consume(Eol, Expected("end of line after print statement."))?; Ok(Statement::PrintStmt { value: expr }) } - fn expr_statement(&mut self) -> anyhow::Result { + fn expr_statement(&mut self) -> Result { let expr = self.expression()?; - self.consume(Eol, "Expect end of line after expression.")?; + self.consume(Eol, Expected("end of line after expression."))?; Ok(Statement::ExpressionStmt { expression: expr }) } - fn expression(&mut self) -> anyhow::Result { + fn expression(&mut self) -> Result { self.or() } - fn or(&mut self) -> anyhow::Result { + fn or(&mut self) -> Result { let expr = self.and()?; self.binary(vec![TokenType::LogicalOr], expr) } - fn and(&mut self) -> anyhow::Result { + fn and(&mut self) -> Result { let expr = self.bit_and()?; self.binary(vec![TokenType::LogicalAnd], expr) } - fn bit_and(&mut self) -> anyhow::Result { + fn bit_and(&mut self) -> Result { let expr = self.bit_or()?; self.binary(vec![TokenType::BitAnd], expr) } - fn bit_or(&mut self) -> anyhow::Result { + fn bit_or(&mut self) -> Result { let expr = self.bit_xor()?; self.binary(vec![TokenType::BitOr], expr) } - fn bit_xor(&mut self) -> anyhow::Result { + fn bit_xor(&mut self) -> Result { let expr = self.equality()?; self.binary(vec![TokenType::BitXor], expr) } - fn equality(&mut self) -> anyhow::Result { + fn equality(&mut self) -> Result { let expr = self.comparison()?; self.binary(vec![TokenType::EqualEqual, TokenType::BangEqual], expr) } - fn comparison(&mut self) -> anyhow::Result { + fn comparison(&mut self) -> Result { let expr = self.bitshift()?; self.binary(vec![Greater, GreaterEqual, Less, LessEqual], expr) } - fn bitshift(&mut self) -> anyhow::Result { + fn bitshift(&mut self) -> Result { let expr = self.term()?; self.binary(vec![GreaterGreater, LessLess], expr) } - fn term(&mut self) -> anyhow::Result { + fn term(&mut self) -> Result { let expr = self.factor()?; self.binary(vec![Minus, Plus], expr) } - fn factor(&mut self) -> anyhow::Result { + fn factor(&mut self) -> Result { let expr = self.unary()?; self.binary(vec![Slash, Star], expr) } @@ -343,7 +342,7 @@ impl AstCompiler { &mut self, types: Vec, mut expr: Expression, - ) -> anyhow::Result { + ) -> Result { while self.match_token(types.clone()) { let operator = self.previous().clone(); let right = self.comparison()?; @@ -357,7 +356,7 @@ impl AstCompiler { Ok(expr) } - fn unary(&mut self) -> anyhow::Result { + fn unary(&mut self) -> Result { if self.match_token(vec![Bang, Minus]) { let operator = self.previous().clone(); let right = self.unary()?; @@ -371,7 +370,7 @@ impl AstCompiler { } } - fn primary(&mut self) -> anyhow::Result { + fn primary(&mut self) -> Result { debug!("primary {:?}", self.peek()); Ok(if self.match_token(vec![LeftBracket]) { self.list()? @@ -393,13 +392,13 @@ impl AstCompiler { Expression::Literal { line: self.peek().line, literaltype: Integer, - value: Value::I64(self.previous().lexeme.parse()?), + value: Value::I64(self.previous().lexeme.parse().map_err(|e|ParseError(format!("{:?}",e)))?), } } else if self.match_token(vec![FloatingPoint]) { Expression::Literal { line: self.peek().line, literaltype: FloatingPoint, - value: Value::F64(self.previous().lexeme.parse()?), + value: Value::F64(self.previous().lexeme.parse().map_err(|e|ParseError(format!("{:?}",e)))?), } } else if self.match_token(vec![StringType]) { Expression::Literal { @@ -415,7 +414,7 @@ impl AstCompiler { } } else if self.match_token(vec![LeftParen]) { let expr = self.expression()?; - self.consume(RightParen, "Expect ')' after expression.")?; + self.consume(RightParen, Expected("')' after expression."))?; Expression::Grouping { line: self.peek().line, expression: Box::new(expr), @@ -431,14 +430,14 @@ impl AstCompiler { }) } - fn list(&mut self) -> anyhow::Result { + fn list(&mut self) -> Result { let mut list = vec![]; while !self.match_token(vec![RightBracket]) { list.push(self.expression()?); if self.peek().token_type == TokenType::Comma { self.advance(); } else { - self.consume(RightBracket, "Expect ']' after list.")?; + self.consume(RightBracket, Expected("']' at the end of the list."))?; break; } } @@ -449,17 +448,17 @@ impl AstCompiler { }) } - fn map(&mut self) -> anyhow::Result { + fn map(&mut self) -> Result { let mut entries = vec![]; while !self.match_token(vec![RightBrace]) { let key = self.expression()?; - self.consume(Colon, "Expect ':' after map key.")?; + self.consume(Colon, Expected("':' after map key."))?; let value = self.expression()?; entries.push((key, value)); if self.peek().token_type == TokenType::Comma { self.advance(); } else { - self.consume(RightBrace, "Expect '}' after map.")?; + self.consume(RightBrace, Expected("'}' after map."))?; break; } } @@ -470,7 +469,7 @@ impl AstCompiler { }) } - fn variable_lookup(&mut self, token: &Token) -> anyhow::Result { + fn variable_lookup(&mut self, token: &Token) -> Result { let (var_name, var_type) = self .vars .iter() @@ -482,7 +481,7 @@ impl AstCompiler { } }) .find(|e| e.0 == &token.lexeme) - .ok_or_else(|| return anyhow::anyhow!("Unknown variable: {:?}", token))?; + .ok_or_else(|| return CompilerError::UndeclaredVariable(token.clone()))?; Ok(Expression::Variable { name: var_name.to_string(), var_type: var_type.clone(), @@ -490,20 +489,19 @@ impl AstCompiler { }) } - fn function_call(&mut self, name: String) -> anyhow::Result { + fn function_call(&mut self, name: String) -> Result { let function_name = self.functions.get(&name).unwrap().name.lexeme.clone(); let function = self.functions.get(&function_name).unwrap().clone(); let mut arguments = vec![]; while !self.match_token(vec![RightParen]) { if arguments.len() >= 25 { - return Err(anyhow::anyhow!("Too many parameters.")); + return Err(TooManyParameters); } let arg = self.expression()?; let arg_type = arg.infer_type(); if arg_type != function.parameters[arguments.len()].var_type { - return Err(anyhow::anyhow!( - "Incompatible argument types. Expected {}, found {}", + return Err(IncompatibleTypes( function.parameters[arguments.len()].var_type, arg_type )); @@ -512,7 +510,7 @@ impl AstCompiler { if self.peek().token_type == TokenType::Comma { self.advance(); } else { - self.consume(RightParen, "Expect ')' after arguments.")?; + self.consume(RightParen, Expected("')' after arguments."))?; break; } } @@ -525,16 +523,17 @@ impl AstCompiler { }) } - fn consume(&mut self, token_type: TokenType, message: &str) -> anyhow::Result { + fn consume(&mut self, token_type: TokenType, message: CompilerError) -> Result { if self.check(token_type) { self.advance(); } else { self.had_error = true; - return Err(anyhow::anyhow!( - "{} at {:?}", - message.to_string(), - self.peek() - )); + // return Err(anyhow::anyhow!( + // "{} at {:?}", + // message.to_string(), + // self.peek() + // )); + return Err(message); } Ok(self.previous().clone()) } @@ -580,7 +579,7 @@ impl AstCompiler { fn calculate_type( declared_type: Option, inferred_type: TokenType, -) -> anyhow::Result { +) -> Result { Ok(if let Some(declared_type) = declared_type { if declared_type != inferred_type { match (declared_type, inferred_type) { @@ -596,8 +595,7 @@ fn calculate_type( (U64, I32) => U64, (StringType, _) => StringType, // meh, this all needs rigorous testing. Update: this is in progress _ => { - return Err(anyhow::anyhow!( - "Incompatible types. Expected {}, found {}", + return Err(IncompatibleTypes( declared_type, inferred_type )); @@ -615,7 +613,7 @@ fn calculate_type( ListType => ListType, MapType => MapType, Object => Object, - _ => panic!("Unexpected type"), + _ => return Err(CompilerError::UnexpectedType(inferred_type)), } }) } diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index bd1db56..8e708ef 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -10,12 +10,13 @@ use crate::vm::{ OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, }; use std::collections::HashMap; +use crate::errors::CompilerError; pub fn compile( namespace: Option<&str>, ast: &Vec, registry: &mut HashMap, -) -> anyhow::Result<()> { +) -> Result<(),CompilerError> { compile_name(ast, namespace, registry) } @@ -23,7 +24,7 @@ pub(crate) fn compile_function( function: &Function, registry: &mut HashMap, namespace: &str, -) -> anyhow::Result { +) -> Result { let mut compiler = Compiler::new(&function.name.lexeme); for parm in &function.parameters { let name = parm.name.lexeme.clone(); @@ -39,7 +40,7 @@ pub(crate) fn compile_name( ast: &Vec, namespace: Option<&str>, registry: &mut HashMap, -) -> anyhow::Result<()> { +) -> Result<(),CompilerError> { let name=namespace.unwrap_or("main"); let compiler = Compiler::new(name); let chunk = compiler.compile(ast, registry, name)?; @@ -74,7 +75,7 @@ impl Compiler { ast: &Vec, registry: &mut HashMap, namespace: &str, - ) -> anyhow::Result { + ) -> Result { //TODO can likely be removed for statement in ast { if let Statement::FunctionStmt { function } = statement { @@ -99,7 +100,7 @@ impl Compiler { statement: &Statement, registry: &mut HashMap, namespace: &str, - ) -> anyhow::Result<()> { + ) -> Result<(), CompilerError> { self.current_line = statement.line(); match statement { Statement::VarStmt { @@ -140,7 +141,7 @@ impl Compiler { namespace: &str, expression: &Expression, registry: &mut HashMap, - ) -> anyhow::Result<()> { + ) -> Result<(), CompilerError> { match expression { Expression::FunctionCall { name, arguments, .. @@ -229,7 +230,7 @@ impl Compiler { var_type: &TokenType, name_index: usize, initializer: &Expression, - ) -> anyhow::Result<()> { + ) -> Result<(), CompilerError> { let def_op = match var_type { TokenType::I32 => OP_DEF_I32, TokenType::I64 => OP_DEF_I64, diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs index 14a0a34..26ef21c 100644 --- a/src/compiler_tests.rs +++ b/src/compiler_tests.rs @@ -1,13 +1,13 @@ #[cfg(test)] mod tests { - use crate::compile; + use crate::{compile, run}; use crate::scanner::scan; use crate::value::Value; use crate::vm::interpret; #[test] fn literal_int() { - assert!(compile("1").is_ok()); + assert_eq!(run("1"), Ok(Value::I64(1))); } #[test] @@ -52,7 +52,7 @@ mod tests { if let Err(e) = &r { assert_eq!( e.to_string(), - "error at line 1: Incompatible types. Expected u32, found i32/64" + "Type mismatch at line 1: Incompatible types. Expected u32, found i32/64" ); } } @@ -64,7 +64,7 @@ mod tests { if let Err(e) = &r { assert_eq!( e.to_string(), - "error at line 1: Incompatible types. Expected u64, found i32/64" + "Type mismatch at line 1: Incompatible types. Expected u64, found i32/64" ); } } @@ -76,7 +76,7 @@ mod tests { if let Err(e) = &r { assert_eq!( e.to_string(), - "error at line 1: Incompatible types. Expected u64, found string" + "Type mismatch at line 1: Incompatible types. Expected u64, found string" ); } } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..c4d6772 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,61 @@ +use thiserror::Error; +use crate::tokens::{Token, TokenType}; + +#[derive(Error, Debug, PartialEq)] +pub enum Error { + #[error(transparent)] + Compiler(#[from] CompilerError), + #[error(transparent)] + Runtime(#[from] RuntimeError), + #[error("Platform error {0}")] + Platform(String), +} + + +#[derive(Error, Debug, PartialEq)] +pub enum CompilerError { + #[error("Compilation failed")] + Failure, + #[error("Too many parameters")] + TooManyParameters, + #[error("Expected {0}")] + Expected(&'static str), + #[error("unexpected indent level {0} vs expected {1}")] + UnexpectedIndent(usize,usize), + #[error("Type mismatch at line {0}: {1}")] + TypeError(usize, Box), + #[error("Uninitialized variables are not allowed.")] + UninitializedVariable, + #[error("Incompatible types. Expected {0}, found {1}")] + IncompatibleTypes(TokenType, TokenType), + #[error("Error parsing number {0}")] + ParseError(String), + #[error("Undeclared variable: {0:?}")] + UndeclaredVariable(Token), + #[error("Unexpected identifier at line {0}")] + UnexpectedIdentifier(usize), + #[error("Unterminated {0} at line {1}")] + Unterminated(&'static str, usize), + #[error("Illegal char length for {0} at line {1}")] + IllegalCharLength(String, usize), + #[error("Unexpected type {0}")] + UnexpectedType(TokenType) +} + +#[derive(Error, Debug, PartialEq)] +pub enum RuntimeError { + #[error("Error while executing")] + Value(#[from] ValueError), + #[error("Error occurred")] + Something, + #[error("Expected {0}, got {1}")] + Expected(&'static str, &'static str), +} + +#[derive(Error, Debug, PartialEq)] +pub enum ValueError { + #[error("{0}")] + CannotAnd(&'static str), + #[error("{0}")] + Some(&'static str), +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 541e929..00446c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,32 @@ -use std::collections::HashMap; use crate::scanner::scan; +use crate::value::Value; +use crate::vm::interpret; +use std::collections::HashMap; +use crate::errors::Error; pub mod ast_compiler; pub mod bytecode_compiler; pub mod chunk; +mod compiler_tests; mod keywords; pub mod scanner; -mod compiler_tests; mod tokens; mod value; pub mod vm; +pub mod errors; -pub fn compile(src: &str) -> anyhow::Result> { +pub fn compile(src: &str) -> Result, Error> { let tokens = scan(src)?; let mut registry = HashMap::new(); - let ast= ast_compiler::compile(tokens)?; + let ast = ast_compiler::compile(tokens)?; bytecode_compiler::compile(None, &ast, &mut registry)?; Ok(registry) } + +fn run(src: &str) -> Result { + let tokens = scan(src)?; + let mut registry = HashMap::new(); + let ast = ast_compiler::compile(tokens)?; + bytecode_compiler::compile(None, &ast, &mut registry)?; + interpret(®istry, "main").map_err(Error::from) +} diff --git a/src/main.rs b/src/main.rs index 9eb235c..9056623 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use axum::extract::{Path, State}; +use axum::extract::{State}; use axum::http::StatusCode; use axum::routing::get; use axum::{Json, Router}; @@ -9,12 +9,13 @@ use crudlang::scanner::scan; use crudlang::vm::{interpret, interpret_async}; use std::collections::HashMap; use std::fs; -use std::hash::Hash; use std::sync::Arc; use walkdir::WalkDir; +use crudlang::errors::Error; +use crudlang::errors::Error::Platform; #[tokio::main] -async fn main() -> anyhow::Result<()> { +async fn main() -> Result<(), crudlang::errors::Error> { tracing_subscriber::fmt::init(); let mut paths = HashMap::new(); @@ -23,12 +24,12 @@ async fn main() -> anyhow::Result<()> { let path = entry.path(); if path.is_file() && path.ends_with("web.crud") { print!("compiling {:?}: ", path); - let source = fs::read_to_string(path)?; + let source = fs::read_to_string(path).map_err(map_underlying())?; let tokens = scan(&source)?; match ast_compiler::compile(tokens) { Ok(statements) => { let path = path - .strip_prefix("source")? + .strip_prefix("source").map_err(|e|Platform(e.to_string()))? .to_str() .unwrap() .replace(".crud", ""); @@ -58,13 +59,17 @@ async fn main() -> anyhow::Result<()> { get(handle_get).with_state(state.clone()), ); } - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; - println!("listening on {}", listener.local_addr()?); - axum::serve(listener, app).await?; + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.map_err(map_underlying())?; + println!("listening on {}", listener.local_addr().map_err(map_underlying())?); + axum::serve(listener, app).await.map_err(map_underlying())?; } Ok(()) } +fn map_underlying() -> fn(std::io::Error) -> Error { + |e| Error::Platform(e.to_string()) +} + #[derive(Clone)] struct AppState { name: String, diff --git a/src/scanner.rs b/src/scanner.rs index bcb1c22..c99fbe0 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,4 +1,3 @@ -use anyhow::anyhow; use crate::tokens::TokenType::{BitXor, FloatingPoint, Integer}; use crate::{ keywords, @@ -7,8 +6,10 @@ use crate::{ TokenType::{self}, }, }; +use crate::errors::CompilerError; +use crate::errors::CompilerError::{IllegalCharLength, UnexpectedIdentifier, Unterminated}; -pub fn scan(source: &str) -> anyhow::Result> { +pub fn scan(source: &str) -> Result, CompilerError> { let scanner = Scanner { chars: source.chars().collect(), current: 0, @@ -21,7 +22,7 @@ pub fn scan(source: &str) -> anyhow::Result> { } impl Scanner { - fn scan(mut self) -> anyhow::Result> { + fn scan(mut self) -> Result, CompilerError> { while !self.is_at_end() { self.start = self.current; self.scan_token()?; @@ -31,7 +32,7 @@ impl Scanner { Ok(self.tokens) } - fn scan_token(&mut self) -> anyhow::Result<()>{ + fn scan_token(&mut self) -> Result<(),CompilerError> { let c = self.advance(); if self.new_line && (c == ' ' || c == '\t') { self.add_token(TokenType::Indent); @@ -138,7 +139,7 @@ impl Scanner { } else if is_alpha(c) { self.identifier(); } else { - return Err(anyhow!("Unexpected identifier at line {}", self.line)); + return Err(UnexpectedIdentifier(self.line)); } } } @@ -173,13 +174,13 @@ impl Scanner { self.add_token_with_value(if has_dot { FloatingPoint } else { Integer }, value); } - fn char(&mut self) -> anyhow::Result<()>{ + fn char(&mut self) -> Result<(), CompilerError>{ while self.peek() != '\'' && !self.is_at_end() { self.advance(); } if self.is_at_end() { - return Err(anyhow!("Unterminated char at {}", self.line)) + return Err(Unterminated("char", self.line)) } self.advance(); @@ -188,13 +189,13 @@ impl Scanner { .iter() .collect(); if value.len() != 1 { - return Err(anyhow!("Illegal char length for {} at line {}", value, self.line)) + return Err(IllegalCharLength(value, self.line)) } self.add_token_with_value(TokenType::Char, value); Ok(()) } - fn string(&mut self) -> anyhow::Result<()>{ + fn string(&mut self) -> Result<(),CompilerError> { while self.peek() != '"' && !self.is_at_end() { if self.peek() == '\n' { self.line += 1; @@ -203,7 +204,7 @@ impl Scanner { } if self.is_at_end() { - return Err(anyhow!("Unterminated string at {}", self.line)) + return Err(Unterminated("string", self.line)) } self.advance(); diff --git a/src/value.rs b/src/value.rs index ffeb772..cd0ca83 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,10 +1,10 @@ -use anyhow::anyhow; use chrono::{DateTime, Utc}; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Shl, Shr, Sub}; +use crate::errors::ValueError; #[derive(Debug, Clone)] pub struct Object { @@ -143,7 +143,7 @@ fn to_string(f: &mut Formatter, map: &HashMap) -> std::fmt::Result } impl Neg for &Value { - type Output = anyhow::Result; + type Output = Result; fn neg(self) -> Self::Output { match self { @@ -151,13 +151,13 @@ impl Neg for &Value { Value::I64(i) => Ok(Value::I64(-i)), Value::F32(i) => Ok(Value::F32(-i)), Value::F64(i) => Ok(Value::F64(-i)), - _ => Err(anyhow!("Cannot negate")), + _ => Err(ValueError::Some("Cannot negate")), } } } impl Add<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn add(self, rhs: &Value) -> Self::Output { if let Value::List(s) = self { @@ -189,14 +189,14 @@ impl Add<&Value> for &Value { } (Value::String(s1), Value::Map(m)) => Ok(Value::String(format!("{}{:?}", s1, m))), //enum? - _ => Err(anyhow!("Cannot add")), + _ => Err(ValueError::Some("Cannot add")), } } } } impl Sub<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn sub(self, rhs: &Value) -> Self::Output { match (self, rhs) { @@ -207,13 +207,13 @@ impl Sub<&Value> for &Value { (Value::F32(a), Value::F32(b)) => Ok(Value::F32(a - b)), (Value::F64(a), Value::F64(b)) => Ok(Value::F64(a - b)), //enum? - _ => Err(anyhow!("Cannot subtract")), + _ => Err(ValueError::Some("Cannot subtract")), } } } impl Mul<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn mul(self, rhs: &Value) -> Self::Output { match (self, rhs) { @@ -223,13 +223,13 @@ impl Mul<&Value> for &Value { (Value::U64(a), Value::U64(b)) => Ok(Value::U64(a * b)), (Value::F32(a), Value::F32(b)) => Ok(Value::F32(a * b)), (Value::F64(a), Value::F64(b)) => Ok(Value::F64(a * b)), - _ => Err(anyhow!("Cannot multiply")), + _ => Err(ValueError::Some("Cannot multiply")), } } } impl Div<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn div(self, rhs: &Value) -> Self::Output { match (self, rhs) { @@ -239,52 +239,52 @@ impl Div<&Value> for &Value { (Value::U64(a), Value::U64(b)) => Ok(Value::U64(a / b)), (Value::F32(a), Value::F32(b)) => Ok(Value::F32(a / b)), (Value::F64(a), Value::F64(b)) => Ok(Value::F64(a / b)), - _ => Err(anyhow!("Cannot divide")), + _ => Err(ValueError::Some("Cannot divide")), } } } impl BitAnd<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn bitand(self, rhs: &Value) -> Self::Output { match (self, rhs) { (Value::I32(a), Value::I32(b)) => Ok(Value::I32(a & b)), (Value::I64(a), Value::I64(b)) => Ok(Value::I64(a & b)), (Value::U32(a), Value::U32(b)) => Ok(Value::U32(a & b)), (Value::U64(a), Value::U64(b)) => Ok(Value::U64(a & b)), - _ => Err(anyhow!("Cannot do bitwise-and on")), + _ => Err(ValueError::Some("Cannot do bitwise-and on")), } } } impl BitOr<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn bitor(self, rhs: &Value) -> Self::Output { match (self, rhs) { (Value::I32(a), Value::I32(b)) => Ok(Value::I32(a | b)), (Value::I64(a), Value::I64(b)) => Ok(Value::I64(a | b)), (Value::U32(a), Value::U32(b)) => Ok(Value::U32(a | b)), (Value::U64(a), Value::U64(b)) => Ok(Value::U64(a | b)), - _ => Err(anyhow!("Cannot do bitwise-or on")), + _ => Err(ValueError::Some("Cannot do bitwise-or on")), } } } impl BitXor<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn bitxor(self, rhs: &Value) -> Self::Output { match (self, rhs) { (Value::I32(a), Value::I32(b)) => Ok(Value::I32(a ^ b)), (Value::I64(a), Value::I64(b)) => Ok(Value::I64(a ^ b)), (Value::U32(a), Value::U32(b)) => Ok(Value::U32(a ^ b)), (Value::U64(a), Value::U64(b)) => Ok(Value::U64(a ^ b)), - _ => Err(anyhow!("Cannot do bitwise-xor on")), + _ => Err(ValueError::Some("Cannot do bitwise-xor on")), } } } impl Not for &Value { - type Output = anyhow::Result; + type Output = Result; fn not(self) -> Self::Output { match self { @@ -293,33 +293,33 @@ impl Not for &Value { Value::I64(i64) => Ok(Value::I64(!i64)), Value::U32(u32) => Ok(Value::U32(!u32)), Value::U64(u64) => Ok(Value::U64(!u64)), - _ => Err(anyhow!("Cannot calculate not")), + _ => Err(ValueError::Some("Cannot calculate not")), } } } impl Shl<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn shl(self, rhs: &Value) -> Self::Output { match (self, rhs) { (Value::I32(a), Value::I32(b)) => Ok(Value::I32(a << b)), (Value::I64(a), Value::I64(b)) => Ok(Value::I64(a << b)), (Value::U32(a), Value::U32(b)) => Ok(Value::U32(a << b)), (Value::U64(a), Value::U64(b)) => Ok(Value::U64(a << b)), - _ => Err(anyhow!("Cannot shift left on")), + _ => Err(ValueError::Some("Cannot shift left on")), } } } impl Shr<&Value> for &Value { - type Output = anyhow::Result; + type Output = Result; fn shr(self, rhs: &Value) -> Self::Output { match (self, rhs) { (Value::I32(a), Value::I32(b)) => Ok(Value::I32(a >> b)), (Value::I64(a), Value::I64(b)) => Ok(Value::I64(a >> b)), (Value::U32(a), Value::U32(b)) => Ok(Value::U32(a >> b)), (Value::U64(a), Value::U64(b)) => Ok(Value::U64(a >> b)), - _ => Err(anyhow!("Cannot shift right on")), + _ => Err(ValueError::Some("Cannot shift right on")), } } } @@ -337,19 +337,19 @@ impl PartialEq for Value { (Value::String(a), Value::String(b)) => a == b, (Value::Char(a), Value::Char(b)) => a == b, (Value::Date(a), Value::Date(b)) => a == b, - // (Value::List(a), Value::List(b)) => a == b, - // (Value::Map(a), Value::Map(b)) => { - // let mut equal = true; - // for (k, v) in a.iter() { - // if !b.contains_key(k) || b.get(k).unwrap() != v { - // //safe unwrap - // equal = false; - // break; - // } - // } - // equal - // } - // struct? + (Value::List(a), Value::List(b)) => a == b, + (Value::Map(a), Value::Map(b)) => { + let mut equal = true; + for (k, v) in a.iter() { + if !b.contains_key(k) || b.get(k).unwrap() != v { + //safe unwrap + equal = false; + break; + } + } + equal + } + // TODO objects _ => false, //? } } diff --git a/src/vm.rs b/src/vm.rs index d9a2876..8e42177 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,6 +1,7 @@ use crate::chunk::Chunk; +use crate::errors::RuntimeError::{Something}; +use crate::errors::{RuntimeError, ValueError}; use crate::value::Value; -use anyhow::anyhow; use std::collections::HashMap; use tracing::debug; @@ -11,9 +12,8 @@ macro_rules! define_var { if let Value::$variant(_) = value { $self.local_vars.insert(name, value); } else { - return Err(anyhow!( - concat!("Expected ", stringify!($variant), ", got {:?}"), - value + return Err(RuntimeError::Expected( + stringify!($variant), stringify!(value), )); } }}; @@ -27,7 +27,7 @@ pub struct Vm<'a> { registry: &'a HashMap, } -pub fn interpret(registry: &HashMap, function: &str) -> anyhow::Result { +pub fn interpret(registry: &HashMap, function: &str) -> Result { let chunk = registry.get(function).unwrap().clone(); // for (key,value) in registry.iter() { // println!("{}", key); @@ -43,7 +43,7 @@ pub fn interpret(registry: &HashMap, function: &str) -> anyhow::R vm.run(&chunk, vec![]) } -pub async fn interpret_async(registry: &HashMap, function: &str) -> anyhow::Result { +pub async fn interpret_async(registry: &HashMap, function: &str) -> Result { let chunk = registry.get(function).unwrap().clone(); let mut vm = Vm { ip: 0, @@ -55,7 +55,7 @@ pub async fn interpret_async(registry: &HashMap, function: &str) vm.run(&chunk, vec![]) } -pub fn interpret_function(chunk: &Chunk, args: Vec) -> anyhow::Result { +pub fn interpret_function(chunk: &Chunk, args: Vec) -> Result { let mut vm = Vm { ip: 0, stack: vec![], @@ -67,13 +67,13 @@ pub fn interpret_function(chunk: &Chunk, args: Vec) -> anyhow::Result Vm<'a> { - fn run(&mut self, chunk: &Chunk, args: Vec) -> anyhow::Result { + fn run(&mut self, chunk: &Chunk, args: Vec) -> Result { for arg in args { self.push(arg); } loop { if self.error_occurred { - return Err(anyhow!("Error occurred")); + return Err(Something); } debug!("{:?}", self.stack); let opcode = chunk.code[self.ip]; @@ -91,14 +91,14 @@ impl <'a> Vm<'a> { if let (Value::Bool(a), Value::Bool(b)) = (a, b) { Ok(Value::Bool(*a && *b)) } else { - Err(anyhow!("Cannot and")) + Err(ValueError::Some("Cannot and")) } }), OP_OR => binary_op(self, |a, b| { if let (Value::Bool(a), Value::Bool(b)) = (a, b) { Ok(Value::Bool(*a || *b)) } else { - Err(anyhow!("Cannot compare")) + Err(ValueError::Some("Cannot compare")) } }), OP_NOT => unary_op(self, |a| !a), @@ -210,7 +210,7 @@ impl <'a> Vm<'a> { } } -fn binary_op(vm: &mut Vm, op: impl Fn(&Value, &Value) -> anyhow::Result + Copy) { +fn binary_op(vm: &mut Vm, op: impl Fn(&Value, &Value) -> Result + Copy) { let b = vm.pop(); let a = vm.pop(); @@ -218,13 +218,13 @@ fn binary_op(vm: &mut Vm, op: impl Fn(&Value, &Value) -> anyhow::Result + match result { Ok(result) => vm.push(result), Err(e) => { - println!("Error: {} {:?} and {:?}", e.to_string(), a, b); vm.error_occurred = true; + println!("Error: {} {:?} and {:?}", e.to_string(), a, b); } } } -fn unary_op(stack: &mut Vm, op: impl Fn(&Value) -> anyhow::Result + Copy) { +fn unary_op(stack: &mut Vm, op: impl Fn(&Value) -> Result + Copy) { let a = stack.pop(); let result = op(&a); match result {