feat(control_tree): Phase 136/137 - return literal and add expression (dev-only)

Phase 136 P0: Return literal (Integer) support
- Extend loop(true) break-once to support `return 7`
- Fixtures: phase136_loop_true_break_once_return_literal_min.hako (exit code 7)
- VM/LLVM EXE parity achieved

Phase 137 P0: Return add expression support
- Extend to support `return x + 2` and `return 5 + 3`
- LHS: Variable or Integer literal
- RHS: Integer literal only
- Fixtures:
  - phase137_loop_true_break_once_return_add_min.hako (exit code 3)
  - phase137_loop_true_break_once_return_add_const_min.hako (exit code 8)
  - phase137_loop_true_break_once_post_return_add_min.hako (exit code 13)
- VM/LLVM EXE parity achieved

Implementation:
- Added lower_return_value_to_vid() method in loop_true_break_once.rs
- Replaced extract_variable_name() with unified return value lowering
- Supported patterns: Variable, Integer literal, BinaryOp Add
- Out-of-scope patterns return Ok(None) for fallback
- SSOT documentation added (lines 29-46)

Tests: 5 fixtures + 10 smoke tests (5 VM + 5 LLVM EXE), all PASS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-19 00:15:32 +09:00
parent 91c7dfbf0b
commit ff09adebe0
16 changed files with 703 additions and 23 deletions

View File

