feat(loop-phi): Add body-local variable PHI generation for Rust AST loops

Phase 25.1c/k: Fix ValueId undefined errors in loops with body-local variables

**Problem:**
- FuncScannerBox.scan_all_boxes/1 and BreakFinderBox._find_loops/2 had ValueId
  undefined errors for variables declared inside loop bodies
- LoopFormBuilder only generated PHIs for preheader variables, missing body-locals
- Example: `local ch = s.substring(i, i+1)` inside loop → undefined on next iteration

**Solution:**
1. **Rust AST path** (src/mir/loop_builder.rs):
   - Detect body-local variables by comparing body_end_vars vs current_vars
   - Generate empty PHI nodes at loop header for body-local variables
   - Seal PHIs with latch + continue snapshot inputs after seal_phis()
   - Added HAKO_LOOP_PHI_TRACE=1 logging for debugging

2. **JSON v0 path** (already fixed in previous session):
   - src/runner/json_v0_bridge/lowering/loop_.rs handles body-locals
   - Uses same strategy but for JSON v0 bridge lowering

**Results:**
-  FuncScannerBox.scan_all_boxes: 41 body-local PHIs generated
-  Main.main (demo harness): 23 body-local PHIs generated
- ⚠️ Still some ValueId undefined errors remaining (exit PHI issue)

**Files changed:**
- src/mir/loop_builder.rs: body-local PHI generation logic
- lang/src/compiler/entry/func_scanner.hako: debug logging
- /tmp/stageb_funcscan_demo.hako: test harness

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-19 23:12:01 +09:00
parent fb256670a1
commit 525e59bc8d
35 changed files with 1795 additions and 607 deletions

View File

