249 lines
8.3 KiB
Rust
249 lines
8.3 KiB
Rust
|
|
//! Phase 184: Update Expression Environment
|
||
|
|
//!
|
||
|
|
//! This module provides a unified variable resolution layer for carrier update expressions.
|
||
|
|
//! It combines ConditionEnv (condition variables) and LoopBodyLocalEnv (body-local variables)
|
||
|
|
//! with clear priority order.
|
||
|
|
//!
|
||
|
|
//! ## Design Philosophy
|
||
|
|
//!
|
||
|
|
//! **Single Responsibility**: This module ONLY handles variable resolution priority logic.
|
||
|
|
//! It does NOT:
|
||
|
|
//! - Store variables (that's ConditionEnv and LoopBodyLocalEnv)
|
||
|
|
//! - Lower AST to JoinIR (that's JoinIrBuilder)
|
||
|
|
//! - Emit update instructions (that's CarrierUpdateEmitter)
|
||
|
|
//!
|
||
|
|
//! ## Box-First Design
|
||
|
|
//!
|
||
|
|
//! Following 箱理論 (Box Theory) principles:
|
||
|
|
//! - **Composition**: Combines two environments without owning them
|
||
|
|
//! - **Clear priority**: Condition variables take precedence
|
||
|
|
//! - **Lightweight**: No allocation, just references
|
||
|
|
|
||
|
|
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||
|
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||
|
|
use crate::mir::ValueId;
|
||
|
|
|
||
|
|
/// Unified environment for carrier update expression variable resolution
|
||
|
|
///
|
||
|
|
/// This structure provides a composition layer that resolves variables
|
||
|
|
/// with the following priority order:
|
||
|
|
///
|
||
|
|
/// 1. **Condition variables** (ConditionEnv) - Highest priority
|
||
|
|
/// - Loop parameters (e.g., `i`, `end`, `p`)
|
||
|
|
/// - Variables used in condition expressions
|
||
|
|
///
|
||
|
|
/// 2. **Body-local variables** (LoopBodyLocalEnv) - Fallback priority
|
||
|
|
/// - Variables declared in loop body (e.g., `local temp`)
|
||
|
|
/// - Only accessible if not shadowed by condition variables
|
||
|
|
///
|
||
|
|
/// # Example
|
||
|
|
///
|
||
|
|
/// ```nyash
|
||
|
|
/// loop(i < 5) { // i in ConditionEnv
|
||
|
|
/// local temp = i * 2 // temp in LoopBodyLocalEnv
|
||
|
|
/// sum = sum + temp // Resolves: sum (cond), temp (body)
|
||
|
|
/// i = i + 1
|
||
|
|
/// }
|
||
|
|
/// ```
|
||
|
|
///
|
||
|
|
/// ```ignore
|
||
|
|
/// let condition_env = /* ... i, sum ... */;
|
||
|
|
/// let body_local_env = /* ... temp ... */;
|
||
|
|
/// let update_env = UpdateEnv::new(&condition_env, &body_local_env);
|
||
|
|
///
|
||
|
|
/// // Resolve "sum" → ConditionEnv (priority 1)
|
||
|
|
/// assert_eq!(update_env.resolve("sum"), Some(ValueId(X)));
|
||
|
|
///
|
||
|
|
/// // Resolve "temp" → LoopBodyLocalEnv (priority 2)
|
||
|
|
/// assert_eq!(update_env.resolve("temp"), Some(ValueId(Y)));
|
||
|
|
///
|
||
|
|
/// // Resolve "unknown" → None
|
||
|
|
/// assert_eq!(update_env.resolve("unknown"), None);
|
||
|
|
/// ```
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub struct UpdateEnv<'a> {
|
||
|
|
/// Condition variable environment (priority 1)
|
||
|
|
condition_env: &'a ConditionEnv,
|
||
|
|
|
||
|
|
/// Body-local variable environment (priority 2)
|
||
|
|
body_local_env: &'a LoopBodyLocalEnv,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<'a> UpdateEnv<'a> {
|
||
|
|
/// Create a new UpdateEnv with priority-ordered resolution
|
||
|
|
///
|
||
|
|
/// # Arguments
|
||
|
|
///
|
||
|
|
/// * `condition_env` - Condition variable environment (highest priority)
|
||
|
|
/// * `body_local_env` - Body-local variable environment (fallback)
|
||
|
|
pub fn new(
|
||
|
|
condition_env: &'a ConditionEnv,
|
||
|
|
body_local_env: &'a LoopBodyLocalEnv,
|
||
|
|
) -> Self {
|
||
|
|
Self {
|
||
|
|
condition_env,
|
||
|
|
body_local_env,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Resolve a variable name to JoinIR ValueId
|
||
|
|
///
|
||
|
|
/// Resolution order:
|
||
|
|
/// 1. Try condition_env.get(name)
|
||
|
|
/// 2. If not found, try body_local_env.get(name)
|
||
|
|
/// 3. If still not found, return None
|
||
|
|
///
|
||
|
|
/// # Arguments
|
||
|
|
///
|
||
|
|
/// * `name` - Variable name to resolve
|
||
|
|
///
|
||
|
|
/// # Returns
|
||
|
|
///
|
||
|
|
/// * `Some(ValueId)` - Variable found in one of the environments
|
||
|
|
/// * `None` - Variable not found in either environment
|
||
|
|
pub fn resolve(&self, name: &str) -> Option<ValueId> {
|
||
|
|
self.condition_env
|
||
|
|
.get(name)
|
||
|
|
.or_else(|| self.body_local_env.get(name))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if a variable exists in either environment
|
||
|
|
pub fn contains(&self, name: &str) -> bool {
|
||
|
|
self.resolve(name).is_some()
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get reference to condition environment (for debugging/diagnostics)
|
||
|
|
pub fn condition_env(&self) -> &ConditionEnv {
|
||
|
|
self.condition_env
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get reference to body-local environment (for debugging/diagnostics)
|
||
|
|
pub fn body_local_env(&self) -> &LoopBodyLocalEnv {
|
||
|
|
self.body_local_env
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
// Helper: Create a test ConditionEnv
|
||
|
|
fn test_condition_env() -> ConditionEnv {
|
||
|
|
let mut env = ConditionEnv::new();
|
||
|
|
env.insert("i".to_string(), ValueId(10));
|
||
|
|
env.insert("sum".to_string(), ValueId(20));
|
||
|
|
env.insert("end".to_string(), ValueId(30));
|
||
|
|
env
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper: Create a test LoopBodyLocalEnv
|
||
|
|
fn test_body_local_env() -> LoopBodyLocalEnv {
|
||
|
|
let mut env = LoopBodyLocalEnv::new();
|
||
|
|
env.insert("temp".to_string(), ValueId(50));
|
||
|
|
env.insert("digit".to_string(), ValueId(60));
|
||
|
|
env
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_resolve_condition_priority() {
|
||
|
|
// Condition variables should be found first
|
||
|
|
let cond_env = test_condition_env();
|
||
|
|
let body_env = LoopBodyLocalEnv::new(); // Empty
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
assert_eq!(update_env.resolve("i"), Some(ValueId(10)));
|
||
|
|
assert_eq!(update_env.resolve("sum"), Some(ValueId(20)));
|
||
|
|
assert_eq!(update_env.resolve("end"), Some(ValueId(30)));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_resolve_body_local_fallback() {
|
||
|
|
// Body-local variables should be found when not in condition env
|
||
|
|
let cond_env = ConditionEnv::new(); // Empty
|
||
|
|
let body_env = test_body_local_env();
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
assert_eq!(update_env.resolve("temp"), Some(ValueId(50)));
|
||
|
|
assert_eq!(update_env.resolve("digit"), Some(ValueId(60)));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_resolve_priority_order() {
|
||
|
|
// Condition env takes priority over body-local env
|
||
|
|
let mut cond_env = ConditionEnv::new();
|
||
|
|
cond_env.insert("x".to_string(), ValueId(100)); // Condition: x=100
|
||
|
|
|
||
|
|
let mut body_env = LoopBodyLocalEnv::new();
|
||
|
|
body_env.insert("x".to_string(), ValueId(200)); // Body-local: x=200
|
||
|
|
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
// Should resolve to condition env value (100), not body-local (200)
|
||
|
|
assert_eq!(update_env.resolve("x"), Some(ValueId(100)));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_resolve_not_found() {
|
||
|
|
// Variable not in either environment → None
|
||
|
|
let cond_env = test_condition_env();
|
||
|
|
let body_env = test_body_local_env();
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
assert_eq!(update_env.resolve("unknown"), None);
|
||
|
|
assert_eq!(update_env.resolve("nonexistent"), None);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_resolve_combined_lookup() {
|
||
|
|
// Mixed lookup: some in condition, some in body-local
|
||
|
|
let cond_env = test_condition_env();
|
||
|
|
let body_env = test_body_local_env();
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
// Condition variables
|
||
|
|
assert_eq!(update_env.resolve("i"), Some(ValueId(10)));
|
||
|
|
assert_eq!(update_env.resolve("sum"), Some(ValueId(20)));
|
||
|
|
|
||
|
|
// Body-local variables
|
||
|
|
assert_eq!(update_env.resolve("temp"), Some(ValueId(50)));
|
||
|
|
assert_eq!(update_env.resolve("digit"), Some(ValueId(60)));
|
||
|
|
|
||
|
|
// Not found
|
||
|
|
assert_eq!(update_env.resolve("unknown"), None);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_contains() {
|
||
|
|
let cond_env = test_condition_env();
|
||
|
|
let body_env = test_body_local_env();
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
assert!(update_env.contains("i"));
|
||
|
|
assert!(update_env.contains("temp"));
|
||
|
|
assert!(!update_env.contains("unknown"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_empty_environments() {
|
||
|
|
// Both environments empty
|
||
|
|
let cond_env = ConditionEnv::new();
|
||
|
|
let body_env = LoopBodyLocalEnv::new();
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
assert_eq!(update_env.resolve("anything"), None);
|
||
|
|
assert!(!update_env.contains("anything"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_accessor_methods() {
|
||
|
|
// Test diagnostic accessor methods
|
||
|
|
let cond_env = test_condition_env();
|
||
|
|
let body_env = test_body_local_env();
|
||
|
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||
|
|
|
||
|
|
// Should return references to underlying environments
|
||
|
|
assert_eq!(update_env.condition_env().len(), 3);
|
||
|
|
assert_eq!(update_env.body_local_env().len(), 2);
|
||
|
|
}
|
||
|
|
}
|