From 0bce2ae9ebd192356953ce678afea26c6d7c5200 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Fri, 14 Nov 2025 17:37:22 +0100 Subject: [PATCH] fix reassignment bugs --- src/ast_compiler.rs | 12 +++++-- src/builtins/list.rs | 65 ++++++++++++++++++++++++++++++++++++ src/builtins/mod.rs | 24 ++++++++------ src/builtins/string.rs | 33 +++++++++---------- src/bytecode_compiler.rs | 37 +++++++++++++++------ src/compiler_tests.rs | 71 ++++++++++++++++++++++++++++++++-------- src/errors.rs | 4 +++ src/main.rs | 2 +- src/repl.rs | 8 ++++- src/tokens.rs | 2 ++ src/value.rs | 5 +++ src/vm.rs | 26 ++++++++++----- 12 files changed, 226 insertions(+), 63 deletions(-) create mode 100644 src/builtins/list.rs diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index 2f270f9..28440e8 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -68,7 +68,6 @@ impl AstCompiler { &mut self, symbol_table: &mut SymbolTable, ) -> Result, CompilerErrorAtLine> { - self.current_line(); if !self.had_error { let mut statements = vec![]; while !self.is_at_end() { @@ -365,10 +364,19 @@ impl AstCompiler { } fn bit_xor(&mut self, symbol_table: &mut SymbolTable) -> Expr { - let expr = self.equality(symbol_table)?; + let expr = self.assignment(symbol_table)?; self.binary(&[TokenType::BitXor], expr, symbol_table) } + fn assignment(&mut self, symbol_table: &mut SymbolTable) -> Expr { + let expr = self.equality(symbol_table)?; + self.binary( + &[TokenType::Equal], + expr, + symbol_table, + ) + } + fn equality(&mut self, symbol_table: &mut SymbolTable) -> Expr { let expr = self.comparison(symbol_table)?; self.binary( diff --git a/src/builtins/list.rs b/src/builtins/list.rs new file mode 100644 index 0000000..e5e2361 --- /dev/null +++ b/src/builtins/list.rs @@ -0,0 +1,65 @@ +use crate::ast_compiler::Parameter; +use crate::builtins::{FunctionMap, Signature, add, expected}; +use crate::errors::RuntimeError; +use crate::tokens::TokenType; +use crate::tokens::TokenType::U64; +use crate::value::{Value, u64}; +use std::collections::HashMap; + +macro_rules! mut_list_fn { + (mut $list:ident, mut $args:ident => $body:expr) => { + |self_val: Value, mut $args: Vec| -> Result { + match self_val { + Value::List(mut $list) => $body, + _ => Err(expected_a_list()), + } + } + }; +} + +pub(crate) fn list_functions() -> FunctionMap { + let mut list_functions: FunctionMap = HashMap::new(); + let functions = &mut list_functions; + add( + functions, + "len", + Signature::new( + vec![], + U64, + mut_list_fn!(mut self_val, mut _args => Ok(u64(self_val.len() as u64))), + ), + ); + add( + functions, + "push", + Signature::new( + vec![Parameter::new("element", TokenType::Any)], + U64, + mut_list_fn!(mut list, mut args => { + list.push(args.remove(0)); + Ok(Value::List(list)) + }), + ), + ); + add( + functions, + "remove", + Signature::new( + vec![Parameter::new("index", U64)], + U64, + mut_list_fn!(mut list, mut args => { + let index = args.remove(0).cast_usize().unwrap(); + if index >= list.len() { + return Err(RuntimeError::IndexOutOfBounds(index, list.len())) + } + list.remove(index); + Ok(Value::List(list)) + }), + ), + ); + list_functions +} + +fn expected_a_list() -> RuntimeError { + expected("list") +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 0204599..d73596f 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,24 +1,26 @@ mod string; +mod list; -use crate::builtins::string::string_methods; +use crate::builtins::string::string_functions; use crate::errors::{CompilerError, RuntimeError}; use crate::tokens::TokenType; use crate::value::Value; use std::collections::HashMap; use std::sync::LazyLock; use crate::ast_compiler::Parameter; +use crate::builtins::list::list_functions; pub(crate) struct Signature { pub(crate) parameters: Vec, pub(crate) return_type: TokenType, - pub(crate) function: MethodFn, + pub(crate) function: FunctionFn, } impl Signature { pub(crate) fn new( parameters: Vec, return_type: TokenType, - function: MethodFn, + function: FunctionFn, ) -> Self { Self { parameters, @@ -32,17 +34,19 @@ impl Signature { } } -pub(crate) type MethodFn = fn(Value, Vec) -> Result; -pub(crate) type MethodMap = HashMap; -pub(crate) type MethodTable = HashMap; +pub(crate) type FunctionFn = fn(Value, Vec) -> Result; +pub(crate) type FunctionMap = HashMap; +pub(crate) type FunctionTable = HashMap; + +static METHODS: LazyLock = LazyLock::new(|| { + let mut table: FunctionTable = HashMap::new(); + table.insert("string".to_string(), string_functions()); + table.insert("list".to_string(), list_functions()); -static METHODS: LazyLock = LazyLock::new(|| { - let mut table: MethodTable = HashMap::new(); - table.insert("string".to_string(), string_methods()); table }); -pub(crate) fn add(m: &mut MethodMap, name: &str, method: Signature) { +pub(crate) fn add(m: &mut FunctionMap, name: &str, method: Signature) { m.insert(name.to_string(), method); } diff --git a/src/builtins/string.rs b/src/builtins/string.rs index 83d0c38..1755e36 100644 --- a/src/builtins/string.rs +++ b/src/builtins/string.rs @@ -1,35 +1,35 @@ -use crate::builtins::{MethodMap, Parameter, Signature, add, expected}; +use crate::builtins::{FunctionMap, Parameter, Signature, add, expected}; use crate::errors::RuntimeError; use crate::tokens::TokenType::{StringType, U64}; -use crate::value::{Value, bool, i64, string}; +use crate::value::{Value, bool, i64, string, u64}; use regex::Regex; use std::collections::HashMap; -pub(crate) fn string_methods() -> MethodMap { - let mut string_methods: MethodMap = HashMap::new(); - let m = &mut string_methods; - add(m, "len", Signature::new(vec![], U64, string_len)); +pub(crate) fn string_functions() -> FunctionMap { + let mut string_functions: FunctionMap = HashMap::new(); + let functions = &mut string_functions; + add(functions, "len", Signature::new(vec![], U64, string_len)); add( - m, + functions, "to_uppercase", Signature::new(vec![], StringType, string_to_uppercase), ); add( - m, + functions, "to_lowercase", Signature::new(vec![], StringType, string_to_lowercase), ); - add(m, "contains", Signature::new(vec![], StringType, string_contains)); - add(m, "reverse", Signature::new(vec![], StringType, string_reverse)); - add(m, "trim", Signature::new(vec![], StringType, string_trim)); + add(functions, "contains", Signature::new(vec![Parameter::new("key", StringType)], StringType, string_contains)); + add(functions, "reverse", Signature::new(vec![], StringType, string_reverse)); + add(functions, "trim", Signature::new(vec![], StringType, string_trim)); add( - m, + functions, "trim_start", Signature::new(vec![], StringType, string_trim_start), ); - add(m, "trim_end", Signature::new(vec![], StringType, string_trim_end)); + add(functions, "trim_end", Signature::new(vec![], StringType, string_trim_end)); add( - m, + functions, "replace_all", Signature::new( vec![ @@ -40,12 +40,12 @@ pub(crate) fn string_methods() -> MethodMap { string_replace_all, ), ); - string_methods + string_functions } fn string_len(self_val: Value, _args: Vec) -> Result { match self_val { - Value::String(s) => Ok(i64(s.len() as i64)), + Value::String(s) => Ok(u64(s.len() as u64)), _ => Err(expected_a_string()), } } @@ -98,7 +98,6 @@ fn string_trim_end(self_val: Value, _: Vec) -> Result Err(expected_a_string()), } } -//TODO check arity in compiler (generically) fn string_replace_all(receiver: Value, args: Vec) -> Result { let pattern = if let Value::String(s) = &args[0] { Regex::new(s).map_err(|_| RuntimeError::IllegalArgumentException("Invalid regex".into()))? diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index 878e632..e23c556 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -3,6 +3,7 @@ use crate::ast_compiler::{Expression, Function, Parameter, Statement}; use crate::builtins::lookup; use crate::chunk::Chunk; use crate::errors::CompilerError::{IncompatibleTypes, UndeclaredVariable}; +use crate::errors::RuntimeError::IllegalArgumentsException; use crate::errors::{CompilerError, CompilerErrorAtLine}; use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::tokens::TokenType; @@ -12,11 +13,12 @@ use crate::vm::{ OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CALL_BUILTIN, OP_CONSTANT, OP_DEF_LIST, OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_LIST_GET, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, - OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, + OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, }; use crate::{Registry, SymbolTable}; +use clap::arg; use std::collections::HashMap; -use std::mem; +use std::ops::Deref; pub fn compile( qualified_name: Option<&str>, @@ -216,7 +218,13 @@ impl Compiler { .add_constant(Value::String(method_name.to_string())) }); let signature = lookup(&receiver_type, method_name).map_err(|e| self.raise(e))?; - + if signature.parameters.len() != arguments.len() { + return Err(self.raise(CompilerError::IllegalArgumentsException( + format!("{}.{}", receiver_type, method_name), + signature.parameters.len(), + arguments.len(), + ))); + } self.get_arguments_in_order( namespace, symbols, @@ -279,22 +287,31 @@ impl Compiler { self.compile_expression(namespace, left, symbols, registry)?; self.compile_expression(namespace, right, symbols, registry)?; match operator.token_type { - TokenType::Plus => self.emit_byte(OP_ADD), - TokenType::Minus => self.emit_byte(OP_SUBTRACT), - TokenType::Star => self.emit_byte(OP_MULTIPLY), - TokenType::Slash => self.emit_byte(OP_DIVIDE), TokenType::BitAnd => self.emit_byte(OP_BITAND), - TokenType::Pipe => self.emit_byte(OP_BITOR), TokenType::BitXor => self.emit_byte(OP_BITXOR), - TokenType::GreaterGreater => self.emit_byte(OP_SHR), - TokenType::LessLess => self.emit_byte(OP_SHL), + TokenType::Equal => { + if let Expression::Variable { name, .. } = left.deref() { + let index = self.vars.get(name).unwrap(); + self.emit_bytes(OP_ASSIGN, *index as u16); + self.emit_byte(OP_POP); + } else { + return Err(self.raise(UndeclaredVariable("".to_string()))); + } + } TokenType::EqualEqual => self.emit_byte(OP_EQUAL), TokenType::Greater => self.emit_byte(OP_GREATER), TokenType::GreaterEqual => self.emit_byte(OP_GREATER_EQUAL), + TokenType::GreaterGreater => self.emit_byte(OP_SHR), TokenType::Less => self.emit_byte(OP_LESS), TokenType::LessEqual => self.emit_byte(OP_LESS_EQUAL), + TokenType::LessLess => self.emit_byte(OP_SHL), TokenType::LogicalAnd => self.emit_byte(OP_AND), TokenType::LogicalOr => self.emit_byte(OP_OR), + TokenType::Minus => self.emit_byte(OP_SUBTRACT), + TokenType::Pipe => self.emit_byte(OP_BITOR), + TokenType::Plus => self.emit_byte(OP_ADD), + TokenType::Slash => self.emit_byte(OP_DIVIDE), + TokenType::Star => self.emit_byte(OP_MULTIPLY), _ => unimplemented!("binary other than plus, minus, star, slash"), } } diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs index 81e3f07..a263fe9 100644 --- a/src/compiler_tests.rs +++ b/src/compiler_tests.rs @@ -1,8 +1,12 @@ #[cfg(test)] mod tests { + use crate::errors::CompilerError::IllegalArgumentsException; + use crate::errors::CompilerErrorAtLine; + use crate::errors::CrudLangError::{Compiler, Runtime}; use crate::value::{Value, string}; use crate::{compile, run}; use chrono::DateTime; + use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds}; #[test] fn literal_int() { @@ -165,14 +169,8 @@ p"#); assert!(result.is_ok()); let result = result.unwrap(); if let Value::Map(map) = result { - assert_eq!( - map.get(&string("name")).unwrap(), - &string("Dent") - ); - assert_eq!( - map.get(&string("age")).unwrap(), - &Value::I64(40) - ); + assert_eq!(map.get(&string("name")).unwrap(), &string("Dent")); + assert_eq!(map.get(&string("age")).unwrap(), &Value::I64(40)); } } @@ -183,10 +181,7 @@ m"#); let result = result.unwrap(); if let Value::Map(map) = result { - assert_eq!( - map.get(&string("name")).unwrap(), - &string("Dent") - ); + assert_eq!(map.get(&string("name")).unwrap(), &string("Dent")); } } @@ -267,7 +262,7 @@ date"#), #[test] fn string_len() { - assert_eq!(run(r#""abc".len()"#), Ok(Value::I64(3))); + assert_eq!(run(r#""abc".len()"#), Ok(Value::U64(3))); } #[test] @@ -275,6 +270,56 @@ date"#), assert_eq!(run(r#""Hello".replace_all("l","p")"#), Ok(string("Heppo"))); } + #[test] + fn string_replace_wrong_nr_of_args() { + assert_eq!( + run(r#""Hello".replace_all("l")"#), + Err(Compiler(CompilerErrorAtLine { + error: IllegalArgumentsException("string.replace_all".to_string(), 2, 1), + line: 1 + })) + ); + } + + #[test] + fn string_replace_wrong_type_of_args() { + assert_eq!( + run(r#""Hello".replace_all("l", 1)"#), + Err(Runtime(IllegalArgumentException("Illegal replacement. Expected a string but got 1".to_string()))) + ); + } + + #[test] + fn string_contains() { + assert_eq!(run(r#""Hello".contains("l")"#), Ok(Value::Bool(true))); + } + + #[test] + fn list_length(){ + assert_eq!(run(r#"[1,2,3].len()"#), Ok(Value::U64(3))); + } + + #[test] + fn list_push(){ + assert_eq!(run(r#"[1,2].push(3)"#), Ok(Value::List(vec![Value::I64(1), Value::I64(2), Value::I64(3)]))); + } + + #[test] + fn list_remove(){ + assert_eq!(run(r#"[1,2,3].remove(0)"#), Ok(Value::List(vec![Value::I64(2), Value::I64(3)]))); + } + + #[test] + fn list_remove_out_of_bounds(){ + assert_eq!(run(r#"[1,2,3].remove(4)"#), Err(Runtime(IndexOutOfBounds(4, 3)))); + } + + #[test] + fn reassign(){ + assert_eq!(run(r#"let a=1 +a=2"#), Ok(Value::Void)); + } + // #[test] // fn package() { // assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48))); diff --git a/src/errors.rs b/src/errors.rs index 87ea3d5..01d3a26 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -69,6 +69,8 @@ pub enum CompilerError { IllegalIndexArgument(TokenType), #[error("Illegal argument: '{0}' cannot be indexed")] IllegalTypeToIndex(String), + #[error("The number of of arguments for {0} is not correct. Should be {1}, got {2}")] + IllegalArgumentsException(String,usize,usize), } #[derive(Error, Debug, PartialEq)] @@ -87,6 +89,8 @@ pub enum RuntimeError { IllegalArgumentException(String), #[error("Expected {0}")] ExpectedType(String), + #[error("Index out of bounds: {0} > {1}")] + IndexOutOfBounds(usize, usize), } #[derive(Error, Debug, PartialEq)] diff --git a/src/main.rs b/src/main.rs index 92b97a5..428f3e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,7 +57,7 @@ async fn main() -> Result<(), CrudLangError> { ); if args.repl { - std::thread::spawn(move || tipi_lang::repl::start(swap.clone()).unwrap()); + let _ = std::thread::spawn(move || tipi_lang::repl::start(swap.clone())); } axum::serve(listener, app).await.map_err(map_underlying())?; diff --git a/src/repl.rs b/src/repl.rs index 37031ec..fd4bdb6 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -34,7 +34,13 @@ pub fn start(registry: Arc>>) -> Result<(), CrudL let tokens = scan(input)?; - let ast = ast_compiler::compile(None, tokens, &mut symbol_table)?; + let ast = match ast_compiler::compile(None, tokens, &mut symbol_table){ + Ok(ast) => ast, + Err(e) => { + println!("{}", e); + continue; + } + }; symbol_builder::build("", &ast, &mut symbol_table); match bytecode_compiler.compile(&ast, &symbol_table, &mut registry_copy, "") { diff --git a/src/tokens.rs b/src/tokens.rs index 2c11790..02e477e 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -19,6 +19,7 @@ impl Token { #[derive(Debug, PartialEq, Clone, Hash)] pub enum TokenType { + Any, Bang, BangEqual, BitAnd, @@ -93,6 +94,7 @@ pub enum TokenType { impl fmt::Display for TokenType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + TokenType::Any => write!(f, "any"), TokenType::StringType => write!(f, "string"), TokenType::DateTime => write!(f, "datetime"), TokenType::Char => write!(f, "char"), diff --git a/src/value.rs b/src/value.rs index 272f4e5..7b35143 100644 --- a/src/value.rs +++ b/src/value.rs @@ -40,6 +40,10 @@ pub(crate) fn i64(v: impl Into) -> Value { Value::I64(v.into()) } +pub(crate) fn u64(v: impl Into) -> Value { + Value::U64(v.into()) +} + pub(crate) fn bool(v: impl Into) -> Value { Value::Bool(v.into()) } @@ -419,6 +423,7 @@ impl PartialEq for Value { equal } // TODO objects + (Value::Void, Value::Void) => true, _ => false, //? } } diff --git a/src/vm.rs b/src/vm.rs index c359ce6..725fcfa 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -156,13 +156,7 @@ impl Vm { let index = self.read(chunk); let (var_type, name) = chunk.vars.get(index).unwrap(); let value = self.pop(); - let value = match var_type { - TokenType::U32 => value.cast_u32()?, - TokenType::U64 => value.cast_u64()?, - TokenType::F32 => value.cast_f32()?, - TokenType::I32 => value.cast_i32()?, - _ => value, - }; + let value = Self::number(var_type, value)?; self.local_vars.insert(name.to_string(), value); } OP_DEF_MAP => { @@ -178,8 +172,8 @@ impl Vm { OP_GET => { let var_index = self.read(chunk); let (_, name_index) = chunk.vars.get(var_index).unwrap(); - let value = self.local_vars.get(name_index).unwrap(); - self.push(value.clone()); // not happy , take ownership, no clone + let value = self.local_vars.remove(name_index).unwrap(); + self.push(value); } OP_LIST_GET => { let index = self.pop(); @@ -205,6 +199,9 @@ impl Vm { let return_value = crate::builtins::call(&receiver_type_name, &function_name, receiver, args)?; self.push(return_value); } + OP_POP =>{ + self.pop(); // discards the value + } OP_CALL => { let function_name_index = self.read(chunk); let num_args = self.read(chunk); @@ -259,6 +256,17 @@ impl Vm { } } + fn number(var_type: &TokenType, value: Value) -> Result { + let value = match var_type { + TokenType::U32 => value.cast_u32()?, + TokenType::U64 => value.cast_u64()?, + TokenType::F32 => value.cast_f32()?, + TokenType::I32 => value.cast_i32()?, + _ => value, + }; + Ok(value) + } + fn read(&mut self, chunk: &Chunk) -> usize { self.ip += 1; chunk.code[self.ip - 1] as usize