@ -1,29 +1,254 @@
//! Box Definition parser (scaffold)
#![allow(dead_code)]
//! Box Definition Parser Module
//!
//! This module will progressively take over parsing of large `parse_box_declaration`
//! by splitting header and member parsing into focused units.
//! For now, it provides only type skeletons to stage the refactor safely.
//! Box宣言box, interface box, static boxの解析を担当
//! Nyashの中核概念「Everything is Box」を実現する重要モジュール
use crate::ast::ASTNode;
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
use std::collections::HashMap;
pub mod header;
pub mod members;
pub mod validators;
pub mod interface;
/// Facade to host the staged migration.
pub(crate) struct BoxDefParserFacade;
impl BoxDefParserFacade {
/// Entry planned: parse full box declaration (header + members).
/// Not wired yet; use NyashParser::parse_box_declaration for now.
pub(crate) fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
Err(ParseError::UnexpectedToken {
found: crate::tokenizer::TokenType::EOF,
expected: "box declaration (facade not wired)".to_string(),
line: 0,
})
}
/// Thin wrappers to keep the main loop tidy (behavior-preserving)
fn box_try_block_first_property(
p: &mut NyashParser,
methods: &mut HashMap<String, ASTNode>,
birth_once_props: &mut Vec<String>,
) -> Result<bool, ParseError> {
members::properties::try_parse_block_first_property(
p, methods, birth_once_props,
)
}
fn box_try_method_postfix_after_last(
p: &mut NyashParser,
methods: &mut HashMap<String, ASTNode>,
last_method_name: &Option<String>,
) -> Result<bool, ParseError> {
members::postfix::try_parse_method_postfix_after_last_method(
p, methods, last_method_name,
)
}
fn box_try_init_block(
p: &mut NyashParser,
init_fields: &mut Vec<String>,
weak_fields: &mut Vec<String>,
) -> Result<bool, ParseError> {
members::fields::parse_init_block_if_any(
p, init_fields, weak_fields,
)
}
fn box_try_constructor(
p: &mut NyashParser,
is_override: bool,
constructors: &mut HashMap<String, ASTNode>,
) -> Result<bool, ParseError> {
if let Some((key, node)) = members::constructors::try_parse_constructor(p, is_override)? {
constructors.insert(key, node);
return Ok(true);
}
Ok(false)
}
fn box_try_visibility(
p: &mut NyashParser,
visibility: &str,
methods: &mut HashMap<String, ASTNode>,
fields: &mut Vec<String>,
public_fields: &mut Vec<String>,
private_fields: &mut Vec<String>,
last_method_name: &mut Option<String>,
) -> Result<bool, ParseError> {
members::fields::try_parse_visibility_block_or_single(
p,
visibility,
methods,
fields,
public_fields,
private_fields,
last_method_name,
)
}
/// Parse either a method or a header-first field/property starting with `name`.
/// Updates `methods`/`fields` and `last_method_name` as appropriate.
fn box_try_method_or_field(
p: &mut NyashParser,
name: String,
is_override: bool,
methods: &mut HashMap<String, ASTNode>,
fields: &mut Vec<String>,
birth_once_props: &Vec<String>,
last_method_name: &mut Option<String>,
) -> Result<bool, ParseError> {
if let Some(method) = members::methods::try_parse_method(
p,
name.clone(),
is_override,
birth_once_props,
)? {
*last_method_name = Some(name.clone());
methods.insert(name, method);
return Ok(true);
}
// Fallback: header-first field/property (computed/once/birth_once handled inside)
members::fields::try_parse_header_first_field_or_property(
p,
name,
methods,
fields,
)
}
/// box宣言をパース: box Name { fields... methods... }
pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
// Accept either 'box' or 'flow' (flow is syntactic sugar for static box)
if !p.match_token(&TokenType::BOX) && !p.match_token(&TokenType::FLOW) {
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "'box' or 'flow'".to_string(),
line: p.current_token().line,
});
}
p.advance(); // consume BOX or FLOW
let (name, type_parameters, extends, implements) =
header::parse_header(p)?;
p.consume(TokenType::LBRACE)?;
let mut fields = Vec::new();
let mut methods = HashMap::new();
let mut public_fields: Vec<String> = Vec::new();
let mut private_fields: Vec<String> = Vec::new();
let mut constructors = HashMap::new();
let mut init_fields = Vec::new();
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
// Track birth_once properties to inject eager init into birth()
let mut birth_once_props: Vec<String> = Vec::new();
let mut last_method_name: Option<String> = None;
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
// 分類(段階移行用の観測): 将来の分岐移譲のための前処理
if crate::config::env::parser_stage3() {
if let Ok(kind) = members::common::classify_member(p) {
let _ = kind; // 現段階では観測のみ(無副作用)
}
}
// nyashモードblock-first: { body } as (once|birth_once)? name : Type
if box_try_block_first_property(p, &mut methods, &mut birth_once_props)? { continue; }
// Fallback: method-level postfix catch/cleanup after a method (non-static box)
if box_try_method_postfix_after_last(p, &mut methods, &last_method_name)? { continue; }
// RBRACEに到達していればループを抜ける
if p.match_token(&TokenType::RBRACE) {
break;
}
// initブロックの処理initメソッドではない場合のみ
if box_try_init_block(p, &mut init_fields, &mut weak_fields)? { continue; }
// overrideキーワードをチェック
let mut is_override = false;
if p.match_token(&TokenType::OVERRIDE) {
is_override = true;
p.advance();
}
// constructor parsing moved to members::constructors
if box_try_constructor(p, is_override, &mut constructors)? { continue; }
// 🚨 birth()統一システム: Box名コンストラクタ無効化
validators::forbid_box_named_constructor(p, &name)?;
// 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り
if let TokenType::IDENTIFIER(field_or_method) = &p.current_token().token_type {
let field_or_method = field_or_method.clone();
p.advance();
// 可視性: public/private ブロック/単行
if box_try_visibility(
p,
&field_or_method,
&mut methods,
&mut fields,
&mut public_fields,
&mut private_fields,
&mut last_method_name,
)? { continue; }
// Unified Members (header-first) gate: support once/birth_once via members::properties
if crate::config::env::unified_members() && (field_or_method == "once" || field_or_method == "birth_once") {
if members::properties::try_parse_unified_property(
p,
&field_or_method,
&mut methods,
&mut birth_once_props,
)? {
last_method_name = None; // do not attach method-level postfix here
continue;
}
}
// メソッド or フィールド(委譲)
if box_try_method_or_field(
p,
field_or_method,
is_override,
&mut methods,
&mut fields,
&birth_once_props,
&mut last_method_name,
)? { continue; }
} else {
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),
found: p.current_token().token_type.clone(),
line: p.current_token().line,
});
}
}
p.consume(TokenType::RBRACE)?;
// 🚫 Disallow method named same as the box (constructor-like confusion)
validators::validate_no_ctor_like_name(p, &name, &methods)?;
// 🔥 Override validation
for parent in &extends {
p.validate_override_methods(&name, parent, &methods)?;
}
// birth_once 相互依存の簡易検出(宣言間の循環)
validators::validate_birth_once_cycles(p, &methods)?;
Ok(ASTNode::BoxDeclaration {
name,
fields,
public_fields,
private_fields,
methods,
constructors,
init_fields,
weak_fields, // 🔗 Add weak fields to AST
is_interface: false,
extends,
implements,
type_parameters,
is_static: false, // 通常のboxはnon-static
static_init: None, // 通常のboxはstatic初期化ブロックなし
span: Span::unknown(),
})
}
/// interface box宣言をパース: interface box Name { methods... }
pub fn parse_interface_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
interface::parse_interface_box(p)
}

