//! Phase 168: BoolExprLowerer - Boolean Expression Lowering to SSA //! //! This module provides lowering of complex boolean expressions (AST → SSA) //! for use within JoinIR loop patterns. It handles: //! - Comparisons: `<`, `==`, `!=`, `<=`, `>=`, `>` //! - Logical operators: `&&`, `||`, `!` //! - Mixed conditions: `ch == " " || ch == "\t" || ch == "\n"` //! //! ## Design Philosophy //! //! BoolExprLowerer is a SEPARATE module from loop patterns (Pattern1-4). //! It focuses purely on expression lowering, while loop patterns handle //! control flow structure. //! //! **Separation of Concerns**: //! - Loop patterns (Pattern1-4): Loop structure (header, body, exit) //! - BoolExprLowerer: Expression evaluation (AST → SSA ValueId) //! //! ## Target Use Case //! //! JsonParserBox methods `_trim` and `_skip_whitespace` have OR chains: //! ```nyash //! ch == " " || ch == "\t" || ch == "\n" || ch == "\r" //! ``` //! //! This lowerer converts such expressions into SSA form: //! ```text //! %cmp1 = Compare Eq %ch " " //! %cmp2 = Compare Eq %ch "\t" //! %cmp3 = Compare Eq %ch "\n" //! %cmp4 = Compare Eq %ch "\r" //! %or1 = BinOp Or %cmp1 %cmp2 //! %or2 = BinOp Or %or1 %cmp3 //! %result = BinOp Or %or2 %cmp4 //! ``` use crate::ast::{ASTNode, BinaryOperator, UnaryOperator}; use crate::mir::builder::MirBuilder; use crate::mir::{BinaryOp, CompareOp, MirInstruction, MirType, ValueId}; /// BoolExprLowerer - Converts boolean expression AST to SSA form /// /// This box handles lowering of complex boolean expressions within loop conditions. /// It produces ValueIds that can be used by loop patterns for control flow decisions. pub struct BoolExprLowerer<'a> { builder: &'a mut MirBuilder, } impl<'a> BoolExprLowerer<'a> { /// Create a new BoolExprLowerer with access to MirBuilder pub fn new(builder: &'a mut MirBuilder) -> Self { BoolExprLowerer { builder } } /// Lower a boolean expression AST to SSA form /// /// # Arguments /// /// * `cond_ast` - AST node representing the boolean condition /// /// # Returns /// /// * `Result` - Register holding the result (bool 0/1), or error /// /// # Supported Operators /// /// - Comparisons: `<`, `==`, `!=`, `<=`, `>=`, `>` /// - Logical: `&&`, `||`, `!` /// - Variables and literals pub fn lower_condition(&mut self, cond_ast: &ASTNode) -> Result { match cond_ast { // Comparison operations: <, ==, !=, <=, >=, > ASTNode::BinaryOp { operator, left, right, .. } => match operator { BinaryOperator::Less | BinaryOperator::Equal | BinaryOperator::NotEqual | BinaryOperator::LessEqual | BinaryOperator::GreaterEqual | BinaryOperator::Greater => { // Lower comparison operators let lhs = self.lower_condition(left)?; let rhs = self.lower_condition(right)?; let dst = self.builder.next_value_id(); let cmp_op = match operator { BinaryOperator::Less => CompareOp::Lt, BinaryOperator::Equal => CompareOp::Eq, BinaryOperator::NotEqual => CompareOp::Ne, BinaryOperator::LessEqual => CompareOp::Le, BinaryOperator::GreaterEqual => CompareOp::Ge, BinaryOperator::Greater => CompareOp::Gt, _ => unreachable!(), }; // Emit Compare instruction self.builder .emit_instruction(MirInstruction::Compare { dst, op: cmp_op, lhs, rhs, })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); Ok(dst) } BinaryOperator::And => { // Logical AND: evaluate both sides and combine let lhs = self.lower_condition(left)?; let rhs = self.lower_condition(right)?; let dst = self.builder.next_value_id(); // Emit BinOp And instruction self.builder .emit_instruction(MirInstruction::BinOp { dst, op: BinaryOp::BitAnd, // Use BitAnd for logical AND lhs, rhs, })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); Ok(dst) } BinaryOperator::Or => { // Logical OR: evaluate both sides and combine let lhs = self.lower_condition(left)?; let rhs = self.lower_condition(right)?; let dst = self.builder.next_value_id(); // Emit BinOp Or instruction self.builder .emit_instruction(MirInstruction::BinOp { dst, op: BinaryOp::BitOr, // Use BitOr for logical OR lhs, rhs, })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); Ok(dst) } _ => { // Other operators (arithmetic, etc.) - delegate to builder self.builder.build_expression(cond_ast.clone()) } }, // Unary NOT operator ASTNode::UnaryOp { operator: UnaryOperator::Not, operand, .. } => { let operand_val = self.lower_condition(operand)?; let dst = self.builder.next_value_id(); // Emit UnaryOp Not instruction self.builder .emit_instruction(MirInstruction::UnaryOp { dst, op: crate::mir::UnaryOp::Not, operand: operand_val, })?; // Mark result type as Bool self.builder.value_types.insert(dst, MirType::Bool); Ok(dst) } // Variables - delegate to builder ASTNode::Variable { .. } => self.builder.build_expression(cond_ast.clone()), // Literals - delegate to builder ASTNode::Literal { .. } => self.builder.build_expression(cond_ast.clone()), // Method calls - delegate to builder ASTNode::MethodCall { .. } => self.builder.build_expression(cond_ast.clone()), // Field access - delegate to builder ASTNode::FieldAccess { .. } => self.builder.build_expression(cond_ast.clone()), // Other operators - delegate to builder _ => self.builder.build_expression(cond_ast.clone()), } } } #[cfg(test)] mod tests { use super::*; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use crate::mir::builder::MirBuilder; use crate::mir::FunctionSignature; /// Helper to create a test MirBuilder fn create_test_builder() -> MirBuilder { let mut builder = MirBuilder::new(); // Initialize a test function let sig = FunctionSignature { name: "test_function".to_string(), params: vec!["i".to_string(), "ch".to_string()], arity: 2, return_type: crate::mir::MirType::Integer, }; builder.start_function(sig); builder.start_new_block(); builder } /// Test: Simple comparison (i < 10) #[test] fn test_simple_comparison() { let mut builder = create_test_builder(); let mut lowerer = BoolExprLowerer::new(&mut builder); // AST: i < 10 let ast = ASTNode::BinaryOp { operator: BinaryOperator::Less, left: Box::new(ASTNode::Variable { name: "i".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Literal { value: LiteralValue::Integer(10), span: Span::unknown(), }), span: Span::unknown(), }; let result = lowerer.lower_condition(&ast); assert!(result.is_ok(), "Simple comparison should succeed"); } /// Test: OR chain (ch == " " || ch == "\t") #[test] fn test_or_chain() { let mut builder = create_test_builder(); let mut lowerer = BoolExprLowerer::new(&mut builder); // AST: ch == " " || ch == "\t" let ast = ASTNode::BinaryOp { operator: BinaryOperator::Or, left: Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(ASTNode::Variable { name: "ch".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Literal { value: LiteralValue::String(" ".to_string()), span: Span::unknown(), }), span: Span::unknown(), }), right: Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(ASTNode::Variable { name: "ch".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Literal { value: LiteralValue::String("\t".to_string()), span: Span::unknown(), }), span: Span::unknown(), }), span: Span::unknown(), }; let result = lowerer.lower_condition(&ast); assert!(result.is_ok(), "OR chain should succeed"); } /// Test: Complex mixed condition (i < len && (c == " " || c == "\t")) #[test] fn test_complex_mixed_condition() { let mut builder = create_test_builder(); let mut lowerer = BoolExprLowerer::new(&mut builder); // AST: i < len && (c == " " || c == "\t") let ast = ASTNode::BinaryOp { operator: BinaryOperator::And, left: Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Less, left: Box::new(ASTNode::Variable { name: "i".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Variable { name: "len".to_string(), span: Span::unknown(), }), span: Span::unknown(), }), right: Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Or, left: Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(ASTNode::Variable { name: "c".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Literal { value: LiteralValue::String(" ".to_string()), span: Span::unknown(), }), span: Span::unknown(), }), right: Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(ASTNode::Variable { name: "c".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Literal { value: LiteralValue::String("\t".to_string()), span: Span::unknown(), }), span: Span::unknown(), }), span: Span::unknown(), }), span: Span::unknown(), }; let result = lowerer.lower_condition(&ast); assert!(result.is_ok(), "Complex mixed condition should succeed"); } /// Test: NOT operator (!condition) #[test] fn test_not_operator() { let mut builder = create_test_builder(); let mut lowerer = BoolExprLowerer::new(&mut builder); // AST: !(i < 10) let ast = ASTNode::UnaryOp { operator: crate::ast::UnaryOperator::Not, operand: Box::new(ASTNode::BinaryOp { operator: BinaryOperator::Less, left: Box::new(ASTNode::Variable { name: "i".to_string(), span: Span::unknown(), }), right: Box::new(ASTNode::Literal { value: LiteralValue::Integer(10), span: Span::unknown(), }), span: Span::unknown(), }), span: Span::unknown(), }; let result = lowerer.lower_condition(&ast); assert!(result.is_ok(), "NOT operator should succeed"); } }