Files
hakorune/src/mir/join_ir/lowering/bool_expr_lowerer.rs

372 lines
14 KiB
Rust
Raw Normal View History

//! 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<ValueId, String>` - 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<ValueId, String> {
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()),
}
}
}
// TODO: These tests need to be updated to use the new MirBuilder API
// #[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");
// }
// }