View File

@ -1,257 +0,0 @@
/*!
* Box Definition Parser Module
*
* Box宣言box, interface box, static boxの解析を担当
* Nyashの中核概念「Everything is Box」を実現する重要モジュール
*/
use crate::ast::{ASTNode, Span};
use crate::parser::declarations::box_def::header as box_header;
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;
impl NyashParser {
/// Thin wrappers to keep the main loop tidy (behavior-preserving)
fn box_try_block_first_property(
&mut self,
methods: &mut HashMap<String, ASTNode>,
birth_once_props: &mut Vec<String>,
) -> Result<bool, ParseError> {
crate::parser::declarations::box_def::members::properties::try_parse_block_first_property(
self, methods, birth_once_props,
)
}
fn box_try_method_postfix_after_last(
&mut self,
methods: &mut HashMap<String, ASTNode>,
last_method_name: &Option<String>,
) -> Result<bool, ParseError> {
crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
self, methods, last_method_name,
)
}
fn box_try_init_block(
&mut self,
init_fields: &mut Vec<String>,
weak_fields: &mut Vec<String>,
) -> Result<bool, ParseError> {
crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
self, init_fields, weak_fields,
)
}
fn box_try_constructor(
&mut self,
is_override: bool,
constructors: &mut HashMap<String, ASTNode>,
) -> Result<bool, ParseError> {
if let Some((key, node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? {
constructors.insert(key, node);
return Ok(true);
}
Ok(false)
}
fn box_try_visibility(
&mut self,
visibility: &str,
methods: &mut HashMap<String, ASTNode>,
fields: &mut Vec<String>,
public_fields: &mut Vec<String>,
private_fields: &mut Vec<String>,
last_method_name: &mut Option<String>,
) -> Result<bool, ParseError> {
crate::parser::declarations::box_def::members::fields::try_parse_visibility_block_or_single(
self,
visibility,
methods,
fields,
public_fields,
private_fields,
last_method_name,
)
}
/// Parse either a method or a header-first field/property starting with `name`.
/// Updates `methods`/`fields` and `last_method_name` as appropriate.
fn box_try_method_or_field(
&mut self,
name: String,
is_override: bool,
methods: &mut HashMap<String, ASTNode>,
fields: &mut Vec<String>,
birth_once_props: &Vec<String>,
last_method_name: &mut Option<String>,
) -> Result<bool, ParseError> {
if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method(
self,
name.clone(),
is_override,
birth_once_props,
)? {
*last_method_name = Some(name.clone());
methods.insert(name, method);
return Ok(true);
}
// Fallback: header-first field/property (computed/once/birth_once handled inside)
crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
self,
name,
methods,
fields,
)
}
// parse_unified_member_block_first moved to members::properties
// parse_method_postfix_after_last_method moved to members::postfix
/// box宣言をパース: box Name { fields... methods... }
pub fn parse_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
// Accept either 'box' or 'flow' (flow is syntactic sugar for static box)
if !self.match_token(&TokenType::BOX) && !self.match_token(&TokenType::FLOW) {
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "'box' or 'flow'".to_string(),
line: self.current_token().line,
});
}
self.advance(); // consume BOX or FLOW
let (name, type_parameters, extends, implements) =
box_header::parse_header(self)?;
self.consume(TokenType::LBRACE)?;
let mut fields = Vec::new();
let mut methods = HashMap::new();
let mut public_fields: Vec<String> = Vec::new();
let mut private_fields: Vec<String> = Vec::new();
let mut constructors = HashMap::new();
let mut init_fields = Vec::new();
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
// Track birth_once properties to inject eager init into birth()
let mut birth_once_props: Vec<String> = Vec::new();
let mut last_method_name: Option<String> = None;
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
// 分類(段階移行用の観測): 将来の分岐移譲のための前処理
if crate::config::env::parser_stage3() {
if let Ok(kind) = crate::parser::declarations::box_def::members::common::classify_member(self) {
let _ = kind; // 現段階では観測のみ(無副作用)
}
}
// nyashモードblock-first: { body } as (once|birth_once)? name : Type
if self.box_try_block_first_property(&mut methods, &mut birth_once_props)? { continue; }
// Fallback: method-level postfix catch/cleanup after a method (non-static box)
if self.box_try_method_postfix_after_last(&mut methods, &last_method_name)? { continue; }
// RBRACEに到達していればループを抜ける
if self.match_token(&TokenType::RBRACE) {
break;
}
// initブロックの処理initメソッドではない場合のみ
if self.box_try_init_block(&mut init_fields, &mut weak_fields)? { continue; }
// overrideキーワードをチェック
let mut is_override = false;
if self.match_token(&TokenType::OVERRIDE) {
is_override = true;
self.advance();
}
// constructor parsing moved to members::constructors
if self.box_try_constructor(is_override, &mut constructors)? { continue; }
// 🚨 birth()統一システム: Box名コンストラクタ無効化
crate::parser::declarations::box_def::validators::forbid_box_named_constructor(self, &name)?;
// 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
let field_or_method = field_or_method.clone();
self.advance();
// 可視性: public/private ブロック/単行
if self.box_try_visibility(
&field_or_method,
&mut methods,
&mut fields,
&mut public_fields,
&mut private_fields,
&mut last_method_name,
)? { continue; }
// Unified Members (header-first) gate: support once/birth_once via members::properties
if crate::config::env::unified_members() && (field_or_method == "once" || field_or_method == "birth_once") {
if crate::parser::declarations::box_def::members::properties::try_parse_unified_property(
self,
&field_or_method,
&mut methods,
&mut birth_once_props,
)? {
last_method_name = None; // do not attach method-level postfix here
continue;
}
}
// メソッド or フィールド(委譲)
if self.box_try_method_or_field(
field_or_method,
is_override,
&mut methods,
&mut fields,
&birth_once_props,
&mut last_method_name,
)? { continue; }
} else {
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),
found: self.current_token().token_type.clone(),
line: self.current_token().line,
});
}
}
self.consume(TokenType::RBRACE)?;
// 🚫 Disallow method named same as the box (constructor-like confusion)
crate::parser::declarations::box_def::validators::validate_no_ctor_like_name(self, &name, &methods)?;
// 🔥 Override validation
for parent in &extends {
self.validate_override_methods(&name, parent, &methods)?;
}
// birth_once 相互依存の簡易検出(宣言間の循環)
crate::parser::declarations::box_def::validators::validate_birth_once_cycles(self, &methods)?;
Ok(ASTNode::BoxDeclaration {
name,
fields,
public_fields,
private_fields,
methods,
constructors,
init_fields,
weak_fields, // 🔗 Add weak fields to AST
is_interface: false,
extends,
implements,
type_parameters,
is_static: false, // 通常のboxはnon-static
static_init: None, // 通常のboxはstatic初期化ブロックなし
span: Span::unknown(),
})
}
/// interface box宣言をパース: interface box Name { methods... }
pub fn parse_interface_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
crate::parser::declarations::box_def::interface::parse_interface_box(self)
}
}
// ast_collect_me_fields moved into box_def::validators (private helper)

