From 1a8f4660b2c28e46d3f65a55810d04da9cfd2d6f Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sat, 15 Nov 2025 12:48:23 +0100 Subject: [PATCH] a very simple if expression --- src/ast_compiler.rs | 47 +++++++++++++++++++++++++---------- src/builtins/string.rs | 2 +- src/bytecode_compiler.rs | 41 ++++++++++++++++++++++--------- src/compiler_tests.rs | 53 +++++++++++++++++++++++++++++++--------- src/repl.rs | 2 +- src/symbol_builder.rs | 18 +++++++++++++- src/vm.rs | 13 +++++++++- 7 files changed, 135 insertions(+), 41 deletions(-) diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index 28440e8..2c2b03c 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -6,12 +6,7 @@ use crate::errors::CompilerError::{ }; use crate::errors::CompilerErrorAtLine; use crate::symbol_builder::{Symbol, calculate_type, infer_type}; -use crate::tokens::TokenType::{ - Bang, Bool, Char, Colon, DateTime, Dot, 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, SingleRightArrow, Slash, Star, StringType, True, U32, U64, Unknown, -}; +use crate::tokens::TokenType::{Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, False, FloatingPoint, Fn, Greater, GreaterEqual, GreaterGreater, Identifier, If, Indent, Integer, LeftBrace, LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace, RightBracket, RightParen, SingleRightArrow, Slash, Star, StringType, True, U32, U64, Unknown, Else}; use crate::tokens::{Token, TokenType}; use crate::value::Value; use crate::{Expr, Stmt, SymbolTable}; @@ -261,8 +256,7 @@ impl AstCompiler { 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); + self.inc_indent(); let body = self.compile(symbol_table)?; @@ -320,11 +314,36 @@ impl AstCompiler { fn statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt { if self.match_token(&[Print]) { self.print_statement(symbol_table) + } else if self.match_token(&[If]) { + self.if_statement(symbol_table) } else { self.expr_statement(symbol_table) } } + fn if_statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt { + let condition = self.expression(symbol_table)?; + self.consume(&Colon, Expected("':' after if condition."))?; + + self.inc_indent(); + let then_branch = self.compile(symbol_table)?; + + let else_branch = if self.check(&Else) { + self.consume(&Else, Expected("'else' after if condition."))?; + self.consume(&Colon, Expected("':' after 'else'."))?; + + self.inc_indent(); + Some(self.compile(symbol_table)?) + } else { + None + }; + Ok(Statement::IfStatement {condition, then_branch, else_branch}) + } + + fn inc_indent(&mut self) { + self.indent.push(self.indent.last().unwrap() + 1); + } + fn print_statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt { let expr = self.expression(symbol_table)?; self.consume(&Eol, Expected("end of line after print statement."))?; @@ -370,11 +389,7 @@ impl AstCompiler { fn assignment(&mut self, symbol_table: &mut SymbolTable) -> Expr { let expr = self.equality(symbol_table)?; - self.binary( - &[TokenType::Equal], - expr, - symbol_table, - ) + self.binary(&[TokenType::Equal], expr, symbol_table) } fn equality(&mut self, symbol_table: &mut SymbolTable) -> Expr { @@ -790,6 +805,11 @@ pub enum Statement { if_expr: Expression, then_expr: Expression, }, + IfStatement{ + condition: Expression, + then_branch: Vec, + else_branch: Option> + } } impl Statement { @@ -801,6 +821,7 @@ impl Statement { Statement::FunctionStmt { function, .. } => function.name.line, Statement::ObjectStmt { name, .. } => name.line, Statement::GuardStatement { if_expr, .. } => if_expr.line(), + Statement::IfStatement { condition, .. } => condition.line(), } } } diff --git a/src/builtins/string.rs b/src/builtins/string.rs index 1755e36..394df81 100644 --- a/src/builtins/string.rs +++ b/src/builtins/string.rs @@ -1,7 +1,7 @@ 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, u64}; +use crate::value::{Value, bool, string, u64}; use regex::Regex; use std::collections::HashMap; diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index e23c556..c0988c2 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -3,20 +3,18 @@ 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; use crate::tokens::TokenType::Unknown; -use crate::value::Value; +use crate::value::{Value}; 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_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, + OP_GREATER_EQUAL, OP_IF, OP_IF_ELSE, OP_LESS, OP_LESS_EQUAL, OP_LIST_GET, OP_MULTIPLY, + OP_NEGATE, OP_NOT, OP_OR, 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::ops::Deref; @@ -57,12 +55,7 @@ pub(crate) fn compile_in_namespace( let name = namespace.unwrap_or("main"); let mut compiler = Compiler::new(name); let chunk = compiler.compile(ast, symbols, registry, name)?; - let qname = if let Some(namespace) = namespace { - format!("{}/{}", namespace, "main") - } else { - "main".to_string() - }; - registry.insert(qname, chunk); + registry.insert(name.to_string(), chunk); Ok(()) } @@ -157,6 +150,30 @@ impl Compiler { Statement::GuardStatement { .. } => { unimplemented!("guard statement") } + Statement::IfStatement { + condition, + then_branch, + else_branch, + } => { + self.compile_expression(namespace, condition, symbols, registry)?; + compile( + Some(&format!("{}.?",namespace)), + then_branch, + symbols, + registry, + )?; + if else_branch.is_none() { + self.emit_byte(OP_IF); + } else { + self.emit_byte(OP_IF_ELSE); + compile( + Some(format!("{}.:", namespace).as_str()), + else_branch.as_ref().unwrap(), + symbols, + registry, + )?; + } + } } Ok(()) } @@ -218,7 +235,7 @@ 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() { + if signature.arity() != arguments.len() { return Err(self.raise(CompilerError::IllegalArgumentsException( format!("{}.{}", receiver_type, method_name), signature.parameters.len(), diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs index a263fe9..d43697a 100644 --- a/src/compiler_tests.rs +++ b/src/compiler_tests.rs @@ -3,10 +3,10 @@ mod tests { use crate::errors::CompilerError::IllegalArgumentsException; use crate::errors::CompilerErrorAtLine; use crate::errors::CrudLangError::{Compiler, Runtime}; + use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds}; use crate::value::{Value, string}; use crate::{compile, run}; use chrono::DateTime; - use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds}; #[test] fn literal_int() { @@ -285,7 +285,9 @@ date"#), 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()))) + Err(Runtime(IllegalArgumentException( + "Illegal replacement. Expected a string but got 1".to_string() + ))) ); } @@ -295,29 +297,56 @@ date"#), } #[test] - fn list_length(){ + 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)]))); + 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)]))); + 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)))); + 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)); + fn reassign() { + assert_eq!( + run(r#"let a=1 +a=2"#), + Ok(Value::Void) + ); + } + + #[test] + fn if_expr() { + assert_eq!( + run(r#" +if true: + 2 +"#), + Ok(Value::I64(2)) + ); } // #[test] diff --git a/src/repl.rs b/src/repl.rs index fd4bdb6..f5c06e1 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,7 +1,7 @@ use crate::chunk::Chunk; use crate::errors::CrudLangError; use crate::scanner::scan; -use crate::vm::{Vm, interpret}; +use crate::vm::Vm; use crate::{ast_compiler, bytecode_compiler, map_underlying, recompile, symbol_builder}; use arc_swap::ArcSwap; use std::collections::HashMap; diff --git a/src/symbol_builder.rs b/src/symbol_builder.rs index 7d9d617..786a073 100644 --- a/src/symbol_builder.rs +++ b/src/symbol_builder.rs @@ -1,4 +1,5 @@ use crate::ast_compiler::{Expression, Parameter, Statement}; +use crate::builtins::{Signature, lookup}; use crate::errors::CompilerError; use crate::errors::CompilerError::IncompatibleTypes; use crate::tokens::TokenType::{ @@ -9,6 +10,7 @@ use crate::tokens::TokenType::{ use crate::tokens::{Token, TokenType}; use log::debug; use std::collections::HashMap; +use std::ops::Deref; pub enum Symbol { Function { @@ -192,7 +194,21 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap) -> Token _ => Unknown, } } - Expression::MethodCall { receiver, .. } => infer_type(receiver, symbols), + Expression::MethodCall { + receiver, + method_name, + .. + } => { + if let Expression::Literal { value, .. } = receiver.deref() { + if let Ok(signature) = lookup(&value.to_string(), method_name) { + signature.return_type.clone() + } else { + unreachable!()//? + } + } else { + infer_type(receiver, symbols) + } + } Expression::Stop { .. } => TokenType::Unknown, // Expression::PathMatch { .. } => TokenType::Unknown, Expression::NamedParameter { .. } => TokenType::Unknown, diff --git a/src/vm.rs b/src/vm.rs index 725fcfa..f7eb18a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -251,6 +251,15 @@ impl Vm { self.push(result); } } + OP_IF => { + let condition = self.pop(); + if condition == Value::Bool(true) { + if let Some(then) = self.registry.get(&format!("{}.?",chunk.name)){ + let result = interpret_function(then, vec![])?; + self.push(result); + } + } + } _ => {} } } @@ -284,7 +293,7 @@ impl Vm { fn pop(&mut self) -> Value { self.stack .pop() - .unwrap_or_else(|| Value::Error("Error occurred".to_string())) + .unwrap_or_else(|| Value::Error("Stack underflow".to_string())) } } @@ -362,3 +371,5 @@ pub const OP_DEF_F64: u16 = 40; pub const OP_ASSIGN: u16 = 41; pub const OP_LIST_GET: u16 = 42; pub const OP_CALL_BUILTIN: u16 = 43; +pub const OP_IF: u16 = 44; +pub const OP_IF_ELSE: u16 = 45;