455 lines
15 KiB
Rust
455 lines
15 KiB
Rust
|
|
//! Phase 231: Expression Lowering with Unified Scope Management
|
||
|
|
//!
|
||
|
|
//! This module provides a pilot implementation of expression lowering that uses
|
||
|
|
//! ScopeManager for variable resolution. It's a thin wrapper around existing
|
||
|
|
//! condition_lowerer logic, focusing on API unification rather than reimplementation.
|
||
|
|
//!
|
||
|
|
//! ## Design Philosophy
|
||
|
|
//!
|
||
|
|
//! **Box-First**: ExprLowerer is a "box" that encapsulates expression lowering
|
||
|
|
//! logic with clean boundaries: takes AST + ScopeManager, returns ValueId + instructions.
|
||
|
|
//!
|
||
|
|
//! **Incremental Adoption**: Phase 231 starts with Condition context only.
|
||
|
|
//! Future phases will expand to support General expressions (method calls, etc.).
|
||
|
|
//!
|
||
|
|
//! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers
|
||
|
|
//! to fall back to legacy paths.
|
||
|
|
|
||
|
|
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
|
||
|
|
use crate::mir::ValueId;
|
||
|
|
use crate::mir::builder::MirBuilder;
|
||
|
|
use super::scope_manager::ScopeManager;
|
||
|
|
use super::condition_lowerer::lower_condition_to_joinir;
|
||
|
|
use super::condition_env::ConditionEnv;
|
||
|
|
|
||
|
|
/// Phase 231: Expression lowering context
|
||
|
|
///
|
||
|
|
/// Defines the context in which an expression is being lowered, which affects
|
||
|
|
/// what AST nodes are supported and how they're translated.
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
|
pub enum ExprContext {
|
||
|
|
/// Loop condition expression (limited subset: comparisons, logical ops)
|
||
|
|
Condition,
|
||
|
|
|
||
|
|
/// General expression (future: method calls, box ops, etc.)
|
||
|
|
#[allow(dead_code)] // Phase 231: Not yet implemented
|
||
|
|
General,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Phase 231: Expression lowering error
|
||
|
|
///
|
||
|
|
/// Explicit error types allow callers to handle different failure modes
|
||
|
|
/// (e.g., fall back to legacy path for unsupported nodes).
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub enum ExprLoweringError {
|
||
|
|
/// AST node type not supported in this context
|
||
|
|
UnsupportedNode(String),
|
||
|
|
|
||
|
|
/// Variable not found in any scope
|
||
|
|
VariableNotFound(String),
|
||
|
|
|
||
|
|
/// Type error during lowering (e.g., non-boolean in condition)
|
||
|
|
TypeError(String),
|
||
|
|
|
||
|
|
/// Internal lowering error (from condition_lowerer)
|
||
|
|
LoweringError(String),
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::fmt::Display for ExprLoweringError {
|
||
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
|
|
match self {
|
||
|
|
ExprLoweringError::UnsupportedNode(msg) => write!(f, "Unsupported node: {}", msg),
|
||
|
|
ExprLoweringError::VariableNotFound(name) => write!(f, "Variable not found: {}", name),
|
||
|
|
ExprLoweringError::TypeError(msg) => write!(f, "Type error: {}", msg),
|
||
|
|
ExprLoweringError::LoweringError(msg) => write!(f, "Lowering error: {}", msg),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Phase 231: Expression lowerer (pilot implementation)
|
||
|
|
///
|
||
|
|
/// This struct provides a unified interface for lowering AST expressions to
|
||
|
|
/// JoinIR instructions, using ScopeManager for variable resolution.
|
||
|
|
///
|
||
|
|
/// ## Current Scope (Phase 231)
|
||
|
|
///
|
||
|
|
/// - **Context**: Condition only (loop/break conditions)
|
||
|
|
/// - **Supported**: Literals, variables, comparisons (<, >, ==, !=, <=, >=), logical ops (and, or, not)
|
||
|
|
/// - **Not Supported**: Method calls, NewBox, complex expressions
|
||
|
|
///
|
||
|
|
/// ## Usage Pattern
|
||
|
|
///
|
||
|
|
/// ```ignore
|
||
|
|
/// let scope = Pattern2ScopeManager { ... };
|
||
|
|
/// let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, builder);
|
||
|
|
///
|
||
|
|
/// match expr_lowerer.lower(&break_condition_ast) {
|
||
|
|
/// Ok(value_id) => {
|
||
|
|
/// // Use value_id in JoinIR
|
||
|
|
/// }
|
||
|
|
/// Err(ExprLoweringError::UnsupportedNode(_)) => {
|
||
|
|
/// // Fall back to legacy condition_to_joinir path
|
||
|
|
/// }
|
||
|
|
/// Err(e) => {
|
||
|
|
/// // Handle other errors (variable not found, etc.)
|
||
|
|
/// }
|
||
|
|
/// }
|
||
|
|
/// ```
|
||
|
|
pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
|
||
|
|
/// Scope manager for variable resolution
|
||
|
|
scope: &'env S,
|
||
|
|
|
||
|
|
/// Expression context (Condition vs General)
|
||
|
|
context: ExprContext,
|
||
|
|
|
||
|
|
/// MIR builder (for ValueId allocation, not used in Phase 231)
|
||
|
|
#[allow(dead_code)] // Phase 231: Reserved for future use
|
||
|
|
builder: &'builder mut MirBuilder,
|
||
|
|
|
||
|
|
/// Debug flag (inherited from caller)
|
||
|
|
debug: bool,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||
|
|
/// Create a new expression lowerer
|
||
|
|
///
|
||
|
|
/// # Arguments
|
||
|
|
///
|
||
|
|
/// * `scope` - ScopeManager for variable resolution
|
||
|
|
/// * `context` - Expression context (Condition or General)
|
||
|
|
/// * `builder` - MIR builder (for future use)
|
||
|
|
pub fn new(scope: &'env S, context: ExprContext, builder: &'builder mut MirBuilder) -> Self {
|
||
|
|
Self {
|
||
|
|
scope,
|
||
|
|
context,
|
||
|
|
builder,
|
||
|
|
debug: false,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Enable debug output
|
||
|
|
pub fn with_debug(mut self, debug: bool) -> Self {
|
||
|
|
self.debug = debug;
|
||
|
|
self
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Lower an expression to JoinIR ValueId
|
||
|
|
///
|
||
|
|
/// Phase 231: This is the main entry point. Currently delegates to
|
||
|
|
/// lower_condition for Condition context.
|
||
|
|
///
|
||
|
|
/// # Returns
|
||
|
|
///
|
||
|
|
/// * `Ok(ValueId)` - Expression result ValueId
|
||
|
|
/// * `Err(ExprLoweringError)` - Lowering failed (caller can fall back to legacy)
|
||
|
|
pub fn lower(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||
|
|
match self.context {
|
||
|
|
ExprContext::Condition => self.lower_condition(ast),
|
||
|
|
ExprContext::General => {
|
||
|
|
Err(ExprLoweringError::UnsupportedNode(
|
||
|
|
"General expression context not yet implemented (Phase 231)".to_string()
|
||
|
|
))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Lower a condition expression to JoinIR ValueId
|
||
|
|
///
|
||
|
|
/// Phase 231: Thin wrapper around condition_lowerer. The main innovation
|
||
|
|
/// is using ScopeManager for variable resolution instead of direct ConditionEnv.
|
||
|
|
///
|
||
|
|
/// # Returns
|
||
|
|
///
|
||
|
|
/// * `Ok(ValueId)` - Condition result ValueId (boolean)
|
||
|
|
/// * `Err(ExprLoweringError)` - Lowering failed
|
||
|
|
fn lower_condition(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||
|
|
// 1. Check if AST is supported in condition context
|
||
|
|
if !Self::is_supported_condition(ast) {
|
||
|
|
return Err(ExprLoweringError::UnsupportedNode(
|
||
|
|
format!("Unsupported condition node: {:?}", ast)
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Build ConditionEnv from ScopeManager
|
||
|
|
// This is the key integration point: we translate ScopeManager's view
|
||
|
|
// into the ConditionEnv format expected by condition_lowerer.
|
||
|
|
let condition_env = self.build_condition_env_from_scope(ast)?;
|
||
|
|
|
||
|
|
// 3. Delegate to existing condition_lowerer
|
||
|
|
// Phase 231: We use the existing, well-tested lowering logic.
|
||
|
|
let mut value_counter = 1000u32; // Phase 231: Start high to avoid collisions
|
||
|
|
let mut alloc_value = || {
|
||
|
|
let id = ValueId(value_counter);
|
||
|
|
value_counter += 1;
|
||
|
|
id
|
||
|
|
};
|
||
|
|
|
||
|
|
let (result_value, _instructions) = lower_condition_to_joinir(
|
||
|
|
ast,
|
||
|
|
&mut alloc_value,
|
||
|
|
&condition_env,
|
||
|
|
).map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||
|
|
|
||
|
|
if self.debug {
|
||
|
|
eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value);
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(result_value)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Build ConditionEnv from ScopeManager
|
||
|
|
///
|
||
|
|
/// This method extracts all variables referenced in the AST and resolves
|
||
|
|
/// them through ScopeManager, building a ConditionEnv for condition_lowerer.
|
||
|
|
fn build_condition_env_from_scope(&self, ast: &ASTNode) -> Result<ConditionEnv, ExprLoweringError> {
|
||
|
|
let mut env = ConditionEnv::new();
|
||
|
|
|
||
|
|
// Extract all variable names from the AST
|
||
|
|
let var_names = Self::extract_variable_names(ast);
|
||
|
|
|
||
|
|
// Resolve each variable through ScopeManager
|
||
|
|
for name in var_names {
|
||
|
|
if let Some(value_id) = self.scope.lookup(&name) {
|
||
|
|
env.insert(name.clone(), value_id);
|
||
|
|
} else {
|
||
|
|
return Err(ExprLoweringError::VariableNotFound(name));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(env)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Extract all variable names from an AST node (recursively)
|
||
|
|
fn extract_variable_names(ast: &ASTNode) -> Vec<String> {
|
||
|
|
let mut names = Vec::new();
|
||
|
|
Self::extract_variable_names_recursive(ast, &mut names);
|
||
|
|
names.sort();
|
||
|
|
names.dedup();
|
||
|
|
names
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Recursive helper for variable name extraction
|
||
|
|
fn extract_variable_names_recursive(ast: &ASTNode, names: &mut Vec<String>) {
|
||
|
|
match ast {
|
||
|
|
ASTNode::Variable { name, .. } => {
|
||
|
|
names.push(name.clone());
|
||
|
|
}
|
||
|
|
ASTNode::BinaryOp { left, right, .. } => {
|
||
|
|
Self::extract_variable_names_recursive(left, names);
|
||
|
|
Self::extract_variable_names_recursive(right, names);
|
||
|
|
}
|
||
|
|
ASTNode::UnaryOp { operand, .. } => {
|
||
|
|
Self::extract_variable_names_recursive(operand, names);
|
||
|
|
}
|
||
|
|
// Phase 231: Only support simple expressions
|
||
|
|
_ => {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if an AST node is supported in condition context
|
||
|
|
///
|
||
|
|
/// Phase 231: Conservative whitelist. We only support patterns we know work.
|
||
|
|
fn is_supported_condition(ast: &ASTNode) -> bool {
|
||
|
|
match ast {
|
||
|
|
// Literals: Integer, Bool
|
||
|
|
ASTNode::Literal { .. } => true,
|
||
|
|
|
||
|
|
// Variables
|
||
|
|
ASTNode::Variable { .. } => true,
|
||
|
|
|
||
|
|
// Comparison operators
|
||
|
|
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||
|
|
let op_supported = matches!(
|
||
|
|
operator,
|
||
|
|
BinaryOperator::Less
|
||
|
|
| BinaryOperator::Greater
|
||
|
|
| BinaryOperator::Equal
|
||
|
|
| BinaryOperator::NotEqual
|
||
|
|
| BinaryOperator::LessEqual
|
||
|
|
| BinaryOperator::GreaterEqual
|
||
|
|
| BinaryOperator::And
|
||
|
|
| BinaryOperator::Or
|
||
|
|
);
|
||
|
|
|
||
|
|
op_supported
|
||
|
|
&& Self::is_supported_condition(left)
|
||
|
|
&& Self::is_supported_condition(right)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Unary operators (not)
|
||
|
|
ASTNode::UnaryOp { operator, operand, .. } => {
|
||
|
|
matches!(operator, UnaryOperator::Not)
|
||
|
|
&& Self::is_supported_condition(operand)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Everything else is unsupported
|
||
|
|
_ => false,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
use crate::ast::{Span, LiteralValue};
|
||
|
|
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, VarScopeKind};
|
||
|
|
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||
|
|
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||
|
|
|
||
|
|
// Helper to create a test MirBuilder (Phase 231: minimal stub)
|
||
|
|
fn create_test_builder() -> MirBuilder {
|
||
|
|
MirBuilder::new()
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_expr_lowerer_simple_comparison() {
|
||
|
|
let mut condition_env = ConditionEnv::new();
|
||
|
|
condition_env.insert("i".to_string(), ValueId(100));
|
||
|
|
|
||
|
|
let carrier_info = CarrierInfo {
|
||
|
|
loop_var_name: "i".to_string(),
|
||
|
|
loop_var_id: ValueId(1),
|
||
|
|
carriers: vec![],
|
||
|
|
trim_helper: None,
|
||
|
|
promoted_loopbodylocals: vec![],
|
||
|
|
};
|
||
|
|
|
||
|
|
let scope = Pattern2ScopeManager {
|
||
|
|
condition_env: &condition_env,
|
||
|
|
loop_body_local_env: None,
|
||
|
|
captured_env: None,
|
||
|
|
carrier_info: &carrier_info,
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut builder = create_test_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 mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||
|
|
let result = expr_lowerer.lower(&ast);
|
||
|
|
|
||
|
|
assert!(result.is_ok(), "Should lower simple comparison successfully");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_expr_lowerer_variable_not_found() {
|
||
|
|
let condition_env = ConditionEnv::new();
|
||
|
|
|
||
|
|
let carrier_info = CarrierInfo {
|
||
|
|
loop_var_name: "i".to_string(),
|
||
|
|
loop_var_id: ValueId(1),
|
||
|
|
carriers: vec![],
|
||
|
|
trim_helper: None,
|
||
|
|
promoted_loopbodylocals: vec![],
|
||
|
|
};
|
||
|
|
|
||
|
|
let scope = Pattern2ScopeManager {
|
||
|
|
condition_env: &condition_env,
|
||
|
|
loop_body_local_env: None,
|
||
|
|
captured_env: None,
|
||
|
|
carrier_info: &carrier_info,
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut builder = create_test_builder();
|
||
|
|
|
||
|
|
// AST: unknown_var < 10
|
||
|
|
let ast = ASTNode::BinaryOp {
|
||
|
|
operator: BinaryOperator::Less,
|
||
|
|
left: Box::new(ASTNode::Variable {
|
||
|
|
name: "unknown_var".to_string(),
|
||
|
|
span: Span::unknown(),
|
||
|
|
}),
|
||
|
|
right: Box::new(ASTNode::Literal {
|
||
|
|
value: LiteralValue::Integer(10),
|
||
|
|
span: Span::unknown(),
|
||
|
|
}),
|
||
|
|
span: Span::unknown(),
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||
|
|
let result = expr_lowerer.lower(&ast);
|
||
|
|
|
||
|
|
assert!(matches!(result, Err(ExprLoweringError::VariableNotFound(_))));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_expr_lowerer_unsupported_node() {
|
||
|
|
let condition_env = ConditionEnv::new();
|
||
|
|
|
||
|
|
let carrier_info = CarrierInfo {
|
||
|
|
loop_var_name: "i".to_string(),
|
||
|
|
loop_var_id: ValueId(1),
|
||
|
|
carriers: vec![],
|
||
|
|
trim_helper: None,
|
||
|
|
promoted_loopbodylocals: vec![],
|
||
|
|
};
|
||
|
|
|
||
|
|
let scope = Pattern2ScopeManager {
|
||
|
|
condition_env: &condition_env,
|
||
|
|
loop_body_local_env: None,
|
||
|
|
captured_env: None,
|
||
|
|
carrier_info: &carrier_info,
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut builder = create_test_builder();
|
||
|
|
|
||
|
|
// AST: MethodCall (unsupported in condition context)
|
||
|
|
let ast = ASTNode::MethodCall {
|
||
|
|
object: Box::new(ASTNode::Variable {
|
||
|
|
name: "s".to_string(),
|
||
|
|
span: Span::unknown(),
|
||
|
|
}),
|
||
|
|
method: "length".to_string(),
|
||
|
|
arguments: vec![],
|
||
|
|
span: Span::unknown(),
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||
|
|
let result = expr_lowerer.lower(&ast);
|
||
|
|
|
||
|
|
assert!(matches!(result, Err(ExprLoweringError::UnsupportedNode(_))));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_is_supported_condition() {
|
||
|
|
// Supported: 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(),
|
||
|
|
};
|
||
|
|
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||
|
|
|
||
|
|
// Unsupported: MethodCall
|
||
|
|
let ast = ASTNode::MethodCall {
|
||
|
|
object: Box::new(ASTNode::Variable {
|
||
|
|
name: "s".to_string(),
|
||
|
|
span: Span::unknown(),
|
||
|
|
}),
|
||
|
|
method: "length".to_string(),
|
||
|
|
arguments: vec![],
|
||
|
|
span: Span::unknown(),
|
||
|
|
};
|
||
|
|
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||
|
|
}
|
||
|
|
}
|