feat(control_tree): add StepTree builder (dev-only)
This commit is contained in:
@ -166,6 +166,12 @@ impl MirBuilder {
|
||||
|
||||
/// 🎯 箱理論: Step 4 - 本体lowering
|
||||
fn lower_function_body(&mut self, body: Vec<ASTNode>) -> Result<(), String> {
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
let tree = crate::mir::control_tree::StepTreeBuilderBox::build_from_block(&body);
|
||||
crate::mir::builder::control_flow::joinir::trace::trace()
|
||||
.dev("control_tree/step_tree", &tree.to_compact_string());
|
||||
}
|
||||
|
||||
eprintln!("[DEBUG/lower_function_body] body.len() = {}", body.len());
|
||||
let program_ast = function_lowering::wrap_in_program(body);
|
||||
eprintln!("[DEBUG/lower_function_body] About to call build_expression");
|
||||
|
||||
@ -42,6 +42,34 @@ pub(in crate::mir::builder) fn choose_pattern_kind(
|
||||
let has_continue = ast_features::detect_continue_in_body(body);
|
||||
let has_break = ast_features::detect_break_in_body(body);
|
||||
|
||||
// Phase 110: StepTree parity check (structure-only SSOT).
|
||||
//
|
||||
// This is dev-only; strict mode turns mismatch into a fail-fast.
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
use crate::ast::Span;
|
||||
use crate::mir::control_tree::StepTreeBuilderBox;
|
||||
|
||||
let loop_ast = ASTNode::Loop {
|
||||
condition: Box::new(condition.clone()),
|
||||
body: body.to_vec(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let tree = StepTreeBuilderBox::build_from_ast(&loop_ast);
|
||||
if tree.features.has_break != has_break || tree.features.has_continue != has_continue {
|
||||
let msg = format!(
|
||||
"[choose_pattern_kind/STEPTREE_PARITY] step_tree(break={}, cont={}) != extractor(break={}, cont={})",
|
||||
tree.features.has_break, tree.features.has_continue, has_break, has_continue
|
||||
);
|
||||
|
||||
if crate::config::env::joinir_dev::strict_enabled() {
|
||||
panic!("{}", msg);
|
||||
} else {
|
||||
trace::trace().dev("choose_pattern_kind/step_tree_parity", &msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 193: Extract features using modularized extractor
|
||||
let features = ast_features::extract_features(condition, body, has_continue, has_break);
|
||||
|
||||
|
||||
12
src/mir/control_tree/mod.rs
Normal file
12
src/mir/control_tree/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! ControlTree / StepTree (structure-only SSOT)
|
||||
//!
|
||||
//! Policy:
|
||||
//! - This module must NOT reference MIR/JoinIR values (`ValueId`, `BlockId`, `PHI`, ...).
|
||||
//! - It only describes AST control structure and derived features/capabilities.
|
||||
|
||||
mod step_tree;
|
||||
|
||||
pub use step_tree::{
|
||||
AstSummary, StepNode, StepStmtKind, StepTree, StepTreeBuilderBox, StepTreeFeatures,
|
||||
};
|
||||
|
||||
565
src/mir/control_tree/step_tree.rs
Normal file
565
src/mir/control_tree/step_tree.rs
Normal file
@ -0,0 +1,565 @@
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct StepTree {
|
||||
pub root: StepNode,
|
||||
pub features: StepTreeFeatures,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StepTreeFeatures {
|
||||
pub has_if: bool,
|
||||
pub has_loop: bool,
|
||||
pub has_break: bool,
|
||||
pub has_continue: bool,
|
||||
pub has_return: bool,
|
||||
pub max_if_depth: u32,
|
||||
pub max_loop_depth: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StepNode {
|
||||
Block(Vec<StepNode>),
|
||||
If {
|
||||
cond: AstSummary,
|
||||
then_branch: Box<StepNode>,
|
||||
else_branch: Option<Box<StepNode>>,
|
||||
span: Span,
|
||||
},
|
||||
Loop {
|
||||
cond: AstSummary,
|
||||
body: Box<StepNode>,
|
||||
span: Span,
|
||||
},
|
||||
Stmt { kind: StepStmtKind, span: Span },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StepStmtKind {
|
||||
LocalDecl { vars: Vec<String> },
|
||||
Assign,
|
||||
Print,
|
||||
Return,
|
||||
Break,
|
||||
Continue,
|
||||
Other(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AstSummary {
|
||||
Variable(String),
|
||||
Literal(LiteralValue),
|
||||
Unary {
|
||||
op: UnaryOperator,
|
||||
expr: Box<AstSummary>,
|
||||
},
|
||||
Binary {
|
||||
op: BinaryOperator,
|
||||
lhs: Box<AstSummary>,
|
||||
rhs: Box<AstSummary>,
|
||||
},
|
||||
Other(&'static str),
|
||||
}
|
||||
|
||||
impl StepTree {
|
||||
pub fn to_compact_string(&self) -> String {
|
||||
let mut out = String::new();
|
||||
self.root.write_compact(&mut out, 0);
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl StepNode {
|
||||
fn write_compact(&self, out: &mut String, indent: usize) {
|
||||
let pad = " ".repeat(indent);
|
||||
match self {
|
||||
StepNode::Block(nodes) => {
|
||||
out.push_str(&format!("{pad}Block(len={})\n", nodes.len()));
|
||||
for n in nodes {
|
||||
n.write_compact(out, indent + 1);
|
||||
}
|
||||
}
|
||||
StepNode::If {
|
||||
cond,
|
||||
then_branch,
|
||||
else_branch,
|
||||
..
|
||||
} => {
|
||||
out.push_str(&format!(
|
||||
"{pad}If(cond={})\n",
|
||||
cond.to_compact_string()
|
||||
));
|
||||
out.push_str(&format!("{pad} then:\n"));
|
||||
then_branch.write_compact(out, indent + 2);
|
||||
if let Some(else_branch) = else_branch {
|
||||
out.push_str(&format!("{pad} else:\n"));
|
||||
else_branch.write_compact(out, indent + 2);
|
||||
}
|
||||
}
|
||||
StepNode::Loop { cond, body, .. } => {
|
||||
out.push_str(&format!(
|
||||
"{pad}Loop(cond={})\n",
|
||||
cond.to_compact_string()
|
||||
));
|
||||
body.write_compact(out, indent + 1);
|
||||
}
|
||||
StepNode::Stmt { kind, .. } => {
|
||||
out.push_str(&format!("{pad}Stmt({})\n", kind.to_compact_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StepStmtKind {
|
||||
fn to_compact_string(&self) -> String {
|
||||
match self {
|
||||
StepStmtKind::LocalDecl { vars } => format!("local({})", vars.join(",")),
|
||||
StepStmtKind::Assign => "assign".to_string(),
|
||||
StepStmtKind::Print => "print".to_string(),
|
||||
StepStmtKind::Return => "return".to_string(),
|
||||
StepStmtKind::Break => "break".to_string(),
|
||||
StepStmtKind::Continue => "continue".to_string(),
|
||||
StepStmtKind::Other(name) => format!("other:{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AstSummary {
|
||||
fn to_compact_string(&self) -> String {
|
||||
match self {
|
||||
AstSummary::Variable(name) => format!("var:{name}"),
|
||||
AstSummary::Literal(lit) => format!("lit:{lit:?}"),
|
||||
AstSummary::Unary { op, expr } => format!("({op:?} {})", expr.to_compact_string()),
|
||||
AstSummary::Binary { op, lhs, rhs } => format!(
|
||||
"({} {} {})",
|
||||
lhs.to_compact_string(),
|
||||
op,
|
||||
rhs.to_compact_string()
|
||||
),
|
||||
AstSummary::Other(k) => format!("other:{k}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StepTreeBuilderBox;
|
||||
|
||||
impl StepTreeBuilderBox {
|
||||
pub fn build_from_ast(ast: &ASTNode) -> StepTree {
|
||||
match ast {
|
||||
ASTNode::Program { statements, .. } => Self::build_from_block(statements),
|
||||
ASTNode::ScopeBox { body, .. } => Self::build_from_block(body),
|
||||
_ => {
|
||||
let (node, features) = Self::build_node(ast, 0, 0);
|
||||
StepTree {
|
||||
root: node,
|
||||
features,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_from_block(stmts: &[ASTNode]) -> StepTree {
|
||||
let mut nodes = Vec::with_capacity(stmts.len());
|
||||
let mut features = StepTreeFeatures::default();
|
||||
for stmt in stmts {
|
||||
let (node, node_features) = Self::build_node(stmt, 0, 0);
|
||||
nodes.push(node);
|
||||
features = merge_features(features, node_features);
|
||||
}
|
||||
StepTree {
|
||||
root: StepNode::Block(nodes),
|
||||
features,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_node(ast: &ASTNode, if_depth: u32, loop_depth: u32) -> (StepNode, StepTreeFeatures) {
|
||||
match ast {
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
span,
|
||||
} => {
|
||||
let cond = summarize_ast(condition);
|
||||
let (then_node, then_features) =
|
||||
Self::build_block_node(then_body, if_depth + 1, loop_depth);
|
||||
let (else_node, else_features) = match else_body {
|
||||
Some(else_body) => {
|
||||
let (node, f) =
|
||||
Self::build_block_node(else_body, if_depth + 1, loop_depth);
|
||||
(Some(Box::new(node)), f)
|
||||
}
|
||||
None => (None, StepTreeFeatures::default()),
|
||||
};
|
||||
let mut features = StepTreeFeatures {
|
||||
has_if: true,
|
||||
max_if_depth: (if_depth + 1).max(then_features.max_if_depth),
|
||||
..StepTreeFeatures::default()
|
||||
};
|
||||
features = merge_features(features, then_features);
|
||||
features = merge_features(features, else_features);
|
||||
|
||||
(
|
||||
StepNode::If {
|
||||
cond,
|
||||
then_branch: Box::new(then_node),
|
||||
else_branch: else_node,
|
||||
span: span.clone(),
|
||||
},
|
||||
features,
|
||||
)
|
||||
}
|
||||
ASTNode::Loop {
|
||||
condition, body, span, ..
|
||||
} => {
|
||||
let cond = summarize_ast(condition);
|
||||
let (body_node, body_features) =
|
||||
Self::build_block_node(body, if_depth, loop_depth + 1);
|
||||
let mut features = StepTreeFeatures {
|
||||
has_loop: true,
|
||||
max_loop_depth: (loop_depth + 1).max(body_features.max_loop_depth),
|
||||
..StepTreeFeatures::default()
|
||||
};
|
||||
features = merge_features(features, body_features);
|
||||
(
|
||||
StepNode::Loop {
|
||||
cond,
|
||||
body: Box::new(body_node),
|
||||
span: span.clone(),
|
||||
},
|
||||
features,
|
||||
)
|
||||
}
|
||||
ASTNode::ScopeBox { body, span } => {
|
||||
let (node, features) = Self::build_block_node(body, if_depth, loop_depth);
|
||||
(node.with_span(span.clone()), features)
|
||||
}
|
||||
ASTNode::Return { span, .. } => (
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Return,
|
||||
span: span.clone(),
|
||||
},
|
||||
StepTreeFeatures {
|
||||
has_return: true,
|
||||
..StepTreeFeatures::default()
|
||||
},
|
||||
),
|
||||
ASTNode::Break { span } => (
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Break,
|
||||
span: span.clone(),
|
||||
},
|
||||
StepTreeFeatures {
|
||||
has_break: true,
|
||||
..StepTreeFeatures::default()
|
||||
},
|
||||
),
|
||||
ASTNode::Continue { span } => (
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Continue,
|
||||
span: span.clone(),
|
||||
},
|
||||
StepTreeFeatures {
|
||||
has_continue: true,
|
||||
..StepTreeFeatures::default()
|
||||
},
|
||||
),
|
||||
ASTNode::Local {
|
||||
variables, span, ..
|
||||
} => (
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::LocalDecl {
|
||||
vars: variables.clone(),
|
||||
},
|
||||
span: span.clone(),
|
||||
},
|
||||
StepTreeFeatures::default(),
|
||||
),
|
||||
ASTNode::Assignment { span, .. } => (
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Assign,
|
||||
span: span.clone(),
|
||||
},
|
||||
StepTreeFeatures::default(),
|
||||
),
|
||||
ASTNode::Print { span, .. } => (
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Print,
|
||||
span: span.clone(),
|
||||
},
|
||||
StepTreeFeatures::default(),
|
||||
),
|
||||
other => (
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Other(ast_kind_name(other)),
|
||||
span: span_of(other),
|
||||
},
|
||||
StepTreeFeatures::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_block_node(
|
||||
stmts: &[ASTNode],
|
||||
if_depth: u32,
|
||||
loop_depth: u32,
|
||||
) -> (StepNode, StepTreeFeatures) {
|
||||
let mut nodes = Vec::with_capacity(stmts.len());
|
||||
let mut features = StepTreeFeatures::default();
|
||||
for stmt in stmts {
|
||||
let (node, node_features) = Self::build_node(stmt, if_depth, loop_depth);
|
||||
nodes.push(node);
|
||||
features = merge_features(features, node_features);
|
||||
}
|
||||
(StepNode::Block(nodes), features)
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_features(mut a: StepTreeFeatures, b: StepTreeFeatures) -> StepTreeFeatures {
|
||||
a.has_if |= b.has_if;
|
||||
a.has_loop |= b.has_loop;
|
||||
a.has_break |= b.has_break;
|
||||
a.has_continue |= b.has_continue;
|
||||
a.has_return |= b.has_return;
|
||||
a.max_if_depth = a.max_if_depth.max(b.max_if_depth);
|
||||
a.max_loop_depth = a.max_loop_depth.max(b.max_loop_depth);
|
||||
a
|
||||
}
|
||||
|
||||
fn summarize_ast(ast: &ASTNode) -> AstSummary {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => AstSummary::Variable(name.clone()),
|
||||
ASTNode::Literal { value, .. } => AstSummary::Literal(value.clone()),
|
||||
ASTNode::UnaryOp {
|
||||
operator, operand, ..
|
||||
} => AstSummary::Unary {
|
||||
op: operator.clone(),
|
||||
expr: Box::new(summarize_ast(operand)),
|
||||
},
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => AstSummary::Binary {
|
||||
op: operator.clone(),
|
||||
lhs: Box::new(summarize_ast(left)),
|
||||
rhs: Box::new(summarize_ast(right)),
|
||||
},
|
||||
other => AstSummary::Other(ast_kind_name(other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn ast_kind_name(ast: &ASTNode) -> &'static str {
|
||||
match ast {
|
||||
ASTNode::Program { .. } => "Program",
|
||||
ASTNode::Assignment { .. } => "Assignment",
|
||||
ASTNode::Print { .. } => "Print",
|
||||
ASTNode::If { .. } => "If",
|
||||
ASTNode::Loop { .. } => "Loop",
|
||||
ASTNode::While { .. } => "While",
|
||||
ASTNode::ForRange { .. } => "ForRange",
|
||||
ASTNode::Return { .. } => "Return",
|
||||
ASTNode::Break { .. } => "Break",
|
||||
ASTNode::Continue { .. } => "Continue",
|
||||
ASTNode::UsingStatement { .. } => "UsingStatement",
|
||||
ASTNode::ImportStatement { .. } => "ImportStatement",
|
||||
ASTNode::Nowait { .. } => "Nowait",
|
||||
ASTNode::AwaitExpression { .. } => "AwaitExpression",
|
||||
ASTNode::QMarkPropagate { .. } => "QMarkPropagate",
|
||||
ASTNode::MatchExpr { .. } => "MatchExpr",
|
||||
ASTNode::ArrayLiteral { .. } => "ArrayLiteral",
|
||||
ASTNode::MapLiteral { .. } => "MapLiteral",
|
||||
ASTNode::Lambda { .. } => "Lambda",
|
||||
ASTNode::Arrow { .. } => "Arrow",
|
||||
ASTNode::TryCatch { .. } => "TryCatch",
|
||||
ASTNode::Throw { .. } => "Throw",
|
||||
ASTNode::BoxDeclaration { .. } => "BoxDeclaration",
|
||||
ASTNode::FunctionDeclaration { .. } => "FunctionDeclaration",
|
||||
ASTNode::GlobalVar { .. } => "GlobalVar",
|
||||
ASTNode::Literal { .. } => "Literal",
|
||||
ASTNode::Variable { .. } => "Variable",
|
||||
ASTNode::UnaryOp { .. } => "UnaryOp",
|
||||
ASTNode::BinaryOp { .. } => "BinaryOp",
|
||||
ASTNode::GroupedAssignmentExpr { .. } => "GroupedAssignmentExpr",
|
||||
ASTNode::MethodCall { .. } => "MethodCall",
|
||||
ASTNode::Call { .. } => "Call",
|
||||
ASTNode::FunctionCall { .. } => "FunctionCall",
|
||||
ASTNode::FieldAccess { .. } => "FieldAccess",
|
||||
ASTNode::Index { .. } => "Index",
|
||||
ASTNode::New { .. } => "New",
|
||||
ASTNode::This { .. } => "This",
|
||||
ASTNode::Me { .. } => "Me",
|
||||
ASTNode::FromCall { .. } => "FromCall",
|
||||
ASTNode::ThisField { .. } => "ThisField",
|
||||
ASTNode::MeField { .. } => "MeField",
|
||||
ASTNode::Local { .. } => "Local",
|
||||
ASTNode::ScopeBox { .. } => "ScopeBox",
|
||||
ASTNode::Outbox { .. } => "Outbox",
|
||||
}
|
||||
}
|
||||
|
||||
fn span_of(ast: &ASTNode) -> Span {
|
||||
match ast {
|
||||
ASTNode::Program { span, .. } => span.clone(),
|
||||
ASTNode::Assignment { span, .. } => span.clone(),
|
||||
ASTNode::Print { span, .. } => span.clone(),
|
||||
ASTNode::If { span, .. } => span.clone(),
|
||||
ASTNode::Loop { span, .. } => span.clone(),
|
||||
ASTNode::While { span, .. } => span.clone(),
|
||||
ASTNode::ForRange { span, .. } => span.clone(),
|
||||
ASTNode::Return { span, .. } => span.clone(),
|
||||
ASTNode::Break { span } => span.clone(),
|
||||
ASTNode::Continue { span } => span.clone(),
|
||||
ASTNode::UsingStatement { span, .. } => span.clone(),
|
||||
ASTNode::ImportStatement { span, .. } => span.clone(),
|
||||
ASTNode::Nowait { span, .. } => span.clone(),
|
||||
ASTNode::AwaitExpression { span, .. } => span.clone(),
|
||||
ASTNode::QMarkPropagate { span, .. } => span.clone(),
|
||||
ASTNode::MatchExpr { span, .. } => span.clone(),
|
||||
ASTNode::ArrayLiteral { span, .. } => span.clone(),
|
||||
ASTNode::MapLiteral { span, .. } => span.clone(),
|
||||
ASTNode::Lambda { span, .. } => span.clone(),
|
||||
ASTNode::Arrow { span, .. } => span.clone(),
|
||||
ASTNode::TryCatch { span, .. } => span.clone(),
|
||||
ASTNode::Throw { span, .. } => span.clone(),
|
||||
ASTNode::BoxDeclaration { span, .. } => span.clone(),
|
||||
ASTNode::FunctionDeclaration { span, .. } => span.clone(),
|
||||
ASTNode::GlobalVar { span, .. } => span.clone(),
|
||||
ASTNode::Literal { span, .. } => span.clone(),
|
||||
ASTNode::Variable { span, .. } => span.clone(),
|
||||
ASTNode::UnaryOp { span, .. } => span.clone(),
|
||||
ASTNode::BinaryOp { span, .. } => span.clone(),
|
||||
ASTNode::GroupedAssignmentExpr { span, .. } => span.clone(),
|
||||
ASTNode::MethodCall { span, .. } => span.clone(),
|
||||
ASTNode::Call { span, .. } => span.clone(),
|
||||
ASTNode::FunctionCall { span, .. } => span.clone(),
|
||||
ASTNode::FieldAccess { span, .. } => span.clone(),
|
||||
ASTNode::Index { span, .. } => span.clone(),
|
||||
ASTNode::New { span, .. } => span.clone(),
|
||||
ASTNode::This { span } => span.clone(),
|
||||
ASTNode::Me { span } => span.clone(),
|
||||
ASTNode::FromCall { span, .. } => span.clone(),
|
||||
ASTNode::ThisField { span, .. } => span.clone(),
|
||||
ASTNode::MeField { span, .. } => span.clone(),
|
||||
ASTNode::Local { span, .. } => span.clone(),
|
||||
ASTNode::ScopeBox { span, .. } => span.clone(),
|
||||
ASTNode::Outbox { span, .. } => span.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
impl StepNode {
|
||||
fn with_span(self, span: Span) -> StepNode {
|
||||
match self {
|
||||
StepNode::Block(nodes) => StepNode::Block(nodes),
|
||||
StepNode::If {
|
||||
cond,
|
||||
then_branch,
|
||||
else_branch,
|
||||
..
|
||||
} => StepNode::If {
|
||||
cond,
|
||||
then_branch,
|
||||
else_branch,
|
||||
span,
|
||||
},
|
||||
StepNode::Loop { cond, body, .. } => StepNode::Loop { cond, body, span },
|
||||
StepNode::Stmt { kind, .. } => StepNode::Stmt { kind, span },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{Span, ASTNode, BinaryOperator, LiteralValue};
|
||||
|
||||
fn str_lit(s: &str) -> ASTNode {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::String(s.to_string()),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn eq(a: ASTNode, b: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(a),
|
||||
right: Box::new(b),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_x(num: i64) -> ASTNode {
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(num),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_step_tree_if_only_nested_if_is_structural() {
|
||||
// Equivalent shape to Phase103 "if-only merge" fixture:
|
||||
//
|
||||
// local x = 0
|
||||
// if "x" == "x" { if "y" == "z" { x=1 } else { x=2 } } else { x=3 }
|
||||
// print(x)
|
||||
let ast = vec![
|
||||
ASTNode::Local {
|
||||
variables: vec!["x".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: Span::unknown(),
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::If {
|
||||
condition: Box::new(eq(str_lit("x"), str_lit("x"))),
|
||||
then_body: vec![ASTNode::If {
|
||||
condition: Box::new(eq(str_lit("y"), str_lit("z"))),
|
||||
then_body: vec![assign_x(1)],
|
||||
else_body: Some(vec![assign_x(2)]),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
else_body: Some(vec![assign_x(3)]),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Print {
|
||||
expression: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
|
||||
let tree = StepTreeBuilderBox::build_from_block(&ast);
|
||||
assert!(tree.features.has_if);
|
||||
assert!(!tree.features.has_loop);
|
||||
assert_eq!(tree.features.max_if_depth, 2);
|
||||
|
||||
match tree.root {
|
||||
StepNode::Block(nodes) => {
|
||||
assert_eq!(nodes.len(), 3);
|
||||
match &nodes[1] {
|
||||
StepNode::If { then_branch, .. } => match &**then_branch {
|
||||
StepNode::Block(inner_nodes) => match &inner_nodes[0] {
|
||||
StepNode::If { .. } => {}
|
||||
other => panic!("expected nested If, got {other:?}"),
|
||||
},
|
||||
other => panic!("expected Block in then_branch, got {other:?}"),
|
||||
},
|
||||
other => panic!("expected If at index 1, got {other:?}"),
|
||||
}
|
||||
}
|
||||
other => panic!("expected root Block, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ pub mod utils; // Phase 15 control flow utilities for root treatment
|
||||
// pub mod lowerers; // reserved: Stage-3 loop lowering (while/for-range)
|
||||
pub mod cfg_extractor; // Phase 154: CFG extraction for hako_check
|
||||
pub mod control_form;
|
||||
pub mod control_tree; // Phase 110: Structure-only SSOT (StepTree)
|
||||
pub mod function_emission; // FunctionEmissionBox(MirFunction直編集の発行ヘルパ)
|
||||
pub mod hints; // scaffold: zero-cost guidance (no-op)
|
||||
pub mod join_ir; // Phase 26-H: 関数正規化IR(JoinIR)
|
||||
|
||||
Reference in New Issue
Block a user