371 lines
13 KiB
Rust
371 lines
13 KiB
Rust
|
|
//! 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");
|
||
|
|
}
|
||
|
|
}
|