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

371 lines
13 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()),
}
}
}
#[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");
}
}