WIP if expression. Moved print from statement to builtin

This commit is contained in:
Shautvast 2025-11-23 20:46:02 +01:00
parent f858ccf6a4
commit 408d772247
6 changed files with 127 additions and 87 deletions

View file

@ -202,6 +202,22 @@ fn add(a:i64, b:i64) -> i64:
let sum = add(1,2) 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** **An actual controller**
``` ```
fn get() -> string: fn get() -> string:
@ -215,6 +231,3 @@ fn add(a: string, b: string) -> string:
ISSUES ISSUES
* Make everything an expression. If is a statement and so it can not be type checked * Make everything an expression. If is a statement and so it can not be type checked
* improve indenting * 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

View file

@ -1,5 +1,5 @@
use crate::builtins::{FunctionMap, Signature, add}; 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::errors::RuntimeError;
use crate::value::Value; use crate::value::Value;
use std::collections::HashMap; use std::collections::HashMap;
@ -9,10 +9,25 @@ pub(crate) static GLOBAL_FUNCTIONS: LazyLock<FunctionMap> = LazyLock::new(|| {
let mut global_functions: FunctionMap = HashMap::new(); let mut global_functions: FunctionMap = HashMap::new();
let functions = &mut global_functions; let functions = &mut global_functions;
add(functions, "now", Signature::new(vec![], DateTime, now)); 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 global_functions
}); });
fn println(_self_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
print(_self_val, args)?;
println!();
Ok(Value::Void)
}
fn print(_self_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
for arg in args {
print!("{}", arg);
}
Ok(Value::Void)
}
fn now(_self_val: Value, _args: Vec<Value>) -> Result<Value, RuntimeError> { fn now(_self_val: Value, _args: Vec<Value>) -> Result<Value, RuntimeError> {
Ok(Value::DateTime(Box::new(chrono::DateTime::from( Ok(Value::DateTime(Box::new(chrono::DateTime::from(
chrono::Utc::now(), chrono::Utc::now(),

View file

@ -5,7 +5,7 @@ use crate::compiler::assembly_pass::Op::{
Dup, Equal, Get, Goto, GotoIf, GotoIfNot, Greater, GreaterEqual, Less, LessEqual, ListGet, Dup, Equal, Get, Goto, GotoIf, GotoIfNot, Greater, GreaterEqual, Less, LessEqual, ListGet,
Multiply, Negate, Not, NotEqual, Or, Pop, Print, Return, Shr, Subtract, 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::ast_pass::{Expression, Function, Parameter, Statement};
use crate::compiler::tokens::TokenType; use crate::compiler::tokens::TokenType;
use crate::compiler::tokens::TokenType::Unknown; use crate::compiler::tokens::TokenType::Unknown;
@ -213,31 +213,6 @@ impl AsmPass {
Statement::GuardStatement { .. } => { Statement::GuardStatement { .. } => {
unimplemented!("guard statement") 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 { Statement::ForStatement {
loop_var, loop_var,
range, range,
@ -282,6 +257,31 @@ impl AsmPass {
registry: &mut AsmRegistry, registry: &mut AsmRegistry,
) -> Result<(), CompilerErrorAtLine> { ) -> Result<(), CompilerErrorAtLine> {
match expression { 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 { Expression::FunctionCall {
name, arguments, .. name, arguments, ..
} => { } => {

View file

@ -1,5 +1,7 @@
use crate::builtins::globals::GLOBAL_FUNCTIONS;
use crate::compiler::ast_pass::Expression::{ 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::{ use crate::compiler::tokens::TokenType::{
Bang, Bool, Char, Colon, DateTime, Dot, Else, Eof, Eol, Equal, False, FloatingPoint, Fn, For, 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 crate::{DATE_FORMAT_TIMEZONE, Expr, Stmt, SymbolTable};
use log::debug; use log::debug;
use std::collections::HashMap; use std::collections::HashMap;
use crate::builtins::globals::GLOBAL_FUNCTIONS;
pub fn compile( pub fn compile(
path: Option<&str>, path: Option<&str>,
@ -237,7 +238,9 @@ impl AstCompiler {
fn function_declaration(&mut self, symbol_table: &mut SymbolTable) -> Stmt { fn function_declaration(&mut self, symbol_table: &mut SymbolTable) -> Stmt {
let name_token = self.consume(&Identifier, Expected("function name."))?; let name_token = self.consume(&Identifier, Expected("function name."))?;
if GLOBAL_FUNCTIONS.contains_key(name_token.lexeme.as_str()) { 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."))?; self.consume(&LeftParen, Expected("'(' after function name."))?;
let mut parameters = vec![]; let mut parameters = vec![];
@ -325,11 +328,7 @@ impl AstCompiler {
} }
fn statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt { fn statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt {
if self.match_token(&[Print]) { if self.match_token(&[For]) {
self.print_statement(symbol_table)
} else if self.match_token(&[If]) {
self.if_statement(symbol_table)
} else if self.match_token(&[For]) {
self.for_statement(symbol_table) self.for_statement(symbol_table)
} else { } else {
self.expr_statement(symbol_table) 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) { fn inc_indent(&mut self) {
self.indent.push(self.indent.last().unwrap() + 1); 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 { fn expr_statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt {
let expr = self.expression(symbol_table)?; let expr = self.expression(symbol_table)?;
if !self.is_at_end() { if !self.is_at_end() {
@ -516,6 +486,33 @@ impl AstCompiler {
operator, operator,
right: Box::new(right), 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 { } else {
self.get(symbol_table) self.get(symbol_table)
} }
@ -865,11 +862,6 @@ pub enum Statement {
if_expr: Expression, if_expr: Expression,
then_expr: Expression, then_expr: Expression,
}, },
IfStatement {
condition: Expression,
then_branch: Vec<Statement>,
else_branch: Option<Vec<Statement>>,
},
ForStatement { ForStatement {
loop_var: Token, loop_var: Token,
range: Expression, range: Expression,
@ -886,7 +878,6 @@ impl Statement {
Statement::FunctionStmt { function, .. } => function.name.line, Statement::FunctionStmt { function, .. } => function.name.line,
Statement::ObjectStmt { name, .. } => name.line, Statement::ObjectStmt { name, .. } => name.line,
Statement::GuardStatement { if_expr, .. } => if_expr.line(), Statement::GuardStatement { if_expr, .. } => if_expr.line(),
Statement::IfStatement { condition, .. } => condition.line(),
Statement::ForStatement { loop_var, .. } => loop_var.line, Statement::ForStatement { loop_var, .. } => loop_var.line,
} }
} }
@ -989,6 +980,11 @@ pub enum Expression {
receiver: Box<Expression>, receiver: Box<Expression>,
field: String, field: String,
}, },
IfExpression {
condition: Box<Expression>,
then_branch: Vec<Statement>,
else_branch: Option<Vec<Statement>>,
},
} }
impl Expression { impl Expression {
@ -1010,6 +1006,7 @@ impl Expression {
MapGet { .. } => 0, MapGet { .. } => 0,
ListGet { .. } => 0, ListGet { .. } => 0,
FieldGet { .. } => 0, FieldGet { .. } => 0,
IfExpression { condition, .. } => condition.line(),
} }
} }
} }

View file

@ -1,5 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::DATE_FORMAT_TIMEZONE;
use crate::compiler::{compile, run}; use crate::compiler::{compile, run};
use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName}; use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName};
use crate::errors::CompilerErrorAtLine; use crate::errors::CompilerErrorAtLine;
@ -7,7 +8,6 @@ mod tests {
use crate::errors::TipiLangError::{Compiler, Runtime}; use crate::errors::TipiLangError::{Compiler, Runtime};
use crate::value::{Value, string}; use crate::value::{Value, string};
use chrono::DateTime; use chrono::DateTime;
use crate::DATE_FORMAT_TIMEZONE;
#[test] #[test]
fn literal_int() { fn literal_int() {
@ -241,9 +241,7 @@ m["name"]"#);
run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100" run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100"
date"#), date"#),
Ok(Value::DateTime(Box::new( Ok(Value::DateTime(Box::new(
DateTime::parse_from_str( DateTime::parse_from_str("2025-11-09 16:44:28.000 +0100", DATE_FORMAT_TIMEZONE)
"2025-11-09 16:44:28.000 +0100", DATE_FORMAT_TIMEZONE
)
.unwrap() .unwrap()
.into() .into()
))) )))
@ -420,9 +418,24 @@ sum
#[test] #[test]
fn global_fns_are_not_allowed() { fn global_fns_are_not_allowed() {
let value = run(r#"fn now():"#); 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] // #[test]
// fn package() { // fn package() {
// assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48))); // assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48)));

View file

@ -79,6 +79,7 @@ pub fn calculate_type(
Ok(if declared_type != &Unknown { Ok(if declared_type != &Unknown {
if declared_type != inferred_type { if declared_type != inferred_type {
match (declared_type, inferred_type) { match (declared_type, inferred_type) {
(I64, Unknown) => I64,
(I32, I64) => I32, //need this? (I32, I64) => I32, //need this?
(I32, Integer) => I32, (I32, Integer) => I32,
(U32, I64) => U32, (U32, I64) => U32,
@ -210,12 +211,13 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap<String, Symbol>) -> Token
infer_type(receiver, symbols) infer_type(receiver, symbols)
} }
} }
Expression::Stop { .. } => TokenType::Unknown, Expression::Stop { .. } => Unknown,
// Expression::PathMatch { .. } => TokenType::Unknown, // Expression::PathMatch { .. } => Unknown,
Expression::NamedParameter { .. } => TokenType::Unknown, Expression::NamedParameter { .. } => Unknown,
Expression::ListGet { .. } => TokenType::Unknown, Expression::ListGet { .. } => Unknown,
Expression::MapGet { .. } => TokenType::Unknown, Expression::MapGet { .. } => Unknown,
Expression::FieldGet { .. } => TokenType::Unknown, Expression::FieldGet { .. } => Unknown,
Expression::Range { lower, .. } => infer_type(lower, symbols), Expression::Range { lower, .. } => infer_type(lower, symbols),
Expression::IfExpression { .. } => Unknown,
} }
} }