View File

@ -5,10 +5,8 @@
* Box定義、関数定義、use文などの宣言を処理
*/
pub mod box_definition;
pub mod box_def;
pub mod dependency_helpers;
pub mod static_box;
pub mod static_def;
// Re-export commonly used items

View File

@ -1,180 +0,0 @@
/*!
* Static Box Definition Parser
*
* static box宣言と関連ヘルパー関数
*/
use crate::ast::{ASTNode, Span};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;
impl NyashParser {
/// static box宣言をパース: static box Name { ... }
pub fn parse_static_box(&mut self) -> Result<ASTNode, ParseError> {
self.consume(TokenType::BOX)?;
let (name, type_parameters, extends, implements) =
crate::parser::declarations::static_def::header::parse_static_header(self)?;
self.consume(TokenType::LBRACE)?;
let mut fields = Vec::new();
let mut methods = HashMap::new();
let constructors = HashMap::new();
let mut init_fields = Vec::new();
let mut weak_fields = Vec::new(); // 🔗 Track weak fields for static box
let mut static_init: Option<Vec<ASTNode>> = None;
// Track last inserted method name to allow postfix catch/cleanup fallback parsing
let mut last_method_name: Option<String> = None;
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
// Tolerate blank lines between members
while self.match_token(&TokenType::NEWLINE) { self.advance(); }
let trace = std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1");
if trace {
eprintln!(
"[parser][static-box] loop token={:?}",
self.current_token().token_type
);
}
// Fallback: method-level postfix catch/cleanup immediately following a method
if crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
self, &mut methods, &last_method_name,
)? { continue; }
// RBRACEに到達していればループを抜ける
if self.match_token(&TokenType::RBRACE) {
break;
}
// 🔥 static 初期化子の処理(厳密ゲート互換)
if let Some(body) = crate::parser::declarations::static_def::members::parse_static_initializer_if_any(self)? {
static_init = Some(body);
continue;
} else if self.match_token(&TokenType::STATIC) {
// 互換用の暫定ガード既定OFF: using テキスト結合の継ぎ目で誤って 'static' が入った場合に
// ループを抜けて外側の '}' 消費に委ねる。既定では無効化し、文脈エラーとして扱う。
if std::env::var("NYASH_PARSER_SEAM_BREAK_ON_STATIC").ok().as_deref() == Some("1") {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[parser][static-box][seam] encountered 'static' inside static box; breaking (compat shim)");
}
break;
}
}
// initブロックの処理共通ヘルパに委譲
if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
self, &mut init_fields, &mut weak_fields,
)? { continue; }
// 🔧 Safety valve: if we encounter statement keywords (LOCAL, RETURN, etc.) at member level,
// it means we've likely exited a method body prematurely. Break to close the static box.
match self.current_token().token_type {
TokenType::LOCAL | TokenType::RETURN | TokenType::IF | TokenType::LOOP |
TokenType::BREAK | TokenType::CONTINUE | TokenType::PRINT => {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[parser][static-box][safety] encountered statement keyword {:?} at member level (line {}); assuming premature method body exit",
self.current_token().token_type, self.current_token().line);
}
break;
}
_ => {}
}
// Seam/robustness: tolerate stray tokens between members (text-merge or prelude seams)
// NYASH_PARSER_SEAM_TOLERANT=1 (dev/ci既定): ASSIGN を継ぎ目として箱を閉じるbreak
// NYASH_PARSER_SEAM_TOLERANT=0 (prod既定): ASSIGN でエラーFail-Fast
match &self.current_token().token_type {
TokenType::SEMICOLON | TokenType::NEWLINE => { self.advance(); continue; }
// If we encounter a bare '=' at member level, treat as seam boundary (gated by flag)
// Resynchronize by advancing to the closing '}' so outer logic can consume it.
TokenType::ASSIGN => {
let seam_tolerant = std::env::var("NYASH_PARSER_SEAM_TOLERANT")
.ok()
.as_deref() == Some("1");
if seam_tolerant {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!(
"[parser][static-box][seam] encountered ASSIGN at member level (line {}); treating as seam boundary (closing box)",
self.current_token().line
);
}
// advance until '}' or EOF
while !self.is_at_end() && !self.match_token(&TokenType::RBRACE) {
self.advance();
}
// do not consume RBRACE here; let trailing logic handle it
break; // 継ぎ目として箱を閉じる
} else {
// Prod: strict mode, fail fast on unexpected ASSIGN
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),
found: self.current_token().token_type.clone(),
line: self.current_token().line,
});
}
}
TokenType::IDENTIFIER(field_or_method) => {
let field_or_method = field_or_method.clone();
self.advance();
crate::parser::declarations::static_def::members::try_parse_method_or_field(
self, field_or_method, &mut methods, &mut fields, &mut last_method_name,
)?;
}
_ => {
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),
found: self.current_token().token_type.clone(),
line: self.current_token().line,
});
}
}
}
// Tolerate trailing NEWLINE(s) before the closing '}' of the static box
while self.match_token(&TokenType::NEWLINE) { self.advance(); }
if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") {
eprintln!(
"[parser][static-box] closing '}}' at token={:?}",
self.current_token().token_type
);
}
// Consume the closing RBRACE of the static box
self.consume(TokenType::RBRACE)?;
if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") {
eprintln!("[parser][static-box] successfully closed static box '{}'", name);
}
// 🔥 Static初期化ブロックから依存関係を抽出
if let Some(ref init_stmts) = static_init {
let dependencies = self.extract_dependencies_from_statements(init_stmts);
self.static_box_dependencies
.insert(name.clone(), dependencies);
} else {
self.static_box_dependencies
.insert(name.clone(), std::collections::HashSet::new());
}
Ok(ASTNode::BoxDeclaration {
name,
fields,
public_fields: vec![],
private_fields: vec![],
methods,
constructors,
init_fields,
weak_fields, // 🔗 Add weak fields to static box construction
is_interface: false,
extends,
implements,
type_parameters,
is_static: true, // 🔥 static boxフラグを設定
static_init, // 🔥 static初期化ブロック
span: Span::unknown(),
})
}
}