@ -26,6 +26,22 @@
//! - Post-loop (Phase 132-P4): One assignment + return (reuse Phase 130's lower_assign_stmt)
//! - Post-loop (Phase 133-P0): Multiple assignments + return (extend Phase 132-P4)
//!
//! ## Return Value Lowering SSOT (Phase 137+)
//!
//! - Function: `lower_return_value_to_vid()`
//! - Responsibility: Lower return values (variable, literal, expr) to ValueId
//! - Supported patterns:
//! - Variable: env lookup
//! - Integer literal: Const generation
//! - Add expr (Phase 137): x + 2 → BinOp(Add, env[x], Const(2))
//! - Fallback: Out-of-scope patterns return `Ok(None)` for legacy routing
//!
//! ### Boxification Trigger
//!
//! If 2+ files need identical return lowering logic, promote to:
//! - `normalized_shadow/common/return_value_lowerer_box.rs`
//! - Single responsibility: return value → ValueId conversion
//!
//! ## Fail-Fast
//!
//! - Out of scope → Ok(None) (fallback to legacy)
@ -33,11 +49,11 @@
use super::env_layout::EnvLayout;
use super::legacy::LegacyLowerer;
use crate::ast::{ASTNode, LiteralValue};
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind, StepTree};
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::control_tree::step_tree::{AstNodeHandle, StepNode, StepStmtKind, StepTree};
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
use crate::mir::join_ir::lowering::error_tags;
use crate::mir::join_ir::{ConstValue, JoinFunction, JoinFuncId, JoinInst, JoinModule, MirLikeInst};
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinFunction, JoinFuncId, JoinInst, JoinModule, MirLikeInst};
use crate::mir::join_ir_vm_bridge::join_func_name;
use crate::mir::ValueId;
use std::collections::BTreeMap;
@ -393,17 +409,21 @@ impl LoopTrueBreakOnceBuilderBox {
let StepNode::Stmt { kind: StepStmtKind::Return { value_ast }, .. } = return_node else {
return Ok(None);
};
if let Some(ast_handle) = value_ast {
if let Some(var_name) = Self::extract_variable_name(&ast_handle.0) {
if let Some(vid) = env_post_k.get(&var_name).copied() {
// Lower post-loop return (Phase 136: support variable + integer literal)
if let Some(_ast_handle) = value_ast {
// Return with value
match Self::lower_return_value_to_vid(value_ast, &mut post_k_func.body, &mut next_value_id, &env_post_k)? {
Some(vid) => {
post_k_func.body.push(JoinInst::Ret { value: Some(vid) });
} else {
return Ok(None); // Variable not in env
}
} else {
return Ok(None); // Return value is not a variable
None => {
// Out of scope (unsupported return value type)
return Ok(None);
}
}
} else {
// Return void
post_k_func.body.push(JoinInst::Ret { value: None });
}
@ -455,16 +475,16 @@ impl LoopTrueBreakOnceBuilderBox {
match &post_nodes[0] {
StepNode::Stmt { kind, .. } => match kind {
StepStmtKind::Return { value_ast } => {
if let Some(ast_handle) = value_ast {
// Return variable from env
if let Some(var_name) = Self::extract_variable_name(&ast_handle.0) {
if let Some(vid) = env_k_exit.get(&var_name).copied() {
if let Some(_ast_handle) = value_ast {
// Return with value (Phase 136: variable + integer literal)
match Self::lower_return_value_to_vid(value_ast, &mut k_exit_func.body, &mut next_value_id, &env_k_exit)? {
Some(vid) => {
k_exit_func.body.push(JoinInst::Ret { value: Some(vid) });
} else {
return Ok(None); // Variable not in env
}
} else {
return Ok(None); // Return value is not a variable
None => {
// Out of scope
return Ok(None);
}
}
} else {
// Return void
@ -606,11 +626,119 @@ impl LoopTrueBreakOnceBuilderBox {
}
}
/// Extract variable name from AST node
fn extract_variable_name(ast: &ASTNode) -> Option<String> {
match ast {
ASTNode::Variable { name, .. } => Some(name.clone()),
_ => None,
/// Lower return value (variable or integer literal) to ValueId
///
/// Phase 136 P0: Support return variable and return integer literal
///
/// Returns:
/// - Ok(Some(vid)): variable from env or newly generated const
/// - Ok(None): out of scope (fallback to default behavior)
///
/// Note: Does NOT return Err - unsupported patterns return Ok(None) for fallback
fn lower_return_value_to_vid(
value_ast: &Option<AstNodeHandle>,
body: &mut Vec<JoinInst>,
next_value_id: &mut u32,
env: &BTreeMap<String, ValueId>,
) -> Result<Option<ValueId>, String> {
match value_ast {
None => {
// void return
Ok(Some(ValueId(0))) // Dummy - caller handles void separately
}
Some(ast_handle) => {
match ast_handle.0.as_ref() {
// Variable: lookup in env
ASTNode::Variable { name, .. } => {
if let Some(&vid) = env.get(name) {
Ok(Some(vid))
} else {
// Variable not in env - out of scope
Ok(None)
}
}
// Integer literal: generate Const instruction (Phase 123 pattern)
ASTNode::Literal { value: LiteralValue::Integer(i), .. } => {
let const_vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_vid,
value: ConstValue::Integer(*i),
}));
Ok(Some(const_vid))
}
// Phase 137 P0: BinaryOp (x + 2) support
ASTNode::BinaryOp { operator, left, right, .. } => {
// Phase 137 contract: Add only
if !matches!(operator, BinaryOperator::Add) {
return Ok(None); // out of scope
}
// Lower LHS (Variable or Integer literal)
let lhs_vid = match left.as_ref() {
ASTNode::Variable { name, .. } => {
// Get from env
if let Some(&vid) = env.get(name) {
vid
} else {
// Variable not in env - out of scope
return Ok(None);
}
}
ASTNode::Literal { value: LiteralValue::Integer(i), .. } => {
// Generate Const for LHS integer literal
let vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::Integer(*i),
}));
vid
}
_ => {
// Other LHS types - out of scope
return Ok(None);
}
};
// Lower RHS (Integer literal only)
let rhs_vid = match right.as_ref() {
ASTNode::Literal { value: LiteralValue::Integer(i), .. } => {
// Generate Const for RHS integer literal
let vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::Integer(*i),
}));
vid
}
_ => {
// Other RHS types - out of scope (e.g., return x + y)
return Ok(None);
}
};
// Generate BinOp Add
let result_vid = ValueId(*next_value_id);
*next_value_id += 1;
body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: result_vid,
op: BinOpKind::Add,
lhs: lhs_vid,
rhs: rhs_vid,
}));
Ok(Some(result_vid))
}
_ => {
// Other return value types - out of scope
Ok(None)
}
}
}
}
}
}