diff --git a/libs/compiler/src/test/binary_expression.rs b/libs/compiler/src/test/binary_expression.rs index 68ccba6..8f890d4 100644 --- a/libs/compiler/src/test/binary_expression.rs +++ b/libs/compiler/src/test/binary_expression.rs @@ -84,6 +84,14 @@ fn stress_test_negation_with_stack_spillover() -> anyhow::Result<()> { compiled, indoc! { " + j main + main: + add r1 -1 -2 + add r2 -5 -6 + mul r3 -4 r2 + add r4 -3 r3 + mul r5 r1 r4 + move r8 r5 #negationHell " } ); diff --git a/libs/compiler/src/v2.rs b/libs/compiler/src/v2.rs index bd16f8c..a0e0098 100644 --- a/libs/compiler/src/v2.rs +++ b/libs/compiler/src/v2.rs @@ -747,3 +747,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 af35d12..4decc64 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -152,7 +152,33 @@ impl Parser { Ok(self.current_token.as_ref()) } + /// Parses an expression, handling binary operations with correct precedence. fn expression(&mut self) -> Result, Error> { + // Parse the Left Hand Side (unary/primary expression) + let lhs = self.unary()?; + + let Some(lhs) = lhs else { + 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)?))); + } + // 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()) { + self.tokenizer.seek(SeekFrom::Current(-1))?; + return Ok(Some(Expression::Binary(self.binary(lhs)?))); + } + + Ok(Some(lhs)) + } + + /// Parses a unary or primary expression. + /// This handles prefix operators (like negation) and atomic expressions (literals, variables, etc.), + /// but stops before consuming binary operators. + fn unary(&mut self) -> Result, Error> { macro_rules! matches_keyword { ($keyword:expr, $($pattern:pat),+) => { matches!($keyword, $($pattern)|+) @@ -167,7 +193,7 @@ impl Parser { return Ok(None); } - let expr = Some(match current_token.token_type { + let expr = match current_token.token_type { // match unsupported keywords TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) => @@ -217,7 +243,10 @@ impl Parser { // match minus symbols to handle negative numbers or negated expressions TokenType::Symbol(Symbol::Minus) => { self.assign_next()?; // consume the `-` symbol - let inner_expr = self.expression()?.ok_or(Error::UnexpectedEOF)?; + // IMPORTANT: We call `unary()` here, NOT `expression()`. + // This ensures negation binds tightly to the operand and doesn't consume binary ops. + // e.g. `-1 + 2` parses as `(-1) + 2` + let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?; Expression::Negation(boxed!(inner_expr)) } @@ -225,23 +254,8 @@ impl Parser { _ => { return Err(Error::UnexpectedToken(current_token.clone())); } - }); - - let Some(expr) = expr else { - 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(expr)?))); - } - // 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()) { - self.tokenizer.seek(SeekFrom::Current(-1))?; - return Ok(Some(Expression::Binary(self.binary(expr)?))); - } - Ok(Some(expr)) } @@ -259,17 +273,19 @@ impl Parser { } // A priority expression ( -> (1 + 2) <- + 3 ) TokenType::Symbol(Symbol::LParen) => self.priority().map(Expression::Priority), - TokenType::Symbol(Symbol::Minus) => { - self.assign_next()?; - let inner = self.get_binary_child_node()?; - Ok(Expression::Negation(boxed!(inner))) - } // A function invocation TokenType::Identifier(_) if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => { self.invocation().map(Expression::Invocation) } + // Handle Negation + TokenType::Symbol(Symbol::Minus) => { + self.assign_next()?; + // recurse to handle double negation or simple negation of atoms + let inner = self.get_binary_child_node()?; + Ok(Expression::Negation(boxed!(inner))) + } _ => Err(Error::UnexpectedToken(current_token.clone())), } }