From 408d7722479fde0cd099bb37c44e8bca37f278a9 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sun, 23 Nov 2025 20:46:02 +0100 Subject: [PATCH] WIP if expression. Moved print from statement to builtin --- README.md | 21 +++++++-- src/builtins/globals.rs | 17 ++++++- src/compiler/assembly_pass.rs | 52 ++++++++++----------- src/compiler/ast_pass.rs | 83 ++++++++++++++++------------------ src/compiler/compiler_tests.rs | 27 ++++++++--- src/symbol_builder.rs | 14 +++--- 6 files changed, 127 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 58db44a..0b46a6a 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,22 @@ fn add(a:i64, b:i64) -> i64: let sum = add(1,2) ``` +**for loops** +``` +for i in [1,2,3]: + print(i) +``` + +**if / else expressions ** +``` +let a = if true: + 42 +else: + 0 +print(a) +``` +=>42 + **An actual controller** ``` fn get() -> string: @@ -214,7 +230,4 @@ fn add(a: string, b: string) -> string: ISSUES * Make everything an expression. If is a statement and so it can not be type checked -* improve indenting -* add an extra pass that creates a more IR-like representation (enum instead of bytes) - * easier debugging - * chunk debug to stdout can be removed \ No newline at end of file +* improve indenting \ No newline at end of file diff --git a/src/builtins/globals.rs b/src/builtins/globals.rs index f135dda..5b3017d 100644 --- a/src/builtins/globals.rs +++ b/src/builtins/globals.rs @@ -1,5 +1,5 @@ use crate::builtins::{FunctionMap, Signature, add}; -use crate::compiler::tokens::TokenType::DateTime; +use crate::compiler::tokens::TokenType::{DateTime, Void}; use crate::errors::RuntimeError; use crate::value::Value; use std::collections::HashMap; @@ -9,10 +9,25 @@ pub(crate) static GLOBAL_FUNCTIONS: LazyLock = LazyLock::new(|| { let mut global_functions: FunctionMap = HashMap::new(); let functions = &mut global_functions; add(functions, "now", Signature::new(vec![], DateTime, now)); + add(functions, "print", Signature::new(vec![], Void, print)); + add(functions, "println", Signature::new(vec![], Void, println)); global_functions }); +fn println(_self_val: Value, args: Vec) -> Result { + print(_self_val, args)?; + println!(); + Ok(Value::Void) +} + +fn print(_self_val: Value, args: Vec) -> Result { + for arg in args { + print!("{}", arg); + } + Ok(Value::Void) +} + fn now(_self_val: Value, _args: Vec) -> Result { Ok(Value::DateTime(Box::new(chrono::DateTime::from( chrono::Utc::now(), diff --git a/src/compiler/assembly_pass.rs b/src/compiler/assembly_pass.rs index 56c21a0..fe4d9e8 100644 --- a/src/compiler/assembly_pass.rs +++ b/src/compiler/assembly_pass.rs @@ -5,7 +5,7 @@ use crate::compiler::assembly_pass::Op::{ Dup, Equal, Get, Goto, GotoIf, GotoIfNot, Greater, GreaterEqual, Less, LessEqual, ListGet, Multiply, Negate, Not, NotEqual, Or, Pop, Print, Return, Shr, Subtract, }; -use crate::compiler::ast_pass::Expression::NamedParameter; +use crate::compiler::ast_pass::Expression::{IfExpression, NamedParameter}; use crate::compiler::ast_pass::{Expression, Function, Parameter, Statement}; use crate::compiler::tokens::TokenType; use crate::compiler::tokens::TokenType::Unknown; @@ -213,31 +213,6 @@ impl AsmPass { Statement::GuardStatement { .. } => { unimplemented!("guard statement") } - Statement::IfStatement { - condition, - then_branch, - else_branch, - } => { - self.compile_expression(namespace, condition, symbols, registry)?; - - self.emit(Dup); - self.emit(GotoIfNot(0)); // placeholder - let goto_addr1 = self.chunk.code.len() - 1; - self.emit(Pop); - self.compile_statements(then_branch, symbols, registry, namespace)?; - self.emit(Goto(0)); - let goto_addr2 = self.chunk.code.len() - 1; // placeholder - self.chunk.code[goto_addr1] = GotoIfNot(self.chunk.code.len()); - if else_branch.is_some() { - self.compile_statements( - else_branch.as_ref().unwrap(), - symbols, - registry, - namespace, - )?; - } - self.chunk.code[goto_addr2] = Op::Goto(self.chunk.code.len()); - } Statement::ForStatement { loop_var, range, @@ -282,6 +257,31 @@ impl AsmPass { registry: &mut AsmRegistry, ) -> Result<(), CompilerErrorAtLine> { match expression { + IfExpression { + condition, + then_branch, + else_branch, + } => { + self.compile_expression(namespace, condition, symbols, registry)?; + + self.emit(Dup); + self.emit(GotoIfNot(0)); // placeholder + let goto_addr1 = self.chunk.code.len() - 1; + self.emit(Pop); + self.compile_statements(then_branch, symbols, registry, namespace)?; + self.emit(Goto(0)); + let goto_addr2 = self.chunk.code.len() - 1; // placeholder + self.chunk.code[goto_addr1] = GotoIfNot(self.chunk.code.len()); + if else_branch.is_some() { + self.compile_statements( + else_branch.as_ref().unwrap(), + symbols, + registry, + namespace, + )?; + } + self.chunk.code[goto_addr2] = Op::Goto(self.chunk.code.len()); + } Expression::FunctionCall { name, arguments, .. } => { diff --git a/src/compiler/ast_pass.rs b/src/compiler/ast_pass.rs index c51fb47..588b00c 100644 --- a/src/compiler/ast_pass.rs +++ b/src/compiler/ast_pass.rs @@ -1,5 +1,7 @@ +use crate::builtins::globals::GLOBAL_FUNCTIONS; use crate::compiler::ast_pass::Expression::{ - Assignment, FieldGet, FunctionCall, ListGet, MapGet, MethodCall, NamedParameter, Stop, Variable, + Assignment, FieldGet, FunctionCall, IfExpression, ListGet, MapGet, MethodCall, NamedParameter, + Stop, Variable, }; use crate::compiler::tokens::TokenType::{ Bang, Bool, Char, Colon, DateTime, Dot, Else, Eof, Eol, Equal, False, FloatingPoint, Fn, For, @@ -18,7 +20,6 @@ use crate::value::Value; use crate::{DATE_FORMAT_TIMEZONE, Expr, Stmt, SymbolTable}; use log::debug; use std::collections::HashMap; -use crate::builtins::globals::GLOBAL_FUNCTIONS; pub fn compile( path: Option<&str>, @@ -237,7 +238,9 @@ impl AstCompiler { fn function_declaration(&mut self, symbol_table: &mut SymbolTable) -> Stmt { let name_token = self.consume(&Identifier, Expected("function name."))?; if GLOBAL_FUNCTIONS.contains_key(name_token.lexeme.as_str()) { - return Err(self.raise(CompilerError::ReservedFunctionName(name_token.lexeme.clone()))) + return Err(self.raise(CompilerError::ReservedFunctionName( + name_token.lexeme.clone(), + ))); } self.consume(&LeftParen, Expected("'(' after function name."))?; let mut parameters = vec![]; @@ -325,11 +328,7 @@ 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 if self.match_token(&[For]) { + if self.match_token(&[For]) { self.for_statement(symbol_table) } else { self.expr_statement(symbol_table) @@ -352,39 +351,10 @@ impl AstCompiler { }) } - 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."))?; - Ok(Statement::PrintStmt { value: expr }) - } - fn expr_statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt { let expr = self.expression(symbol_table)?; if !self.is_at_end() { @@ -516,6 +486,33 @@ impl AstCompiler { operator, right: Box::new(right), }) + } else { + self.equals(symbol_table) + } + } + + fn equals(&mut self, symbol_table: &mut SymbolTable) -> Expr { + if self.match_token(&[If]) { + 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(IfExpression { + condition: Box::new(condition), + then_branch, + else_branch, + }) } else { self.get(symbol_table) } @@ -865,11 +862,6 @@ pub enum Statement { if_expr: Expression, then_expr: Expression, }, - IfStatement { - condition: Expression, - then_branch: Vec, - else_branch: Option>, - }, ForStatement { loop_var: Token, range: Expression, @@ -886,7 +878,6 @@ 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(), Statement::ForStatement { loop_var, .. } => loop_var.line, } } @@ -989,6 +980,11 @@ pub enum Expression { receiver: Box, field: String, }, + IfExpression { + condition: Box, + then_branch: Vec, + else_branch: Option>, + }, } impl Expression { @@ -1010,6 +1006,7 @@ impl Expression { MapGet { .. } => 0, ListGet { .. } => 0, FieldGet { .. } => 0, + IfExpression { condition, .. } => condition.line(), } } } diff --git a/src/compiler/compiler_tests.rs b/src/compiler/compiler_tests.rs index db5f208..394ded1 100644 --- a/src/compiler/compiler_tests.rs +++ b/src/compiler/compiler_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use crate::DATE_FORMAT_TIMEZONE; use crate::compiler::{compile, run}; use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName}; use crate::errors::CompilerErrorAtLine; @@ -7,7 +8,6 @@ mod tests { use crate::errors::TipiLangError::{Compiler, Runtime}; use crate::value::{Value, string}; use chrono::DateTime; - use crate::DATE_FORMAT_TIMEZONE; #[test] fn literal_int() { @@ -241,11 +241,9 @@ m["name"]"#); run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100" date"#), Ok(Value::DateTime(Box::new( - DateTime::parse_from_str( - "2025-11-09 16:44:28.000 +0100", DATE_FORMAT_TIMEZONE - ) - .unwrap() - .into() + DateTime::parse_from_str("2025-11-09 16:44:28.000 +0100", DATE_FORMAT_TIMEZONE) + .unwrap() + .into() ))) ); } @@ -420,9 +418,24 @@ sum #[test] fn global_fns_are_not_allowed() { let value = run(r#"fn now():"#); - assert_eq!(value, Err(Compiler(CompilerErrorAtLine { error: ReservedFunctionName("now".to_string()), line: 1 }))); + assert_eq!( + value, + Err(Compiler(CompilerErrorAtLine { + error: ReservedFunctionName("now".to_string()), + line: 1 + })) + ); } + #[test] + fn test() { + run(r#" +let a:i64 = if true: + 42 +else: + 0 +a"#).unwrap(); + } // #[test] // fn package() { // assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48))); diff --git a/src/symbol_builder.rs b/src/symbol_builder.rs index 3bcf4a4..6e66043 100644 --- a/src/symbol_builder.rs +++ b/src/symbol_builder.rs @@ -79,6 +79,7 @@ pub fn calculate_type( Ok(if declared_type != &Unknown { if declared_type != inferred_type { match (declared_type, inferred_type) { + (I64, Unknown) => I64, (I32, I64) => I32, //need this? (I32, Integer) => I32, (U32, I64) => U32, @@ -210,12 +211,13 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap) -> Token infer_type(receiver, symbols) } } - Expression::Stop { .. } => TokenType::Unknown, - // Expression::PathMatch { .. } => TokenType::Unknown, - Expression::NamedParameter { .. } => TokenType::Unknown, - Expression::ListGet { .. } => TokenType::Unknown, - Expression::MapGet { .. } => TokenType::Unknown, - Expression::FieldGet { .. } => TokenType::Unknown, + Expression::Stop { .. } => Unknown, + // Expression::PathMatch { .. } => Unknown, + Expression::NamedParameter { .. } => Unknown, + Expression::ListGet { .. } => Unknown, + Expression::MapGet { .. } => Unknown, + Expression::FieldGet { .. } => Unknown, Expression::Range { lower, .. } => infer_type(lower, symbols), + Expression::IfExpression { .. } => Unknown, } }