switched to thiserror for better error handling

This commit is contained in:
Shautvast 2025-11-01 09:36:09 +01:00
parent 0bd6048083
commit 55a30afd06
11 changed files with 252 additions and 174 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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<Token>) -> anyhow::Result<Vec<Statement>> {
pub fn compile(tokens: Vec<Token>) -> Result<Vec<Statement>, 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<Vec<Statement>> {
fn compile_tokens(&mut self) -> Result<Vec<Statement>,CompilerError> {
self.collect_functions()?;
self.reset();
self.compile()
}
fn compile(&mut self) -> anyhow::Result<Vec<Statement>> {
fn compile(&mut self) -> Result<Vec<Statement>, 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<Option<Statement>> {
fn indent(&mut self) -> Result<Option<Statement>, 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<Statement> {
fn declaration(&mut self) -> Result<Statement, CompilerError> {
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<Statement> {
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<Statement, CompilerError> {
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<Statement> {
let name_token = self.consume(Identifier, "Expect function name.")?;
self.consume(LeftParen, "Expect '(' after function name.")?;
fn function_declaration(&mut self) -> Result<Statement, CompilerError> {
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<Statement> {
let name_token = self.consume(Identifier, "Expect variable name.")?;
fn let_declaration(&mut self) -> Result<Statement, CompilerError> {
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<Statement> {
fn statement(&mut self) -> Result<Statement, CompilerError> {
if self.match_token(vec![Print]) {
self.print_statement()
} else {
@ -273,68 +272,68 @@ impl AstCompiler {
}
}
fn print_statement(&mut self) -> anyhow::Result<Statement> {
fn print_statement(&mut self) -> Result<Statement, CompilerError> {
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<Statement> {
fn expr_statement(&mut self) -> Result<Statement, CompilerError> {
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<Expression> {
fn expression(&mut self) -> Result<Expression, CompilerError> {
self.or()
}
fn or(&mut self) -> anyhow::Result<Expression> {
fn or(&mut self) -> Result<Expression, CompilerError> {
let expr = self.and()?;
self.binary(vec![TokenType::LogicalOr], expr)
}
fn and(&mut self) -> anyhow::Result<Expression> {
fn and(&mut self) -> Result<Expression, CompilerError> {
let expr = self.bit_and()?;
self.binary(vec![TokenType::LogicalAnd], expr)
}
fn bit_and(&mut self) -> anyhow::Result<Expression> {
fn bit_and(&mut self) -> Result<Expression, CompilerError> {
let expr = self.bit_or()?;
self.binary(vec![TokenType::BitAnd], expr)
}
fn bit_or(&mut self) -> anyhow::Result<Expression> {
fn bit_or(&mut self) -> Result<Expression, CompilerError> {
let expr = self.bit_xor()?;
self.binary(vec![TokenType::BitOr], expr)
}
fn bit_xor(&mut self) -> anyhow::Result<Expression> {
fn bit_xor(&mut self) -> Result<Expression, CompilerError> {
let expr = self.equality()?;
self.binary(vec![TokenType::BitXor], expr)
}
fn equality(&mut self) -> anyhow::Result<Expression> {
fn equality(&mut self) -> Result<Expression, CompilerError> {
let expr = self.comparison()?;
self.binary(vec![TokenType::EqualEqual, TokenType::BangEqual], expr)
}
fn comparison(&mut self) -> anyhow::Result<Expression> {
fn comparison(&mut self) -> Result<Expression, CompilerError> {
let expr = self.bitshift()?;
self.binary(vec![Greater, GreaterEqual, Less, LessEqual], expr)
}
fn bitshift(&mut self) -> anyhow::Result<Expression> {
fn bitshift(&mut self) -> Result<Expression, CompilerError> {
let expr = self.term()?;
self.binary(vec![GreaterGreater, LessLess], expr)
}
fn term(&mut self) -> anyhow::Result<Expression> {
fn term(&mut self) -> Result<Expression, CompilerError> {
let expr = self.factor()?;
self.binary(vec![Minus, Plus], expr)
}
fn factor(&mut self) -> anyhow::Result<Expression> {
fn factor(&mut self) -> Result<Expression, CompilerError> {
let expr = self.unary()?;
self.binary(vec![Slash, Star], expr)
}
@ -343,7 +342,7 @@ impl AstCompiler {
&mut self,
types: Vec<TokenType>,
mut expr: Expression,
) -> anyhow::Result<Expression> {
) -> Result<Expression, CompilerError> {
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<Expression> {
fn unary(&mut self) -> Result<Expression, CompilerError> {
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<Expression> {
fn primary(&mut self) -> Result<Expression, CompilerError> {
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<Expression> {
fn list(&mut self) -> Result<Expression, CompilerError> {
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<Expression> {
fn map(&mut self) -> Result<Expression, CompilerError> {
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<Expression> {
fn variable_lookup(&mut self, token: &Token) -> Result<Expression, CompilerError> {
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<Expression> {
fn function_call(&mut self, name: String) -> Result<Expression, CompilerError> {
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<Token> {
fn consume(&mut self, token_type: TokenType, message: CompilerError) -> Result<Token, CompilerError> {
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<TokenType>,
inferred_type: TokenType,
) -> anyhow::Result<TokenType> {
) -> Result<TokenType, CompilerError> {
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)),
}
})
}

View file

@ -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<Statement>,
registry: &mut HashMap<String, Chunk>,
) -> anyhow::Result<()> {
) -> Result<(),CompilerError> {
compile_name(ast, namespace, registry)
}
@ -23,7 +24,7 @@ pub(crate) fn compile_function(
function: &Function,
registry: &mut HashMap<String, Chunk>,
namespace: &str,
) -> anyhow::Result<Chunk> {
) -> Result<Chunk, CompilerError> {
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<Statement>,
namespace: Option<&str>,
registry: &mut HashMap<String, Chunk>,
) -> 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<Statement>,
registry: &mut HashMap<String, Chunk>,
namespace: &str,
) -> anyhow::Result<Chunk> {
) -> Result<Chunk, CompilerError> {
//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<String, Chunk>,
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<String, Chunk>,
) -> 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,

View file

@ -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"
);
}
}

61
src/errors.rs Normal file
View file

@ -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<CompilerError>),
#[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),
}

View file

@ -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<HashMap<String, chunk::Chunk>> {
pub fn compile(src: &str) -> Result<HashMap<String, chunk::Chunk>, 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<Value, Error> {
let tokens = scan(src)?;
let mut registry = HashMap::new();
let ast = ast_compiler::compile(tokens)?;
bytecode_compiler::compile(None, &ast, &mut registry)?;
interpret(&registry, "main").map_err(Error::from)
}

View file

@ -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,

View file

@ -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<Vec<Token>> {
pub fn scan(source: &str) -> Result<Vec<Token>, CompilerError> {
let scanner = Scanner {
chars: source.chars().collect(),
current: 0,
@ -21,7 +22,7 @@ pub fn scan(source: &str) -> anyhow::Result<Vec<Token>> {
}
impl Scanner {
fn scan(mut self) -> anyhow::Result<Vec<Token>> {
fn scan(mut self) -> Result<Vec<Token>, 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();

View file

@ -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<Value, Value>) -> std::fmt::Result
}
impl Neg for &Value {
type Output = anyhow::Result<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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<Value>;
type Output = Result<Value, ValueError>;
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, //?
}
}

View file

@ -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<String, Chunk>,
}
pub fn interpret(registry: &HashMap<String, Chunk>, function: &str) -> anyhow::Result<Value> {
pub fn interpret(registry: &HashMap<String, Chunk>, function: &str) -> Result<Value, RuntimeError> {
let chunk = registry.get(function).unwrap().clone();
// for (key,value) in registry.iter() {
// println!("{}", key);
@ -43,7 +43,7 @@ pub fn interpret(registry: &HashMap<String, Chunk>, function: &str) -> anyhow::R
vm.run(&chunk, vec![])
}
pub async fn interpret_async(registry: &HashMap<String, Chunk>, function: &str) -> anyhow::Result<Value> {
pub async fn interpret_async(registry: &HashMap<String, Chunk>, function: &str) -> Result<Value, RuntimeError> {
let chunk = registry.get(function).unwrap().clone();
let mut vm = Vm {
ip: 0,
@ -55,7 +55,7 @@ pub async fn interpret_async(registry: &HashMap<String, Chunk>, function: &str)
vm.run(&chunk, vec![])
}
pub fn interpret_function(chunk: &Chunk, args: Vec<Value>) -> anyhow::Result<Value> {
pub fn interpret_function(chunk: &Chunk, args: Vec<Value>) -> Result<Value, RuntimeError> {
let mut vm = Vm {
ip: 0,
stack: vec![],
@ -67,13 +67,13 @@ pub fn interpret_function(chunk: &Chunk, args: Vec<Value>) -> anyhow::Result<Val
}
impl <'a> Vm<'a> {
fn run(&mut self, chunk: &Chunk, args: Vec<Value>) -> anyhow::Result<Value> {
fn run(&mut self, chunk: &Chunk, args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value> + Copy) {
fn binary_op(vm: &mut Vm, op: impl Fn(&Value, &Value) -> Result<Value, ValueError> + 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<Value> +
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<Value> + Copy) {
fn unary_op(stack: &mut Vm, op: impl Fn(&Value) -> Result<Value, ValueError> + Copy) {
let a = stack.pop();
let result = op(&a);
match result {