From dd433e174674300c5f89d6ef1820110028cba933 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 25 Nov 2025 00:26:17 -0700 Subject: [PATCH 1/5] Basic support for logical expressions --- libs/compiler/src/test/logic_expression.rs | 119 ++++++++++++ libs/compiler/src/test/mod.rs | 1 + libs/compiler/src/v1.rs | 109 ++++++++++- libs/parser/src/lib.rs | 203 ++++++++++++++++----- libs/parser/src/tree_node.rs | 2 + 5 files changed, 392 insertions(+), 42 deletions(-) create mode 100644 libs/compiler/src/test/logic_expression.rs diff --git a/libs/compiler/src/test/logic_expression.rs b/libs/compiler/src/test/logic_expression.rs new file mode 100644 index 0000000..e4a2ead --- /dev/null +++ b/libs/compiler/src/test/logic_expression.rs @@ -0,0 +1,119 @@ +use crate::compile; +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn test_comparison_expressions() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let isGreater = 10 > 5; + let isLess = 5 < 10; + let isEqual = 5 == 5; + let isNotEqual = 5 != 10; + let isGreaterOrEqual = 10 >= 10; + let isLessOrEqual = 5 <= 5; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + sgt r1 10 5 + move r8 r1 #isGreater + slt r2 5 10 + move r9 r2 #isLess + seq r3 5 5 + move r10 r3 #isEqual + sne r4 5 10 + move r11 r4 #isNotEqual + sge r5 10 10 + move r12 r5 #isGreaterOrEqual + sle r6 5 5 + move r13 r6 #isLessOrEqual + " + } + ); + + Ok(()) +} + +#[test] +fn test_logical_and_or_not() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let logic1 = 1 && 1; + let logic2 = 1 || 0; + let logic3 = !1; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + and r1 1 1 + move r8 r1 #logic1 + or r2 1 0 + move r9 r2 #logic2 + seq r3 1 0 + move r10 r3 #logic3 + " + } + ); + + Ok(()) +} + +#[test] +fn test_complex_logic() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let logic = (10 > 5) && (5 < 10); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + sgt r1 10 5 + slt r2 5 10 + and r3 r1 r2 + move r8 r3 #logic + " + } + ); + + Ok(()) +} + +#[test] +fn test_math_with_logic() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let logic = (1 + 2) > 1; + " + }; + + assert_eq!( + compiled, + indoc! { + " + + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index 09fa1aa..05e91a0 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -44,3 +44,4 @@ mod binary_expression; mod declaration_function_invocation; mod declaration_literal; mod function_declaration; +mod logic_expression; diff --git a/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs index 8e0fd28..b7fc5d1 100644 --- a/libs/compiler/src/v1.rs +++ b/libs/compiler/src/v1.rs @@ -3,7 +3,7 @@ use parser::{ Parser as ASTParser, tree_node::{ BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, - FunctionExpression, InvocationExpression, Literal, + FunctionExpression, InvocationExpression, Literal, LogicalExpression, }, }; use quick_error::quick_error; @@ -165,6 +165,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let result = self.expression_binary(bin_expr, scope)?; Ok(Some(result)) } + Expression::Logical(log_expr) => { + let result = self.expression_logical(log_expr, scope)?; + Ok(Some(result)) + } Expression::Literal(Literal::Number(num)) => { let temp_name = self.next_temp_name(); let loc = scope.add_variable(&temp_name, LocationRequest::Temp)?; @@ -280,6 +284,20 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } var_loc } + Expression::Logical(log_expr) => { + let result = self.expression_logical(log_expr, scope)?; + let var_loc = scope.add_variable(&var_name, LocationRequest::Persist)?; + + // Move result from temp to new persistent variable + let result_reg = self.resolve_register(&result.location)?; + self.emit_variable_assignment(&var_name, &var_loc, result_reg)?; + + // Free the temp result + if let Some(name) = result.temp_name { + scope.free_temp(name)?; + } + var_loc + } Expression::Variable(name) => { let src_loc = scope.get_location_of(&name)?; let var_loc = scope.add_variable(&var_name, LocationRequest::Persist)?; @@ -374,6 +392,15 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { stack.free_temp(name)?; } } + Expression::Logical(log_expr) => { + // Compile the logical expression to a temp register + let result = self.expression_logical(log_expr, stack)?; + let reg_str = self.resolve_register(&result.location)?; + self.write_output(format!("push {reg_str}"))?; + if let Some(name) = result.temp_name { + stack.free_temp(name)?; + } + } _ => { return Err(Error::Unknown(format!( "Attempted to call `{}` with an unsupported argument type", @@ -524,6 +551,73 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { }) } + fn expression_logical<'v>( + &mut self, + expr: LogicalExpression, + scope: &mut VariableScope<'v>, + ) -> Result { + match expr { + LogicalExpression::Not(inner) => { + let (inner_str, cleanup) = self.compile_operand(*inner, scope)?; + + let result_name = self.next_temp_name(); + let result_loc = scope.add_variable(&result_name, LocationRequest::Temp)?; + let result_reg = self.resolve_register(&result_loc)?; + + // seq rX rY 0 => if rY == 0 set rX = 1 else rX = 0 + self.write_output(format!("seq {result_reg} {inner_str} 0"))?; + + if let Some(name) = cleanup { + scope.free_temp(name)?; + } + + Ok(CompilationResult { + location: result_loc, + temp_name: Some(result_name), + }) + } + _ => { + let (op_str, left_expr, right_expr) = match expr { + LogicalExpression::And(l, r) => ("and", l, r), + LogicalExpression::Or(l, r) => ("or", l, r), + LogicalExpression::Equal(l, r) => ("seq", l, r), + LogicalExpression::NotEqual(l, r) => ("sne", l, r), + LogicalExpression::GreaterThan(l, r) => ("sgt", l, r), + LogicalExpression::GreaterThanOrEqual(l, r) => ("sge", l, r), + LogicalExpression::LessThan(l, r) => ("slt", l, r), + LogicalExpression::LessThanOrEqual(l, r) => ("sle", l, r), + LogicalExpression::Not(_) => unreachable!(), + }; + + // Compile LHS + let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; + // Compile RHS + let (rhs_str, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; + + // Allocate result register + let result_name = self.next_temp_name(); + let result_loc = scope.add_variable(&result_name, LocationRequest::Temp)?; + let result_reg = self.resolve_register(&result_loc)?; + + // Emit instruction: op result lhs rhs + self.write_output(format!("{op_str} {result_reg} {lhs_str} {rhs_str}"))?; + + // Clean up operand temps + if let Some(name) = lhs_cleanup { + scope.free_temp(name)?; + } + if let Some(name) = rhs_cleanup { + scope.free_temp(name)?; + } + + Ok(CompilationResult { + location: result_loc, + temp_name: Some(result_name), + }) + } + } + } + fn expression_block<'v>( &mut self, mut expr: BlockExpression, @@ -623,6 +717,18 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { scope.free_temp(name)?; } } + Expression::Logical(log_expr) => { + let result = self.expression_logical(log_expr, scope)?; + let result_reg = self.resolve_register(&result.location)?; + self.write_output(format!( + "move r{} {}", + VariableScope::RETURN_REGISTER, + result_reg + ))?; + if let Some(name) = result.temp_name { + scope.free_temp(name)?; + } + } _ => { return Err(Error::Unknown(format!( "Unsupported `return` statement: {:?}", @@ -748,3 +854,4 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } } + diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index 9e2a635..e504361 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -164,15 +164,22 @@ impl Parser { return Ok(None); }; - // check if the next or current token is an operator - if self_matches_peek!(self, TokenType::Symbol(s) if s.is_operator()) { - return Ok(Some(Expression::Binary(self.binary(lhs)?))); + // check if the next or current token is an operator, comparison, or logical symbol + if self_matches_peek!( + self, + TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() + ) { + return Ok(Some(self.infix(lhs)?)); } - // This is an edge case. We need to move back one token if the current token is an operator - // so the binary expression can pick up the operator - else if self_matches_current!(self, TokenType::Symbol(s) if s.is_operator()) { + // This is an edge case. We need to move back one token if the current token is an + // operator, comparison, or logical symbol so the binary expression can pick up + // the operator + else if self_matches_current!( + self, + TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() + ) { self.tokenizer.seek(SeekFrom::Current(-1))?; - return Ok(Some(Expression::Binary(self.binary(lhs)?))); + return Ok(Some(self.infix(lhs)?)); } Ok(Some(lhs)) @@ -254,6 +261,13 @@ impl Parser { Expression::Negation(boxed!(inner_expr)) } + // match logical NOT `!` + TokenType::Symbol(Symbol::LogicalNot) => { + self.assign_next()?; // consume the `!` symbol + let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?; + Expression::Logical(LogicalExpression::Not(boxed!(inner_expr))) + } + _ => { return Err(Error::UnexpectedToken(current_token.clone())); } @@ -262,7 +276,7 @@ impl Parser { Ok(Some(expr)) } - fn get_binary_child_node(&mut self) -> Result { + fn get_infix_child_node(&mut self) -> Result { let current_token = token_from_option!(self.current_token); match current_token.token_type { @@ -286,9 +300,15 @@ impl Parser { TokenType::Symbol(Symbol::Minus) => { self.assign_next()?; // recurse to handle double negation or simple negation of atoms - let inner = self.get_binary_child_node()?; + let inner = self.get_infix_child_node()?; Ok(Expression::Negation(boxed!(inner))) } + // Handle Logical Not + TokenType::Symbol(Symbol::LogicalNot) => { + self.assign_next()?; + let inner = self.get_infix_child_node()?; + Ok(Expression::Logical(LogicalExpression::Not(boxed!(inner)))) + } _ => Err(Error::UnexpectedToken(current_token.clone())), } } @@ -345,8 +365,8 @@ impl Parser { }) } - /// Handles mathmatical expressions in the explicit order of PEMDAS - fn binary(&mut self, previous: Expression) -> Result { + /// Handles mathmatical and logical expressions in the explicit order of operations + fn infix(&mut self, previous: Expression) -> Result { // We cannot use recursion here, as we need to handle the precedence of the operators // We need to use a loop to parse the binary expressions. @@ -354,15 +374,18 @@ impl Parser { // first, make sure the previous expression supports binary expressions match previous { - Expression::Binary(_) // 1 + 2 + 3 - | Expression::Invocation(_) // add() + 3 - | Expression::Priority(_) // (1 + 2) + 3 - | Expression::Literal(Literal::Number(_)) // 1 + 2 (no addition of strings) - | Expression::Variable(_) // x + 2 - | Expression::Negation(_) // -1 + 2 - => {} + Expression::Binary(_) + | Expression::Logical(_) + | Expression::Invocation(_) + | Expression::Priority(_) + | Expression::Literal(Literal::Number(_)) + | Expression::Variable(_) + | Expression::Negation(_) => {} _ => { - return Err(Error::InvalidSyntax(current_token.clone(), String::from("Invalid expression for binary operation"))) + return Err(Error::InvalidSyntax( + current_token.clone(), + String::from("Invalid expression for binary/logical operation"), + )); } } @@ -372,12 +395,15 @@ impl Parser { let mut operators = Vec::::new(); // +, + // build the expressions and operators vectors - while token_matches!(current_token, TokenType::Symbol(s) if s.is_operator()) { - // We are guaranteed to have an operator symbol here as we checked in the while loop + while token_matches!( + current_token, + TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() + ) { + // We are guaranteed to have an operator/comparison/logical symbol here as we checked in the while loop let operator = extract_token_data!(current_token, TokenType::Symbol(s), s); operators.push(operator); self.assign_next()?; - expressions.push(self.get_binary_child_node()?); + expressions.push(self.get_infix_child_node()?); current_token = token_from_option!(self.get_next()?).clone(); } @@ -394,7 +420,7 @@ impl Parser { // This means that we need to keep track of the current iteration to ensure we are // removing the correct expressions from the vector - // Loop through operators, and build the binary expressions for exponential operators only + // --- PRECEDENCE LEVEL 1: Exponent (**) --- for (i, operator) in operators.iter().enumerate().rev() { if operator == &Symbol::Exp { let right = expressions.remove(i + 1); @@ -405,12 +431,10 @@ impl Parser { ); } } - - // remove all the exponential operators from the operators vector operators.retain(|symbol| symbol != &Symbol::Exp); - let mut current_iteration = 0; - // Loop through operators, and build the binary expressions for multiplication and division operators + // --- PRECEDENCE LEVEL 2: Multiplicative (*, /, %) --- + let mut current_iteration = 0; for (i, operator) in operators.iter().enumerate() { if matches!(operator, Symbol::Slash | Symbol::Asterisk | Symbol::Percent) { let index = i - current_iteration; @@ -430,21 +454,18 @@ impl Parser { index, Expression::Binary(BinaryExpression::Modulo(boxed!(left), boxed!(right))), ), - // safety: we have already checked for the operator _ => unreachable!(), } current_iteration += 1; } } - - // remove all the multiplication and division operators from the operators vector operators .retain(|symbol| !matches!(symbol, Symbol::Asterisk | Symbol::Percent | Symbol::Slash)); - current_iteration = 0; - // Loop through operators, and build the binary expressions for addition and subtraction operators + // --- PRECEDENCE LEVEL 3: Additive (+, -) --- + current_iteration = 0; for (i, operator) in operators.iter().enumerate() { - if operator == &Symbol::Plus || operator == &Symbol::Minus { + if matches!(operator, Symbol::Plus | Symbol::Minus) { let index = i - current_iteration; let left = expressions.remove(index); let right = expressions.remove(index); @@ -458,16 +479,120 @@ impl Parser { index, Expression::Binary(BinaryExpression::Subtract(boxed!(left), boxed!(right))), ), - // safety: we have already checked for the operator _ => unreachable!(), } current_iteration += 1; } } - - // remove all the addition and subtraction operators from the operators vector operators.retain(|symbol| !matches!(symbol, Symbol::Plus | Symbol::Minus)); + // --- PRECEDENCE LEVEL 4: Comparison (<, >, <=, >=) --- + current_iteration = 0; + for (i, operator) in operators.iter().enumerate() { + if operator.is_comparison() && !matches!(operator, Symbol::Equal | Symbol::NotEqual) { + let index = i - current_iteration; + let left = expressions.remove(index); + let right = expressions.remove(index); + + match operator { + Symbol::LessThan => expressions.insert( + index, + Expression::Logical(LogicalExpression::LessThan( + boxed!(left), + boxed!(right), + )), + ), + Symbol::GreaterThan => expressions.insert( + index, + Expression::Logical(LogicalExpression::GreaterThan( + boxed!(left), + boxed!(right), + )), + ), + Symbol::LessThanOrEqual => expressions.insert( + index, + Expression::Logical(LogicalExpression::LessThanOrEqual( + boxed!(left), + boxed!(right), + )), + ), + Symbol::GreaterThanOrEqual => expressions.insert( + index, + Expression::Logical(LogicalExpression::GreaterThanOrEqual( + boxed!(left), + boxed!(right), + )), + ), + _ => unreachable!(), + } + current_iteration += 1; + } + } + operators.retain(|symbol| { + !symbol.is_comparison() || matches!(symbol, Symbol::Equal | Symbol::NotEqual) + }); + + // --- PRECEDENCE LEVEL 5: Equality (==, !=) --- + current_iteration = 0; + for (i, operator) in operators.iter().enumerate() { + if matches!(operator, Symbol::Equal | Symbol::NotEqual) { + let index = i - current_iteration; + let left = expressions.remove(index); + let right = expressions.remove(index); + + match operator { + Symbol::Equal => expressions.insert( + index, + Expression::Logical(LogicalExpression::Equal(boxed!(left), boxed!(right))), + ), + Symbol::NotEqual => expressions.insert( + index, + Expression::Logical(LogicalExpression::NotEqual( + boxed!(left), + boxed!(right), + )), + ), + _ => unreachable!(), + } + current_iteration += 1; + } + } + operators.retain(|symbol| !matches!(symbol, Symbol::Equal | Symbol::NotEqual)); + + // --- PRECEDENCE LEVEL 6: Logical AND (&&) --- + current_iteration = 0; + for (i, operator) in operators.iter().enumerate() { + if matches!(operator, Symbol::LogicalAnd) { + let index = i - current_iteration; + let left = expressions.remove(index); + let right = expressions.remove(index); + + expressions.insert( + index, + Expression::Logical(LogicalExpression::And(boxed!(left), boxed!(right))), + ); + current_iteration += 1; + } + } + operators.retain(|symbol| !matches!(symbol, Symbol::LogicalAnd)); + + // --- PRECEDENCE LEVEL 7: Logical OR (||) --- + current_iteration = 0; + for (i, operator) in operators.iter().enumerate() { + if matches!(operator, Symbol::LogicalOr) { + let index = i - current_iteration; + let left = expressions.remove(index); + let right = expressions.remove(index); + + expressions.insert( + index, + Expression::Logical(LogicalExpression::Or(boxed!(left), boxed!(right))), + ); + current_iteration += 1; + } + } + operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr)); + // Ensure there is only one expression left in the expressions vector, and no operators left if expressions.len() != 1 || !operators.is_empty() { return Err(Error::InvalidSyntax( @@ -484,11 +609,7 @@ impl Parser { self.tokenizer.seek(SeekFrom::Current(-1))?; } - // Ensure the last expression is a binary expression - match expressions.pop().unwrap() { - Expression::Binary(binary) => Ok(binary), - _ => unreachable!(), - } + Ok(expressions.pop().unwrap()) } fn priority(&mut self) -> Result, Error> { diff --git a/libs/parser/src/tree_node.rs b/libs/parser/src/tree_node.rs index 456a9c4..dbb4b94 100644 --- a/libs/parser/src/tree_node.rs +++ b/libs/parser/src/tree_node.rs @@ -5,6 +5,7 @@ use tokenizer::token::Number; pub enum Literal { Number(Number), String(String), + Boolean(bool), } impl std::fmt::Display for Literal { @@ -12,6 +13,7 @@ impl std::fmt::Display for Literal { match self { Literal::Number(n) => write!(f, "{}", n), Literal::String(s) => write!(f, "\"{}\"", s), + Literal::Boolean(b) => write!(f, "{}", if *b { 1 } else { 0 }), } } } From 31ca798e55577a61626c7be4fb07a96417909fff Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 25 Nov 2025 00:49:12 -0700 Subject: [PATCH 2/5] Add support for the 'while' keyword --- libs/compiler/src/test/declaration_literal.rs | 60 +++++++++++++++++++ libs/compiler/src/test/logic_expression.rs | 60 ++++++++++++++++++- libs/compiler/src/v1.rs | 35 +++++++++++ libs/parser/src/lib.rs | 17 +++--- libs/tokenizer/src/lib.rs | 36 +++++++++++ libs/tokenizer/src/token.rs | 2 + 6 files changed, 202 insertions(+), 8 deletions(-) diff --git a/libs/compiler/src/test/declaration_literal.rs b/libs/compiler/src/test/declaration_literal.rs index bcfec16..9316e54 100644 --- a/libs/compiler/src/test/declaration_literal.rs +++ b/libs/compiler/src/test/declaration_literal.rs @@ -85,3 +85,63 @@ fn variable_declaration_negative() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn test_boolean_declaration() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let t = true; + let f = false; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 #t + move r9 0 #f + " + } + ); + + Ok(()) +} + +#[test] +fn test_boolean_return() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn getTrue() { + return true; + }; + + let val = getTrue(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + getTrue: + push ra + move r15 1 #returnValue + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + jal getTrue + move r8 r15 #val + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/logic_expression.rs b/libs/compiler/src/test/logic_expression.rs index e4a2ead..55bdda6 100644 --- a/libs/compiler/src/test/logic_expression.rs +++ b/libs/compiler/src/test/logic_expression.rs @@ -110,7 +110,65 @@ fn test_math_with_logic() -> anyhow::Result<()> { compiled, indoc! { " - + j main + main: + add r1 1 2 + sgt r2 r1 1 + move r8 r2 #logic + " + } + ); + + Ok(()) +} + +#[test] +fn test_boolean_in_logic() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let res = true && false; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + and r1 1 0 + move r8 r1 #res + " + } + ); + + Ok(()) +} + +#[test] +fn test_invert_a_boolean() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let i = true; + let y = !i; + + let result = y == false; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 #i + seq r1 r8 0 + move r9 r1 #y + seq r2 r9 0 + move r10 r2 #result " } ); diff --git a/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs index b7fc5d1..a8b5201 100644 --- a/libs/compiler/src/v1.rs +++ b/libs/compiler/src/v1.rs @@ -178,6 +178,16 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { temp_name: Some(temp_name), })) } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + let temp_name = self.next_temp_name(); + let loc = scope.add_variable(&temp_name, LocationRequest::Temp)?; + self.emit_variable_assignment(&temp_name, &loc, val)?; + Ok(Some(CompilationResult { + location: loc, + temp_name: Some(temp_name), + })) + } Expression::Variable(name) => { let loc = scope.get_location_of(&name)?; Ok(Some(CompilationResult { @@ -258,6 +268,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.emit_variable_assignment(&var_name, &var_location, num)?; var_location } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + let var_location = + scope.add_variable(var_name.clone(), LocationRequest::Persist)?; + + self.emit_variable_assignment(&var_name, &var_location, val)?; + var_location + } Expression::Invocation(invoke_expr) => { self.expression_function_invocation(invoke_expr, scope)?; @@ -364,6 +382,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let num_str = num.to_string(); self.write_output(format!("push {num_str}"))?; } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + self.write_output(format!("push {val}"))?; + } Expression::Variable(var_name) => match stack.get_location_of(var_name)? { VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { self.write_output(format!("push r{reg}"))?; @@ -471,6 +493,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { return Ok((n.to_string(), None)); } + // Optimization for boolean literals + if let Expression::Literal(Literal::Boolean(b)) = expr { + return Ok((if b { "1".to_string() } else { "0".to_string() }, None)); + } + // Optimization for negated literals used as operands. // E.g., `1 + -2` -> return "-2" string, no register used. if let Expression::Negation(inner) = &expr @@ -705,6 +732,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { num, )?; } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + self.emit_variable_assignment( + "returnValue", + &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + val, + )?; + } Expression::Binary(bin_expr) => { let result = self.expression_binary(bin_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index e504361..b01c195 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -171,9 +171,8 @@ impl Parser { ) { return Ok(Some(self.infix(lhs)?)); } - // This is an edge case. We need to move back one token if the current token is an - // operator, comparison, or logical symbol so the binary expression can pick up - // the operator + // This is an edge case. We need to move back one token if the current token is an operator + // so the binary expression can pick up the operator else if self_matches_current!( self, TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() @@ -245,7 +244,9 @@ impl Parser { TokenType::Symbol(Symbol::LBrace) => Expression::Block(self.block()?), // match literal expressions with a semi-colon afterwards - TokenType::Number(_) | TokenType::String(_) => Expression::Literal(self.literal()?), + TokenType::Number(_) | TokenType::String(_) | TokenType::Boolean(_) => { + Expression::Literal(self.literal()?) + } // match priority expressions with a left parenthesis TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?), @@ -280,8 +281,8 @@ impl Parser { let current_token = token_from_option!(self.current_token); match current_token.token_type { - // A literal number - TokenType::Number(_) => self.literal().map(Expression::Literal), + // A literal number or boolean + TokenType::Number(_) | TokenType::Boolean(_) => self.literal().map(Expression::Literal), // A plain variable TokenType::Identifier(ident) if !self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => @@ -378,7 +379,7 @@ impl Parser { | Expression::Logical(_) | Expression::Invocation(_) | Expression::Priority(_) - | Expression::Literal(Literal::Number(_)) + | Expression::Literal(_) | Expression::Variable(_) | Expression::Negation(_) => {} _ => { @@ -755,6 +756,7 @@ impl Parser { let literal = match current_token.token_type { TokenType::Number(num) => Literal::Number(num), TokenType::String(string) => Literal::String(string), + TokenType::Boolean(boolean) => Literal::Boolean(boolean), _ => return Err(Error::UnexpectedToken(current_token.clone())), }; @@ -1050,3 +1052,4 @@ impl Parser { } } } + diff --git a/libs/tokenizer/src/lib.rs b/libs/tokenizer/src/lib.rs index ad767f6..4afb2e2 100644 --- a/libs/tokenizer/src/lib.rs +++ b/libs/tokenizer/src/lib.rs @@ -409,6 +409,7 @@ impl Tokenizer { "device" if next_ws!() => keyword!(Device), "loop" if next_ws!() => keyword!(Loop), "break" if next_ws!() => keyword!(Break), + "while" if next_ws!() => keyword!(While), // boolean literals "true" if next_ws!() => { @@ -886,4 +887,39 @@ mod tests { Ok(()) } + + #[test] + fn test_compact_syntax() -> Result<()> { + let mut tokenizer = Tokenizer::from(String::from("if(true) while(false)")); + + // if(true) + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Keyword(Keyword::If) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Symbol(Symbol::LParen) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Boolean(true) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Symbol(Symbol::RParen) + ); + + // while(false) + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Keyword(Keyword::While) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Symbol(Symbol::LParen) + ); + + Ok(()) + } } diff --git a/libs/tokenizer/src/token.rs b/libs/tokenizer/src/token.rs index 93a090c..5e7d597 100644 --- a/libs/tokenizer/src/token.rs +++ b/libs/tokenizer/src/token.rs @@ -228,4 +228,6 @@ pub enum Keyword { Loop, /// Represents the `break` keyword Break, + /// Represents the `while` keyword + While, } From b5bf9a94a691405a4eb9235014050bdfdd6d305a Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 25 Nov 2025 01:22:54 -0700 Subject: [PATCH 3/5] Working logic and if / else / else if statements --- libs/compiler/src/test/branching.rs | 158 ++++++++++++++++++++++++++++ libs/compiler/src/test/mod.rs | 1 + libs/compiler/src/v1.rs | 107 ++++++++++++++++++- libs/parser/src/lib.rs | 81 ++++++++++++-- libs/parser/src/tree_node.rs | 27 ++++- 5 files changed, 362 insertions(+), 12 deletions(-) create mode 100644 libs/compiler/src/test/branching.rs diff --git a/libs/compiler/src/test/branching.rs b/libs/compiler/src/test/branching.rs new file mode 100644 index 0000000..fb06024 --- /dev/null +++ b/libs/compiler/src/test/branching.rs @@ -0,0 +1,158 @@ +use crate::compile; +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn test_if_statement() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 10; + if (a > 5) { + a = 20; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 10 #a + sgt r1 r8 5 + beq r1 0 L1 + move r8 20 #a + L1: + " + } + ); + + Ok(()) +} + +#[test] +fn test_if_else_statement() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 0; + if (10 > 5) { + a = 1; + } else { + a = 2; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #a + sgt r1 10 5 + beq r1 0 L2 + move r8 1 #a + j L1 + L2: + move r8 2 #a + L1: + " + } + ); + + Ok(()) +} + +#[test] +fn test_if_else_if_statement() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 0; + if (a == 1) { + a = 10; + } else if (a == 2) { + a = 20; + } else { + a = 30; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #a + seq r1 r8 1 + beq r1 0 L2 + move r8 10 #a + j L1 + L2: + seq r2 r8 2 + beq r2 0 L4 + move r8 20 #a + j L3 + L4: + move r8 30 #a + L3: + L1: + " + } + ); + + Ok(()) +} + +#[test] +fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 1; + let b = 2; + let c = 3; + let d = 4; + let e = 5; + let f = 6; + let g = 7; + let h = 8; // Spilled to stack (offset 0) + + if (a == 1) { + h = 99; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 #a + move r9 2 #b + move r10 3 #c + move r11 4 #d + move r12 5 #e + move r13 6 #f + move r14 7 #g + push 8 #h + seq r1 r8 1 + beq r1 0 L1 + sub r0 sp 1 + put db r0 99 #h + L1: + " + } + ); + + Ok(()) +} + diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index 05e91a0..c2d7389 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -41,6 +41,7 @@ macro_rules! compile { }}; } mod binary_expression; +mod branching; mod declaration_function_invocation; mod declaration_literal; mod function_declaration; diff --git a/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs index a8b5201..1cc79ac 100644 --- a/libs/compiler/src/v1.rs +++ b/libs/compiler/src/v1.rs @@ -2,8 +2,9 @@ use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableS use parser::{ Parser as ASTParser, tree_node::{ - BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, - FunctionExpression, InvocationExpression, Literal, LogicalExpression, + AssignmentExpression, BinaryExpression, BlockExpression, DeviceDeclarationExpression, + Expression, FunctionExpression, IfExpression, InvocationExpression, Literal, + LogicalExpression, }, }; use quick_error::quick_error; @@ -75,6 +76,7 @@ pub struct Compiler<'a, W: std::io::Write> { declared_main: bool, config: CompilerConfig, temp_counter: usize, + label_counter: usize, } impl<'a, W: std::io::Write> Compiler<'a, W> { @@ -93,6 +95,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { declared_main: false, config: config.unwrap_or_default(), temp_counter: 0, + label_counter: 0, } } @@ -120,6 +123,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { format!("__binary_temp_{}", self.temp_counter) } + fn next_label_name(&mut self) -> String { + self.label_counter += 1; + format!("L{}", self.label_counter) + } + fn expression<'v>( &mut self, expr: Expression, @@ -134,6 +142,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.expression_block(expr_block, scope)?; Ok(None) } + Expression::If(expr_if) => { + self.expression_if(expr_if, scope)?; + Ok(None) + } Expression::DeviceDeclaration(expr_dev) => { self.expression_device(expr_dev)?; Ok(None) @@ -145,6 +157,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { temp_name: None, })) } + Expression::Assignment(assign_expr) => { + self.expression_assignment(assign_expr, scope)?; + Ok(None) + } Expression::Invocation(expr_invoke) => { self.expression_function_invocation(expr_invoke, scope)?; // Invocation returns result in r15 (RETURN_REGISTER). @@ -353,6 +369,50 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(Some(loc)) } + fn expression_assignment<'v>( + &mut self, + expr: AssignmentExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + let AssignmentExpression { + identifier, + expression, + } = expr; + + let location = scope.get_location_of(&identifier)?; + let (val_str, cleanup) = self.compile_operand(*expression, scope)?; + + let debug_tag = if self.config.debug { + format!(" #{}", identifier) + } else { + String::new() + }; + + match location { + VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { + self.write_output(format!("move r{reg} {val_str}{debug_tag}"))?; + } + VariableLocation::Stack(offset) => { + // Calculate address: sp - offset + self.write_output(format!( + "sub r{0} sp {offset}", + VariableScope::TEMP_STACK_REGISTER + ))?; + // Store value to stack/db at address + self.write_output(format!( + "put db r{0} {val_str}{debug_tag}", + VariableScope::TEMP_STACK_REGISTER + ))?; + } + } + + if let Some(name) = cleanup { + scope.free_temp(name)?; + } + + Ok(()) + } + fn expression_function_invocation( &mut self, invoke_expr: InvocationExpression, @@ -467,6 +527,49 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + fn expression_if<'v>( + &mut self, + expr: IfExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + let end_label = self.next_label_name(); + let else_label = if expr.else_branch.is_some() { + self.next_label_name() + } else { + end_label.clone() + }; + + // Compile Condition + let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; + + // If condition is FALSE (0), jump to else_label + self.write_output(format!("beq {cond_str} 0 {else_label}"))?; + + if let Some(name) = cleanup { + scope.free_temp(name)?; + } + + // Compile Body + // Scope variables in body are ephemeral to the block, handled by expression_block + self.expression_block(expr.body, scope)?; + + // If we have an else branch, we need to jump over it after the 'if' body + if expr.else_branch.is_some() { + self.write_output(format!("j {end_label}"))?; + self.write_output(format!("{else_label}:"))?; + + match *expr.else_branch.unwrap() { + Expression::Block(block) => self.expression_block(block, scope)?, + Expression::If(if_expr) => self.expression_if(if_expr, scope)?, + _ => unreachable!("Parser ensures else branch is Block or If"), + } + } + + self.write_output(format!("{end_label}:"))?; + + Ok(()) + } + /// Helper to resolve a location to a register string (e.g., "r0"). /// Note: This does not handle Stack locations automatically, as they require /// instruction emission to load. Use `compile_operand` for general handling. diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index b01c195..d1abfda 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -204,9 +204,7 @@ impl Parser { let expr = match current_token.token_type { // match unsupported keywords - TokenType::Keyword(e) - if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) => - { + TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum, Keyword::While) => { return Err(Error::UnsupportedKeyword(current_token.clone())); } @@ -218,6 +216,9 @@ impl Parser { // match functions with a `fn` keyword TokenType::Keyword(Keyword::Fn) => Expression::Function(self.function()?), + // match if statements + TokenType::Keyword(Keyword::If) => Expression::If(self.if_expression()?), + // match syscalls with a `syscall` keyword TokenType::Identifier(ref id) if SysCall::is_syscall(id) => { Expression::Syscall(self.syscall()?) @@ -711,10 +712,19 @@ impl Parser { let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; let return_expr = Expression::Return(boxed!(expression)); expressions.push(return_expr); - self.assign_next()?; - } - self.assign_next()?; + // check for semicolon + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) { + return Err(Error::UnexpectedToken(next.clone())); + } + + // check for right brace + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::RBrace)) { + return Err(Error::UnexpectedToken(next.clone())); + } + } Ok(BlockExpression(expressions)) } @@ -763,6 +773,65 @@ impl Parser { Ok(literal) } + fn if_expression(&mut self) -> Result { + let current_token = token_from_option!(self.current_token); + if !self_matches_current!(self, TokenType::Keyword(Keyword::If)) { + return Err(Error::UnexpectedToken(current_token.clone())); + } + + // consume 'if' + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::LParen)) { + return Err(Error::UnexpectedToken(next.clone())); + } + self.assign_next()?; + + // parse condition + let condition = self.expression()?.ok_or(Error::UnexpectedEOF)?; + + // check for ')' + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::RParen)) { + return Err(Error::UnexpectedToken(next.clone())); + } + + // check for '{' + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::LBrace)) { + return Err(Error::UnexpectedToken(next.clone())); + } + + // parse body + let body = self.block()?; + + // check for 'else' + let else_branch = if self_matches_peek!(self, TokenType::Keyword(Keyword::Else)) { + self.assign_next()?; // consume 'else' + + if self_matches_peek!(self, TokenType::Keyword(Keyword::If)) { + // else if ... + self.assign_next()?; + Some(boxed!(Expression::If(self.if_expression()?))) + } else if self_matches_peek!(self, TokenType::Symbol(Symbol::LBrace)) { + // else { ... } + self.assign_next()?; + Some(boxed!(Expression::Block(self.block()?))) + } else { + return Err(Error::UnexpectedToken( + token_from_option!(self.get_next()?).clone(), + )); + } + } else { + None + }; + + Ok(IfExpression { + condition: boxed!(condition), + body, + else_branch, + }) + } + fn function(&mut self) -> Result { let current_token = token_from_option!(self.current_token); // Sanify check that the current token is a `fn` keyword diff --git a/libs/parser/src/tree_node.rs b/libs/parser/src/tree_node.rs index dbb4b94..13ad72b 100644 --- a/libs/parser/src/tree_node.rs +++ b/libs/parser/src/tree_node.rs @@ -168,35 +168,54 @@ impl std::fmt::Display for DeviceDeclarationExpression { } } +#[derive(Debug, PartialEq, Eq)] +pub struct IfExpression { + pub condition: Box, + pub body: BlockExpression, + pub else_branch: Option>, +} + +impl std::fmt::Display for IfExpression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(if ({}) {}", self.condition, self.body)?; + if let Some(else_branch) = &self.else_branch { + write!(f, " else {}", else_branch)?; + } + write!(f, ")") + } +} + #[derive(Debug, PartialEq, Eq)] pub enum Expression { Assignment(AssignmentExpression), Binary(BinaryExpression), Block(BlockExpression), Declaration(String, Box), + DeviceDeclaration(DeviceDeclarationExpression), Function(FunctionExpression), + If(IfExpression), Invocation(InvocationExpression), Literal(Literal), Logical(LogicalExpression), Negation(Box), Priority(Box), Return(Box), - Variable(String), - DeviceDeclaration(DeviceDeclarationExpression), Syscall(SysCall), + Variable(String), } impl std::fmt::Display for Expression { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Expression::Assignment(e) => write!(f, "{}", e), + Expression::Binary(e) => write!(f, "{}", e), Expression::Literal(l) => write!(f, "{}", l), Expression::Negation(e) => write!(f, "(-{})", e), - Expression::Binary(e) => write!(f, "{}", e), Expression::Logical(e) => write!(f, "{}", e), - Expression::Assignment(e) => write!(f, "{}", e), Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e), Expression::Function(e) => write!(f, "{}", e), Expression::Block(e) => write!(f, "{}", e), + Expression::If(e) => write!(f, "{}", e), Expression::Invocation(e) => write!(f, "{}", e), Expression::Variable(id) => write!(f, "{}", id), Expression::Priority(e) => write!(f, "({})", e), From 05b16d59a47d4d34d19f12fd9b6db2de879363e1 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 25 Nov 2025 01:30:12 -0700 Subject: [PATCH 4/5] loops --- libs/compiler/src/test/loops.rs | 108 ++++++++++++++++++++++++++++++++ libs/compiler/src/test/mod.rs | 1 + libs/compiler/src/v1.rs | 87 ++++++++++++++++++++++++- libs/parser/src/lib.rs | 74 +++++++++++++++++++++- libs/parser/src/tree_node.rs | 44 ++++++++++--- 5 files changed, 304 insertions(+), 10 deletions(-) create mode 100644 libs/compiler/src/test/loops.rs diff --git a/libs/compiler/src/test/loops.rs b/libs/compiler/src/test/loops.rs new file mode 100644 index 0000000..0e8a838 --- /dev/null +++ b/libs/compiler/src/test/loops.rs @@ -0,0 +1,108 @@ +use crate::compile; +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn test_infinite_loop() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 0; + loop { + a = a + 1; + } + " + }; + + // Labels: L1 (start), L2 (end) + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #a + L1: + add r1 r8 1 + move r8 r1 #a + j L1 + L2: + " + } + ); + + Ok(()) +} + +#[test] +fn test_loop_break() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 0; + loop { + a = a + 1; + if (a > 10) { + break; + } + } + " + }; + + // Labels: L1 (start), L2 (end), L3 (if end - implicit else label) + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #a + L1: + add r1 r8 1 + move r8 r1 #a + sgt r2 r8 10 + beq r2 0 L3 + j L2 + L3: + j L1 + L2: + " + } + ); + + Ok(()) +} + +#[test] +fn test_while_loop() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 0; + while (a < 10) { + a = a + 1; + } + " + }; + + // Labels: L1 (start), L2 (end) + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #a + L1: + slt r1 r8 10 + beq r1 0 L2 + add r2 r8 1 + move r8 r2 #a + j L1 + L2: + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/mod.rs b/libs/compiler/src/test/mod.rs index c2d7389..47893fc 100644 --- a/libs/compiler/src/test/mod.rs +++ b/libs/compiler/src/test/mod.rs @@ -46,3 +46,4 @@ mod declaration_function_invocation; mod declaration_literal; mod function_declaration; mod logic_expression; +mod loops; diff --git a/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs index 1cc79ac..e36da2c 100644 --- a/libs/compiler/src/v1.rs +++ b/libs/compiler/src/v1.rs @@ -4,7 +4,7 @@ use parser::{ tree_node::{ AssignmentExpression, BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, InvocationExpression, Literal, - LogicalExpression, + LogicalExpression, LoopExpression, WhileExpression, }, }; use quick_error::quick_error; @@ -77,6 +77,7 @@ pub struct Compiler<'a, W: std::io::Write> { config: CompilerConfig, temp_counter: usize, label_counter: usize, + loop_stack: Vec, // Stores the 'end' label of the current loops } impl<'a, W: std::io::Write> Compiler<'a, W> { @@ -96,6 +97,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { config: config.unwrap_or_default(), temp_counter: 0, label_counter: 0, + loop_stack: Vec::new(), } } @@ -146,6 +148,18 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.expression_if(expr_if, scope)?; Ok(None) } + Expression::Loop(expr_loop) => { + self.expression_loop(expr_loop, scope)?; + Ok(None) + } + Expression::While(expr_while) => { + self.expression_while(expr_while, scope)?; + Ok(None) + } + Expression::Break => { + self.expression_break()?; + Ok(None) + } Expression::DeviceDeclaration(expr_dev) => { self.expression_device(expr_dev)?; Ok(None) @@ -570,6 +584,77 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { Ok(()) } + fn expression_loop<'v>( + &mut self, + expr: LoopExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + let start_label = self.next_label_name(); + let end_label = self.next_label_name(); + + // Push end label to stack for 'break' + self.loop_stack.push(end_label.clone()); + + self.write_output(format!("{start_label}:"))?; + + // Compile Body + self.expression_block(expr.body, scope)?; + + // Jump back to start + self.write_output(format!("j {start_label}"))?; + self.write_output(format!("{end_label}:"))?; + + self.loop_stack.pop(); + + Ok(()) + } + + fn expression_while<'v>( + &mut self, + expr: WhileExpression, + scope: &mut VariableScope<'v>, + ) -> Result<(), Error> { + let start_label = self.next_label_name(); + let end_label = self.next_label_name(); + + // Push end label to stack for 'break' + self.loop_stack.push(end_label.clone()); + + self.write_output(format!("{start_label}:"))?; + + // Compile Condition + let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; + + // If condition is FALSE, jump to end + self.write_output(format!("beq {cond_str} 0 {end_label}"))?; + + if let Some(name) = cleanup { + scope.free_temp(name)?; + } + + // Compile Body + self.expression_block(expr.body, scope)?; + + // Jump back to start + self.write_output(format!("j {start_label}"))?; + self.write_output(format!("{end_label}:"))?; + + self.loop_stack.pop(); + + Ok(()) + } + + fn expression_break(&mut self) -> Result<(), Error> { + if let Some(label) = self.loop_stack.last() { + self.write_output(format!("j {label}"))?; + Ok(()) + } else { + // This is a semantic error, but for now we can return a generic error + // Ideally we'd have a specific error type for this + Err(Error::Unknown("Break statement outside of loop".into())) + } + } + /// Helper to resolve a location to a register string (e.g., "r0"). /// Note: This does not handle Stack locations automatically, as they require /// instruction emission to load. Use `compile_operand` for general handling. diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index d1abfda..92c5a12 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -204,7 +204,7 @@ impl Parser { let expr = match current_token.token_type { // match unsupported keywords - TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum, Keyword::While) => { + TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum) => { return Err(Error::UnsupportedKeyword(current_token.clone())); } @@ -219,6 +219,22 @@ impl Parser { // match if statements TokenType::Keyword(Keyword::If) => Expression::If(self.if_expression()?), + // match loop statements + TokenType::Keyword(Keyword::Loop) => Expression::Loop(self.loop_expression()?), + + // match while statements + TokenType::Keyword(Keyword::While) => Expression::While(self.while_expression()?), + + // match break statements + TokenType::Keyword(Keyword::Break) => { + // make sure the next token is a semi-colon + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) { + return Err(Error::UnexpectedToken(next.clone())); + } + Expression::Break + } + // match syscalls with a `syscall` keyword TokenType::Identifier(ref id) if SysCall::is_syscall(id) => { Expression::Syscall(self.syscall()?) @@ -832,6 +848,61 @@ impl Parser { }) } + fn loop_expression(&mut self) -> Result { + let current_token = token_from_option!(self.current_token); + if !self_matches_current!(self, TokenType::Keyword(Keyword::Loop)) { + return Err(Error::UnexpectedToken(current_token.clone())); + } + + // check for '{' + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::LBrace)) { + return Err(Error::UnexpectedToken(next.clone())); + } + + // parse body + let body = self.block()?; + + Ok(LoopExpression { body }) + } + + fn while_expression(&mut self) -> Result { + let current_token = token_from_option!(self.current_token); + if !self_matches_current!(self, TokenType::Keyword(Keyword::While)) { + return Err(Error::UnexpectedToken(current_token.clone())); + } + + // consume 'while' + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::LParen)) { + return Err(Error::UnexpectedToken(next.clone())); + } + self.assign_next()?; + + // parse condition + let condition = self.expression()?.ok_or(Error::UnexpectedEOF)?; + + // check for ')' + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::RParen)) { + return Err(Error::UnexpectedToken(next.clone())); + } + + // check for '{' + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::LBrace)) { + return Err(Error::UnexpectedToken(next.clone())); + } + + // parse body + let body = self.block()?; + + Ok(WhileExpression { + condition: boxed!(condition), + body, + }) + } + fn function(&mut self) -> Result { let current_token = token_from_option!(self.current_token); // Sanify check that the current token is a `fn` keyword @@ -1121,4 +1192,3 @@ impl Parser { } } } - diff --git a/libs/parser/src/tree_node.rs b/libs/parser/src/tree_node.rs index 13ad72b..b634beb 100644 --- a/libs/parser/src/tree_node.rs +++ b/libs/parser/src/tree_node.rs @@ -185,11 +185,35 @@ impl std::fmt::Display for IfExpression { } } +#[derive(Debug, PartialEq, Eq)] +pub struct LoopExpression { + pub body: BlockExpression, +} + +impl std::fmt::Display for LoopExpression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(loop {})", self.body) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct WhileExpression { + pub condition: Box, + pub body: BlockExpression, +} + +impl std::fmt::Display for WhileExpression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(while {} {})", self.condition, self.body) + } +} + #[derive(Debug, PartialEq, Eq)] pub enum Expression { Assignment(AssignmentExpression), Binary(BinaryExpression), Block(BlockExpression), + Break, Declaration(String, Box), DeviceDeclaration(DeviceDeclarationExpression), Function(FunctionExpression), @@ -197,11 +221,13 @@ pub enum Expression { Invocation(InvocationExpression), Literal(Literal), Logical(LogicalExpression), + Loop(LoopExpression), Negation(Box), Priority(Box), Return(Box), Syscall(SysCall), Variable(String), + While(WhileExpression), } impl std::fmt::Display for Expression { @@ -209,19 +235,23 @@ impl std::fmt::Display for Expression { match self { Expression::Assignment(e) => write!(f, "{}", e), Expression::Binary(e) => write!(f, "{}", e), - Expression::Literal(l) => write!(f, "{}", l), - Expression::Negation(e) => write!(f, "(-{})", e), - Expression::Logical(e) => write!(f, "{}", e), - Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e), - Expression::Function(e) => write!(f, "{}", e), Expression::Block(e) => write!(f, "{}", e), + Expression::Break => write!(f, "break"), + Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e), + Expression::DeviceDeclaration(e) => write!(f, "{}", e), + Expression::Function(e) => write!(f, "{}", e), Expression::If(e) => write!(f, "{}", e), Expression::Invocation(e) => write!(f, "{}", e), - Expression::Variable(id) => write!(f, "{}", id), + Expression::Literal(l) => write!(f, "{}", l), + Expression::Logical(e) => write!(f, "{}", e), + Expression::Loop(e) => write!(f, "{}", e), + Expression::Negation(e) => write!(f, "(-{})", e), Expression::Priority(e) => write!(f, "({})", e), Expression::Return(e) => write!(f, "(return {})", e), - Expression::DeviceDeclaration(e) => write!(f, "{}", e), Expression::Syscall(e) => write!(f, "{}", e), + Expression::Variable(id) => write!(f, "{}", id), + Expression::While(e) => write!(f, "{}", e), } } } + From 81f412453b03f1030445ae4a2135b5c47dd0d65b Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 25 Nov 2025 01:47:49 -0700 Subject: [PATCH 5/5] continue statements now working --- libs/compiler/src/test/loops.rs | 41 +++++++++++++++++++++++++++++++++ libs/compiler/src/v1.rs | 29 +++++++++++++++++------ libs/parser/src/lib.rs | 11 +++++++++ libs/parser/src/tree_node.rs | 3 ++- libs/tokenizer/src/lib.rs | 1 + libs/tokenizer/src/token.rs | 2 ++ 6 files changed, 79 insertions(+), 8 deletions(-) diff --git a/libs/compiler/src/test/loops.rs b/libs/compiler/src/test/loops.rs index 0e8a838..c421a99 100644 --- a/libs/compiler/src/test/loops.rs +++ b/libs/compiler/src/test/loops.rs @@ -106,3 +106,44 @@ fn test_while_loop() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn test_loop_continue() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let a = 0; + loop { + a = a + 1; + if (a < 5) { + continue; + } + break; + } + " + }; + + // Labels: L1 (start), L2 (end), L3 (if end) + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 #a + L1: + add r1 r8 1 + move r8 r1 #a + slt r2 r8 5 + beq r2 0 L3 + j L1 + L3: + j L2 + j L1 + L2: + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs index e36da2c..7329091 100644 --- a/libs/compiler/src/v1.rs +++ b/libs/compiler/src/v1.rs @@ -77,7 +77,7 @@ pub struct Compiler<'a, W: std::io::Write> { config: CompilerConfig, temp_counter: usize, label_counter: usize, - loop_stack: Vec, // Stores the 'end' label of the current loops + loop_stack: Vec<(String, String)>, // Stores (start_label, end_label) } impl<'a, W: std::io::Write> Compiler<'a, W> { @@ -160,6 +160,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.expression_break()?; Ok(None) } + Expression::Continue => { + self.expression_continue()?; + Ok(None) + } Expression::DeviceDeclaration(expr_dev) => { self.expression_device(expr_dev)?; Ok(None) @@ -592,8 +596,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let start_label = self.next_label_name(); let end_label = self.next_label_name(); - // Push end label to stack for 'break' - self.loop_stack.push(end_label.clone()); + // Push labels to stack for 'break' and 'continue' + self.loop_stack + .push((start_label.clone(), end_label.clone())); self.write_output(format!("{start_label}:"))?; @@ -617,8 +622,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let start_label = self.next_label_name(); let end_label = self.next_label_name(); - // Push end label to stack for 'break' - self.loop_stack.push(end_label.clone()); + // Push labels to stack for 'break' and 'continue' + self.loop_stack + .push((start_label.clone(), end_label.clone())); self.write_output(format!("{start_label}:"))?; @@ -645,8 +651,8 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } fn expression_break(&mut self) -> Result<(), Error> { - if let Some(label) = self.loop_stack.last() { - self.write_output(format!("j {label}"))?; + if let Some((_, end_label)) = self.loop_stack.last() { + self.write_output(format!("j {end_label}"))?; Ok(()) } else { // This is a semantic error, but for now we can return a generic error @@ -655,6 +661,15 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { } } + fn expression_continue(&mut self) -> Result<(), Error> { + if let Some((start_label, _)) = self.loop_stack.last() { + self.write_output(format!("j {start_label}"))?; + Ok(()) + } else { + Err(Error::Unknown("Continue statement outside of loop".into())) + } + } + /// Helper to resolve a location to a register string (e.g., "r0"). /// Note: This does not handle Stack locations automatically, as they require /// instruction emission to load. Use `compile_operand` for general handling. diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index 92c5a12..308a4d6 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -235,6 +235,16 @@ impl Parser { Expression::Break } + // match continue statements + TokenType::Keyword(Keyword::Continue) => { + // make sure the next token is a semi-colon + let next = token_from_option!(self.get_next()?); + if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) { + return Err(Error::UnexpectedToken(next.clone())); + } + Expression::Continue + } + // match syscalls with a `syscall` keyword TokenType::Identifier(ref id) if SysCall::is_syscall(id) => { Expression::Syscall(self.syscall()?) @@ -1192,3 +1202,4 @@ impl Parser { } } } + diff --git a/libs/parser/src/tree_node.rs b/libs/parser/src/tree_node.rs index b634beb..3e49e09 100644 --- a/libs/parser/src/tree_node.rs +++ b/libs/parser/src/tree_node.rs @@ -214,6 +214,7 @@ pub enum Expression { Binary(BinaryExpression), Block(BlockExpression), Break, + Continue, Declaration(String, Box), DeviceDeclaration(DeviceDeclarationExpression), Function(FunctionExpression), @@ -237,6 +238,7 @@ impl std::fmt::Display for Expression { Expression::Binary(e) => write!(f, "{}", e), Expression::Block(e) => write!(f, "{}", e), Expression::Break => write!(f, "break"), + Expression::Continue => write!(f, "continue"), Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e), Expression::DeviceDeclaration(e) => write!(f, "{}", e), Expression::Function(e) => write!(f, "{}", e), @@ -254,4 +256,3 @@ impl std::fmt::Display for Expression { } } } - diff --git a/libs/tokenizer/src/lib.rs b/libs/tokenizer/src/lib.rs index 4afb2e2..9e0bfb8 100644 --- a/libs/tokenizer/src/lib.rs +++ b/libs/tokenizer/src/lib.rs @@ -410,6 +410,7 @@ impl Tokenizer { "loop" if next_ws!() => keyword!(Loop), "break" if next_ws!() => keyword!(Break), "while" if next_ws!() => keyword!(While), + "continue" if next_ws!() => keyword!(Continue), // boolean literals "true" if next_ws!() => { diff --git a/libs/tokenizer/src/token.rs b/libs/tokenizer/src/token.rs index 5e7d597..bea72dc 100644 --- a/libs/tokenizer/src/token.rs +++ b/libs/tokenizer/src/token.rs @@ -210,6 +210,8 @@ impl Symbol { #[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)] pub enum Keyword { + /// Represents the `continue` keyword + Continue, /// Represents the `let` keyword Let, /// Represents the `fn` keyword