feat(joinir): Phase 172 - Trim pattern JoinIR lowering implementation
Implement actual MIR generation for Trim pattern loops, enabling LoopBodyLocal variables to work through bool carrier promotion. ## Implementation (pattern2_with_break.rs) - emit_whitespace_check(): Generate OR chain for whitespace comparison - extract_substring_args(): Extract s, start from substring call - Initial carrier generation: ch0 = s.substring(start, start+1) - Whitespace check: is_ch_match0 = (ch0 == " " || "\t" || "\n" || "\r") - ConditionEnv integration: Register carrier for JoinIR condition - Break condition replacement: !is_ch_match instead of original ch checks ## Architecture - Host MIR: substring calls, OR chain evaluation, BoxCall - JoinIR: Only sees bool carrier for break control - No new JoinIR instructions added ## Documentation - phase172-trim-lowering-impl.md: Design and implementation details - loop_pattern_space.md: Analysis of all loop pattern combinations Test: Trim loops compile and generate JoinIR successfully Build: 0 errors, clean compilation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -5,6 +5,99 @@ use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
use super::super::trace;
|
||||
|
||||
// Phase 172: Helper function for Trim pattern whitespace checking
|
||||
/// Generate MIR for OR chain of whitespace character comparisons
|
||||
///
|
||||
/// Creates: ch == " " || ch == "\t" || ch == "\n" || ch == "\r" ...
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder` - MirBuilder for emitting instructions
|
||||
/// * `ch_value` - ValueId of character to check
|
||||
/// * `whitespace_chars` - List of whitespace characters to compare against
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// ValueId of bool result (true if ch matches any whitespace char)
|
||||
fn emit_whitespace_check(
|
||||
builder: &mut MirBuilder,
|
||||
ch_value: ValueId,
|
||||
whitespace_chars: &[String],
|
||||
) -> Result<ValueId, String> {
|
||||
use crate::mir::builder::emission::constant::emit_string;
|
||||
use crate::mir::builder::emission::compare::emit_eq_to;
|
||||
use crate::mir::types::BinaryOp;
|
||||
use crate::mir::instruction::MirInstruction;
|
||||
|
||||
if whitespace_chars.is_empty() {
|
||||
return Err("[emit_whitespace_check] Empty whitespace_chars".to_string());
|
||||
}
|
||||
|
||||
let mut result_opt: Option<ValueId> = None;
|
||||
|
||||
for ws_char in whitespace_chars {
|
||||
// ws_const = const " " (or "\t", etc.)
|
||||
let ws_const = emit_string(builder, ws_char.clone());
|
||||
|
||||
// eq_check = ch == ws_const
|
||||
let eq_dst = builder.value_gen.next();
|
||||
emit_eq_to(builder, eq_dst, ch_value, ws_const)?;
|
||||
|
||||
result_opt = Some(if let Some(prev_result) = result_opt {
|
||||
// result = prev_result || eq_check
|
||||
let dst = builder.value_gen.next();
|
||||
builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst,
|
||||
op: BinaryOp::Or,
|
||||
lhs: prev_result,
|
||||
rhs: eq_dst,
|
||||
})?;
|
||||
dst
|
||||
} else {
|
||||
// First comparison, no OR needed yet
|
||||
eq_dst
|
||||
});
|
||||
}
|
||||
|
||||
result_opt.ok_or_else(|| {
|
||||
"[emit_whitespace_check] Internal error: result should be Some".to_string()
|
||||
})
|
||||
}
|
||||
|
||||
// Phase 172-2: Extract substring arguments from Trim pattern
|
||||
/// Extract the substring method call arguments from loop body
|
||||
///
|
||||
/// Looks for pattern: local ch = s.substring(start, start+1)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// (object_name, start_expr) tuple if found
|
||||
fn extract_substring_args(loop_body: &[ASTNode], var_name: &str) -> Option<(String, Box<ASTNode>)> {
|
||||
for stmt in loop_body {
|
||||
// Look for: local ch = ...
|
||||
if let ASTNode::Local { variables, initial_values, .. } = stmt {
|
||||
for (i, var) in variables.iter().enumerate() {
|
||||
if var == var_name {
|
||||
if let Some(Some(init_expr)) = initial_values.get(i) {
|
||||
// Check if it's a substring method call
|
||||
if let ASTNode::MethodCall { object, method, arguments, .. } = init_expr.as_ref() {
|
||||
if method == "substring" && arguments.len() == 2 {
|
||||
// Extract object name
|
||||
if let ASTNode::Variable { name, .. } = object.as_ref() {
|
||||
// Return object name and start expression
|
||||
// (We assume second arg is start+1, first arg is start)
|
||||
return Some((name.clone(), Box::new(arguments[0].clone())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Phase 194: Detection function for Pattern 2
|
||||
///
|
||||
/// Phase 192: Updated to structure-based detection
|
||||
@ -221,32 +314,90 @@ impl MirBuilder {
|
||||
carrier_info.carrier_count()
|
||||
);
|
||||
|
||||
// Phase 171-impl-Trim: Check if this is a safe Trim pattern
|
||||
if let Some(helper) = carrier_info.trim_helper() {
|
||||
if helper.is_safe_trim() {
|
||||
eprintln!("[pattern2/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction");
|
||||
eprintln!("[pattern2/trim] Carrier: '{}', original var: '{}', whitespace chars: {:?}",
|
||||
helper.carrier_name, helper.original_var, helper.whitespace_chars);
|
||||
// Phase 172: Implement Trim pattern lowering
|
||||
// Clone helper data to avoid borrow conflicts
|
||||
let trim_helper_data = carrier_info.trim_helper().map(|h| {
|
||||
(h.carrier_name.clone(), h.original_var.clone(), h.whitespace_chars.clone(), h.is_safe_trim())
|
||||
});
|
||||
|
||||
// Phase 171-impl-Trim: Validation successful!
|
||||
// Phase 172+ will implement the actual JoinIR generation for Trim patterns
|
||||
// For now, return an informative message that the pattern is recognized but not yet lowered
|
||||
return Err(format!(
|
||||
"[cf_loop/pattern2] ✅ Trim pattern validation successful! \
|
||||
Carrier '{}' ready for Phase 172 implementation. \
|
||||
(Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)",
|
||||
helper.carrier_name
|
||||
));
|
||||
if let Some((carrier_name, original_var, whitespace_chars, is_safe)) = trim_helper_data {
|
||||
if is_safe {
|
||||
eprintln!("[pattern2/trim] Safe Trim pattern detected, implementing lowering");
|
||||
eprintln!("[pattern2/trim] Carrier: '{}', original var: '{}', whitespace chars: {:?}",
|
||||
carrier_name, original_var, whitespace_chars);
|
||||
|
||||
// Phase 172-2: Extract substring pattern and generate initial check
|
||||
let (s_name, start_expr) = extract_substring_args(_body, &original_var)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"[cf_loop/pattern2] Failed to extract substring pattern for Trim carrier '{}'",
|
||||
carrier_name
|
||||
)
|
||||
})?;
|
||||
|
||||
eprintln!("[pattern2/trim] Extracted substring pattern: s='{}', start={:?}", s_name, start_expr);
|
||||
|
||||
// Get ValueIds for string and start
|
||||
let s_id = self.variable_map.get(&s_name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern2/trim] String variable '{}' not found", s_name))?;
|
||||
|
||||
// Compile start expression to get ValueId
|
||||
let start_id = self.build_expression_impl(*start_expr)?;
|
||||
|
||||
// Generate: start + 1
|
||||
use crate::mir::builder::emission::constant::emit_integer;
|
||||
use crate::mir::types::BinaryOp;
|
||||
use crate::mir::instruction::MirInstruction;
|
||||
let one = emit_integer(self, 1);
|
||||
let start_plus_1 = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::BinOp {
|
||||
dst: start_plus_1,
|
||||
op: BinaryOp::Add,
|
||||
lhs: start_id,
|
||||
rhs: one,
|
||||
})?;
|
||||
|
||||
// Generate: ch0 = s.substring(start, start+1)
|
||||
let ch0 = self.value_gen.next();
|
||||
self.emit_method_call(
|
||||
Some(ch0),
|
||||
s_id,
|
||||
"substring".to_string(),
|
||||
vec![start_id, start_plus_1],
|
||||
)?;
|
||||
|
||||
eprintln!("[pattern2/trim] Generated initial substring call: ch0 = {:?}", ch0);
|
||||
|
||||
// Generate: is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...)
|
||||
let is_ch_match0 = emit_whitespace_check(self, ch0, &whitespace_chars)?;
|
||||
|
||||
eprintln!("[pattern2/trim] Generated initial whitespace check: is_ch_match0 = {:?}", is_ch_match0);
|
||||
|
||||
// Register carrier in variable_map
|
||||
self.variable_map.insert(carrier_name.clone(), is_ch_match0);
|
||||
|
||||
eprintln!("[pattern2/trim] Registered carrier '{}' in variable_map", carrier_name);
|
||||
|
||||
// Update carrier_info with actual ValueId
|
||||
carrier_info.loop_var_id = is_ch_match0;
|
||||
carrier_info.loop_var_name = carrier_name.clone();
|
||||
|
||||
eprintln!("[pattern2/trim] Updated carrier_info: loop_var='{}', loop_var_id={:?}",
|
||||
carrier_info.loop_var_name, carrier_info.loop_var_id);
|
||||
|
||||
// Phase 172-4: Break condition will be replaced below after JoinIR generation
|
||||
eprintln!("[pattern2/trim] Trim pattern lowering enabled, proceeding to JoinIR generation");
|
||||
} else {
|
||||
return Err(format!(
|
||||
"[cf_loop/pattern2] Trim pattern detected but not safe: carrier='{}', whitespace_count={}",
|
||||
helper.carrier_name,
|
||||
helper.whitespace_count()
|
||||
carrier_name,
|
||||
whitespace_chars.len()
|
||||
));
|
||||
}
|
||||
} else {
|
||||
} else if carrier_info.trim_helper().is_some() {
|
||||
return Err(format!(
|
||||
"[cf_loop/pattern2] Promoted but no TrimLoopHelper attached (carrier: '{}')",
|
||||
"[cf_loop/pattern2] Promoted but TrimLoopHelper check failed (carrier: '{}')",
|
||||
trim_info.carrier_name
|
||||
));
|
||||
}
|
||||
@ -262,9 +413,52 @@ impl MirBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 172-4: Replace break condition for Trim pattern and add carrier to ConditionEnv
|
||||
let effective_break_condition = if let Some(helper) = carrier_info.trim_helper() {
|
||||
if helper.is_safe_trim() {
|
||||
// Add carrier to ConditionEnv
|
||||
let carrier_host_id = self.variable_map.get(&helper.carrier_name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern2/trim] Carrier '{}' not in variable_map", helper.carrier_name))?;
|
||||
|
||||
let carrier_join_id = alloc_join_value(); // Allocate JoinIR-local ValueId
|
||||
|
||||
env.insert(helper.carrier_name.clone(), carrier_join_id);
|
||||
condition_bindings.push(ConditionBinding {
|
||||
name: helper.carrier_name.clone(),
|
||||
host_value: carrier_host_id,
|
||||
join_value: carrier_join_id,
|
||||
});
|
||||
|
||||
eprintln!("[pattern2/trim] Added carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}",
|
||||
helper.carrier_name, carrier_host_id, carrier_join_id);
|
||||
|
||||
// Create negated carrier check: !is_ch_match
|
||||
use crate::ast::{Span, UnaryOperator};
|
||||
|
||||
let carrier_var_node = ASTNode::Variable {
|
||||
name: helper.carrier_name.clone(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let negated_carrier = ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(carrier_var_node),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
eprintln!("[pattern2/trim] Replaced break condition with !{}", helper.carrier_name);
|
||||
negated_carrier
|
||||
} else {
|
||||
break_condition_node.clone()
|
||||
}
|
||||
} else {
|
||||
break_condition_node.clone()
|
||||
};
|
||||
|
||||
// Phase 169 / Phase 171-fix / Phase 172-3 / Phase 170-B: Call Pattern 2 lowerer with break_condition
|
||||
// Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, &break_condition_node, &env, &loop_var_name) {
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, &effective_break_condition, &env, &loop_var_name) {
|
||||
Ok((module, meta)) => (module, meta),
|
||||
Err(e) => {
|
||||
// Phase 195: Use unified trace
|
||||
|
||||
Reference in New Issue
Block a user