Phase 33 NORM canon test: enforce normalized dev route for P1/P2/JP mini
This commit is contained in:
@ -20,6 +20,7 @@
|
||||
//! - 単一関数: cond 評価 → Select(cond, then_val, else_val) → Ret
|
||||
|
||||
use super::{AstToJoinIrLowerer, BTreeMap, ExtractCtx, JoinFunction, JoinInst, JoinModule};
|
||||
use crate::mir::join_ir::JoinIrPhase;
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// If Return pattern の共通 lowering
|
||||
@ -152,6 +153,7 @@ impl AstToJoinIrLowerer {
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(func_id),
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
//! - `create_k_exit_function()`: k_exit 関数生成
|
||||
|
||||
use super::{AstToJoinIrLowerer, JoinModule};
|
||||
use crate::mir::join_ir::JoinIrPhase;
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
@ -403,5 +404,6 @@ pub fn build_join_module(
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use super::BTreeMap;
|
||||
use super::{AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule};
|
||||
use crate::mir::join_ir::JoinIrPhase;
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// Phase 34-8: Break/Continue 付きループの lowering(パターン検出)
|
||||
@ -435,6 +436,7 @@ impl AstToJoinIrLowerer {
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,6 +644,7 @@ impl AstToJoinIrLowerer {
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
|
||||
@ -889,6 +892,7 @@ impl AstToJoinIrLowerer {
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,56 @@ mod tests;
|
||||
pub(crate) use context::ExtractCtx;
|
||||
pub(crate) use stmt_handlers::StatementEffect;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum FunctionRoute {
|
||||
IfReturn,
|
||||
LoopFrontend,
|
||||
NestedIf,
|
||||
ReadQuoted,
|
||||
}
|
||||
|
||||
fn resolve_function_route(func_name: &str) -> Result<FunctionRoute, String> {
|
||||
const IF_RETURN_NAMES: &[&str] = &["test", "local", "_read_value_from_pair"];
|
||||
const LOOP_NAMES: &[&str] = &["simple", "filter", "print_tokens", "map", "reduce", "fold", "jsonparser_skip_ws_mini"];
|
||||
|
||||
if IF_RETURN_NAMES.contains(&func_name) {
|
||||
return Ok(FunctionRoute::IfReturn);
|
||||
}
|
||||
|
||||
if LOOP_NAMES.contains(&func_name) {
|
||||
return Ok(FunctionRoute::LoopFrontend);
|
||||
}
|
||||
|
||||
if func_name == "parse_loop" {
|
||||
if crate::config::env::joinir_dev_enabled()
|
||||
&& std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1")
|
||||
{
|
||||
return Ok(FunctionRoute::NestedIf);
|
||||
}
|
||||
return Err(
|
||||
"[joinir/frontend] 'parse_loop' requires HAKO_JOINIR_NESTED_IF=1 (dev only)"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if func_name == "read_quoted_from" {
|
||||
if crate::config::env::joinir_dev_enabled()
|
||||
&& std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1")
|
||||
{
|
||||
return Ok(FunctionRoute::ReadQuoted);
|
||||
}
|
||||
return Err(
|
||||
"[joinir/frontend] 'read_quoted_from' requires HAKO_JOINIR_READ_QUOTED=1 (dev only)"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"[joinir/frontend] unsupported function '{}' (dev fixture not registered)",
|
||||
func_name
|
||||
))
|
||||
}
|
||||
|
||||
/// AST/CFG → JoinIR 変換器
|
||||
///
|
||||
/// Phase 34-2: Program(JSON v0) から tiny IfSelect ケースを JoinIR に変換
|
||||
@ -85,51 +135,17 @@ impl AstToJoinIrLowerer {
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
// 3. 関数名で分岐(Phase P3: LoopFrontendBinding 層導入)
|
||||
//
|
||||
// パターン分類:
|
||||
// - If Return pattern: test/local/_read_value_from_pair
|
||||
// - Loop pattern: simple 等 → LoopFrontendBinding 経由
|
||||
// - NestedIfMerge pattern: parse_loop (dev flag gated)
|
||||
// - ReadQuoted pattern: read_quoted_from (dev flag gated)
|
||||
match func_name {
|
||||
"test" | "local" | "_read_value_from_pair" => {
|
||||
self.lower_if_return_pattern(program_json)
|
||||
}
|
||||
"simple" | "filter" | "print_tokens" | "map" | "reduce" | "fold" => {
|
||||
// Phase P3: LoopFrontendBinding 層経由でディスパッチ
|
||||
loop_frontend_binding::lower_loop_by_function_name(self, program_json)
|
||||
}
|
||||
"parse_loop" => {
|
||||
// Phase 41-4: NestedIfMerge pattern (dev flag gated)
|
||||
// Guard: JoinIR dev + HAKO_JOINIR_NESTED_IF=1 を要求
|
||||
if crate::config::env::joinir_dev_enabled()
|
||||
&& std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1")
|
||||
{
|
||||
self.lower_nested_if_pattern(program_json)
|
||||
} else {
|
||||
// Dev flag が OFF の場合は panic(旧ルートにフォールバックするため)
|
||||
panic!(
|
||||
"parse_loop NestedIfMerge requires HAKO_JOINIR_NESTED_IF=1. \
|
||||
Set env var to enable Phase 41-4 route."
|
||||
);
|
||||
}
|
||||
}
|
||||
"read_quoted_from" => {
|
||||
// Phase 45: read_quoted_from pattern (dev flag gated)
|
||||
// Guard if + Loop with break + accumulator
|
||||
if crate::config::env::joinir_dev_enabled()
|
||||
&& std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1")
|
||||
{
|
||||
self.lower_read_quoted_pattern(program_json)
|
||||
} else {
|
||||
panic!(
|
||||
"read_quoted_from JoinIR requires HAKO_JOINIR_READ_QUOTED=1. \
|
||||
Set env var to enable Phase 45 route."
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => panic!("Unsupported function: {}", func_name),
|
||||
let route = resolve_function_route(func_name)
|
||||
.unwrap_or_else(|msg| panic!("{msg}"));
|
||||
|
||||
match route {
|
||||
FunctionRoute::IfReturn => self.lower_if_return_pattern(program_json),
|
||||
FunctionRoute::LoopFrontend => loop_frontend_binding::lower_loop_by_function_name(
|
||||
self,
|
||||
program_json,
|
||||
),
|
||||
FunctionRoute::NestedIf => self.lower_nested_if_pattern(program_json),
|
||||
FunctionRoute::ReadQuoted => self.lower_read_quoted_pattern(program_json),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
|
||||
use super::BTreeMap;
|
||||
use super::{AstToJoinIrLowerer, ExtractCtx, JoinFunction, JoinInst, JoinModule};
|
||||
use crate::mir::join_ir::JoinIrPhase;
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// Phase 41-4.2: ネスト if パターンの lowering
|
||||
@ -193,6 +194,7 @@ impl AstToJoinIrLowerer {
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(func_id),
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ use super::BTreeMap;
|
||||
use super::{
|
||||
AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule, MergePair,
|
||||
};
|
||||
use crate::mir::join_ir::JoinIrPhase;
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// Phase 45: read_quoted_from パターンの lowering
|
||||
@ -494,6 +495,7 @@ impl AstToJoinIrLowerer {
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,13 +110,12 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
};
|
||||
|
||||
// Emit Compare instruction
|
||||
self.builder
|
||||
.emit_instruction(MirInstruction::Compare {
|
||||
dst,
|
||||
op: cmp_op,
|
||||
lhs,
|
||||
rhs,
|
||||
})?;
|
||||
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);
|
||||
@ -130,13 +129,12 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
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,
|
||||
})?;
|
||||
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);
|
||||
@ -150,13 +148,12 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
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,
|
||||
})?;
|
||||
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);
|
||||
@ -179,12 +176,11 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
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,
|
||||
})?;
|
||||
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);
|
||||
@ -217,7 +213,7 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
// 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();
|
||||
@ -232,13 +228,13 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
// 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,
|
||||
@ -252,17 +248,17 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
// }),
|
||||
// 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,
|
||||
@ -292,17 +288,17 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
// }),
|
||||
// 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,
|
||||
@ -348,17 +344,17 @@ impl<'a> BoolExprLowerer<'a> {
|
||||
// }),
|
||||
// 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,
|
||||
|
||||
@ -210,15 +210,12 @@ impl CarrierInfo {
|
||||
variable_map: &BTreeMap<String, ValueId>, // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
) -> Result<Self, String> {
|
||||
// Find loop variable
|
||||
let loop_var_id = variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Loop variable '{}' not found in variable_map",
|
||||
loop_var_name
|
||||
)
|
||||
})?;
|
||||
let loop_var_id = variable_map.get(&loop_var_name).copied().ok_or_else(|| {
|
||||
format!(
|
||||
"Loop variable '{}' not found in variable_map",
|
||||
loop_var_name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Collect all non-loop-var variables as carriers
|
||||
let mut carriers: Vec<CarrierVar> = variable_map
|
||||
@ -280,9 +277,10 @@ impl CarrierInfo {
|
||||
let mut carriers = Vec::new();
|
||||
|
||||
for name in carrier_names {
|
||||
let host_id = variable_map.get(&name).copied().ok_or_else(|| {
|
||||
format!("Carrier variable '{}' not found in variable_map", name)
|
||||
})?;
|
||||
let host_id = variable_map
|
||||
.get(&name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("Carrier variable '{}' not found in variable_map", name))?;
|
||||
|
||||
carriers.push(CarrierVar {
|
||||
name,
|
||||
@ -408,7 +406,9 @@ impl CarrierInfo {
|
||||
/// eprintln!("Whitespace chars: {:?}", helper.whitespace_chars);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn trim_helper(&self) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> {
|
||||
pub fn trim_helper(
|
||||
&self,
|
||||
) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> {
|
||||
self.trim_helper.as_ref()
|
||||
}
|
||||
|
||||
@ -430,7 +430,10 @@ impl CarrierInfo {
|
||||
/// * `Some(ValueId)` - 対応する carrier の join_id が見つかった場合
|
||||
/// * `None` - promoted_loopbodylocals に含まれない、または join_id 未設定の場合
|
||||
pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option<ValueId> {
|
||||
if !self.promoted_loopbodylocals.contains(&original_name.to_string()) {
|
||||
if !self
|
||||
.promoted_loopbodylocals
|
||||
.contains(&original_name.to_string())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -646,29 +649,21 @@ mod tests {
|
||||
CarrierVar {
|
||||
name: name.to_string(),
|
||||
host_id: ValueId(id),
|
||||
join_id: None, // Phase 177-STRUCT-1
|
||||
join_id: None, // Phase 177-STRUCT-1
|
||||
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
|
||||
init: CarrierInit::FromHost, // Phase 228: Default to FromHost
|
||||
init: CarrierInit::FromHost, // Phase 228: Default to FromHost
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Create a CarrierInfo for testing
|
||||
fn test_carrier_info(loop_var: &str, loop_id: u32, carriers: Vec<CarrierVar>) -> CarrierInfo {
|
||||
CarrierInfo::with_carriers(
|
||||
loop_var.to_string(),
|
||||
ValueId(loop_id),
|
||||
carriers,
|
||||
)
|
||||
CarrierInfo::with_carriers(loop_var.to_string(), ValueId(loop_id), carriers)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_from_empty() {
|
||||
// Merge empty CarrierInfo should not change anything
|
||||
let mut carrier_info = test_carrier_info(
|
||||
"i",
|
||||
5,
|
||||
vec![test_carrier("sum", 10)],
|
||||
);
|
||||
let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]);
|
||||
|
||||
let other = test_carrier_info("j", 20, vec![]);
|
||||
|
||||
@ -681,17 +676,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_merge_from_new_carrier() {
|
||||
// Merge a new carrier that doesn't exist yet
|
||||
let mut carrier_info = test_carrier_info(
|
||||
"i",
|
||||
5,
|
||||
vec![test_carrier("sum", 10)],
|
||||
);
|
||||
let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]);
|
||||
|
||||
let other = test_carrier_info(
|
||||
"j",
|
||||
20,
|
||||
vec![test_carrier("count", 15)],
|
||||
);
|
||||
let other = test_carrier_info("j", 20, vec![test_carrier("count", 15)]);
|
||||
|
||||
carrier_info.merge_from(&other);
|
||||
|
||||
@ -704,11 +691,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_merge_from_duplicate_carrier() {
|
||||
// Merge a carrier with the same name should NOT duplicate
|
||||
let mut carrier_info = test_carrier_info(
|
||||
"i",
|
||||
5,
|
||||
vec![test_carrier("sum", 10)],
|
||||
);
|
||||
let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]);
|
||||
|
||||
let other = test_carrier_info(
|
||||
"j",
|
||||
@ -728,19 +711,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_merge_from_multiple_carriers() {
|
||||
// Merge multiple carriers
|
||||
let mut carrier_info = test_carrier_info(
|
||||
"i",
|
||||
5,
|
||||
vec![test_carrier("sum", 10)],
|
||||
);
|
||||
let mut carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]);
|
||||
|
||||
let other = test_carrier_info(
|
||||
"j",
|
||||
20,
|
||||
vec![
|
||||
test_carrier("count", 15),
|
||||
test_carrier("product", 18),
|
||||
],
|
||||
vec![test_carrier("count", 15), test_carrier("product", 18)],
|
||||
);
|
||||
|
||||
carrier_info.merge_from(&other);
|
||||
@ -758,19 +734,13 @@ mod tests {
|
||||
let mut carrier_info = test_carrier_info(
|
||||
"i",
|
||||
5,
|
||||
vec![
|
||||
test_carrier("zebra", 30),
|
||||
test_carrier("alpha", 10),
|
||||
],
|
||||
vec![test_carrier("zebra", 30), test_carrier("alpha", 10)],
|
||||
);
|
||||
|
||||
let other = test_carrier_info(
|
||||
"j",
|
||||
20,
|
||||
vec![
|
||||
test_carrier("beta", 15),
|
||||
test_carrier("gamma", 18),
|
||||
],
|
||||
vec![test_carrier("beta", 15), test_carrier("gamma", 18)],
|
||||
);
|
||||
|
||||
carrier_info.merge_from(&other);
|
||||
|
||||
@ -67,12 +67,7 @@ pub fn emit_carrier_update_with_env(
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.resolve(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in UpdateEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| format!("Carrier '{}' not found in UpdateEnv", carrier.name))?;
|
||||
|
||||
// Allocate result ValueId
|
||||
let result = alloc_value();
|
||||
@ -99,12 +94,7 @@ pub fn emit_carrier_update_with_env(
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.resolve(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in UpdateEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| format!("Carrier '{}' not found in UpdateEnv", carrier.name))?;
|
||||
|
||||
// Resolve RHS (Phase 184: Now supports body-local variables!)
|
||||
let rhs_id = match rhs {
|
||||
@ -255,12 +245,7 @@ pub fn emit_carrier_update(
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.get(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in ConditionEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| format!("Carrier '{}' not found in ConditionEnv", carrier.name))?;
|
||||
|
||||
// Allocate result ValueId
|
||||
let result = alloc_value();
|
||||
@ -287,12 +272,7 @@ pub fn emit_carrier_update(
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.get(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in ConditionEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| format!("Carrier '{}' not found in ConditionEnv", carrier.name))?;
|
||||
|
||||
// Resolve RHS
|
||||
let rhs_id = match rhs {
|
||||
@ -304,14 +284,12 @@ pub fn emit_carrier_update(
|
||||
}));
|
||||
const_id
|
||||
}
|
||||
UpdateRhs::Variable(var_name) => {
|
||||
env.get(var_name).ok_or_else(|| {
|
||||
format!(
|
||||
"Update RHS variable '{}' not found in ConditionEnv",
|
||||
var_name
|
||||
)
|
||||
})?
|
||||
}
|
||||
UpdateRhs::Variable(var_name) => env.get(var_name).ok_or_else(|| {
|
||||
format!(
|
||||
"Update RHS variable '{}' not found in ConditionEnv",
|
||||
var_name
|
||||
)
|
||||
})?,
|
||||
// Phase 188: String updates now emit JoinIR BinOp
|
||||
// StringAppendLiteral: s = s + "literal"
|
||||
UpdateRhs::StringLiteral(s) => {
|
||||
@ -441,13 +419,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
let result =
|
||||
emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
@ -497,13 +470,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
let result =
|
||||
emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
@ -553,13 +521,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
let result =
|
||||
emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
@ -596,13 +559,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
let result =
|
||||
emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Carrier 'unknown' not found"));
|
||||
@ -627,13 +585,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
let result =
|
||||
emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("doesn't match carrier"));
|
||||
@ -658,16 +611,13 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
let result =
|
||||
emit_carrier_update(&carrier, &update, &mut alloc_value, &env, &mut instructions);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found"));
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.contains("Update RHS variable 'unknown_var' not found"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@ -765,7 +715,12 @@ mod tests {
|
||||
|
||||
// Should use x=100 (condition env), not x=200 (body-local env)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst: _, op: _, lhs: _, rhs }) => {
|
||||
JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: _,
|
||||
op: _,
|
||||
lhs: _,
|
||||
rhs,
|
||||
}) => {
|
||||
assert_eq!(*rhs, ValueId(100)); // Condition env wins
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
|
||||
@ -107,7 +107,8 @@ impl ComplexAddendNormalizer {
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} | ASTNode::BinaryOp {
|
||||
}
|
||||
| ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left,
|
||||
right,
|
||||
|
||||
@ -61,7 +61,7 @@ impl ConditionEnv {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
name_to_join: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
captured: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
captured: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +82,9 @@ impl ConditionEnv {
|
||||
/// Returns `Some(ValueId)` if the variable exists in the environment,
|
||||
/// `None` otherwise.
|
||||
pub fn get(&self, name: &str) -> Option<ValueId> {
|
||||
self.name_to_join.get(name).copied()
|
||||
self.name_to_join
|
||||
.get(name)
|
||||
.copied()
|
||||
.or_else(|| self.captured.get(name).copied())
|
||||
}
|
||||
|
||||
@ -126,7 +128,9 @@ impl ConditionEnv {
|
||||
///
|
||||
/// Phase 200-B: Includes both name_to_join and captured variables.
|
||||
pub fn names(&self) -> Vec<String> {
|
||||
let mut names: Vec<_> = self.name_to_join.keys()
|
||||
let mut names: Vec<_> = self
|
||||
.name_to_join
|
||||
.keys()
|
||||
.chain(self.captured.keys())
|
||||
.cloned()
|
||||
.collect();
|
||||
@ -235,8 +239,8 @@ mod tests {
|
||||
fn test_condition_binding() {
|
||||
let binding = ConditionBinding::new(
|
||||
"start".to_string(),
|
||||
ValueId(33), // HOST
|
||||
ValueId(1), // JoinIR
|
||||
ValueId(33), // HOST
|
||||
ValueId(1), // JoinIR
|
||||
);
|
||||
|
||||
assert_eq!(binding.name, "start");
|
||||
|
||||
@ -100,12 +100,8 @@ where
|
||||
| BinaryOperator::Greater => {
|
||||
lower_comparison(operator, left, right, alloc_value, env, instructions)
|
||||
}
|
||||
BinaryOperator::And => {
|
||||
lower_logical_and(left, right, alloc_value, env, instructions)
|
||||
}
|
||||
BinaryOperator::Or => {
|
||||
lower_logical_or(left, right, alloc_value, env, instructions)
|
||||
}
|
||||
BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, instructions),
|
||||
BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, instructions),
|
||||
_ => Err(format!(
|
||||
"Unsupported binary operator in condition: {:?}",
|
||||
operator
|
||||
@ -264,7 +260,10 @@ where
|
||||
return Err("Float literals not supported in JoinIR conditions yet".to_string());
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("Unsupported literal type in condition: {:?}", value));
|
||||
return Err(format!(
|
||||
"Unsupported literal type in condition: {:?}",
|
||||
value
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@ -317,7 +316,14 @@ where
|
||||
let recv_val = lower_value_expression(object, alloc_value, env, instructions)?;
|
||||
|
||||
// 2. Lower method call using MethodCallLowerer (will lower arguments internally)
|
||||
MethodCallLowerer::lower_for_condition(recv_val, method, arguments, alloc_value, env, instructions)
|
||||
MethodCallLowerer::lower_for_condition(
|
||||
recv_val,
|
||||
method,
|
||||
arguments,
|
||||
alloc_value,
|
||||
env,
|
||||
instructions,
|
||||
)
|
||||
}
|
||||
|
||||
_ => Err(format!(
|
||||
|
||||
@ -15,9 +15,9 @@
|
||||
//! **Fail-Safe**: Implementations return explicit errors for unsupported patterns,
|
||||
//! allowing callers to fall back to alternative paths.
|
||||
|
||||
use super::scope_manager::ScopeManager;
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::ValueId;
|
||||
use super::scope_manager::ScopeManager;
|
||||
|
||||
/// Phase 244: Context for condition lowering
|
||||
///
|
||||
@ -158,12 +158,12 @@ pub trait ConditionLoweringBox<S: ScopeManager> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{Span, LiteralValue, BinaryOperator};
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprLowerer, ExprContext};
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
|
||||
fn span() -> Span {
|
||||
Span::unknown()
|
||||
|
||||
@ -79,7 +79,12 @@ pub enum ConditionPattern {
|
||||
pub fn analyze_condition_pattern(cond: &ASTNode) -> ConditionPattern {
|
||||
match cond {
|
||||
// Comparison operators: ==, !=, <, >, <=, >=
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
// Check if operator is a comparison
|
||||
let is_comparison = matches!(
|
||||
operator,
|
||||
@ -225,12 +230,12 @@ pub struct NormalizedCondition {
|
||||
/// ```
|
||||
fn flip_compare_op(op: CompareOp) -> CompareOp {
|
||||
match op {
|
||||
CompareOp::Lt => CompareOp::Gt, // < → >
|
||||
CompareOp::Gt => CompareOp::Lt, // > → <
|
||||
CompareOp::Le => CompareOp::Ge, // <= → >=
|
||||
CompareOp::Ge => CompareOp::Le, // >= → <=
|
||||
CompareOp::Eq => CompareOp::Eq, // == → == (不変)
|
||||
CompareOp::Ne => CompareOp::Ne, // != → != (不変)
|
||||
CompareOp::Lt => CompareOp::Gt, // < → >
|
||||
CompareOp::Gt => CompareOp::Lt, // > → <
|
||||
CompareOp::Le => CompareOp::Ge, // <= → >=
|
||||
CompareOp::Ge => CompareOp::Le, // >= → <=
|
||||
CompareOp::Eq => CompareOp::Eq, // == → == (不変)
|
||||
CompareOp::Ne => CompareOp::Ne, // != → != (不変)
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,12 +283,24 @@ fn binary_op_to_compare_op(op: &BinaryOperator) -> Option<CompareOp> {
|
||||
/// ```
|
||||
pub fn normalize_comparison(cond: &ASTNode) -> Option<NormalizedCondition> {
|
||||
match cond {
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
// Comparison operator のみ受理
|
||||
let compare_op = binary_op_to_compare_op(operator)?;
|
||||
|
||||
// Case 1: var CmpOp literal (e.g., i > 0)
|
||||
if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Literal { value: LiteralValue::Integer(right_val), .. }) = (left.as_ref(), right.as_ref()) {
|
||||
if let (
|
||||
ASTNode::Variable { name: left_var, .. },
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(right_val),
|
||||
..
|
||||
},
|
||||
) = (left.as_ref(), right.as_ref())
|
||||
{
|
||||
return Some(NormalizedCondition {
|
||||
left_var: left_var.clone(),
|
||||
op: compare_op,
|
||||
@ -292,7 +309,16 @@ pub fn normalize_comparison(cond: &ASTNode) -> Option<NormalizedCondition> {
|
||||
}
|
||||
|
||||
// Case 2: literal CmpOp var (e.g., 0 < i) → 左右反転
|
||||
if let (ASTNode::Literal { value: LiteralValue::Integer(left_val), .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) {
|
||||
if let (
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(left_val),
|
||||
..
|
||||
},
|
||||
ASTNode::Variable {
|
||||
name: right_var, ..
|
||||
},
|
||||
) = (left.as_ref(), right.as_ref())
|
||||
{
|
||||
return Some(NormalizedCondition {
|
||||
left_var: right_var.clone(),
|
||||
op: flip_compare_op(compare_op), // 演算子を反転
|
||||
@ -301,7 +327,13 @@ pub fn normalize_comparison(cond: &ASTNode) -> Option<NormalizedCondition> {
|
||||
}
|
||||
|
||||
// Case 3: var CmpOp var (e.g., i > j)
|
||||
if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) {
|
||||
if let (
|
||||
ASTNode::Variable { name: left_var, .. },
|
||||
ASTNode::Variable {
|
||||
name: right_var, ..
|
||||
},
|
||||
) = (left.as_ref(), right.as_ref())
|
||||
{
|
||||
return Some(NormalizedCondition {
|
||||
left_var: left_var.clone(),
|
||||
op: compare_op,
|
||||
@ -351,7 +383,10 @@ mod tests {
|
||||
fn test_simple_comparison_greater() {
|
||||
// i > 0
|
||||
let cond = binop(BinaryOperator::Greater, var("i"), int_lit(0));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -359,7 +394,10 @@ mod tests {
|
||||
fn test_simple_comparison_less() {
|
||||
// i < 10
|
||||
let cond = binop(BinaryOperator::Less, var("i"), int_lit(10));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -367,7 +405,10 @@ mod tests {
|
||||
fn test_simple_comparison_equal() {
|
||||
// i == 5
|
||||
let cond = binop(BinaryOperator::Equal, var("i"), int_lit(5));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -375,7 +416,10 @@ mod tests {
|
||||
fn test_simple_comparison_not_equal() {
|
||||
// i != 0
|
||||
let cond = binop(BinaryOperator::NotEqual, var("i"), int_lit(0));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -384,7 +428,10 @@ mod tests {
|
||||
// Phase 242-EX-A: i % 2 == 1 (BinaryOp in LHS) is now SimpleComparison
|
||||
let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2));
|
||||
let cond = binop(BinaryOperator::Equal, lhs, int_lit(1));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -393,7 +440,10 @@ mod tests {
|
||||
// Phase 242-EX-A: i == a + b (BinaryOp in RHS) is now SimpleComparison
|
||||
let rhs = binop(BinaryOperator::Add, var("a"), var("b"));
|
||||
let cond = binop(BinaryOperator::Equal, var("i"), rhs);
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -402,7 +452,10 @@ mod tests {
|
||||
// Pattern3/4 で使う典型的なフィルタ条件: i % 2 == 1
|
||||
let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2));
|
||||
let cond = binop(BinaryOperator::Equal, lhs, int_lit(1));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -535,7 +588,10 @@ mod tests {
|
||||
fn test_analyze_pattern_literal_cmp_var() {
|
||||
// Phase 222: 0 < i → SimpleComparison
|
||||
let cond = binop(BinaryOperator::Less, int_lit(0), var("i"));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
|
||||
@ -543,7 +599,10 @@ mod tests {
|
||||
fn test_analyze_pattern_var_cmp_var() {
|
||||
// Phase 222: i > j → SimpleComparison
|
||||
let cond = binop(BinaryOperator::Greater, var("i"), var("j"));
|
||||
assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison);
|
||||
assert_eq!(
|
||||
analyze_condition_pattern(&cond),
|
||||
ConditionPattern::SimpleComparison
|
||||
);
|
||||
assert!(is_simple_comparison(&cond));
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,10 +40,7 @@ use std::collections::BTreeSet;
|
||||
/// );
|
||||
/// // Result: ["end", "len", "start"] (sorted, 'i' excluded)
|
||||
/// ```
|
||||
pub fn extract_condition_variables(
|
||||
cond_ast: &ASTNode,
|
||||
exclude_vars: &[String],
|
||||
) -> Vec<String> {
|
||||
pub fn extract_condition_variables(cond_ast: &ASTNode, exclude_vars: &[String]) -> Vec<String> {
|
||||
let mut all_vars = BTreeSet::new();
|
||||
collect_variables_recursive(cond_ast, &mut all_vars);
|
||||
|
||||
|
||||
@ -152,11 +152,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_normalize_digit_pos_lt_zero() {
|
||||
// Pattern: digit_pos < 0
|
||||
let cond = binary_op(
|
||||
var_node("digit_pos"),
|
||||
BinaryOperator::Less,
|
||||
int_literal(0),
|
||||
);
|
||||
let cond = binary_op(var_node("digit_pos"), BinaryOperator::Less, int_literal(0));
|
||||
|
||||
let normalized = DigitPosConditionNormalizer::normalize(&cond, "digit_pos", "is_digit_pos");
|
||||
|
||||
@ -166,14 +162,12 @@ mod tests {
|
||||
operator: UnaryOperator::Not,
|
||||
operand,
|
||||
..
|
||||
} => {
|
||||
match operand.as_ref() {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
assert_eq!(name, "is_digit_pos");
|
||||
}
|
||||
_ => panic!("Expected Variable node"),
|
||||
} => match operand.as_ref() {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
assert_eq!(name, "is_digit_pos");
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Variable node"),
|
||||
},
|
||||
_ => panic!("Expected UnaryOp Not node"),
|
||||
}
|
||||
}
|
||||
@ -201,21 +195,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_no_normalize_wrong_variable() {
|
||||
// Pattern: other_var < 0 (different variable name)
|
||||
let cond = binary_op(
|
||||
var_node("other_var"),
|
||||
BinaryOperator::Less,
|
||||
int_literal(0),
|
||||
);
|
||||
let cond = binary_op(var_node("other_var"), BinaryOperator::Less, int_literal(0));
|
||||
|
||||
let normalized = DigitPosConditionNormalizer::normalize(&cond, "digit_pos", "is_digit_pos");
|
||||
|
||||
// Should NOT transform (return original)
|
||||
match normalized {
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
..
|
||||
} => {
|
||||
ASTNode::BinaryOp { operator, left, .. } => {
|
||||
assert_eq!(operator, BinaryOperator::Less);
|
||||
match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
@ -231,20 +217,14 @@ mod tests {
|
||||
#[test]
|
||||
fn test_no_normalize_wrong_constant() {
|
||||
// Pattern: digit_pos < 10 (different constant, not 0)
|
||||
let cond = binary_op(
|
||||
var_node("digit_pos"),
|
||||
BinaryOperator::Less,
|
||||
int_literal(10),
|
||||
);
|
||||
let cond = binary_op(var_node("digit_pos"), BinaryOperator::Less, int_literal(10));
|
||||
|
||||
let normalized = DigitPosConditionNormalizer::normalize(&cond, "digit_pos", "is_digit_pos");
|
||||
|
||||
// Should NOT transform (return original)
|
||||
match normalized {
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
right,
|
||||
..
|
||||
operator, right, ..
|
||||
} => {
|
||||
assert_eq!(operator, BinaryOperator::Less);
|
||||
match right.as_ref() {
|
||||
|
||||
@ -15,12 +15,12 @@
|
||||
//! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers
|
||||
//! to fall back to legacy paths.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use super::scope_manager::ScopeManager;
|
||||
use super::condition_lowerer::lower_condition_to_joinir;
|
||||
use super::scope_manager::ScopeManager;
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
mod ast_support;
|
||||
mod scope_resolution;
|
||||
@ -165,11 +165,9 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
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()
|
||||
))
|
||||
}
|
||||
ExprContext::General => Err(ExprLoweringError::UnsupportedNode(
|
||||
"General expression context not yet implemented (Phase 231)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,9 +183,10 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
fn lower_condition(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||||
// 1. Check if AST is supported in condition context
|
||||
if !ast_support::is_supported_condition(ast) {
|
||||
return Err(ExprLoweringError::UnsupportedNode(
|
||||
format!("Unsupported condition node: {:?}", ast)
|
||||
));
|
||||
return Err(ExprLoweringError::UnsupportedNode(format!(
|
||||
"Unsupported condition node: {:?}",
|
||||
ast
|
||||
)));
|
||||
}
|
||||
|
||||
// 2. Build ConditionEnv from ScopeManager
|
||||
@ -204,17 +203,18 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
id
|
||||
};
|
||||
|
||||
let (result_value, instructions) = lower_condition_to_joinir(
|
||||
ast,
|
||||
&mut alloc_value,
|
||||
&condition_env,
|
||||
).map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||||
let (result_value, instructions) =
|
||||
lower_condition_to_joinir(ast, &mut alloc_value, &condition_env)
|
||||
.map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||||
|
||||
// Phase 235: 保存しておき、テストから観察できるようにする
|
||||
self.last_instructions = instructions;
|
||||
|
||||
if self.debug {
|
||||
eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value);
|
||||
eprintln!(
|
||||
"[expr_lowerer/phase231] Lowered condition → ValueId({:?})",
|
||||
result_value
|
||||
);
|
||||
}
|
||||
|
||||
Ok(result_value)
|
||||
@ -230,7 +230,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
// Phase 244: ConditionLoweringBox trait implementation
|
||||
// ============================================================================
|
||||
|
||||
use super::condition_lowering_box::{ConditionLoweringBox, ConditionContext};
|
||||
use super::condition_lowering_box::{ConditionContext, ConditionLoweringBox};
|
||||
|
||||
impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'env, 'builder, S> {
|
||||
/// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer
|
||||
@ -280,13 +280,13 @@ impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'e
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::test_helpers::{bin, lit_i, span, var};
|
||||
use super::*;
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator};
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::{BinOpKind, MirLikeInst, UnaryOp as JoinUnaryOp};
|
||||
use super::test_helpers::{bin, lit_i, span, var};
|
||||
|
||||
// Helper to create a test MirBuilder (Phase 231: minimal stub)
|
||||
fn create_test_builder() -> MirBuilder {
|
||||
@ -340,7 +340,10 @@ mod tests {
|
||||
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");
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should lower simple comparison successfully"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -381,7 +384,10 @@ mod tests {
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(matches!(result, Err(ExprLoweringError::VariableNotFound(_))));
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ExprLoweringError::VariableNotFound(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -406,7 +412,9 @@ mod tests {
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// AST: Break (unsupported in condition context)
|
||||
let ast = ASTNode::Break { span: Span::unknown() };
|
||||
let ast = ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
@ -429,7 +437,9 @@ mod tests {
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Supported: MethodCall
|
||||
let ast = ASTNode::MethodCall {
|
||||
@ -441,10 +451,14 @@ mod tests {
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
|
||||
&ast
|
||||
));
|
||||
|
||||
// Unsupported: Break node
|
||||
let ast = ASTNode::Break { span: Span::unknown() };
|
||||
let ast = ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
}
|
||||
|
||||
@ -507,10 +521,9 @@ mod tests {
|
||||
|
||||
fn assert_has_compare(instructions: &[JoinInst]) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::Compare { .. }
|
||||
))),
|
||||
instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. }))),
|
||||
"Expected at least one Compare instruction, got {:?}",
|
||||
instructions
|
||||
);
|
||||
@ -532,8 +545,11 @@ mod tests {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp { op: JoinUnaryOp::Not, .. }
|
||||
))),
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
op: JoinUnaryOp::Not,
|
||||
..
|
||||
})
|
||||
)),
|
||||
"Expected at least one UnaryOp::Not, got {:?}",
|
||||
instructions
|
||||
);
|
||||
|
||||
@ -5,14 +5,19 @@ pub(crate) fn is_supported_condition(ast: &ASTNode) -> bool {
|
||||
match ast {
|
||||
Literal { .. } => true,
|
||||
Variable { .. } => true,
|
||||
BinaryOp { operator, left, right, .. } => {
|
||||
BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
is_supported_binary_op(operator)
|
||||
&& is_supported_condition(left)
|
||||
&& is_supported_condition(right)
|
||||
}
|
||||
UnaryOp { operator, operand, .. } => {
|
||||
matches!(operator, UnaryOperator::Not) && is_supported_condition(operand)
|
||||
}
|
||||
UnaryOp {
|
||||
operator, operand, ..
|
||||
} => matches!(operator, UnaryOperator::Not) && is_supported_condition(operand),
|
||||
MethodCall { .. } => is_supported_method_call(ast),
|
||||
_ => false,
|
||||
}
|
||||
@ -20,7 +25,10 @@ pub(crate) fn is_supported_condition(ast: &ASTNode) -> bool {
|
||||
|
||||
fn is_supported_binary_op(op: &BinaryOperator) -> bool {
|
||||
use BinaryOperator::*;
|
||||
matches!(op, Less | LessEqual | Greater | GreaterEqual | Equal | NotEqual | And | Or)
|
||||
matches!(
|
||||
op,
|
||||
Less | LessEqual | Greater | GreaterEqual | Equal | NotEqual | And | Or
|
||||
)
|
||||
}
|
||||
|
||||
fn is_supported_method_call(ast: &ASTNode) -> bool {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use super::{ExprLoweringError, ScopeManager};
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use super::{ExprLoweringError, ScopeManager};
|
||||
|
||||
pub(crate) fn build_condition_env_from_scope<S: ScopeManager>(
|
||||
scope: &S,
|
||||
@ -30,7 +30,9 @@ fn collect_vars(ast: &ASTNode, vars: &mut Vec<String>) {
|
||||
collect_vars(right, vars);
|
||||
}
|
||||
ASTNode::UnaryOp { operand, .. } => collect_vars(operand, vars),
|
||||
ASTNode::MethodCall { object, arguments, .. } => {
|
||||
ASTNode::MethodCall {
|
||||
object, arguments, ..
|
||||
} => {
|
||||
collect_vars(object, vars);
|
||||
for arg in arguments {
|
||||
collect_vars(arg, vars);
|
||||
|
||||
@ -30,7 +30,6 @@
|
||||
//! - `value_id_ranges::funcscanner_append_defs` - ValueId allocation strategy
|
||||
//! - `loop_scope_shape::CaseAContext` - Context extraction
|
||||
|
||||
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges;
|
||||
use crate::mir::join_ir::{
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
//! - ValueId/BTreeMap の初期化ボイラープレート統一化
|
||||
//! - Pinned/Carrier 変数の一元管理
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Entry関数構築用の統一ビルダー
|
||||
///
|
||||
|
||||
@ -84,19 +84,19 @@
|
||||
//! - `loop_to_join` - Main loop lowering coordinator
|
||||
|
||||
// Pattern-specific lowering modules
|
||||
pub mod skip_ws;
|
||||
pub mod trim;
|
||||
pub mod append_defs;
|
||||
pub mod skip_ws;
|
||||
pub mod stage1_using_resolver;
|
||||
pub mod trim;
|
||||
|
||||
// Helper modules
|
||||
pub mod entry_builder;
|
||||
pub mod whitespace_check;
|
||||
|
||||
// Re-export public lowering functions
|
||||
pub(crate) use skip_ws::lower_case_a_skip_ws_with_scope;
|
||||
pub(crate) use trim::lower_case_a_trim_with_scope;
|
||||
pub(crate) use append_defs::lower_case_a_append_defs_with_scope;
|
||||
pub(crate) use skip_ws::lower_case_a_skip_ws_with_scope;
|
||||
pub(crate) use stage1_using_resolver::lower_case_a_stage1_usingresolver_with_scope;
|
||||
pub(crate) use trim::lower_case_a_trim_with_scope;
|
||||
|
||||
// Re-export helper utilities
|
||||
|
||||
@ -31,7 +31,6 @@
|
||||
//! - `value_id_ranges::skip_ws` - ValueId allocation strategy
|
||||
//! - `loop_scope_shape::CaseAContext` - Context extraction
|
||||
|
||||
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::skip_ws as vid;
|
||||
|
||||
@ -30,7 +30,6 @@
|
||||
//! - `value_id_ranges::stage1_using_resolver` - ValueId allocation strategy
|
||||
//! - `loop_scope_shape::CaseAContext` - Context extraction
|
||||
|
||||
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::stage1_using_resolver as stage1_vid;
|
||||
|
||||
@ -44,7 +44,6 @@
|
||||
//! - `value_id_ranges::funcscanner_trim` - ValueId allocation strategy
|
||||
//! - `whitespace_check` - Whitespace detection helper (shared with skip_ws)
|
||||
|
||||
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges;
|
||||
use crate::mir::join_ir::{
|
||||
|
||||
@ -59,10 +59,7 @@ impl WhitespaceDetector {
|
||||
/// 具体的な JoinInst 生成は呼び出し側で行う。
|
||||
/// ここは判定ロジック(どの文字を空白と判定するか)を記録する。
|
||||
#[allow(dead_code)]
|
||||
pub fn build_whitespace_check_expr(
|
||||
ch_value: ValueId,
|
||||
_debug: bool,
|
||||
) -> Option<ValueId> {
|
||||
pub fn build_whitespace_check_expr(ch_value: ValueId, _debug: bool) -> Option<ValueId> {
|
||||
// NOTE: JoinInst を生成する実装は呼び出し側で行う
|
||||
// ここは判定ロジック(どの文字を空白と判定するか)を記録
|
||||
|
||||
|
||||
@ -133,7 +133,10 @@ pub fn try_lower_if_to_joinir(
|
||||
// IfMerge が成功すればそれを返す、失敗したら Select を試行
|
||||
// Phase 61-1: context がある場合は with_context() を使用
|
||||
let if_merge_lowerer = if let Some(ctx) = context {
|
||||
crate::mir::join_ir::lowering::if_merge::IfMergeLowerer::with_context(debug_level, ctx.clone())
|
||||
crate::mir::join_ir::lowering::if_merge::IfMergeLowerer::with_context(
|
||||
debug_level,
|
||||
ctx.clone(),
|
||||
)
|
||||
} else {
|
||||
crate::mir::join_ir::lowering::if_merge::IfMergeLowerer::new(debug_level)
|
||||
};
|
||||
@ -153,7 +156,10 @@ pub fn try_lower_if_to_joinir(
|
||||
// 4. IfMerge が失敗したら Select を試行(単一変数パターン)
|
||||
// Phase 61-1: context がある場合は with_context() を使用
|
||||
let if_select_lowerer = if let Some(ctx) = context {
|
||||
crate::mir::join_ir::lowering::if_select::IfSelectLowerer::with_context(debug_level, ctx.clone())
|
||||
crate::mir::join_ir::lowering::if_select::IfSelectLowerer::with_context(
|
||||
debug_level,
|
||||
ctx.clone(),
|
||||
)
|
||||
} else {
|
||||
crate::mir::join_ir::lowering::if_select::IfSelectLowerer::new(debug_level)
|
||||
};
|
||||
|
||||
@ -42,8 +42,8 @@
|
||||
//! ...
|
||||
//! ```
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use super::carrier_info::CarrierRole;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Explicit binding between JoinIR exit value and host variable
|
||||
///
|
||||
@ -278,11 +278,11 @@ impl JoinInlineBoundary {
|
||||
host_outputs: vec![],
|
||||
exit_bindings: vec![],
|
||||
#[allow(deprecated)]
|
||||
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
||||
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
||||
condition_bindings: vec![], // Phase 171-fix: Default to empty
|
||||
expr_result: None, // Phase 33-14: Default to carrier-only pattern
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228: Default to None
|
||||
expr_result: None, // Phase 33-14: Default to carrier-only pattern
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228: Default to None
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,11 +321,11 @@ impl JoinInlineBoundary {
|
||||
host_outputs,
|
||||
exit_bindings: vec![],
|
||||
#[allow(deprecated)]
|
||||
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
||||
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
||||
condition_bindings: vec![], // Phase 171-fix: Default to empty
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
}
|
||||
}
|
||||
|
||||
@ -381,11 +381,11 @@ impl JoinInlineBoundary {
|
||||
host_outputs: vec![],
|
||||
exit_bindings,
|
||||
#[allow(deprecated)]
|
||||
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
||||
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
||||
condition_bindings: vec![], // Phase 171-fix: Default to empty
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,9 +429,9 @@ impl JoinInlineBoundary {
|
||||
#[allow(deprecated)]
|
||||
condition_inputs,
|
||||
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
}
|
||||
}
|
||||
|
||||
@ -479,9 +479,9 @@ impl JoinInlineBoundary {
|
||||
#[allow(deprecated)]
|
||||
condition_inputs,
|
||||
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
}
|
||||
}
|
||||
|
||||
@ -534,11 +534,11 @@ impl JoinInlineBoundary {
|
||||
host_outputs: vec![],
|
||||
exit_bindings: vec![],
|
||||
#[allow(deprecated)]
|
||||
condition_inputs: vec![], // Deprecated, use condition_bindings instead
|
||||
condition_inputs: vec![], // Deprecated, use condition_bindings instead
|
||||
condition_bindings,
|
||||
expr_result: None, // Phase 33-14
|
||||
expr_result: None, // Phase 33-14
|
||||
loop_var_name: None, // Phase 33-16
|
||||
carrier_info: None, // Phase 228
|
||||
carrier_info: None, // Phase 228
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -550,8 +550,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_boundary_inputs_only() {
|
||||
let boundary = JoinInlineBoundary::new_inputs_only(
|
||||
vec![ValueId(0)], // JoinIR uses ValueId(0) for loop var
|
||||
vec![ValueId(4)], // Host has loop var at ValueId(4)
|
||||
vec![ValueId(0)], // JoinIR uses ValueId(0) for loop var
|
||||
vec![ValueId(4)], // Host has loop var at ValueId(4)
|
||||
);
|
||||
|
||||
assert_eq!(boundary.join_inputs.len(), 1);
|
||||
@ -560,17 +560,14 @@ mod tests {
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
assert_eq!(boundary.host_outputs.len(), 0);
|
||||
assert_eq!(boundary.condition_inputs.len(), 0); // Phase 171: Deprecated field
|
||||
assert_eq!(boundary.condition_inputs.len(), 0); // Phase 171: Deprecated field
|
||||
}
|
||||
assert_eq!(boundary.condition_bindings.len(), 0); // Phase 171-fix: New field
|
||||
assert_eq!(boundary.condition_bindings.len(), 0); // Phase 171-fix: New field
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "join_inputs and host_inputs must have same length")]
|
||||
fn test_boundary_mismatched_inputs() {
|
||||
JoinInlineBoundary::new_inputs_only(
|
||||
vec![ValueId(0), ValueId(1)],
|
||||
vec![ValueId(4)],
|
||||
);
|
||||
JoinInlineBoundary::new_inputs_only(vec![ValueId(0), ValueId(1)], vec![ValueId(4)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,9 +24,9 @@
|
||||
//! .build();
|
||||
//! ```
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use super::condition_to_joinir::ConditionBinding;
|
||||
use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Role of a parameter in JoinIR lowering (Phase 200-A)
|
||||
///
|
||||
@ -96,7 +96,7 @@ impl JoinInlineBoundaryBuilder {
|
||||
expr_result: None,
|
||||
loop_var_name: None,
|
||||
carrier_info: None, // Phase 228: Initialize as None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +224,8 @@ impl JoinInlineBoundaryBuilder {
|
||||
// Phase 200-B: Add to condition_bindings without PHI
|
||||
// 1. Allocate JoinIR-local ValueId
|
||||
let join_id = ValueId(
|
||||
(self.boundary.join_inputs.len() + self.boundary.condition_bindings.len()) as u32
|
||||
(self.boundary.join_inputs.len() + self.boundary.condition_bindings.len())
|
||||
as u32,
|
||||
);
|
||||
|
||||
// 2. Create ConditionBinding
|
||||
@ -257,7 +258,9 @@ impl JoinInlineBoundaryBuilder {
|
||||
///
|
||||
/// `Some(ValueId)` if the variable exists in condition_bindings, `None` otherwise.
|
||||
pub fn get_condition_binding(&self, name: &str) -> Option<ValueId> {
|
||||
self.boundary.condition_bindings.iter()
|
||||
self.boundary
|
||||
.condition_bindings
|
||||
.iter()
|
||||
.find(|b| b.name == name)
|
||||
.map(|b| b.join_value)
|
||||
}
|
||||
@ -363,18 +366,18 @@ mod tests {
|
||||
#[test]
|
||||
fn test_builder_pattern3_style() {
|
||||
// Pattern3 style: Two carriers (i + sum), exit_bindings, loop_var_name
|
||||
|
||||
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(vec![ValueId(0), ValueId(1)], vec![ValueId(100), ValueId(101)])
|
||||
.with_exit_bindings(vec![
|
||||
LoopExitBinding {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: ValueId(18),
|
||||
host_slot: ValueId(101),
|
||||
role: CarrierRole::LoopState,
|
||||
}
|
||||
])
|
||||
.with_inputs(
|
||||
vec![ValueId(0), ValueId(1)],
|
||||
vec![ValueId(100), ValueId(101)],
|
||||
)
|
||||
.with_exit_bindings(vec![LoopExitBinding {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: ValueId(18),
|
||||
host_slot: ValueId(101),
|
||||
role: CarrierRole::LoopState,
|
||||
}])
|
||||
.with_loop_var_name(Some("i".to_string()))
|
||||
.build();
|
||||
|
||||
@ -391,8 +394,8 @@ mod tests {
|
||||
// Pattern4 style: Dynamic carrier count, continue support
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(
|
||||
vec![ValueId(0), ValueId(1), ValueId(2)], // i + 2 carriers
|
||||
vec![ValueId(100), ValueId(101), ValueId(102)]
|
||||
vec![ValueId(0), ValueId(1), ValueId(2)], // i + 2 carriers
|
||||
vec![ValueId(100), ValueId(101), ValueId(102)],
|
||||
)
|
||||
.with_exit_bindings(vec![
|
||||
LoopExitBinding {
|
||||
@ -406,7 +409,7 @@ mod tests {
|
||||
join_exit_value: ValueId(20),
|
||||
host_slot: ValueId(101),
|
||||
role: CarrierRole::LoopState,
|
||||
}
|
||||
},
|
||||
])
|
||||
.with_loop_var_name(Some("i".to_string()))
|
||||
.build();
|
||||
|
||||
@ -224,7 +224,11 @@ impl<'a> LoopBodyLocalInitLowerer<'a> {
|
||||
///
|
||||
/// * `Ok(ValueId)` - JoinIR ValueId of computed result
|
||||
/// * `Err(msg)` - Unsupported expression (Fail-Fast)
|
||||
fn lower_init_expr(&mut self, expr: &ASTNode, env: &LoopBodyLocalEnv) -> Result<ValueId, String> {
|
||||
fn lower_init_expr(
|
||||
&mut self,
|
||||
expr: &ASTNode,
|
||||
env: &LoopBodyLocalEnv,
|
||||
) -> Result<ValueId, String> {
|
||||
match expr {
|
||||
// Constant literal: 42, 0, 1, "string" (use Literal with value)
|
||||
ASTNode::Literal { value, .. } => {
|
||||
@ -488,7 +492,6 @@ impl<'a> LoopBodyLocalInitLowerer<'a> {
|
||||
|
||||
Ok(result_id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -133,25 +133,33 @@ pub fn try_lower_loop_pattern_to_joinir(
|
||||
// Step 3: Route to appropriate lowerer based on pattern
|
||||
match pattern {
|
||||
LoopPatternKind::Pattern4Continue => {
|
||||
if let Some(inst) = super::loop_patterns::lower_loop_with_continue_to_joinir(loop_form, lowerer) {
|
||||
if let Some(inst) =
|
||||
super::loop_patterns::lower_loop_with_continue_to_joinir(loop_form, lowerer)
|
||||
{
|
||||
eprintln!("[try_lower_loop_pattern] ✅ Pattern 4 (Continue) matched");
|
||||
return Some(inst);
|
||||
}
|
||||
}
|
||||
LoopPatternKind::Pattern3IfPhi => {
|
||||
if let Some(inst) = super::loop_patterns::lower_loop_with_conditional_phi_to_joinir(loop_form, lowerer) {
|
||||
if let Some(inst) =
|
||||
super::loop_patterns::lower_loop_with_conditional_phi_to_joinir(loop_form, lowerer)
|
||||
{
|
||||
eprintln!("[try_lower_loop_pattern] ✅ Pattern 3 (If-Else PHI) matched");
|
||||
return Some(inst);
|
||||
}
|
||||
}
|
||||
LoopPatternKind::Pattern2Break => {
|
||||
if let Some(inst) = super::loop_patterns::lower_loop_with_break_to_joinir(loop_form, lowerer) {
|
||||
if let Some(inst) =
|
||||
super::loop_patterns::lower_loop_with_break_to_joinir(loop_form, lowerer)
|
||||
{
|
||||
eprintln!("[try_lower_loop_pattern] ✅ Pattern 2 (Break) matched");
|
||||
return Some(inst);
|
||||
}
|
||||
}
|
||||
LoopPatternKind::Pattern1SimpleWhile => {
|
||||
if let Some(inst) = super::loop_patterns::lower_simple_while_to_joinir(loop_form, lowerer) {
|
||||
if let Some(inst) =
|
||||
super::loop_patterns::lower_simple_while_to_joinir(loop_form, lowerer)
|
||||
{
|
||||
eprintln!("[try_lower_loop_pattern] ✅ Pattern 1 (Simple While) matched");
|
||||
return Some(inst);
|
||||
}
|
||||
|
||||
@ -48,13 +48,13 @@
|
||||
|
||||
pub mod simple_while;
|
||||
pub mod with_break;
|
||||
pub mod with_if_phi;
|
||||
pub mod with_continue;
|
||||
pub mod with_if_phi;
|
||||
|
||||
pub use simple_while::lower_simple_while_to_joinir;
|
||||
pub use with_break::lower_loop_with_break_to_joinir;
|
||||
pub use with_if_phi::lower_loop_with_conditional_phi_to_joinir;
|
||||
pub use with_continue::lower_loop_with_continue_to_joinir;
|
||||
pub use with_if_phi::lower_loop_with_conditional_phi_to_joinir;
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions (Shared Utilities)
|
||||
@ -76,7 +76,6 @@ pub use with_continue::lower_loop_with_continue_to_joinir;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Pattern 1: Simple While Loop Tests
|
||||
|
||||
@ -236,15 +236,17 @@ impl CaseALoweringShape {
|
||||
// Phase 170-C-2b: Use UpdateSummary if available
|
||||
if let Some(ref summary) = features.update_summary {
|
||||
// Single carrier with CounterLike update → StringExamination
|
||||
if summary.carriers.len() == 1
|
||||
&& summary.carriers[0].kind == UpdateKind::CounterLike
|
||||
{
|
||||
if summary.carriers.len() == 1 && summary.carriers[0].kind == UpdateKind::CounterLike {
|
||||
return CaseALoweringShape::StringExamination;
|
||||
}
|
||||
|
||||
// Any AccumulationLike carrier → ArrayAccumulation (for single carrier)
|
||||
// or IterationWithAccumulation (for multiple carriers)
|
||||
if summary.carriers.iter().any(|c| c.kind == UpdateKind::AccumulationLike) {
|
||||
if summary
|
||||
.carriers
|
||||
.iter()
|
||||
.any(|c| c.kind == UpdateKind::AccumulationLike)
|
||||
{
|
||||
if carrier_count == 1 {
|
||||
return CaseALoweringShape::ArrayAccumulation;
|
||||
} else {
|
||||
@ -299,12 +301,14 @@ impl CaseALoweringShape {
|
||||
// Note: carriers is BTreeSet<String>, so each item is already a String
|
||||
let carrier_names: Vec<String> = scope.carriers.iter().cloned().collect();
|
||||
let update_summary =
|
||||
crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name(&carrier_names);
|
||||
crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name(
|
||||
&carrier_names,
|
||||
);
|
||||
|
||||
// Create stub features (Phase 170-B will use real LoopFeatures)
|
||||
let stub_features = crate::mir::loop_pattern_detection::LoopFeatures {
|
||||
has_break: false, // Unknown from LoopScopeShape alone
|
||||
has_continue: false, // Unknown from LoopScopeShape alone
|
||||
has_break: false, // Unknown from LoopScopeShape alone
|
||||
has_continue: false, // Unknown from LoopScopeShape alone
|
||||
has_if: false,
|
||||
has_if_else_phi: false,
|
||||
carrier_count,
|
||||
@ -319,7 +323,10 @@ impl CaseALoweringShape {
|
||||
/// Is this a recognized lowering shape?
|
||||
#[allow(dead_code)]
|
||||
pub fn is_recognized(&self) -> bool {
|
||||
!matches!(self, CaseALoweringShape::NotCaseA | CaseALoweringShape::Generic)
|
||||
!matches!(
|
||||
self,
|
||||
CaseALoweringShape::NotCaseA | CaseALoweringShape::Generic
|
||||
)
|
||||
}
|
||||
|
||||
/// Get human-readable name for tracing/debugging
|
||||
@ -340,9 +347,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_shape_display() {
|
||||
assert_eq!(CaseALoweringShape::StringExamination.name(), "StringExamination");
|
||||
assert_eq!(CaseALoweringShape::ArrayAccumulation.name(), "ArrayAccumulation");
|
||||
assert_eq!(CaseALoweringShape::IterationWithAccumulation.name(), "IterationWithAccumulation");
|
||||
assert_eq!(
|
||||
CaseALoweringShape::StringExamination.name(),
|
||||
"StringExamination"
|
||||
);
|
||||
assert_eq!(
|
||||
CaseALoweringShape::ArrayAccumulation.name(),
|
||||
"ArrayAccumulation"
|
||||
);
|
||||
assert_eq!(
|
||||
CaseALoweringShape::IterationWithAccumulation.name(),
|
||||
"IterationWithAccumulation"
|
||||
);
|
||||
assert_eq!(CaseALoweringShape::Generic.name(), "Generic");
|
||||
assert_eq!(CaseALoweringShape::NotCaseA.name(), "NotCaseA");
|
||||
}
|
||||
|
||||
@ -147,7 +147,10 @@ impl LoopToJoinLowerer {
|
||||
}
|
||||
|
||||
// Phase 33-23: Validator箱に検証を委譲
|
||||
if !self.validator.is_supported_case_a(func, ®ion, &exit_edges, &scope) {
|
||||
if !self
|
||||
.validator
|
||||
.is_supported_case_a(func, ®ion, &exit_edges, &scope)
|
||||
{
|
||||
if self.debug {
|
||||
eprintln!(
|
||||
"[LoopToJoinLowerer] rejected by validator: {:?}",
|
||||
|
||||
@ -81,7 +81,8 @@ impl LoopUpdateAnalyzer {
|
||||
pub fn analyze_carrier_updates(
|
||||
body: &[ASTNode],
|
||||
carriers: &[CarrierVar],
|
||||
) -> BTreeMap<String, UpdateExpr> { // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
) -> BTreeMap<String, UpdateExpr> {
|
||||
// Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
let mut updates = BTreeMap::new();
|
||||
|
||||
// Extract carrier names for quick lookup
|
||||
@ -109,7 +110,9 @@ impl LoopUpdateAnalyzer {
|
||||
if let Some(target_name) = Self::extract_variable_name(target) {
|
||||
if carrier_names.contains(&target_name.as_str()) {
|
||||
// This is a carrier update, analyze the RHS
|
||||
if let Some(update_expr) = Self::analyze_update_value(&target_name, value) {
|
||||
if let Some(update_expr) =
|
||||
Self::analyze_update_value(&target_name, value)
|
||||
{
|
||||
updates.insert(target_name, update_expr);
|
||||
}
|
||||
}
|
||||
@ -233,9 +236,7 @@ impl LoopUpdateAnalyzer {
|
||||
} => Some(UpdateRhs::StringLiteral(s.clone())),
|
||||
|
||||
// Variable: sum + i (also handles: result + ch)
|
||||
ASTNode::Variable { name, .. } => {
|
||||
Some(UpdateRhs::Variable(name.clone()))
|
||||
}
|
||||
ASTNode::Variable { name, .. } => Some(UpdateRhs::Variable(name.clone())),
|
||||
|
||||
// Phase 190: Number accumulation pattern detection
|
||||
// This is called from analyze_update_value, so we're analyzing the full RHS of an assignment
|
||||
@ -245,9 +246,9 @@ impl LoopUpdateAnalyzer {
|
||||
|
||||
// Phase 178: Method call or other complex expression
|
||||
// e.g., result + s.substring(pos, end)
|
||||
ASTNode::Call { .. }
|
||||
| ASTNode::MethodCall { .. }
|
||||
| ASTNode::UnaryOp { .. } => Some(UpdateRhs::Other),
|
||||
ASTNode::Call { .. } | ASTNode::MethodCall { .. } | ASTNode::UnaryOp { .. } => {
|
||||
Some(UpdateRhs::Other)
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@ -617,7 +618,11 @@ mod tests {
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
|
||||
// Verify both carriers have updates
|
||||
assert_eq!(updates.len(), 2, "Should detect updates for both i and result");
|
||||
assert_eq!(
|
||||
updates.len(),
|
||||
2,
|
||||
"Should detect updates for both i and result"
|
||||
);
|
||||
|
||||
// Verify i = i + 1 (Const increment)
|
||||
if let Some(UpdateExpr::BinOp { lhs, op, rhs }) = updates.get("i") {
|
||||
@ -640,10 +645,16 @@ mod tests {
|
||||
assert_eq!(*base, 10, "NumberAccumulation should use base 10");
|
||||
assert_eq!(digit_var, "digit_pos", "Should use digit_pos variable");
|
||||
} else {
|
||||
panic!("Expected NumberAccumulation for result update, got {:?}", rhs);
|
||||
panic!(
|
||||
"Expected NumberAccumulation for result update, got {:?}",
|
||||
rhs
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!("Expected BinOp for result update, got {:?}", updates.get("result"));
|
||||
panic!(
|
||||
"Expected BinOp for result update, got {:?}",
|
||||
updates.get("result")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +155,9 @@ impl LoopUpdateSummary {
|
||||
///
|
||||
/// Returns a set of variable names that are assigned (LHS) in the loop body.
|
||||
/// This prevents phantom carriers from non-assigned variables.
|
||||
fn extract_assigned_variables(loop_body: &[crate::ast::ASTNode]) -> std::collections::HashSet<String> {
|
||||
fn extract_assigned_variables(
|
||||
loop_body: &[crate::ast::ASTNode],
|
||||
) -> std::collections::HashSet<String> {
|
||||
use crate::ast::ASTNode;
|
||||
let mut assigned = std::collections::HashSet::new();
|
||||
|
||||
@ -170,7 +172,11 @@ fn extract_assigned_variables(loop_body: &[crate::ast::ASTNode]) -> std::collect
|
||||
visit_node(value, assigned);
|
||||
}
|
||||
// If statement: recurse into then/else branches
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
for stmt in then_body {
|
||||
visit_node(stmt, assigned);
|
||||
}
|
||||
@ -207,7 +213,12 @@ fn classify_update_kind_from_rhs(rhs: &crate::ast::ASTNode) -> UpdateKind {
|
||||
match rhs {
|
||||
// x = x + 1 → CounterLike
|
||||
// x = x + n → AccumulationLike (where n is not 1)
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
if matches!(operator, BinaryOperator::Add) {
|
||||
// Check if left is self-reference (will be validated by caller)
|
||||
if matches!(left.as_ref(), ASTNode::Variable { .. }) {
|
||||
@ -215,9 +226,9 @@ fn classify_update_kind_from_rhs(rhs: &crate::ast::ASTNode) -> UpdateKind {
|
||||
if let ASTNode::Literal { value, .. } = right.as_ref() {
|
||||
if let LiteralValue::Integer(n) = value {
|
||||
if *n == 1 {
|
||||
return UpdateKind::CounterLike; // x = x + 1
|
||||
return UpdateKind::CounterLike; // x = x + 1
|
||||
} else {
|
||||
return UpdateKind::AccumulationLike; // x = x + n
|
||||
return UpdateKind::AccumulationLike; // x = x + n
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -251,7 +262,10 @@ fn classify_update_kind_from_rhs(rhs: &crate::ast::ASTNode) -> UpdateKind {
|
||||
/// Phase 219: Extract assignment RHS for a given variable
|
||||
///
|
||||
/// Returns the RHS expression of the first assignment to `var_name` in loop body.
|
||||
fn find_assignment_rhs<'a>(var_name: &str, loop_body: &'a [crate::ast::ASTNode]) -> Option<&'a crate::ast::ASTNode> {
|
||||
fn find_assignment_rhs<'a>(
|
||||
var_name: &str,
|
||||
loop_body: &'a [crate::ast::ASTNode],
|
||||
) -> Option<&'a crate::ast::ASTNode> {
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
fn visit_node<'a>(var_name: &str, node: &'a ASTNode) -> Option<&'a ASTNode> {
|
||||
@ -265,7 +279,11 @@ fn find_assignment_rhs<'a>(var_name: &str, loop_body: &'a [crate::ast::ASTNode])
|
||||
// Recurse into value
|
||||
visit_node(var_name, value)
|
||||
}
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
for stmt in then_body {
|
||||
if let Some(rhs) = visit_node(var_name, stmt) {
|
||||
return Some(rhs);
|
||||
@ -354,7 +372,7 @@ pub fn analyze_loop_updates_by_name(carrier_names: &[String]) -> LoopUpdateSumma
|
||||
.iter()
|
||||
.map(|name| CarrierUpdateInfo {
|
||||
name: name.clone(),
|
||||
kind: UpdateKind::AccumulationLike, // Default to accumulation
|
||||
kind: UpdateKind::AccumulationLike, // Default to accumulation
|
||||
then_expr: None,
|
||||
else_expr: None,
|
||||
})
|
||||
|
||||
@ -61,11 +61,7 @@ impl LoopViewBuilder {
|
||||
///
|
||||
/// - `Some(JoinModule)`: Lowering成功
|
||||
/// - `None`: 未サポートパターン(フォールバック経路へ)
|
||||
pub fn build(
|
||||
&self,
|
||||
scope: LoopScopeShape,
|
||||
func_name: Option<&str>,
|
||||
) -> Option<JoinModule> {
|
||||
pub fn build(&self, scope: LoopScopeShape, func_name: Option<&str>) -> Option<JoinModule> {
|
||||
let name = func_name.unwrap_or("");
|
||||
|
||||
// Phase 188-Impl-1: Pattern 1 (Simple While Loop) detection
|
||||
@ -139,7 +135,10 @@ impl LoopViewBuilder {
|
||||
&mut join_value_space,
|
||||
) {
|
||||
if self.debug {
|
||||
eprintln!("[LoopViewBuilder] Pattern 1 lowering succeeded for {:?}", name);
|
||||
eprintln!(
|
||||
"[LoopViewBuilder] Pattern 1 lowering succeeded for {:?}",
|
||||
name
|
||||
);
|
||||
}
|
||||
return Some(result);
|
||||
}
|
||||
@ -181,7 +180,9 @@ impl LoopViewBuilder {
|
||||
}
|
||||
CaseALoweringShape::IterationWithAccumulation => {
|
||||
if self.debug {
|
||||
eprintln!("[LoopViewBuilder] Shape: IterationWithAccumulation → stage1 lowerer");
|
||||
eprintln!(
|
||||
"[LoopViewBuilder] Shape: IterationWithAccumulation → stage1 lowerer"
|
||||
);
|
||||
}
|
||||
generic_case_a::lower_case_a_stage1_usingresolver_with_scope(scope)
|
||||
}
|
||||
|
||||
@ -9,6 +9,24 @@ pub(crate) fn build_fragment_meta(
|
||||
i_exit: ValueId,
|
||||
carrier_exit_ids: &[ValueId],
|
||||
) -> JoinFragmentMeta {
|
||||
let dev_on = env::joinir_dev_enabled();
|
||||
|
||||
if carrier_exit_ids.len() != carrier_info.carriers.len() {
|
||||
let msg = format!(
|
||||
"[joinir/boundary] exit value length mismatch: carriers={} exit_ids={}",
|
||||
carrier_info.carriers.len(),
|
||||
carrier_exit_ids.len()
|
||||
);
|
||||
debug_assert!(
|
||||
carrier_exit_ids.len() == carrier_info.carriers.len(),
|
||||
"{}",
|
||||
msg
|
||||
);
|
||||
if dev_on {
|
||||
panic!("{}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
let mut exit_values = Vec::new();
|
||||
exit_values.push((loop_var_name.to_string(), i_exit));
|
||||
|
||||
@ -16,9 +34,9 @@ pub(crate) fn build_fragment_meta(
|
||||
exit_values.push((carrier.name.clone(), carrier_exit_ids[idx]));
|
||||
}
|
||||
|
||||
if env::joinir_debug_level() > 0 {
|
||||
if env::joinir_debug_level() > 0 || dev_on {
|
||||
eprintln!(
|
||||
"[joinir/boundary_builder] Exit values (loop_var='{}', carriers={}): {:?}",
|
||||
"[joinir/boundary] Exit values (loop_var='{}', carriers={}): {:?}",
|
||||
loop_var_name,
|
||||
carrier_info.carriers.len(),
|
||||
exit_values
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::condition_lowering_box::ConditionContext;
|
||||
use crate::mir::join_ir::lowering::condition_to_joinir::lower_condition_to_joinir;
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
|
||||
/// Build a Pattern2ScopeManager for ExprLowerer paths.
|
||||
fn make_scope_manager<'a>(
|
||||
|
||||
@ -62,18 +62,18 @@ use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
|
||||
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; // Phase 244
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; // Phase 244
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; // Phase 244
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
MirLikeInst, UnaryOp,
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||
UnaryOp,
|
||||
};
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
|
||||
use crate::mir::loop_pattern_detection::error_messages::{
|
||||
format_unsupported_condition_error, extract_body_local_names,
|
||||
extract_body_local_names, format_unsupported_condition_error,
|
||||
};
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; // Phase 244
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
|
||||
@ -130,15 +130,15 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
// Phase 170-D-impl-3: Validate that loop condition only uses supported variable scopes
|
||||
// LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables
|
||||
let loop_var_name = carrier_info.loop_var_name.clone();
|
||||
let loop_cond_scope = LoopConditionScopeBox::analyze(
|
||||
&loop_var_name,
|
||||
&[condition],
|
||||
Some(&_scope),
|
||||
);
|
||||
let loop_cond_scope =
|
||||
LoopConditionScopeBox::analyze(&loop_var_name, &[condition], Some(&_scope));
|
||||
|
||||
if loop_cond_scope.has_loop_body_local() {
|
||||
let body_local_names = extract_body_local_names(&loop_cond_scope.vars);
|
||||
return Err(format_unsupported_condition_error("pattern4", &body_local_names));
|
||||
return Err(format_unsupported_condition_error(
|
||||
"pattern4",
|
||||
&body_local_names,
|
||||
));
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
@ -152,7 +152,11 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
eprintln!(
|
||||
"[joinir/pattern4] Phase 202-C: Generating JoinIR for {} carriers: {:?}",
|
||||
carrier_count,
|
||||
carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>()
|
||||
carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.map(|c| &c.name)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// ==================================================================
|
||||
@ -171,7 +175,7 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
for _ in 0..carrier_count {
|
||||
carrier_init_ids.push(join_value_space.alloc_local());
|
||||
}
|
||||
let loop_result = join_value_space.alloc_local(); // result from loop_step
|
||||
let loop_result = join_value_space.alloc_local(); // result from loop_step
|
||||
|
||||
// loop_step() parameters: [i_param, carrier1_param, carrier2_param, ...]
|
||||
let i_param = join_value_space.alloc_local();
|
||||
@ -202,10 +206,12 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
|
||||
// Phase 169 / Phase 171-fix / Phase 244: Lower condition using ConditionLoweringBox trait
|
||||
let (cond_value, mut cond_instructions) = {
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer};
|
||||
use crate::mir::join_ir::lowering::condition_lowering_box::{ConditionLoweringBox, ConditionContext};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::condition_lowering_box::{
|
||||
ConditionContext, ConditionLoweringBox,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
|
||||
// Build minimal ScopeManager for header condition
|
||||
let empty_body_env = LoopBodyLocalEnv::new();
|
||||
@ -221,7 +227,8 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
if ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(condition) {
|
||||
// Phase 244: ExprLowerer via ConditionLoweringBox trait
|
||||
let mut dummy_builder = MirBuilder::new();
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||
let mut expr_lowerer =
|
||||
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||
|
||||
let context = ConditionContext {
|
||||
loop_var_name: loop_var_name.clone(),
|
||||
@ -263,7 +270,7 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
let mut carrier_next_ids: Vec<ValueId> = Vec::new();
|
||||
let mut carrier_merged_ids: Vec<ValueId> = Vec::new();
|
||||
for _ in 0..carrier_count {
|
||||
carrier_next_ids.push(alloc_value()); // carrier_next = carrier + ...
|
||||
carrier_next_ids.push(alloc_value()); // carrier_next = carrier + ...
|
||||
carrier_merged_ids.push(alloc_value()); // carrier_merged = Select(...)
|
||||
}
|
||||
|
||||
@ -303,11 +310,8 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
let mut loop_step_params = vec![i_param];
|
||||
loop_step_params.extend(carrier_param_ids.iter().copied());
|
||||
|
||||
let mut loop_step_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
"loop_step".to_string(),
|
||||
loop_step_params,
|
||||
);
|
||||
let mut loop_step_func =
|
||||
JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_step_params);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Natural Exit Condition Check (Phase 169: from AST)
|
||||
@ -408,9 +412,15 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
let carrier_name = &carrier_info.carriers[idx].name;
|
||||
|
||||
// Phase 197: Extract RHS from update expression metadata
|
||||
eprintln!("[loop_with_continue_minimal] Processing carrier '{}' (idx={})", carrier_name, idx);
|
||||
eprintln!(
|
||||
"[loop_with_continue_minimal] Processing carrier '{}' (idx={})",
|
||||
carrier_name, idx
|
||||
);
|
||||
let rhs = if let Some(update_expr) = carrier_updates.get(carrier_name) {
|
||||
eprintln!("[loop_with_continue_minimal] Found update expr: {:?}", update_expr);
|
||||
eprintln!(
|
||||
"[loop_with_continue_minimal] Found update expr: {:?}",
|
||||
update_expr
|
||||
);
|
||||
match update_expr {
|
||||
UpdateExpr::BinOp { op, rhs, .. } => {
|
||||
// Verify operator is Add (only supported for now)
|
||||
@ -451,12 +461,12 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
// This is effectively a passthrough (no JoinIR update)
|
||||
loop_step_func.body.push(JoinInst::Select {
|
||||
dst: carrier_merged,
|
||||
cond: continue_cond, // Condition doesn't matter when both values are same
|
||||
cond: continue_cond, // Condition doesn't matter when both values are same
|
||||
then_val: carrier_param,
|
||||
else_val: carrier_param,
|
||||
type_hint: None,
|
||||
});
|
||||
continue; // Skip the BinOp and normal Select below
|
||||
continue; // Skip the BinOp and normal Select below
|
||||
}
|
||||
// Phase 178: String updates detected but not lowered to JoinIR yet
|
||||
// Skip JoinIR update - use Select passthrough to keep carrier_merged defined
|
||||
@ -469,12 +479,12 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
// This is effectively a passthrough (no JoinIR update)
|
||||
loop_step_func.body.push(JoinInst::Select {
|
||||
dst: carrier_merged,
|
||||
cond: continue_cond, // Condition doesn't matter when both values are same
|
||||
cond: continue_cond, // Condition doesn't matter when both values are same
|
||||
then_val: carrier_param,
|
||||
else_val: carrier_param,
|
||||
type_hint: None,
|
||||
});
|
||||
continue; // Skip the BinOp and normal Select below
|
||||
continue; // Skip the BinOp and normal Select below
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -495,8 +505,10 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
};
|
||||
|
||||
// carrier_next = carrier_param + rhs
|
||||
eprintln!("[loop_with_continue_minimal] Generating: ValueId({}) = ValueId({}) + ValueId({})",
|
||||
carrier_next.0, carrier_param.0, rhs.0);
|
||||
eprintln!(
|
||||
"[loop_with_continue_minimal] Generating: ValueId({}) = ValueId({}) + ValueId({})",
|
||||
carrier_next.0, carrier_param.0, rhs.0
|
||||
);
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
@ -510,8 +522,8 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
loop_step_func.body.push(JoinInst::Select {
|
||||
dst: carrier_merged,
|
||||
cond: continue_cond,
|
||||
then_val: carrier_param, // Continue: no update
|
||||
else_val: carrier_next, // Normal: update
|
||||
then_val: carrier_param, // Continue: no update
|
||||
else_val: carrier_next, // Normal: update
|
||||
type_hint: None,
|
||||
});
|
||||
}
|
||||
@ -525,7 +537,7 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: tail_call_args,
|
||||
k_next: None, // CRITICAL: None for tail call
|
||||
k_next: None, // CRITICAL: None for tail call
|
||||
dst: None,
|
||||
});
|
||||
|
||||
@ -534,11 +546,8 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
// ==================================================================
|
||||
// k_exit(carrier1_exit, carrier2_exit, ...) function
|
||||
// ==================================================================
|
||||
let mut k_exit_func = JoinFunction::new(
|
||||
k_exit_id,
|
||||
"k_exit".to_string(),
|
||||
carrier_exit_ids.clone(),
|
||||
);
|
||||
let mut k_exit_func =
|
||||
JoinFunction::new(k_exit_id, "k_exit".to_string(), carrier_exit_ids.clone());
|
||||
|
||||
// For now, return the first carrier's exit value (or void if no carriers)
|
||||
// TODO: Consider returning a tuple or using a different mechanism
|
||||
@ -562,7 +571,11 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
eprintln!(
|
||||
"[joinir/pattern4] Carriers: {} ({:?})",
|
||||
carrier_count,
|
||||
carrier_info.carriers.iter().map(|c| c.name.as_str()).collect::<Vec<_>>()
|
||||
carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.map(|c| c.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// ==================================================================
|
||||
|
||||
@ -32,14 +32,16 @@ use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{ExitMeta, JoinFragmentMeta};
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::condition_lowerer::lower_value_expression;
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::mir::join_ir::lowering::condition_pattern::{analyze_condition_pattern, ConditionPattern};
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
MirLikeInst, UnaryOp,
|
||||
use crate::mir::join_ir::lowering::condition_pattern::{
|
||||
analyze_condition_pattern, ConditionPattern,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||
UnaryOp,
|
||||
};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 213: Lower if-sum pattern to JoinIR using AST
|
||||
///
|
||||
@ -83,22 +85,34 @@ pub fn lower_if_sum_pattern(
|
||||
// Uses cond_env for variable resolution (e.g., `len` in `i < len`)
|
||||
let (loop_var, loop_op, loop_lhs_val, loop_limit_val, loop_limit_insts) =
|
||||
extract_loop_condition(loop_condition, &mut alloc_value, cond_env)?;
|
||||
eprintln!("[joinir/pattern3/if-sum] Loop condition: {} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Loop condition: {} {:?} ValueId({})",
|
||||
loop_var, loop_op, loop_limit_val.0
|
||||
);
|
||||
|
||||
// Step 2: Extract if condition info (e.g., i > 0 → var="i", op=Gt, value=ValueId)
|
||||
// Phase 220-D: Now returns ValueId and instructions
|
||||
// Phase 242-EX-A: Now supports complex LHS
|
||||
let (if_var, if_op, if_lhs_val, if_value_val, if_value_insts) =
|
||||
extract_if_condition(if_stmt, &mut alloc_value, cond_env)?;
|
||||
eprintln!("[joinir/pattern3/if-sum] If condition: {} {:?} ValueId({})", if_var, if_op, if_value_val.0);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] If condition: {} {:?} ValueId({})",
|
||||
if_var, if_op, if_value_val.0
|
||||
);
|
||||
|
||||
// Step 3: Extract then-branch update (e.g., sum = sum + 1 → var="sum", addend=1)
|
||||
let (update_var, update_addend) = extract_then_update(if_stmt)?;
|
||||
eprintln!("[joinir/pattern3/if-sum] Then update: {} += {}", update_var, update_addend);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Then update: {} += {}",
|
||||
update_var, update_addend
|
||||
);
|
||||
|
||||
// Step 4: Extract counter update (e.g., i = i + 1 → var="i", step=1)
|
||||
let (counter_var, counter_step) = extract_counter_update(body, &loop_var)?;
|
||||
eprintln!("[joinir/pattern3/if-sum] Counter update: {} += {}", counter_var, counter_step);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Counter update: {} += {}",
|
||||
counter_var, counter_step
|
||||
);
|
||||
|
||||
// Step 5: Generate JoinIR
|
||||
let mut alloc_value = || join_value_space.alloc_local();
|
||||
@ -111,10 +125,10 @@ pub fn lower_if_sum_pattern(
|
||||
|
||||
// === ValueId allocation ===
|
||||
// main() locals
|
||||
let i_init_val = alloc_value(); // i = 0
|
||||
let sum_init_val = alloc_value(); // sum = 0
|
||||
let i_init_val = alloc_value(); // i = 0
|
||||
let sum_init_val = alloc_value(); // sum = 0
|
||||
let count_init_val = alloc_value(); // count = 0 (optional)
|
||||
let loop_result = alloc_value(); // result from loop_step
|
||||
let loop_result = alloc_value(); // result from loop_step
|
||||
|
||||
// loop_step params
|
||||
let i_param = alloc_value();
|
||||
@ -124,19 +138,19 @@ pub fn lower_if_sum_pattern(
|
||||
// loop_step locals
|
||||
// Phase 220-D: loop_limit_val and if_value_val are already allocated by extract_*_condition()
|
||||
// and will be used directly from their return values
|
||||
let cmp_loop = alloc_value(); // loop condition comparison
|
||||
let exit_cond = alloc_value(); // negated loop condition
|
||||
let if_cmp = alloc_value(); // if condition comparison
|
||||
let sum_then = alloc_value(); // sum + update_addend
|
||||
let count_const = alloc_value(); // count increment (1)
|
||||
let count_then = alloc_value(); // count + 1
|
||||
let const_0 = alloc_value(); // 0 for else branch
|
||||
let sum_else = alloc_value(); // sum + 0 (identity)
|
||||
let count_else = alloc_value(); // count + 0 (identity)
|
||||
let sum_new = alloc_value(); // Select result for sum
|
||||
let count_new = alloc_value(); // Select result for count
|
||||
let step_const = alloc_value(); // counter step
|
||||
let i_next = alloc_value(); // i + step
|
||||
let cmp_loop = alloc_value(); // loop condition comparison
|
||||
let exit_cond = alloc_value(); // negated loop condition
|
||||
let if_cmp = alloc_value(); // if condition comparison
|
||||
let sum_then = alloc_value(); // sum + update_addend
|
||||
let count_const = alloc_value(); // count increment (1)
|
||||
let count_then = alloc_value(); // count + 1
|
||||
let const_0 = alloc_value(); // 0 for else branch
|
||||
let sum_else = alloc_value(); // sum + 0 (identity)
|
||||
let count_else = alloc_value(); // count + 0 (identity)
|
||||
let sum_new = alloc_value(); // Select result for sum
|
||||
let count_new = alloc_value(); // Select result for count
|
||||
let step_const = alloc_value(); // counter step
|
||||
let i_next = alloc_value(); // i + step
|
||||
|
||||
// k_exit params
|
||||
let sum_final = alloc_value();
|
||||
@ -148,7 +162,7 @@ pub fn lower_if_sum_pattern(
|
||||
// i_init = 0 (initial value from ctx)
|
||||
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: i_init_val,
|
||||
value: ConstValue::Integer(0), // TODO: Get from AST
|
||||
value: ConstValue::Integer(0), // TODO: Get from AST
|
||||
}));
|
||||
|
||||
// sum_init = 0
|
||||
@ -195,19 +209,23 @@ pub fn lower_if_sum_pattern(
|
||||
// Compare: i < limit (or other op from AST)
|
||||
// Phase 242-EX-A: Use computed LHS if available, otherwise use loop parameter
|
||||
let loop_lhs = loop_lhs_val.unwrap_or(i_param);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_loop,
|
||||
op: loop_op,
|
||||
lhs: loop_lhs,
|
||||
rhs: loop_limit_val,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_loop,
|
||||
op: loop_op,
|
||||
lhs: loop_lhs,
|
||||
rhs: loop_limit_val,
|
||||
}));
|
||||
|
||||
// exit_cond = !cmp_loop
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: exit_cond,
|
||||
op: UnaryOp::Not,
|
||||
operand: cmp_loop,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: exit_cond,
|
||||
op: UnaryOp::Not,
|
||||
operand: cmp_loop,
|
||||
}));
|
||||
|
||||
// Jump to exit if condition is false
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
@ -226,86 +244,110 @@ pub fn lower_if_sum_pattern(
|
||||
// Compare: if_var <op> if_value
|
||||
// Phase 242-EX-A: Use computed LHS if available, otherwise use loop parameter
|
||||
let if_lhs = if_lhs_val.unwrap_or(i_param);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: if_cmp,
|
||||
op: if_op,
|
||||
lhs: if_lhs,
|
||||
rhs: if_value_val,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: if_cmp,
|
||||
op: if_op,
|
||||
lhs: if_lhs,
|
||||
rhs: if_value_val,
|
||||
}));
|
||||
|
||||
// --- Then Branch ---
|
||||
// sum_then = sum + update_addend
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_0,
|
||||
value: ConstValue::Integer(update_addend),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: sum_then,
|
||||
op: BinOpKind::Add,
|
||||
lhs: sum_param,
|
||||
rhs: const_0,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_0,
|
||||
value: ConstValue::Integer(update_addend),
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: sum_then,
|
||||
op: BinOpKind::Add,
|
||||
lhs: sum_param,
|
||||
rhs: const_0,
|
||||
}));
|
||||
|
||||
// count_then = count + 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: count_const,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: count_then,
|
||||
op: BinOpKind::Add,
|
||||
lhs: count_param,
|
||||
rhs: count_const,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: count_const,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: count_then,
|
||||
op: BinOpKind::Add,
|
||||
lhs: count_param,
|
||||
rhs: count_const,
|
||||
}));
|
||||
|
||||
// --- Else Branch ---
|
||||
// sum_else = sum + 0 (identity)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: step_const, // reuse for 0
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: sum_else,
|
||||
op: BinOpKind::Add,
|
||||
lhs: sum_param,
|
||||
rhs: step_const,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: step_const, // reuse for 0
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: sum_else,
|
||||
op: BinOpKind::Add,
|
||||
lhs: sum_param,
|
||||
rhs: step_const,
|
||||
}));
|
||||
|
||||
// count_else = count + 0 (identity)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: count_else,
|
||||
op: BinOpKind::Add,
|
||||
lhs: count_param,
|
||||
rhs: step_const, // 0
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: count_else,
|
||||
op: BinOpKind::Add,
|
||||
lhs: count_param,
|
||||
rhs: step_const, // 0
|
||||
}));
|
||||
|
||||
// --- Select ---
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Select {
|
||||
dst: sum_new,
|
||||
cond: if_cmp,
|
||||
then_val: sum_then,
|
||||
else_val: sum_else,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Select {
|
||||
dst: sum_new,
|
||||
cond: if_cmp,
|
||||
then_val: sum_then,
|
||||
else_val: sum_else,
|
||||
}));
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Select {
|
||||
dst: count_new,
|
||||
cond: if_cmp,
|
||||
then_val: count_then,
|
||||
else_val: count_else,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Select {
|
||||
dst: count_new,
|
||||
cond: if_cmp,
|
||||
then_val: count_then,
|
||||
else_val: count_else,
|
||||
}));
|
||||
|
||||
// --- Counter Update ---
|
||||
let step_const2 = alloc_value();
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: step_const2,
|
||||
value: ConstValue::Integer(counter_step),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_next,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_param,
|
||||
rhs: step_const2,
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: step_const2,
|
||||
value: ConstValue::Integer(counter_step),
|
||||
}));
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_next,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_param,
|
||||
rhs: step_const2,
|
||||
}));
|
||||
|
||||
// --- Tail Recursion ---
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
@ -343,9 +385,18 @@ pub fn lower_if_sum_pattern(
|
||||
let fragment_meta = JoinFragmentMeta::with_expr_result(sum_final, exit_meta);
|
||||
|
||||
eprintln!("[joinir/pattern3/if-sum] Generated AST-based JoinIR");
|
||||
eprintln!("[joinir/pattern3/if-sum] Loop: {} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0);
|
||||
eprintln!("[joinir/pattern3/if-sum] If: {} {:?} ValueId({})", if_var, if_op, if_value_val.0);
|
||||
eprintln!("[joinir/pattern3/if-sum] Phase 215-2: expr_result={:?}", sum_final);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Loop: {} {:?} ValueId({})",
|
||||
loop_var, loop_op, loop_limit_val.0
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] If: {} {:?} ValueId({})",
|
||||
if_var, if_op, if_value_val.0
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Phase 215-2: expr_result={:?}",
|
||||
sum_final
|
||||
);
|
||||
|
||||
Ok((join_module, fragment_meta))
|
||||
}
|
||||
@ -406,7 +457,12 @@ where
|
||||
ConditionValue::Variable(var_name) => {
|
||||
let var_node = ASTNode::Variable {
|
||||
name: var_name,
|
||||
span: crate::ast::Span { start: 0, end: 0, line: 1, column: 1 },
|
||||
span: crate::ast::Span {
|
||||
start: 0,
|
||||
end: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
},
|
||||
};
|
||||
lower_value_expression(&var_node, alloc_value, cond_env, &mut limit_instructions)?
|
||||
}
|
||||
@ -418,7 +474,12 @@ where
|
||||
// Phase 242-EX-A: Normalization failed → handle complex conditions dynamically
|
||||
// Support: `expr CmpOp expr` (e.g., `i % 2 == 1`, `a + b > c`)
|
||||
match cond {
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
use crate::ast::BinaryOperator;
|
||||
|
||||
// Convert operator to CompareOp
|
||||
@ -429,7 +490,12 @@ where
|
||||
BinaryOperator::GreaterEqual => CompareOp::Ge,
|
||||
BinaryOperator::Equal => CompareOp::Eq,
|
||||
BinaryOperator::NotEqual => CompareOp::Ne,
|
||||
_ => return Err(format!("[if-sum] Unsupported operator in condition: {:?}", operator)),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"[if-sum] Unsupported operator in condition: {:?}",
|
||||
operator
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Lower left-hand side (complex expression)
|
||||
@ -473,7 +539,7 @@ where
|
||||
{
|
||||
match if_stmt {
|
||||
ASTNode::If { condition, .. } => {
|
||||
extract_loop_condition(condition, alloc_value, cond_env) // Same format
|
||||
extract_loop_condition(condition, alloc_value, cond_env) // Same format
|
||||
}
|
||||
_ => Err("[if-sum] Expected If statement".to_string()),
|
||||
}
|
||||
@ -490,7 +556,13 @@ fn extract_then_update(if_stmt: &ASTNode) -> Result<(String, i64), String> {
|
||||
if let ASTNode::Assignment { target, value, .. } = stmt {
|
||||
let target_name = extract_variable_name(&**target)?;
|
||||
// Check if value is var + lit
|
||||
if let ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::Add, left, right, .. } = value.as_ref() {
|
||||
if let ASTNode::BinaryOp {
|
||||
operator: crate::ast::BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = value.as_ref()
|
||||
{
|
||||
let lhs_name = extract_variable_name(left)?;
|
||||
if lhs_name == target_name {
|
||||
let addend = extract_integer_literal(right)?;
|
||||
@ -513,7 +585,13 @@ fn extract_counter_update(body: &[ASTNode], loop_var: &str) -> Result<(String, i
|
||||
if let ASTNode::Assignment { target, value, .. } = stmt {
|
||||
if let Ok(target_name) = extract_variable_name(&**target) {
|
||||
if target_name == loop_var {
|
||||
if let ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::Add, left, right, .. } = value.as_ref() {
|
||||
if let ASTNode::BinaryOp {
|
||||
operator: crate::ast::BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = value.as_ref()
|
||||
{
|
||||
let lhs_name = extract_variable_name(left)?;
|
||||
if lhs_name == target_name {
|
||||
let step = extract_integer_literal(right)?;
|
||||
@ -524,7 +602,10 @@ fn extract_counter_update(body: &[ASTNode], loop_var: &str) -> Result<(String, i
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(format!("[if-sum] No counter update found for '{}'", loop_var))
|
||||
Err(format!(
|
||||
"[if-sum] No counter update found for '{}'",
|
||||
loop_var
|
||||
))
|
||||
}
|
||||
|
||||
/// Extract variable name from AST node
|
||||
@ -541,7 +622,10 @@ fn extract_variable_name(node: &ASTNode) -> Result<String, String> {
|
||||
/// For condition values, use `lower_value_expression()` which supports variables.
|
||||
fn extract_integer_literal(node: &ASTNode) -> Result<i64, String> {
|
||||
match node {
|
||||
ASTNode::Literal { value: crate::ast::LiteralValue::Integer(n), .. } => Ok(*n),
|
||||
ASTNode::Literal {
|
||||
value: crate::ast::LiteralValue::Integer(n),
|
||||
..
|
||||
} => Ok(*n),
|
||||
_ => Err(format!("[if-sum] Expected integer literal, got {:?}", node)),
|
||||
}
|
||||
}
|
||||
@ -602,14 +686,8 @@ mod tests {
|
||||
int_lit(1),
|
||||
);
|
||||
|
||||
let sum_update = assignment(
|
||||
var("sum"),
|
||||
bin(BinaryOperator::Add, var("sum"), int_lit(1)),
|
||||
);
|
||||
let counter_update = assignment(
|
||||
var("i"),
|
||||
bin(BinaryOperator::Add, var("i"), int_lit(1)),
|
||||
);
|
||||
let sum_update = assignment(var("sum"), bin(BinaryOperator::Add, var("sum"), int_lit(1)));
|
||||
let counter_update = assignment(var("i"), bin(BinaryOperator::Add, var("i"), int_lit(1)));
|
||||
|
||||
let if_stmt = ASTNode::If {
|
||||
condition: Box::new(if_condition),
|
||||
@ -634,7 +712,9 @@ mod tests {
|
||||
for func in module.functions.values() {
|
||||
for inst in &func.body {
|
||||
match inst {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { op: BinOpKind::Mod, .. }) => {
|
||||
JoinInst::Compute(MirLikeInst::BinOp {
|
||||
op: BinOpKind::Mod, ..
|
||||
}) => {
|
||||
has_mod = true;
|
||||
}
|
||||
JoinInst::Compute(MirLikeInst::Compare { .. }) => {
|
||||
|
||||
@ -116,7 +116,10 @@ impl MethodCallLowerer {
|
||||
if args.len() != expected_arity {
|
||||
return Err(format!(
|
||||
"Arity mismatch: {}.{}() expects {} args, got {}",
|
||||
recv_val.0, method_name, expected_arity, args.len()
|
||||
recv_val.0,
|
||||
method_name,
|
||||
expected_arity,
|
||||
args.len()
|
||||
));
|
||||
}
|
||||
|
||||
@ -127,7 +130,7 @@ impl MethodCallLowerer {
|
||||
arg_ast,
|
||||
alloc_value,
|
||||
env,
|
||||
instructions
|
||||
instructions,
|
||||
)?;
|
||||
lowered_args.push(arg_val);
|
||||
}
|
||||
@ -201,7 +204,10 @@ impl MethodCallLowerer {
|
||||
if args.len() != expected_arity {
|
||||
return Err(format!(
|
||||
"Arity mismatch: {}.{}() expects {} args, got {}",
|
||||
recv_val.0, method_name, expected_arity, args.len()
|
||||
recv_val.0,
|
||||
method_name,
|
||||
expected_arity,
|
||||
args.len()
|
||||
));
|
||||
}
|
||||
|
||||
@ -214,7 +220,7 @@ impl MethodCallLowerer {
|
||||
alloc_value,
|
||||
cond_env,
|
||||
body_local_env,
|
||||
instructions
|
||||
instructions,
|
||||
)?;
|
||||
lowered_args.push(arg_val);
|
||||
}
|
||||
@ -311,8 +317,8 @@ impl MethodCallLowerer {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
|
||||
#[test]
|
||||
fn test_resolve_string_length() {
|
||||
@ -391,7 +397,9 @@ mod tests {
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("not allowed in loop condition"));
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.contains("not allowed in loop condition"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -461,7 +469,9 @@ mod tests {
|
||||
&mut instructions,
|
||||
);
|
||||
assert!(cond_result.is_err());
|
||||
assert!(cond_result.unwrap_err().contains("not allowed in loop condition"));
|
||||
assert!(cond_result
|
||||
.unwrap_err()
|
||||
.contains("not allowed in loop condition"));
|
||||
|
||||
// But IS allowed in init context
|
||||
// Phase 226: Create empty LoopBodyLocalEnv
|
||||
|
||||
@ -24,26 +24,20 @@ pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering
|
||||
pub(crate) mod carrier_update_emitter; // Phase 179: Carrier update instruction emission
|
||||
pub(crate) mod common; // Internal lowering utilities
|
||||
pub mod complex_addend_normalizer; // Phase 192: Complex addend normalization (AST preprocessing)
|
||||
pub mod digitpos_condition_normalizer; // Phase 224-E: DigitPos condition normalizer (digit_pos < 0 → !is_digit_pos)
|
||||
pub mod condition_env; // Phase 171-fix: Condition expression environment
|
||||
pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic
|
||||
pub mod condition_lowering_box; // Phase 244: Unified condition lowering interface (trait-based)
|
||||
pub mod condition_pattern; // Phase 219-fix: If condition pattern detection (simple vs complex)
|
||||
pub mod loop_body_local_env; // Phase 184: Body-local variable environment
|
||||
pub mod loop_body_local_init; // Phase 186: Body-local init expression lowering
|
||||
pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic
|
||||
pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering orchestrator (refactored)
|
||||
pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)
|
||||
pub(crate) mod condition_var_extractor; // Phase 171-fix: Variable extraction from condition AST
|
||||
pub mod continue_branch_normalizer; // Phase 33-19: Continue branch normalization for Pattern B
|
||||
pub mod expr_lowerer; // Phase 231: Unified expression lowering with scope management
|
||||
pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics
|
||||
pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection
|
||||
pub mod digitpos_condition_normalizer; // Phase 224-E: DigitPos condition normalizer (digit_pos < 0 → !is_digit_pos)
|
||||
pub(crate) mod exit_args_resolver; // Internal exit argument resolution
|
||||
pub mod expr_lowerer; // Phase 231: Unified expression lowering with scope management
|
||||
pub mod funcscanner_append_defs;
|
||||
pub mod funcscanner_trim;
|
||||
pub(crate) mod generic_case_a; // Phase 192: Modularized Case A lowering
|
||||
pub mod generic_type_resolver; // Phase 66: P3-C ジェネリック型推論箱
|
||||
pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱
|
||||
pub mod if_dry_runner; // Phase 33-10.0
|
||||
pub(crate) mod if_lowering_router; // Phase 33-12: If-expression routing (re-exported)
|
||||
pub mod if_merge; // Phase 33-7
|
||||
@ -53,20 +47,26 @@ pub(crate) mod if_select; // Phase 33: Internal If/Select lowering
|
||||
pub mod inline_boundary; // Phase 188-Impl-3: JoinIR→Host boundary
|
||||
pub mod inline_boundary_builder; // Phase 200-2: Builder pattern for JoinInlineBoundary
|
||||
pub mod join_value_space; // Phase 201: Unified JoinIR ValueId allocation
|
||||
pub mod loop_body_local_env; // Phase 184: Body-local variable environment
|
||||
pub mod loop_body_local_init; // Phase 186: Body-local init expression lowering
|
||||
pub(crate) mod loop_form_intake; // Internal loop form intake
|
||||
pub mod scope_manager; // Phase 231: Unified variable scope management
|
||||
pub(crate) mod loop_pattern_router; // Phase 33-12: Loop pattern routing (re-exported)
|
||||
pub(crate) mod loop_pattern_validator; // Phase 33-23: Loop structure validation
|
||||
pub(crate) mod loop_patterns; // Phase 188: Pattern-based loop lowering (3 patterns)
|
||||
pub mod loop_scope_shape;
|
||||
pub mod loop_to_join;
|
||||
pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics
|
||||
pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection
|
||||
pub(crate) mod loop_view_builder; // Phase 33-23: Loop lowering dispatch
|
||||
pub mod loop_with_break_minimal; // Phase 188-Impl-2: Pattern 2 minimal lowerer
|
||||
pub mod loop_with_continue_minimal; // Phase 195: Pattern 4 minimal lowerer
|
||||
// Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum
|
||||
pub mod loop_with_continue_minimal;
|
||||
pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)
|
||||
pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱
|
||||
pub mod scope_manager; // Phase 231: Unified variable scope management // Phase 195: Pattern 4 minimal lowerer
|
||||
// Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum
|
||||
pub mod loop_with_if_phi_if_sum; // Phase 213: Pattern 3 AST-based if-sum lowerer (Phase 242-EX-A: supports complex conditions)
|
||||
pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer
|
||||
pub mod min_loop;
|
||||
pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer
|
||||
pub mod skip_ws;
|
||||
pub mod stage1_using_resolver;
|
||||
pub mod stageb_body;
|
||||
|
||||
@ -19,11 +19,11 @@
|
||||
//! Phase 231 starts with Pattern2-specific implementation to validate the design.
|
||||
//! Future phases will generalize to Pattern1, Pattern3, etc.
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use super::carrier_info::CarrierInfo;
|
||||
use super::condition_env::ConditionEnv;
|
||||
use super::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use super::carrier_info::CarrierInfo;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 231: Scope kind for variables
|
||||
///
|
||||
@ -146,8 +146,7 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
|
||||
}
|
||||
|
||||
// 4. Promoted LoopBodyLocal → Carrier lookup(命名規約は CarrierInfo 側に集約)
|
||||
self.carrier_info
|
||||
.resolve_promoted_join_id(name)
|
||||
self.carrier_info.resolve_promoted_join_id(name)
|
||||
}
|
||||
|
||||
fn scope_of(&self, name: &str) -> Option<VarScopeKind> {
|
||||
@ -182,7 +181,7 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit};
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole, CarrierVar};
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedVar;
|
||||
|
||||
#[test]
|
||||
@ -218,15 +217,13 @@ mod tests {
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "i".to_string(),
|
||||
loop_var_id: ValueId(1),
|
||||
carriers: vec![
|
||||
CarrierVar {
|
||||
name: "sum".to_string(),
|
||||
host_id: ValueId(2),
|
||||
join_id: Some(ValueId(101)),
|
||||
role: CarrierRole::LoopState,
|
||||
init: CarrierInit::FromHost,
|
||||
},
|
||||
],
|
||||
carriers: vec![CarrierVar {
|
||||
name: "sum".to_string(),
|
||||
host_id: ValueId(2),
|
||||
join_id: Some(ValueId(101)),
|
||||
role: CarrierRole::LoopState,
|
||||
init: CarrierInit::FromHost,
|
||||
}],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
};
|
||||
@ -250,15 +247,13 @@ mod tests {
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "i".to_string(),
|
||||
loop_var_id: ValueId(1),
|
||||
carriers: vec![
|
||||
CarrierVar {
|
||||
name: "is_digit_pos".to_string(),
|
||||
host_id: ValueId(2),
|
||||
join_id: Some(ValueId(102)),
|
||||
role: CarrierRole::ConditionOnly,
|
||||
init: CarrierInit::BoolConst(false),
|
||||
},
|
||||
],
|
||||
carriers: vec![CarrierVar {
|
||||
name: "is_digit_pos".to_string(),
|
||||
host_id: ValueId(2),
|
||||
join_id: Some(ValueId(102)),
|
||||
role: CarrierRole::ConditionOnly,
|
||||
init: CarrierInit::BoolConst(false),
|
||||
}],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec!["digit_pos".to_string()],
|
||||
};
|
||||
@ -307,7 +302,8 @@ mod tests {
|
||||
condition_env.insert("i".to_string(), ValueId(100));
|
||||
condition_env.insert("len".to_string(), ValueId(201));
|
||||
|
||||
let mut captured_env = crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new();
|
||||
let mut captured_env =
|
||||
crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new();
|
||||
captured_env.add_var(CapturedVar {
|
||||
name: "len".to_string(),
|
||||
host_id: ValueId(42),
|
||||
|
||||
@ -45,11 +45,10 @@
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
MirLikeInst, UnaryOp,
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||
UnaryOp,
|
||||
};
|
||||
|
||||
|
||||
/// Lower Pattern 1 (Simple While Loop) to JoinIR
|
||||
///
|
||||
/// # Phase 188-Impl-3: Pure JoinIR Fragment Generation
|
||||
@ -106,17 +105,17 @@ pub(crate) fn lower_simple_while_minimal(
|
||||
// ValueId allocation (Phase 188-Impl-3: Sequential local IDs)
|
||||
// ==================================================================
|
||||
// main() locals
|
||||
let i_init = alloc_value(); // ValueId(0) - loop init value
|
||||
let loop_result = alloc_value(); // ValueId(1) - result from loop_step
|
||||
let i_init = alloc_value(); // ValueId(0) - loop init value
|
||||
let loop_result = alloc_value(); // ValueId(1) - result from loop_step
|
||||
let const_0_main = alloc_value(); // ValueId(2) - return value
|
||||
|
||||
// loop_step locals
|
||||
let i_param = alloc_value(); // ValueId(3) - parameter
|
||||
let const_3 = alloc_value(); // ValueId(4) - comparison constant
|
||||
let cmp_lt = alloc_value(); // ValueId(5) - i < 3
|
||||
let exit_cond = alloc_value(); // ValueId(6) - !(i < 3)
|
||||
let const_1 = alloc_value(); // ValueId(7) - increment constant
|
||||
let i_next = alloc_value(); // ValueId(8) - i + 1
|
||||
let i_param = alloc_value(); // ValueId(3) - parameter
|
||||
let const_3 = alloc_value(); // ValueId(4) - comparison constant
|
||||
let cmp_lt = alloc_value(); // ValueId(5) - i < 3
|
||||
let exit_cond = alloc_value(); // ValueId(6) - !(i < 3)
|
||||
let const_1 = alloc_value(); // ValueId(7) - increment constant
|
||||
let i_next = alloc_value(); // ValueId(8) - i + 1
|
||||
|
||||
// k_exit locals
|
||||
let const_0_exit = alloc_value(); // ValueId(9) - exit return value
|
||||
@ -151,11 +150,8 @@ pub(crate) fn lower_simple_while_minimal(
|
||||
// ==================================================================
|
||||
// loop_step(i) function
|
||||
// ==================================================================
|
||||
let mut loop_step_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
"loop_step".to_string(),
|
||||
vec![i_param],
|
||||
);
|
||||
let mut loop_step_func =
|
||||
JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![i_param]);
|
||||
|
||||
// exit_cond = !(i < 3)
|
||||
// Step 1: const 3
|
||||
@ -196,9 +192,7 @@ pub(crate) fn lower_simple_while_minimal(
|
||||
// Phase 188-Impl-1-E: Use Print instruction
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Print {
|
||||
value: i_param,
|
||||
}));
|
||||
.push(JoinInst::Compute(MirLikeInst::Print { value: i_param }));
|
||||
|
||||
// i_next = i + 1
|
||||
// Step 1: const 1
|
||||
@ -223,7 +217,7 @@ pub(crate) fn lower_simple_while_minimal(
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next],
|
||||
k_next: None, // CRITICAL: None for tail call
|
||||
k_next: None, // CRITICAL: None for tail call
|
||||
dst: None,
|
||||
});
|
||||
|
||||
|
||||
@ -167,7 +167,7 @@ impl<'a> UpdateEnv<'a> {
|
||||
// Phase 247-EX: Naming convention - "digit_pos" → "digit_value" (not "digit_pos_value")
|
||||
// Extract base name: "digit_pos" → "digit", "pos" → "pos"
|
||||
let base_name = if promoted_name.ends_with("_pos") {
|
||||
&promoted_name[..promoted_name.len() - 4] // Remove "_pos" suffix
|
||||
&promoted_name[..promoted_name.len() - 4] // Remove "_pos" suffix
|
||||
} else {
|
||||
promoted_name.as_str()
|
||||
};
|
||||
@ -359,8 +359,8 @@ mod tests {
|
||||
let mut cond_env = ConditionEnv::new();
|
||||
|
||||
// Register both carriers in ConditionEnv
|
||||
cond_env.insert("is_digit_pos".to_string(), ValueId(100)); // Boolean carrier
|
||||
cond_env.insert("digit_value".to_string(), ValueId(200)); // Integer carrier (digit_pos → digit)
|
||||
cond_env.insert("is_digit_pos".to_string(), ValueId(100)); // Boolean carrier
|
||||
cond_env.insert("digit_value".to_string(), ValueId(200)); // Integer carrier (digit_pos → digit)
|
||||
|
||||
let body_env = LoopBodyLocalEnv::new();
|
||||
let promoted: Vec<String> = vec!["digit_pos".to_string()];
|
||||
|
||||
@ -35,6 +35,9 @@ pub mod verify;
|
||||
// Phase 30.x: JSON serialization (jsonir v0)
|
||||
pub mod json;
|
||||
|
||||
// Phase 26-H.B: Normalized JoinIR (テスト専用ミニ)
|
||||
pub mod normalized;
|
||||
|
||||
// Phase 34-1: Frontend (AST→JoinIR) — skeleton only
|
||||
pub mod frontend;
|
||||
|
||||
@ -44,6 +47,12 @@ pub use lowering::{
|
||||
};
|
||||
|
||||
// Re-export verification functions
|
||||
pub use normalized::{
|
||||
normalize_pattern1_minimal, normalize_pattern2_minimal, normalized_pattern1_to_structured,
|
||||
normalized_pattern2_to_structured, NormalizedModule,
|
||||
};
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub use normalized::fixtures;
|
||||
pub use verify::verify_progress_for_skip_ws;
|
||||
|
||||
// Phase 200-3: Contract verification functions are in merge/mod.rs (private module access)
|
||||
@ -194,6 +203,15 @@ impl LoopExitShape {
|
||||
}
|
||||
}
|
||||
|
||||
/// JoinIR フェーズメタデータ。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum JoinIrPhase {
|
||||
/// Lowering 直後の構造化 JoinIR(Pattern1–5 / CarrierInfo / Boundary/ExitLine)
|
||||
Structured,
|
||||
/// 将来導入予定の正規化済み JoinIR(関数+継続+Env、TailCall-only)
|
||||
Normalized,
|
||||
}
|
||||
|
||||
/// JoinIR 関数
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinFunction {
|
||||
@ -471,9 +489,7 @@ pub enum MirLikeInst {
|
||||
/// Phase 188: Print 文(コンソール出力)
|
||||
/// print(value) の構造を JoinIR で表現
|
||||
/// MIR 変換時に Print 命令に変換
|
||||
Print {
|
||||
value: VarId,
|
||||
},
|
||||
Print { value: VarId },
|
||||
|
||||
/// Phase 188-Impl-3: 条件付き値選択(三項演算子)
|
||||
/// cond が true なら then_val を、false なら else_val を dst に代入
|
||||
@ -535,6 +551,9 @@ pub struct JoinModule {
|
||||
|
||||
/// エントリーポイント関数ID
|
||||
pub entry: Option<JoinFuncId>,
|
||||
|
||||
/// JoinIR のフェーズ(構造化 / 正規化)
|
||||
pub phase: JoinIrPhase,
|
||||
}
|
||||
|
||||
impl JoinModule {
|
||||
@ -542,12 +561,25 @@ impl JoinModule {
|
||||
Self {
|
||||
functions: BTreeMap::new(),
|
||||
entry: None,
|
||||
phase: JoinIrPhase::Structured,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_function(&mut self, func: JoinFunction) {
|
||||
self.functions.insert(func.id, func);
|
||||
}
|
||||
|
||||
pub fn is_structured(&self) -> bool {
|
||||
self.phase == JoinIrPhase::Structured
|
||||
}
|
||||
|
||||
pub fn is_normalized(&self) -> bool {
|
||||
self.phase == JoinIrPhase::Normalized
|
||||
}
|
||||
|
||||
pub fn mark_normalized(&mut self) {
|
||||
self.phase = JoinIrPhase::Normalized;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JoinModule {
|
||||
@ -584,6 +616,16 @@ mod tests {
|
||||
|
||||
assert_eq!(module.functions.len(), 1);
|
||||
assert!(module.functions.contains_key(&JoinFuncId::new(0)));
|
||||
assert_eq!(module.phase, JoinIrPhase::Structured);
|
||||
assert!(module.is_structured());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mark_normalized() {
|
||||
let mut module = JoinModule::new();
|
||||
assert!(module.is_structured());
|
||||
module.mark_normalized();
|
||||
assert!(module.is_normalized());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
840
src/mir/join_ir/normalized.rs
Normal file
840
src/mir/join_ir/normalized.rs
Normal file
@ -0,0 +1,840 @@
|
||||
//! Minimal Normalized JoinIR model (Phase 26-H.B).
|
||||
//!
|
||||
//! テスト専用の極小サブセット。Pattern1 の while だけを Structured → Normalized に
|
||||
//! 変換して遊ぶための足場だよ。本線の Structured→MIR 経路には影響しない。
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinIrPhase,
|
||||
JoinModule, MirLikeInst, UnaryOp,
|
||||
};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::HashSet;
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub mod fixtures;
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub(crate) mod shape_guard;
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use crate::mir::join_ir::normalized::shape_guard::NormalizedDevShape;
|
||||
|
||||
/// 環境レイアウト(最小)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnvLayout {
|
||||
pub id: u32,
|
||||
pub fields: Vec<EnvField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnvField {
|
||||
pub name: String,
|
||||
pub ty: Option<crate::mir::MirType>,
|
||||
pub value_id: Option<ValueId>,
|
||||
}
|
||||
|
||||
/// 正規化済み関数 ID
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct JpFuncId(pub u32);
|
||||
|
||||
/// 正規化済み関数(Kont 兼用、is_kont で区別)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JpFunction {
|
||||
pub id: JpFuncId,
|
||||
pub name: String,
|
||||
pub env_layout: Option<u32>,
|
||||
pub body: Vec<JpInst>,
|
||||
pub is_kont: bool,
|
||||
}
|
||||
|
||||
/// 正規化済み命令(最小セット)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum JpInst {
|
||||
Let {
|
||||
dst: ValueId,
|
||||
op: JpOp,
|
||||
args: Vec<ValueId>,
|
||||
},
|
||||
EnvLoad {
|
||||
dst: ValueId,
|
||||
env: ValueId,
|
||||
field: usize,
|
||||
},
|
||||
EnvStore {
|
||||
env: ValueId,
|
||||
field: usize,
|
||||
src: ValueId,
|
||||
},
|
||||
TailCallFn {
|
||||
target: JpFuncId,
|
||||
env: Vec<ValueId>,
|
||||
},
|
||||
TailCallKont {
|
||||
target: JpFuncId,
|
||||
env: Vec<ValueId>,
|
||||
},
|
||||
If {
|
||||
cond: ValueId,
|
||||
then_target: JpFuncId,
|
||||
else_target: JpFuncId,
|
||||
env: Vec<ValueId>,
|
||||
},
|
||||
}
|
||||
|
||||
/// 演算(Let 用の最小サブセット)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum JpOp {
|
||||
Const(ConstValue),
|
||||
BinOp(BinOpKind),
|
||||
Unary(UnaryOp),
|
||||
Compare(CompareOp),
|
||||
}
|
||||
|
||||
/// Normalized JoinIR モジュール(テスト専用)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NormalizedModule {
|
||||
pub functions: BTreeMap<JpFuncId, JpFunction>,
|
||||
pub entry: Option<JpFuncId>,
|
||||
pub env_layouts: Vec<EnvLayout>,
|
||||
pub phase: JoinIrPhase,
|
||||
/// Structured に戻すためのスナップショット(テスト専用)。
|
||||
pub structured_backup: Option<JoinModule>,
|
||||
}
|
||||
|
||||
impl NormalizedModule {
|
||||
pub fn to_structured(&self) -> Option<JoinModule> {
|
||||
self.structured_backup.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
|
||||
if module.phase != JoinIrPhase::Normalized {
|
||||
return Err("Normalized verifier: phase must be Normalized".to_string());
|
||||
}
|
||||
|
||||
// Env field bounds check
|
||||
if let Some(env) = module.env_layouts.get(0) {
|
||||
let field_count = env.fields.len();
|
||||
for func in module.functions.values() {
|
||||
for inst in &func.body {
|
||||
match inst {
|
||||
JpInst::EnvLoad { field, .. } | JpInst::EnvStore { field, .. } => {
|
||||
if *field >= field_count {
|
||||
return Err(format!(
|
||||
"Env field out of range: {} (fields={})",
|
||||
field, field_count
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for func in module.functions.values() {
|
||||
for inst in &func.body {
|
||||
match inst {
|
||||
JpInst::Let { .. }
|
||||
| JpInst::EnvLoad { .. }
|
||||
| JpInst::EnvStore { .. }
|
||||
| JpInst::TailCallFn { .. }
|
||||
| JpInst::TailCallKont { .. }
|
||||
| JpInst::If { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Tail: allow TailCall or If only
|
||||
if let Some(last) = func.body.last() {
|
||||
match last {
|
||||
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Function '{}' does not end with tail call/if",
|
||||
func.name
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pattern1 専用: Normalized → Structured への簡易逆変換。
|
||||
pub fn normalized_pattern1_to_structured(norm: &NormalizedModule) -> JoinModule {
|
||||
assert_eq!(
|
||||
norm.phase,
|
||||
JoinIrPhase::Normalized,
|
||||
"normalized_pattern1_to_structured expects Normalized phase"
|
||||
);
|
||||
|
||||
let env_layout = norm
|
||||
.env_layouts
|
||||
.get(0)
|
||||
.expect("normalized_pattern1_to_structured: missing env layout");
|
||||
|
||||
let mut module = JoinModule::new();
|
||||
|
||||
for (jp_id, jp_fn) in &norm.functions {
|
||||
let params: Vec<ValueId> = env_layout
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32)))
|
||||
.collect();
|
||||
|
||||
let mut func = JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params);
|
||||
|
||||
for inst in &jp_fn.body {
|
||||
match inst {
|
||||
JpInst::Let { dst, op, args } => match op {
|
||||
JpOp::Const(v) => func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: *dst,
|
||||
value: v.clone(),
|
||||
})),
|
||||
JpOp::BinOp(op) => func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: *dst,
|
||||
op: *op,
|
||||
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
|
||||
})),
|
||||
JpOp::Unary(op) => func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: *dst,
|
||||
op: *op,
|
||||
operand: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||
})),
|
||||
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: *dst,
|
||||
op: *op,
|
||||
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
|
||||
})),
|
||||
},
|
||||
JpInst::TailCallFn { target, env } => func.body.push(JoinInst::Call {
|
||||
func: JoinFuncId(target.0),
|
||||
args: env.clone(),
|
||||
k_next: None,
|
||||
dst: None,
|
||||
}),
|
||||
JpInst::TailCallKont { target, env } => func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId(target.0),
|
||||
args: env.clone(),
|
||||
cond: None,
|
||||
}),
|
||||
JpInst::If {
|
||||
cond,
|
||||
then_target,
|
||||
else_target,
|
||||
env,
|
||||
} => {
|
||||
// Jump to then_target on cond, else jump to else_target (Pattern1 minimal)
|
||||
func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId(then_target.0),
|
||||
args: env.clone(),
|
||||
cond: Some(*cond),
|
||||
});
|
||||
func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId(else_target.0),
|
||||
args: env.clone(),
|
||||
cond: None,
|
||||
});
|
||||
}
|
||||
JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => {
|
||||
// Not used in Pattern1 minimal; ignore for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.add_function(func);
|
||||
}
|
||||
|
||||
module.entry = norm.entry.map(|e| JoinFuncId(e.0));
|
||||
module.phase = JoinIrPhase::Structured;
|
||||
module
|
||||
}
|
||||
|
||||
/// Pattern2 専用のミニ変換(最小サブセット: ループ変数1つ + break、acc などの LoopState を 1 個まで+ホスト 1 個まで)。
|
||||
///
|
||||
/// 制約:
|
||||
/// - structured.phase は Structured であること
|
||||
/// - main/loop_step/k_exit の 3 関数構成(joinir_min_loop 相当)
|
||||
pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
|
||||
assert!(
|
||||
structured.is_structured(),
|
||||
"normalize_pattern2_minimal: expected Structured JoinIR"
|
||||
);
|
||||
|
||||
// Minimal guardrail: Pattern2 mini should have main/loop_step/k_exit only, with 1 loop param.
|
||||
let func_count = structured.functions.len();
|
||||
let loop_func = structured
|
||||
.functions
|
||||
.values()
|
||||
.find(|f| f.name == "loop_step")
|
||||
.or_else(|| structured.functions.get(&JoinFuncId::new(1)))
|
||||
.expect("normalize_pattern2_minimal: loop_step not found");
|
||||
|
||||
assert!(
|
||||
func_count == 3,
|
||||
"normalize_pattern2_minimal: expected 3 functions (entry/loop_step/k_exit) but got {}",
|
||||
func_count
|
||||
);
|
||||
assert!(
|
||||
(1..=3).contains(&loop_func.params.len()),
|
||||
"normalize_pattern2_minimal: expected 1..=3 params (loop var + optional acc + optional host)"
|
||||
);
|
||||
|
||||
let jump_conds = loop_func
|
||||
.body
|
||||
.iter()
|
||||
.filter(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. }))
|
||||
.count();
|
||||
let tail_calls = loop_func
|
||||
.body
|
||||
.iter()
|
||||
.filter(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }))
|
||||
.count();
|
||||
assert!(
|
||||
jump_conds >= 1 && tail_calls >= 1,
|
||||
"normalize_pattern2_minimal: expected at least one conditional jump and one tail call"
|
||||
);
|
||||
|
||||
let env_layout = EnvLayout {
|
||||
id: 0,
|
||||
fields: loop_func
|
||||
.params
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, vid)| EnvField {
|
||||
name: format!("field{}", idx),
|
||||
ty: None,
|
||||
value_id: Some(*vid),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let mut functions = BTreeMap::new();
|
||||
|
||||
for (fid, func) in &structured.functions {
|
||||
let env_layout_id = if func.params.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(env_layout.id)
|
||||
};
|
||||
|
||||
let mut body = Vec::new();
|
||||
for inst in &func.body {
|
||||
match inst {
|
||||
JoinInst::Compute(MirLikeInst::Const { dst, value }) => body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::Const(value.clone()),
|
||||
args: vec![],
|
||||
}),
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::BinOp(*op),
|
||||
args: vec![*lhs, *rhs],
|
||||
})
|
||||
}
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => {
|
||||
body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::Unary(*op),
|
||||
args: vec![*operand],
|
||||
})
|
||||
}
|
||||
JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => {
|
||||
body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::Compare(*op),
|
||||
args: vec![*lhs, *rhs],
|
||||
})
|
||||
}
|
||||
JoinInst::Jump { cont, args, cond } => {
|
||||
if let Some(cond_val) = cond {
|
||||
body.push(JpInst::If {
|
||||
cond: *cond_val,
|
||||
then_target: JpFuncId(cont.0),
|
||||
else_target: JpFuncId(loop_func.id.0),
|
||||
env: args.clone(),
|
||||
});
|
||||
} else {
|
||||
body.push(JpInst::TailCallKont {
|
||||
target: JpFuncId(cont.0),
|
||||
env: args.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
JoinInst::Call { func, args, k_next, .. } => {
|
||||
if k_next.is_none() {
|
||||
body.push(JpInst::TailCallFn {
|
||||
target: JpFuncId(func.0),
|
||||
env: args.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Ret / other instructions are ignored in this minimal prototype
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
functions.insert(
|
||||
JpFuncId(fid.0),
|
||||
JpFunction {
|
||||
id: JpFuncId(fid.0),
|
||||
name: func.name.clone(),
|
||||
env_layout: env_layout_id,
|
||||
body,
|
||||
is_kont: func.name.starts_with("k_"),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let norm = NormalizedModule {
|
||||
functions,
|
||||
entry: structured.entry.map(|e| JpFuncId(e.0)),
|
||||
env_layouts: vec![env_layout],
|
||||
phase: JoinIrPhase::Normalized,
|
||||
structured_backup: Some(structured.clone()),
|
||||
};
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
{
|
||||
verify_normalized_pattern2(&norm).expect("normalized Pattern2 verifier");
|
||||
}
|
||||
|
||||
norm
|
||||
}
|
||||
|
||||
/// Pattern2 専用: Normalized → Structured への簡易逆変換。
|
||||
pub fn normalized_pattern2_to_structured(norm: &NormalizedModule) -> JoinModule {
|
||||
if let Some(backup) = norm.to_structured() {
|
||||
return backup;
|
||||
}
|
||||
|
||||
let env_layout = norm.env_layouts.get(0);
|
||||
|
||||
let mut module = JoinModule::new();
|
||||
|
||||
for (jp_id, jp_fn) in &norm.functions {
|
||||
let params: Vec<ValueId> = jp_fn
|
||||
.env_layout
|
||||
.and_then(|id| env_layout.filter(|layout| layout.id == id))
|
||||
.map(|layout| {
|
||||
layout
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32)))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut func = JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params);
|
||||
|
||||
for inst in &jp_fn.body {
|
||||
match inst {
|
||||
JpInst::Let { dst, op, args } => match op {
|
||||
JpOp::Const(v) => func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: *dst,
|
||||
value: v.clone(),
|
||||
})),
|
||||
JpOp::BinOp(op) => func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: *dst,
|
||||
op: *op,
|
||||
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
|
||||
})),
|
||||
JpOp::Unary(op) => func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: *dst,
|
||||
op: *op,
|
||||
operand: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||
})),
|
||||
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: *dst,
|
||||
op: *op,
|
||||
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
|
||||
})),
|
||||
},
|
||||
JpInst::TailCallFn { target, env } => func.body.push(JoinInst::Call {
|
||||
func: JoinFuncId(target.0),
|
||||
args: env.clone(),
|
||||
k_next: None,
|
||||
dst: None,
|
||||
}),
|
||||
JpInst::TailCallKont { target, env } => func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId(target.0),
|
||||
args: env.clone(),
|
||||
cond: None,
|
||||
}),
|
||||
JpInst::If {
|
||||
cond,
|
||||
then_target,
|
||||
else_target,
|
||||
env,
|
||||
} => {
|
||||
func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId(then_target.0),
|
||||
args: env.clone(),
|
||||
cond: Some(*cond),
|
||||
});
|
||||
func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId(else_target.0),
|
||||
args: env.clone(),
|
||||
cond: None,
|
||||
});
|
||||
}
|
||||
JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => {
|
||||
// Not used in minimal pattern2 bridge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.add_function(func);
|
||||
}
|
||||
|
||||
module.entry = norm.entry.map(|e| JoinFuncId(e.0));
|
||||
module.phase = JoinIrPhase::Structured;
|
||||
module
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn verify_normalized_pattern2(module: &NormalizedModule) -> Result<(), String> {
|
||||
if module.phase != JoinIrPhase::Normalized {
|
||||
return Err("Normalized verifier (Pattern2): phase must be Normalized".to_string());
|
||||
}
|
||||
|
||||
let field_count = module.env_layouts.get(0).map(|e| e.fields.len());
|
||||
|
||||
if let Some(field_count) = field_count {
|
||||
if !(1..=3).contains(&field_count) {
|
||||
return Err(format!(
|
||||
"Normalized Pattern2 expects 1..=3 env fields, got {}",
|
||||
field_count
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for func in module.functions.values() {
|
||||
for inst in &func.body {
|
||||
match inst {
|
||||
JpInst::Let { .. }
|
||||
| JpInst::EnvLoad { .. }
|
||||
| JpInst::EnvStore { .. }
|
||||
| JpInst::TailCallFn { .. }
|
||||
| JpInst::TailCallKont { .. }
|
||||
| JpInst::If { .. } => {}
|
||||
}
|
||||
|
||||
match inst {
|
||||
JpInst::TailCallFn { env, .. }
|
||||
| JpInst::TailCallKont { env, .. }
|
||||
| JpInst::If { env, .. } => {
|
||||
if env.is_empty() {
|
||||
return Err("Normalized Pattern2 env must not be empty".to_string());
|
||||
}
|
||||
if let Some(expected) = field_count {
|
||||
if env.len() > expected {
|
||||
return Err(format!(
|
||||
"Normalized Pattern2 env size exceeds layout: env={}, layout={}",
|
||||
env.len(),
|
||||
expected
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last) = func.body.last() {
|
||||
match last {
|
||||
JpInst::TailCallFn { .. }
|
||||
| JpInst::TailCallKont { .. }
|
||||
| JpInst::If { .. } => {}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Function '{}' does not end with tail call/if",
|
||||
func.name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pattern1 専用のミニ変換。
|
||||
///
|
||||
/// 制約:
|
||||
/// - structured.phase は Structured であること
|
||||
/// - 対象は Pattern1 のシンプル while(break/continue なし)
|
||||
pub fn normalize_pattern1_minimal(structured: &JoinModule) -> NormalizedModule {
|
||||
assert!(
|
||||
structured.is_structured(),
|
||||
"normalize_pattern1_minimal: expected Structured JoinIR"
|
||||
);
|
||||
|
||||
// entry/loop_step/k_exit を前提に、loop_step を拾う
|
||||
let loop_func = structured
|
||||
.functions
|
||||
.values()
|
||||
.find(|f| f.name == "loop_step")
|
||||
.or_else(|| structured.functions.get(&JoinFuncId::new(1)))
|
||||
.expect("normalize_pattern1_minimal: loop_step not found");
|
||||
|
||||
// EnvLayout をざっくり作る(フィールド名は field0, field1,... で代用)
|
||||
let env_layout = EnvLayout {
|
||||
id: 0,
|
||||
fields: loop_func
|
||||
.params
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, vid)| EnvField {
|
||||
name: format!("field{}", idx),
|
||||
ty: None,
|
||||
value_id: Some(*vid),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// loop_step の Compute を Let に写経(Pattern1 では Compute/Call/Ret のみ想定)
|
||||
let mut extra_konts: HashSet<JpFuncId> = HashSet::new();
|
||||
let mut jp_body = Vec::new();
|
||||
for inst in &loop_func.body {
|
||||
match inst {
|
||||
JoinInst::Compute(MirLikeInst::Const { dst, value }) => jp_body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::Const(value.clone()),
|
||||
args: vec![],
|
||||
}),
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
jp_body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::BinOp(*op),
|
||||
args: vec![*lhs, *rhs],
|
||||
})
|
||||
}
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => {
|
||||
jp_body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::Unary(*op),
|
||||
args: vec![*operand],
|
||||
})
|
||||
}
|
||||
JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => {
|
||||
jp_body.push(JpInst::Let {
|
||||
dst: *dst,
|
||||
op: JpOp::Compare(*op),
|
||||
args: vec![*lhs, *rhs],
|
||||
})
|
||||
}
|
||||
// Tail recursion / exit は TailCall と If でざっくり表現
|
||||
JoinInst::Jump { cont, args, cond } => {
|
||||
if let Some(cond_val) = cond {
|
||||
extra_konts.insert(JpFuncId(cont.0));
|
||||
jp_body.push(JpInst::If {
|
||||
cond: *cond_val,
|
||||
then_target: JpFuncId(cont.0),
|
||||
else_target: JpFuncId(loop_func.id.0),
|
||||
env: args.clone(),
|
||||
});
|
||||
} else {
|
||||
extra_konts.insert(JpFuncId(cont.0));
|
||||
jp_body.push(JpInst::TailCallKont {
|
||||
target: JpFuncId(cont.0),
|
||||
env: args.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
JoinInst::Call { func, args, .. } => jp_body.push(JpInst::TailCallFn {
|
||||
target: JpFuncId(func.0),
|
||||
env: args.clone(),
|
||||
}),
|
||||
JoinInst::Ret { value } => {
|
||||
if let Some(v) = value {
|
||||
let kont_id = JpFuncId(loop_func.id.0 + 1);
|
||||
extra_konts.insert(kont_id);
|
||||
jp_body.push(JpInst::TailCallKont {
|
||||
target: kont_id,
|
||||
env: vec![*v],
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Pattern1 の最小変換なので他は無視(将来拡張)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let loop_fn = JpFunction {
|
||||
id: JpFuncId(loop_func.id.0),
|
||||
name: loop_func.name.clone(),
|
||||
env_layout: Some(env_layout.id),
|
||||
body: jp_body,
|
||||
is_kont: false,
|
||||
};
|
||||
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(loop_fn.id, loop_fn);
|
||||
|
||||
for kont_id in extra_konts {
|
||||
functions.entry(kont_id).or_insert_with(|| JpFunction {
|
||||
id: kont_id,
|
||||
name: format!("kont_{}", kont_id.0),
|
||||
env_layout: Some(env_layout.id),
|
||||
body: Vec::new(),
|
||||
is_kont: true,
|
||||
});
|
||||
}
|
||||
|
||||
let norm = NormalizedModule {
|
||||
functions,
|
||||
entry: Some(JpFuncId(loop_func.id.0)),
|
||||
env_layouts: vec![env_layout],
|
||||
phase: JoinIrPhase::Normalized,
|
||||
structured_backup: Some(structured.clone()),
|
||||
};
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
{
|
||||
verify_normalized_pattern1(&norm).expect("normalized verifier");
|
||||
}
|
||||
|
||||
norm
|
||||
}
|
||||
|
||||
/// Dev helper: Structured → Normalized → Structured roundtrip (Pattern1/2 minis only).
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub(crate) fn normalized_dev_roundtrip_structured(
|
||||
module: &JoinModule,
|
||||
) -> Result<JoinModule, String> {
|
||||
if !module.is_structured() {
|
||||
return Err("[joinir/normalized-dev] expected Structured JoinModule".to_string());
|
||||
}
|
||||
|
||||
let shapes = shape_guard::supported_shapes(module);
|
||||
if shapes.is_empty() {
|
||||
return Err("[joinir/normalized-dev] module shape is not supported by normalized_dev".into());
|
||||
}
|
||||
|
||||
let verbose = crate::config::env::joinir_dev_enabled();
|
||||
|
||||
for shape in shapes {
|
||||
if verbose {
|
||||
eprintln!("[joinir/normalized-dev] attempting {:?} normalization", shape);
|
||||
}
|
||||
|
||||
let attempt = match shape {
|
||||
NormalizedDevShape::Pattern1Mini => catch_unwind(AssertUnwindSafe(|| {
|
||||
let norm = normalize_pattern1_minimal(module);
|
||||
normalized_pattern1_to_structured(&norm)
|
||||
})),
|
||||
NormalizedDevShape::Pattern2Mini | NormalizedDevShape::JsonparserSkipWsMini => {
|
||||
catch_unwind(AssertUnwindSafe(|| {
|
||||
let norm = normalize_pattern2_minimal(module);
|
||||
normalized_pattern2_to_structured(&norm)
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
match attempt {
|
||||
Ok(structured) => {
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[joinir/normalized-dev] {:?} normalization succeeded (functions={})",
|
||||
shape,
|
||||
structured.functions.len()
|
||||
);
|
||||
}
|
||||
return Ok(structured);
|
||||
}
|
||||
Err(_) => {
|
||||
if verbose {
|
||||
eprintln!(
|
||||
"[joinir/normalized-dev] {:?} normalization failed (unsupported)",
|
||||
shape
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("[joinir/normalized-dev] all normalization attempts failed".into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn build_structured_pattern1() -> JoinModule {
|
||||
let mut module = JoinModule::new();
|
||||
let mut loop_fn = JoinFunction::new(
|
||||
JoinFuncId::new(1),
|
||||
"loop_step".to_string(),
|
||||
vec![ValueId(10)],
|
||||
);
|
||||
|
||||
loop_fn.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: ValueId(11),
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
loop_fn.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: ValueId(12),
|
||||
op: BinOpKind::Add,
|
||||
lhs: ValueId(10),
|
||||
rhs: ValueId(11),
|
||||
}));
|
||||
loop_fn.body.push(JoinInst::Jump {
|
||||
cont: JoinContId(2),
|
||||
args: vec![ValueId(12)],
|
||||
cond: Some(ValueId(12)), // dummy
|
||||
});
|
||||
|
||||
let mut k_exit =
|
||||
JoinFunction::new(JoinFuncId::new(2), "k_exit".to_string(), vec![ValueId(12)]);
|
||||
k_exit.body.push(JoinInst::Ret {
|
||||
value: Some(ValueId(12)),
|
||||
});
|
||||
|
||||
module.entry = Some(loop_fn.id);
|
||||
module.add_function(loop_fn);
|
||||
module.add_function(k_exit);
|
||||
module
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_pattern1_minimal_smoke() {
|
||||
let structured = build_structured_pattern1();
|
||||
let normalized = normalize_pattern1_minimal(&structured);
|
||||
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
||||
assert!(!normalized.env_layouts.is_empty());
|
||||
assert!(!normalized.functions.is_empty());
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
{
|
||||
verify_normalized_pattern1(&normalized).expect("verifier should pass");
|
||||
}
|
||||
|
||||
let restored = normalized.to_structured().expect("backup");
|
||||
assert!(restored.is_structured());
|
||||
assert_eq!(restored.functions.len(), structured.functions.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_pattern1_roundtrip_structured_equivalent() {
|
||||
let structured = build_structured_pattern1();
|
||||
let normalized = normalize_pattern1_minimal(&structured);
|
||||
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
||||
|
||||
assert!(reconstructed.is_structured());
|
||||
assert_eq!(reconstructed.functions.len(), structured.functions.len());
|
||||
}
|
||||
}
|
||||
114
src/mir/join_ir/normalized/fixtures.rs
Normal file
114
src/mir/join_ir/normalized/fixtures.rs
Normal file
@ -0,0 +1,114 @@
|
||||
#![cfg(feature = "normalized_dev")]
|
||||
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
||||
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Structured Pattern2 (joinir_min_loop 相当) をテスト用に生成するヘルパー。
|
||||
pub fn build_pattern2_minimal_structured() -> JoinModule {
|
||||
let loop_cond = 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(3),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let break_cond = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::GreaterEqual,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut scope = LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
body: BasicBlockId(1),
|
||||
latch: BasicBlockId(2),
|
||||
exit: BasicBlockId(3),
|
||||
pinned: BTreeSet::new(),
|
||||
carriers: BTreeSet::new(),
|
||||
body_locals: BTreeSet::new(),
|
||||
exit_live: BTreeSet::new(),
|
||||
progress_carrier: None,
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
scope.pinned.insert("i".to_string());
|
||||
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("i".to_string(), ValueId(100));
|
||||
|
||||
let carrier_info = crate::mir::join_ir::lowering::carrier_info::CarrierInfo {
|
||||
loop_var_name: "i".to_string(),
|
||||
loop_var_id: ValueId(1),
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
};
|
||||
|
||||
let carrier_updates: BTreeMap<String, UpdateExpr> = BTreeMap::new();
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
|
||||
let (module, _) = lower_loop_with_break_minimal(
|
||||
scope,
|
||||
&loop_cond,
|
||||
&break_cond,
|
||||
&condition_env,
|
||||
&carrier_info,
|
||||
&carrier_updates,
|
||||
&[],
|
||||
None,
|
||||
&mut join_value_space,
|
||||
)
|
||||
.expect("pattern2 minimal lowering should succeed");
|
||||
|
||||
module
|
||||
}
|
||||
|
||||
/// Pattern2 ブレークループ(fixture ベース)を Structured で組み立てるヘルパー。
|
||||
///
|
||||
/// Fixture: docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json
|
||||
pub fn build_pattern2_break_fixture_structured() -> JoinModule {
|
||||
const FIXTURE: &str = include_str!(
|
||||
"../../../../docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json"
|
||||
);
|
||||
|
||||
let program_json: serde_json::Value =
|
||||
serde_json::from_str(FIXTURE).expect("fixture JSON should be valid");
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
lowerer.lower_program_json(&program_json)
|
||||
}
|
||||
|
||||
/// JsonParser 由来のミニ P2 ループ(空白スキップ相当)を Structured で組み立てるヘルパー。
|
||||
///
|
||||
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_mini.program.json
|
||||
pub fn build_jsonparser_skip_ws_structured_for_normalized_dev() -> JoinModule {
|
||||
const FIXTURE: &str = include_str!(
|
||||
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_mini.program.json"
|
||||
);
|
||||
|
||||
let program_json: serde_json::Value =
|
||||
serde_json::from_str(FIXTURE).expect("jsonparser skip_ws fixture should be valid JSON");
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
lowerer.lower_program_json(&program_json)
|
||||
}
|
||||
68
src/mir/join_ir/normalized/shape_guard.rs
Normal file
68
src/mir/join_ir/normalized/shape_guard.rs
Normal file
@ -0,0 +1,68 @@
|
||||
#![cfg(feature = "normalized_dev")]
|
||||
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst, JoinModule};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum NormalizedDevShape {
|
||||
Pattern1Mini,
|
||||
Pattern2Mini,
|
||||
JsonparserSkipWsMini,
|
||||
}
|
||||
|
||||
pub(crate) fn supported_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
||||
let mut shapes = Vec::new();
|
||||
if is_jsonparser_skip_ws_mini(module) {
|
||||
shapes.push(NormalizedDevShape::JsonparserSkipWsMini);
|
||||
}
|
||||
if is_pattern2_mini(module) {
|
||||
shapes.push(NormalizedDevShape::Pattern2Mini);
|
||||
}
|
||||
if is_pattern1_mini(module) {
|
||||
shapes.push(NormalizedDevShape::Pattern1Mini);
|
||||
}
|
||||
shapes
|
||||
}
|
||||
|
||||
pub(crate) fn is_pattern1_mini(module: &JoinModule) -> bool {
|
||||
module.is_structured() && find_loop_step(module).is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn is_pattern2_mini(module: &JoinModule) -> bool {
|
||||
if !module.is_structured() || module.functions.len() != 3 {
|
||||
return false;
|
||||
}
|
||||
let loop_func = match find_loop_step(module) {
|
||||
Some(f) => f,
|
||||
None => return false,
|
||||
};
|
||||
if !(1..=3).contains(&loop_func.params.len()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let has_cond_jump = loop_func
|
||||
.body
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. }));
|
||||
let has_tail_call = loop_func
|
||||
.body
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }));
|
||||
|
||||
has_cond_jump && has_tail_call
|
||||
}
|
||||
|
||||
pub(crate) fn is_jsonparser_skip_ws_mini(module: &JoinModule) -> bool {
|
||||
is_pattern2_mini(module)
|
||||
&& module
|
||||
.functions
|
||||
.values()
|
||||
.any(|f| f.name == "jsonparser_skip_ws_mini")
|
||||
}
|
||||
|
||||
fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> {
|
||||
module
|
||||
.functions
|
||||
.values()
|
||||
.find(|f| f.name == "loop_step")
|
||||
.or_else(|| module.functions.get(&JoinFuncId::new(1)))
|
||||
}
|
||||
Reference in New Issue
Block a user