View File

@ -1,23 +1,179 @@
//! Static Box Definition (staged split)
#![allow(dead_code)]
use crate::ast::ASTNode;
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
use std::collections::HashMap;
pub mod header;
pub mod members;
pub mod validators;
/// Facade placeholder for static box parsing (to be wired gradually).
pub(crate) struct StaticDefFacade;
/// Parse static box declaration: static box Name { ... }
pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
p.consume(TokenType::BOX)?;
let (name, type_parameters, extends, implements) =
header::parse_static_header(p)?;
impl StaticDefFacade {
pub(crate) fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
Err(ParseError::UnexpectedToken {
found: crate::tokenizer::TokenType::EOF,
expected: "static box declaration (facade not wired)".to_string(),
line: 0,
})
p.consume(TokenType::LBRACE)?;
let mut fields = Vec::new();
let mut methods = HashMap::new();
let constructors = HashMap::new();
let mut init_fields = Vec::new();
let mut weak_fields = Vec::new(); // 🔗 Track weak fields for static box
let mut static_init: Option<Vec<ASTNode>> = None;
// Track last inserted method name to allow postfix catch/cleanup fallback parsing
let mut last_method_name: Option<String> = None;
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
// Tolerate blank lines between members
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
let trace = std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1");
if trace {
eprintln!(
"[parser][static-box] loop token={:?}",
p.current_token().token_type
);
}
// Fallback: method-level postfix catch/cleanup immediately following a method
if crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
p, &mut methods, &last_method_name,
)? { continue; }
// RBRACEに到達していればループを抜ける
if p.match_token(&TokenType::RBRACE) {
break;
}
// 🔥 static 初期化子の処理(厳密ゲート互換)
if let Some(body) = members::parse_static_initializer_if_any(p)? {
static_init = Some(body);
continue;
} else if p.match_token(&TokenType::STATIC) {
// 互換用の暫定ガード既定OFF: using テキスト結合の継ぎ目で誤って 'static' が入った場合に
// ループを抜けて外側の '}' 消費に委ねる。既定では無効化し、文脈エラーとして扱う。
if std::env::var("NYASH_PARSER_SEAM_BREAK_ON_STATIC").ok().as_deref() == Some("1") {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[parser][static-box][seam] encountered 'static' inside static box; breaking (compat shim)");
}
break;
}
}
// initブロックの処理共通ヘルパに委譲
if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
p, &mut init_fields, &mut weak_fields,
)? { continue; }
// 🔧 Safety valve: if we encounter statement keywords (LOCAL, RETURN, etc.) at member level,
// it means we've likely exited a method body prematurely. Break to close the static box.
match p.current_token().token_type {
TokenType::LOCAL | TokenType::RETURN | TokenType::IF | TokenType::LOOP |
TokenType::BREAK | TokenType::CONTINUE | TokenType::PRINT => {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[parser][static-box][safety] encountered statement keyword {:?} at member level (line {}); assuming premature method body exit",
p.current_token().token_type, p.current_token().line);
}
break;
}
_ => {}
}
// Seam/robustness: tolerate stray tokens between members (text-merge or prelude seams)
// NYASH_PARSER_SEAM_TOLERANT=1 (dev/ci既定): ASSIGN を継ぎ目として箱を閉じるbreak
// NYASH_PARSER_SEAM_TOLERANT=0 (prod既定): ASSIGN でエラーFail-Fast
match &p.current_token().token_type {
TokenType::SEMICOLON | TokenType::NEWLINE => { p.advance(); continue; }
// If we encounter a bare '=' at member level, treat as seam boundary (gated by flag)
// Resynchronize by advancing to the closing '}' so outer logic can consume it.
TokenType::ASSIGN => {
let seam_tolerant = std::env::var("NYASH_PARSER_SEAM_TOLERANT")
.ok()
.as_deref() == Some("1");
if seam_tolerant {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!(
"[parser][static-box][seam] encountered ASSIGN at member level (line {}); treating as seam boundary (closing box)",
p.current_token().line
);
}
// advance until '}' or EOF
while !p.is_at_end() && !p.match_token(&TokenType::RBRACE) {
p.advance();
}
// do not consume RBRACE here; let trailing logic handle it
break; // 継ぎ目として箱を閉じる
} else {
// Prod: strict mode, fail fast on unexpected ASSIGN
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),
found: p.current_token().token_type.clone(),
line: p.current_token().line,
});
}
}
TokenType::IDENTIFIER(field_or_method) => {
let field_or_method = field_or_method.clone();
p.advance();
members::try_parse_method_or_field(
p, field_or_method, &mut methods, &mut fields, &mut last_method_name,
)?;
}
_ => {
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),
found: p.current_token().token_type.clone(),
line: p.current_token().line,
});
}
}
}
}
// Tolerate trailing NEWLINE(s) before the closing '}' of the static box
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") {
eprintln!(
"[parser][static-box] closing '}}' at token={:?}",
p.current_token().token_type
);
}
// Consume the closing RBRACE of the static box
p.consume(TokenType::RBRACE)?;
if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") {
eprintln!("[parser][static-box] successfully closed static box '{}'", name);
}
// 🔥 Static初期化ブロックから依存関係を抽出
if let Some(ref init_stmts) = static_init {
let dependencies = p.extract_dependencies_from_statements(init_stmts);
p.static_box_dependencies
.insert(name.clone(), dependencies);
} else {
p.static_box_dependencies
.insert(name.clone(), std::collections::HashSet::new());
}
Ok(ASTNode::BoxDeclaration {
name,
fields,
public_fields: vec![],
private_fields: vec![],
methods,
constructors,
init_fields,
weak_fields, // 🔗 Add weak fields to static box construction
is_interface: false,
extends,
implements,
type_parameters,
is_static: true, // 🔥 static boxフラグを設定
static_init, // 🔥 static初期化ブロック
span: Span::unknown(),
})
}