selfhost/runtime: Stage 0-1 runner + MIR JSON loader (summary) with trace; compiler: scopebox/loopform prepass wiring (flags, child args); libs: add P1 standard boxes (console/string/array/map) as thin wrappers; runner: pass --box-pref via env; ops_calls dispatcher skeleton; docs: selfhost executor roadmap + scopebox/loopform notes; smokes: selfhost runner + identity prepasses; CURRENT_TASK: update plan and box lib schedule

This commit is contained in:
Selfhosting Dev
2025-09-22 21:52:39 +09:00
parent b00dc4ec37
commit da78fc174b
72 changed files with 3163 additions and 2557 deletions

View File

@ -6,7 +6,7 @@ use crate::tokenizer::TokenType;
/// Parse the leading header of a box declaration and return
/// (name, type_params, extends, implements). Does not consume the opening '{'.
pub fn parse_header(
pub(crate) fn parse_header(
p: &mut NyashParser,
) -> Result<(String, Vec<String>, Vec<String>, Vec<String>), ParseError> {
// Name

View File

@ -0,0 +1,107 @@
//! Interface box parser: `interface box Name { methods... }`
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
use std::collections::HashMap;
/// Parse `interface box Name { methods... }` and return an AST BoxDeclaration.
/// Caller must be positioned at the beginning of `interface box`.
pub(crate) fn parse_interface_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
p.consume(TokenType::INTERFACE)?;
p.consume(TokenType::BOX)?;
let name = if let TokenType::IDENTIFIER(name) = &p.current_token().token_type {
let name = name.clone();
p.advance();
name
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "identifier".to_string(),
line,
});
};
p.consume(TokenType::LBRACE)?;
p.skip_newlines(); // ブレース後の改行をスキップ
let mut methods = HashMap::new();
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
p.skip_newlines(); // ループ開始時に改行をスキップ
if let TokenType::IDENTIFIER(method_name) = &p.current_token().token_type {
let method_name = method_name.clone();
p.advance();
// インターフェースメソッドはシグネチャのみ
if p.match_token(&TokenType::LPAREN) {
p.advance(); // consume '('
let mut params = Vec::new();
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
params.push(param.clone());
p.advance();
}
if p.match_token(&TokenType::COMMA) {
p.advance();
}
}
p.consume(TokenType::RPAREN)?;
// インターフェースメソッドは実装なし空のbody
let method_decl = ASTNode::FunctionDeclaration {
name: method_name.clone(),
params,
body: vec![], // 空の実装
is_static: false, // インターフェースメソッドは通常静的でない
is_override: false, // デフォルトは非オーバーライド
span: Span::unknown(),
};
methods.insert(method_name, method_decl);
// メソッド宣言後の改行をスキップ
p.skip_newlines();
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "(".to_string(),
line,
});
}
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "method name".to_string(),
line,
});
}
}
p.consume(TokenType::RBRACE)?;
Ok(ASTNode::BoxDeclaration {
name,
fields: vec![], // インターフェースはフィールドなし
public_fields: vec![],
private_fields: vec![],
methods,
constructors: HashMap::new(), // インターフェースにコンストラクタなし
init_fields: vec![], // インターフェースにinitブロックなし
weak_fields: vec![], // インターフェースにweak fieldsなし
is_interface: true, // インターフェースフラグ
extends: vec![], // Multi-delegation: None → vec![] として表現
implements: vec![],
type_parameters: Vec::new(), // インターフェースではジェネリクス未対応
is_static: false, // インターフェースは非static
static_init: None, // インターフェースにstatic initなし
span: Span::unknown(),
})
}

View File

@ -4,7 +4,7 @@ use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MemberKind {
pub(crate) enum MemberKind {
Field,
Method,
Constructor,
@ -14,7 +14,7 @@ pub enum MemberKind {
}
/// Decide member kind via simple lookahead (scaffold placeholder)
pub fn classify_member(p: &mut NyashParser) -> Result<MemberKind, ParseError> {
pub(crate) fn classify_member(p: &mut NyashParser) -> Result<MemberKind, ParseError> {
// block-first: { body } as (once|birth_once)? name : Type
if crate::config::env::unified_members() && p.match_any_token(&[TokenType::LBRACE]) {
return Ok(MemberKind::PropertyComputed);

View File

@ -7,7 +7,7 @@ use crate::tokenizer::TokenType;
/// Try to parse a constructor at current position.
/// Supported: `init(...) {}`, `pack(...) {}`, `birth(...) {}`.
/// Returns Ok(Some((key, node))) when a constructor was parsed and consumed.
pub fn try_parse_constructor(
pub(crate) fn try_parse_constructor(
p: &mut NyashParser,
is_override: bool,
) -> Result<Option<(String, ASTNode)>, ParseError> {

View File

@ -12,7 +12,7 @@ use std::collections::HashMap;
/// - `name: Type => expr` → computed property (getter function generated)
/// - `name: Type { ... } [catch|cleanup]` → computed property block with optional postfix handlers
/// Returns Ok(true) when this function consumed and handled the construct; Ok(false) if not applicable.
pub fn try_parse_header_first_field_or_property(
pub(crate) fn try_parse_header_first_field_or_property(
p: &mut NyashParser,
fname: String,
methods: &mut HashMap<String, ASTNode>,
@ -87,3 +87,104 @@ pub fn try_parse_header_first_field_or_property(
fields.push(fname);
Ok(true)
}
/// Parse a visibility block or a single header-first property with visibility prefix.
/// Handles:
/// - `public { a, b, c }` → pushes to fields/public_fields
/// - `private { ... }` → pushes to fields/private_fields
/// - `public name: Type ...` (delegates to header-first field/property)
/// Returns Ok(true) if consumed, Ok(false) if visibility keyword not matched.
pub(crate) fn try_parse_visibility_block_or_single(
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> {
if visibility != "public" && visibility != "private" {
return Ok(false);
}
if p.match_token(&TokenType::LBRACE) {
p.advance();
p.skip_newlines();
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
if let TokenType::IDENTIFIER(fname) = &p.current_token().token_type {
let fname = fname.clone();
if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
fields.push(fname);
p.advance();
if p.match_token(&TokenType::COMMA) { p.advance(); }
p.skip_newlines();
continue;
}
return Err(ParseError::UnexpectedToken {
expected: "identifier in visibility block".to_string(),
found: p.current_token().token_type.clone(),
line: p.current_token().line,
});
}
p.consume(TokenType::RBRACE)?;
p.skip_newlines();
return Ok(true);
}
if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
let fname = n.clone();
p.advance();
if try_parse_header_first_field_or_property(p, fname.clone(), methods, fields)? {
if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
*last_method_name = None;
p.skip_newlines();
return Ok(true);
} else {
if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
fields.push(fname);
p.skip_newlines();
return Ok(true);
}
}
Ok(false)
}
/// Parse `init { ... }` non-call block to collect initializable fields and weak flags.
/// Returns Ok(true) if consumed; Ok(false) if no `init {` at current position.
pub(crate) fn parse_init_block_if_any(
p: &mut NyashParser,
init_fields: &mut Vec<String>,
weak_fields: &mut Vec<String>,
) -> Result<bool, ParseError> {
if !(p.match_token(&TokenType::INIT) && p.peek_token() != &TokenType::LPAREN) {
return Ok(false);
}
p.advance(); // consume 'init'
p.consume(TokenType::LBRACE)?;
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
p.skip_newlines();
if p.match_token(&TokenType::RBRACE) {
break;
}
let is_weak = if p.match_token(&TokenType::WEAK) {
p.advance();
true
} else {
false
};
if let TokenType::IDENTIFIER(field_name) = &p.current_token().token_type {
init_fields.push(field_name.clone());
if is_weak {
weak_fields.push(field_name.clone());
}
p.advance();
if p.match_token(&TokenType::COMMA) { p.advance(); }
} else {
return Err(ParseError::UnexpectedToken {
expected: if is_weak { "field name after 'weak'" } else { "field name" }.to_string(),
found: p.current_token().token_type.clone(),
line: p.current_token().line,
});
}
}
p.consume(TokenType::RBRACE)?;
Ok(true)
}

View File

@ -6,7 +6,7 @@ use crate::tokenizer::TokenType;
/// Try to parse a method declaration starting at `method_name` (already consumed identifier).
/// Returns Some(method_node) when parsed; None when not applicable (i.e., next token is not '(').
pub fn try_parse_method(
pub(crate) fn try_parse_method(
p: &mut NyashParser,
method_name: String,
is_override: bool,

View File

@ -3,10 +3,11 @@ use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
use std::collections::HashMap;
/// If Stage-3 gate allows, parse optional catch/cleanup after a block body and wrap it.
/// Returns a (possibly) wrapped body.
pub fn wrap_with_optional_postfix(
pub(crate) fn wrap_with_optional_postfix(
p: &mut NyashParser,
body: Vec<ASTNode>,
) -> Result<Vec<ASTNode>, ParseError> {
@ -52,3 +53,66 @@ pub fn wrap_with_optional_postfix(
span: Span::unknown(),
}])
}
/// Try to parse method-level postfix catch/cleanup after the last parsed method.
/// Attaches a TryCatch wrapper around the last method body.
pub(crate) fn try_parse_method_postfix_after_last_method(
p: &mut NyashParser,
methods: &mut HashMap<String, ASTNode>,
last_method_name: &Option<String>,
) -> Result<bool, ParseError> {
if !(p.match_token(&TokenType::CATCH) || p.match_token(&TokenType::CLEANUP)) || last_method_name.is_none() {
return Ok(false);
}
let mname = last_method_name.clone().unwrap();
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
if p.match_token(&TokenType::CATCH) {
p.advance();
p.consume(TokenType::LPAREN)?;
let (exc_ty, exc_var) = p.parse_catch_param()?;
p.consume(TokenType::RPAREN)?;
let catch_body = p.parse_block_statements()?;
catch_clauses.push(crate::ast::CatchClause {
exception_type: exc_ty,
variable_name: exc_var,
body: catch_body,
span: Span::unknown(),
});
p.skip_newlines();
if p.match_token(&TokenType::CATCH) {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "single catch only after method body".to_string(),
line,
});
}
}
let finally_body = if p.match_token(&TokenType::CLEANUP) {
p.advance();
Some(p.parse_block_statements()?)
} else {
None
};
if let Some(mnode) = methods.get_mut(&mname) {
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
let already = body.iter().any(|n| matches!(n, crate::ast::ASTNode::TryCatch { .. }));
if already {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "duplicate postfix catch/cleanup after method".to_string(),
line,
});
}
let old = std::mem::take(body);
*body = vec![crate::ast::ASTNode::TryCatch {
try_body: old,
catch_clauses,
finally_body,
span: crate::ast::Span::unknown(),
}];
}
}
Ok(true)
}

View File

@ -5,9 +5,10 @@ use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
use std::collections::HashMap;
/// Try to parse a unified member property: `once name: Type ...` or `birth_once name: Type ...`
/// Returns Ok(true) if consumed and handled; otherwise Ok(false).
pub fn try_parse_unified_property(
pub(crate) fn try_parse_unified_property(
p: &mut NyashParser,
kind_kw: &str,
methods: &mut HashMap<String, ASTNode>,
@ -115,3 +116,146 @@ pub fn try_parse_unified_property(
methods.insert(getter_name, getter);
Ok(true)
}
/// Try to parse a block-first unified member: `{ body } as [once|birth_once]? name : Type [postfix]`
/// Returns Ok(true) if a member was parsed and emitted into `methods`.
pub(crate) fn try_parse_block_first_property(
p: &mut NyashParser,
methods: &mut HashMap<String, ASTNode>,
birth_once_props: &mut Vec<String>,
) -> Result<bool, ParseError> {
if !(crate::config::env::unified_members() && p.match_token(&TokenType::LBRACE)) {
return Ok(false);
}
// 1) Parse block body first
let mut final_body = p.parse_block_statements()?;
p.skip_newlines();
// 2) Expect 'as'
if let TokenType::IDENTIFIER(kw) = &p.current_token().token_type {
if kw != "as" {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "'as' after block for block-first member".to_string(),
line,
});
}
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "'as' after block for block-first member".to_string(),
line,
});
}
p.advance(); // consume 'as'
// 3) Optional kind keyword: once | birth_once
let mut kind = "computed".to_string();
if let TokenType::IDENTIFIER(k) = &p.current_token().token_type {
if k == "once" || k == "birth_once" {
kind = k.clone();
p.advance();
}
}
// 4) Name : Type
let name = if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
let s = n.clone();
p.advance();
s
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "identifier for member name".to_string(),
line,
});
};
if p.match_token(&TokenType::COLON) {
p.advance();
if let TokenType::IDENTIFIER(_ty) = &p.current_token().token_type {
p.advance();
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "type name after ':'".to_string(),
line,
});
}
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: ": type".to_string(),
line,
});
}
// 5) Optional postfix handlers (Stage3) directly after block (shared helper)
final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, final_body)?;
// 6) Generate methods per kind (fully equivalent to legacy inline branch)
if kind == "once" {
// __compute_once_<name>
let compute_name = format!("__compute_once_{}", name);
let compute = ASTNode::FunctionDeclaration {
name: compute_name.clone(),
params: vec![],
body: final_body,
is_static: false,
is_override: false,
span: Span::unknown(),
};
methods.insert(compute_name.clone(), compute);
// Getter with cache + poison handling
let key = format!("__once_{}", name);
let poison_key = format!("__once_poison_{}", name);
let cached_local = format!("__ny_cached_{}", name);
let poison_local = format!("__ny_poison_{}", name);
let val_local = format!("__ny_val_{}", name);
let me_node = ASTNode::Me { span: Span::unknown() };
let get_cached = ASTNode::MethodCall {
object: Box::new(me_node.clone()),
method: "getField".to_string(),
arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }],
span: Span::unknown(),
};
let local_cached = ASTNode::Local { variables: vec![cached_local.clone()], initial_values: vec![Some(Box::new(get_cached))], span: Span::unknown() };
let cond_cached = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
let then_ret_cached = vec![ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })), span: Span::unknown() }];
let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() };
let get_poison = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }], span: Span::unknown() };
let local_poison = ASTNode::Local { variables: vec![poison_local.clone()], initial_values: vec![Some(Box::new(get_poison))], span: Span::unknown() };
let cond_poison = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
let then_throw = vec![ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }), span: Span::unknown() }];
let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() };
let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() };
let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() };
let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() };
let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() };
let getter_body = vec![local_cached, if_cached, local_poison, if_poison, local_val, set_call, ret_stmt];
let getter_name = format!("__get_once_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
return Ok(true);
}
// birth_once
birth_once_props.push(name.clone());
let compute_name = format!("__compute_birth_{}", name);
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(compute_name.clone(), compute);
let me_node = ASTNode::Me { span: Span::unknown() };
let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("__birth_{}", name)), span: Span::unknown() }], span: Span::unknown() };
let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }];
let getter_name = format!("__get_birth_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
Ok(true)
}

View File

@ -10,14 +10,16 @@ use crate::parser::{NyashParser, ParseError};
pub mod header;
pub mod members;
pub mod validators;
pub mod interface;
/// Facade to host the staged migration.
pub struct BoxDefParserFacade;
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 fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
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(),

View File

@ -0,0 +1,142 @@
//! Validators and light analysis for box members
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
use std::collections::{HashMap, HashSet};
/// Forbid user-defined methods named exactly as the box (constructor-like names).
/// Nyash constructors are explicit: init/pack/birth. A method with the same name
/// as the box is likely a mistaken constructor attempt; reject for clarity.
pub(crate) fn validate_no_ctor_like_name(
p: &mut NyashParser,
box_name: &str,
methods: &HashMap<String, ASTNode>,
) -> Result<(), ParseError> {
if methods.contains_key(box_name) {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: format!(
"method name must not match box name '{}'; use init/pack/birth for constructors",
box_name
),
line,
});
}
Ok(())
}
/// Validate that birth_once properties do not have cyclic dependencies via me.<prop> references
pub(crate) fn validate_birth_once_cycles(
p: &mut NyashParser,
methods: &HashMap<String, ASTNode>,
) -> Result<(), ParseError> {
if !crate::config::env::unified_members() {
return Ok(());
}
// Collect birth_once compute bodies
let mut birth_bodies: HashMap<String, Vec<ASTNode>> = HashMap::new();
for (mname, mast) in methods {
if let Some(prop) = mname.strip_prefix("__compute_birth_") {
if let ASTNode::FunctionDeclaration { body, .. } = mast {
birth_bodies.insert(prop.to_string(), body.clone());
}
}
}
if birth_bodies.is_empty() {
return Ok(());
}
// Build dependency graph: A -> {B | me.B used inside A}
let mut deps: HashMap<String, HashSet<String>> = HashMap::new();
let props: HashSet<String> = birth_bodies.keys().cloned().collect();
for (pname, body) in &birth_bodies {
let used = ast_collect_me_fields(body);
let mut set = HashSet::new();
for u in used {
if props.contains(&u) && u != *pname {
set.insert(u);
}
}
deps.insert(pname.clone(), set);
}
// Detect cycle via DFS
fn has_cycle(
node: &str,
deps: &HashMap<String, HashSet<String>>,
temp: &mut HashSet<String>,
perm: &mut HashSet<String>,
) -> bool {
if perm.contains(node) { return false; }
if !temp.insert(node.to_string()) { return true; } // back-edge
if let Some(ns) = deps.get(node) {
for n in ns {
if has_cycle(n, deps, temp, perm) { return true; }
}
}
temp.remove(node);
perm.insert(node.to_string());
false
}
let mut perm = HashSet::new();
let mut temp = HashSet::new();
for pname in deps.keys() {
if has_cycle(pname, &deps, &mut temp, &mut perm) {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "birth_once declarations must not have cyclic dependencies".to_string(),
line,
});
}
}
Ok(())
}
/// Forbid constructor call with the same name as the box; enforce `birth()` usage.
pub(crate) fn forbid_box_named_constructor(p: &mut NyashParser, box_name: &str) -> Result<(), ParseError> {
if let TokenType::IDENTIFIER(id) = &p.current_token().token_type {
if id == box_name && p.peek_token() == &TokenType::LPAREN {
return Err(ParseError::UnexpectedToken {
expected: format!(
"birth() constructor instead of {}(). Nyash uses birth() for unified constructor syntax.",
box_name
),
found: TokenType::IDENTIFIER(box_name.to_string()),
line: p.current_token().line,
});
}
}
Ok(())
}
/// Collect all `me.<field>` accessed in nodes (flat set)
fn ast_collect_me_fields(nodes: &Vec<ASTNode>) -> std::collections::HashSet<String> {
use std::collections::HashSet;
fn scan(nodes: &Vec<ASTNode>, out: &mut HashSet<String>) {
for n in nodes {
match n {
ASTNode::FieldAccess { object, field, .. } => {
if let ASTNode::Me { .. } = object.as_ref() {
out.insert(field.clone());
}
}
ASTNode::Program { statements, .. } => scan(statements, out),
ASTNode::If { then_body, else_body, .. } => {
scan(then_body, out);
if let Some(eb) = else_body { scan(eb, out); }
}
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
scan(try_body, out);
for c in catch_clauses { scan(&c.body, out); }
if let Some(fb) = finally_body { scan(fb, out); }
}
ASTNode::FunctionDeclaration { body, .. } => scan(body, out),
_ => {}
}
}
}
let mut hs = HashSet::new();
scan(nodes, &mut hs);
hs
}

View File

@ -13,407 +13,50 @@ use crate::tokenizer::TokenType;
use std::collections::HashMap;
impl NyashParser {
/// Forbid user-defined methods named exactly as the box (constructor-like names).
/// Nyash constructors are explicit: init/pack/birth. A method with the same name
/// as the box is likely a mistaken constructor attempt; reject for clarity.
fn validate_no_ctor_like_name(
&mut self,
box_name: &str,
methods: &HashMap<String, ASTNode>,
) -> Result<(), ParseError> {
if methods.contains_key(box_name) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: format!(
"method name must not match box name '{}'; use init/pack/birth for constructors",
box_name
),
line,
});
}
Ok(())
}
/// Validate that birth_once properties do not have cyclic dependencies via me.<prop> references
fn validate_birth_once_cycles(
&mut self,
methods: &HashMap<String, ASTNode>,
) -> Result<(), ParseError> {
if !crate::config::env::unified_members() {
return Ok(());
}
use std::collections::{HashMap, HashSet};
// Collect birth_once compute bodies
let mut birth_bodies: HashMap<String, Vec<ASTNode>> = HashMap::new();
for (mname, mast) in methods {
if let Some(prop) = mname.strip_prefix("__compute_birth_") {
if let ASTNode::FunctionDeclaration { body, .. } = mast {
birth_bodies.insert(prop.to_string(), body.clone());
}
}
}
if birth_bodies.is_empty() {
return Ok(());
}
// Build dependency graph: A -> {B | me.B used inside A}
let mut deps: HashMap<String, HashSet<String>> = HashMap::new();
let props: HashSet<String> = birth_bodies.keys().cloned().collect();
for (p, body) in &birth_bodies {
let used = self.ast_collect_me_fields(body);
let mut set = HashSet::new();
for u in used {
if props.contains(&u) && u != *p {
set.insert(u);
}
}
deps.insert(p.clone(), set);
}
// Detect cycle via DFS
fn has_cycle(
node: &str,
deps: &HashMap<String, HashSet<String>>,
temp: &mut HashSet<String>,
perm: &mut HashSet<String>,
) -> bool {
if perm.contains(node) {
return false;
}
if !temp.insert(node.to_string()) {
return true; // back-edge
}
if let Some(ns) = deps.get(node) {
for n in ns {
if has_cycle(n, deps, temp, perm) {
return true;
}
}
}
temp.remove(node);
perm.insert(node.to_string());
false
}
let mut perm = HashSet::new();
let mut temp = HashSet::new();
for p in deps.keys() {
if has_cycle(p, &deps, &mut temp, &mut perm) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "birth_once declarations must not have cyclic dependencies".to_string(),
line,
});
}
}
Ok(())
}
/// Extracted: parse block-first unified member: `{ body } as [once|birth_once]? name : Type [postfix]`
/// Returns true if a member was parsed and emitted into `methods`.
fn parse_unified_member_block_first(
/// 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> {
if !(crate::config::env::unified_members() && self.match_token(&TokenType::LBRACE)) {
return Ok(false);
}
// 1) Parse block body first
let mut final_body = self.parse_block_statements()?;
self.skip_newlines();
// 2) Expect 'as'
if let TokenType::IDENTIFIER(kw) = &self.current_token().token_type {
if kw != "as" {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "'as' after block for block-first member".to_string(),
line,
});
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "'as' after block for block-first member".to_string(),
line,
});
}
self.advance(); // consume 'as'
// 3) Optional kind keyword: once | birth_once
let mut kind = "computed".to_string();
if let TokenType::IDENTIFIER(k) = &self.current_token().token_type {
if k == "once" || k == "birth_once" {
kind = k.clone();
self.advance();
}
}
// 4) Name : Type
let name = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
let s = n.clone();
self.advance();
s
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "identifier for member name".to_string(),
line,
});
};
if self.match_token(&TokenType::COLON) {
self.advance();
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type {
self.advance();
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "type name after ':'".to_string(),
line,
});
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: ": type".to_string(),
line,
});
}
// 5) Optional postfix handlers (Stage3) directly after block (shared helper)
final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(self, final_body)?;
// 6) Generate methods per kind (fully equivalent to former inline branch)
if kind == "once" {
// __compute_once_<name>
let compute_name = format!("__compute_once_{}", name);
let compute = ASTNode::FunctionDeclaration {
name: compute_name.clone(),
params: vec![],
body: final_body,
is_static: false,
is_override: false,
span: Span::unknown(),
};
methods.insert(compute_name.clone(), compute);
// Getter with cache + poison handling
let key = format!("__once_{}", name);
let poison_key = format!("__once_poison_{}", name);
let cached_local = format!("__ny_cached_{}", name);
let poison_local = format!("__ny_poison_{}", name);
let val_local = format!("__ny_val_{}", name);
let me_node = ASTNode::Me { span: Span::unknown() };
let get_cached = ASTNode::MethodCall {
object: Box::new(me_node.clone()),
method: "getField".to_string(),
arguments: vec![ASTNode::Literal {
value: crate::ast::LiteralValue::String(key.clone()),
span: Span::unknown(),
}],
span: Span::unknown(),
};
let local_cached = ASTNode::Local {
variables: vec![cached_local.clone()],
initial_values: vec![Some(Box::new(get_cached))],
span: Span::unknown(),
};
let cond_cached = ASTNode::BinaryOp {
operator: crate::ast::BinaryOperator::NotEqual,
left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }),
right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }),
span: Span::unknown(),
};
let then_ret_cached = vec![ASTNode::Return {
value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })),
span: Span::unknown(),
}];
let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() };
let get_poison = ASTNode::MethodCall {
object: Box::new(me_node.clone()),
method: "getField".to_string(),
arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }],
span: Span::unknown(),
};
let local_poison = ASTNode::Local {
variables: vec![poison_local.clone()],
initial_values: vec![Some(Box::new(get_poison))],
span: Span::unknown(),
};
let cond_poison = ASTNode::BinaryOp {
operator: crate::ast::BinaryOperator::NotEqual,
left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }),
right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }),
span: Span::unknown(),
};
let then_throw = vec![ASTNode::Throw {
expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }),
span: Span::unknown(),
}];
let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() };
let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() };
let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() };
let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() };
let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() };
let try_body = vec![local_val, set_call, ret_stmt];
let catch_body = vec![
ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }, ASTNode::Literal { value: crate::ast::LiteralValue::Bool(true), span: Span::unknown() }], span: Span::unknown() },
ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' init failed", name)), span: Span::unknown() }), span: Span::unknown() }
];
let trycatch = ASTNode::TryCatch { try_body, catch_clauses: vec![crate::ast::CatchClause { exception_type: None, variable_name: None, body: catch_body, span: Span::unknown() }], finally_body: None, span: Span::unknown() };
let getter_body = vec![local_cached, if_cached, local_poison, if_poison, trycatch];
let getter_name = format!("__get_once_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
} else if kind == "birth_once" {
// Self-cycle guard: birth_once cannot reference itself via me.<name>
if self.ast_contains_me_field(&final_body, &name) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: format!("birth_once '{}' must not reference itself", name), line });
}
birth_once_props.push(name.clone());
let compute_name = format!("__compute_birth_{}", name);
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(compute_name.clone(), compute);
let key = format!("__birth_{}", name);
let me_node = ASTNode::Me { span: Span::unknown() };
let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], span: Span::unknown() };
let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }];
let getter_name = format!("__get_birth_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
} else {
// computed
let getter_name = format!("__get_{}", name);
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
methods.insert(getter_name, getter);
}
self.skip_newlines();
Ok(true)
crate::parser::declarations::box_def::members::properties::try_parse_block_first_property(
self, methods, birth_once_props,
)
}
/// Extracted: method-level postfix catch/cleanup after a method
fn parse_method_postfix_after_last_method(
fn box_try_method_postfix_after_last(
&mut self,
methods: &mut HashMap<String, ASTNode>,
last_method_name: &Option<String>,
) -> Result<bool, ParseError> {
if !(self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
|| last_method_name.is_none()
{
return Ok(false);
}
let mname = last_method_name.clone().unwrap();
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance();
self.consume(TokenType::LPAREN)?;
let (exc_ty, exc_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(crate::ast::CatchClause {
exception_type: exc_ty,
variable_name: exc_var,
body: catch_body,
span: crate::ast::Span::unknown(),
});
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "single catch only after method body".to_string(),
line,
});
}
}
let finally_body = if self.match_token(&TokenType::CLEANUP) {
self.advance();
Some(self.parse_block_statements()?)
} else {
None
};
if let Some(mnode) = methods.get_mut(&mname) {
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
let already = body
.iter()
.any(|n| matches!(n, crate::ast::ASTNode::TryCatch { .. }));
if already {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "duplicate postfix catch/cleanup after method".to_string(),
line,
});
}
let old = std::mem::take(body);
*body = vec![crate::ast::ASTNode::TryCatch {
try_body: old,
catch_clauses,
finally_body,
span: crate::ast::Span::unknown(),
}];
}
}
Ok(true)
crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
self, methods, last_method_name,
)
}
/// Extracted: init { ... } block (non-call form)
fn parse_init_block_if_any(
fn box_try_init_block(
&mut self,
init_fields: &mut Vec<String>,
weak_fields: &mut Vec<String>,
) -> Result<bool, ParseError> {
if !(self.match_token(&TokenType::INIT) && self.peek_token() != &TokenType::LPAREN) {
return Ok(false);
}
self.advance(); // consume 'init'
self.consume(TokenType::LBRACE)?;
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
if self.match_token(&TokenType::RBRACE) {
break;
}
let is_weak = if self.match_token(&TokenType::WEAK) {
self.advance();
true
} else {
false
};
if let TokenType::IDENTIFIER(field_name) = &self.current_token().token_type {
init_fields.push(field_name.clone());
if is_weak {
weak_fields.push(field_name.clone());
}
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance();
}
} else {
return Err(ParseError::UnexpectedToken {
expected: if is_weak {
"field name after 'weak'"
} else {
"field name"
}
.to_string(),
found: self.current_token().token_type.clone(),
line: self.current_token().line,
});
}
}
self.consume(TokenType::RBRACE)?;
Ok(true)
crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
self, init_fields, weak_fields,
)
}
/// Extracted: visibility block or single property header-first
fn parse_visibility_block_or_single(
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>,
@ -422,69 +65,50 @@ impl NyashParser {
private_fields: &mut Vec<String>,
last_method_name: &mut Option<String>,
) -> Result<bool, ParseError> {
if visibility != "public" && visibility != "private" {
return Ok(false);
}
if self.match_token(&TokenType::LBRACE) {
self.advance();
self.skip_newlines();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
if let TokenType::IDENTIFIER(fname) = &self.current_token().token_type {
let fname = fname.clone();
if visibility == "public" {
public_fields.push(fname.clone());
} else {
private_fields.push(fname.clone());
}
fields.push(fname);
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance();
}
self.skip_newlines();
continue;
}
return Err(ParseError::UnexpectedToken {
expected: "identifier in visibility block".to_string(),
found: self.current_token().token_type.clone(),
line: self.current_token().line,
});
}
self.consume(TokenType::RBRACE)?;
self.skip_newlines();
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);
}
if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
let fname = n.clone();
self.advance();
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
self,
fname.clone(),
methods,
fields,
)? {
if visibility == "public" {
public_fields.push(fname.clone());
} else {
private_fields.push(fname.clone());
}
*last_method_name = None;
self.skip_newlines();
return Ok(true);
} else {
// Fallback: record visibility + field name for compatibility
if visibility == "public" {
public_fields.push(fname.clone());
} else {
private_fields.push(fname.clone());
}
fields.push(fname);
self.skip_newlines();
return Ok(true);
}
}
Ok(false)
// 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> {
self.consume(TokenType::BOX)?;
@ -515,10 +139,10 @@ impl NyashParser {
}
// nyashモードblock-first: { body } as (once|birth_once)? name : Type
if self.parse_unified_member_block_first(&mut methods, &mut birth_once_props)? { continue; }
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.parse_method_postfix_after_last_method(&mut methods, &last_method_name)? { continue; }
if self.box_try_method_postfix_after_last(&mut methods, &last_method_name)? { continue; }
// RBRACEに到達していればループを抜ける
if self.match_token(&TokenType::RBRACE) {
@ -526,7 +150,7 @@ impl NyashParser {
}
// initブロックの処理initメソッドではない場合のみ
if self.parse_init_block_if_any(&mut init_fields, &mut weak_fields)? { continue; }
if self.box_try_init_block(&mut init_fields, &mut weak_fields)? { continue; }
// overrideキーワードをチェック
let mut is_override = false;
@ -536,22 +160,10 @@ impl NyashParser {
}
// constructor parsing moved to members::constructors
if let Some((ctor_key, ctor_node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? {
constructors.insert(ctor_key, ctor_node);
continue;
}
if self.box_try_constructor(is_override, &mut constructors)? { continue; }
// 🚨 birth()統一システム: Box名コンストラクタ無効化
// Box名と同じ名前のコンストラクタは禁止birth()のみ許可)
if let TokenType::IDENTIFIER(id) = &self.current_token().token_type {
if id == &name && self.peek_token() == &TokenType::LPAREN {
return Err(ParseError::UnexpectedToken {
expected: format!("birth() constructor instead of {}(). Nyash uses birth() for unified constructor syntax.", name),
found: TokenType::IDENTIFIER(name.clone()),
line: self.current_token().line,
});
}
}
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 {
@ -559,7 +171,7 @@ impl NyashParser {
self.advance();
// 可視性: public/private ブロック/単行
if self.parse_visibility_block_or_single(
if self.box_try_visibility(
&field_or_method,
&mut methods,
&mut fields,
@ -583,26 +195,14 @@ impl NyashParser {
}
// メソッド or フィールド(委譲)
if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method(
self,
field_or_method.clone(),
if self.box_try_method_or_field(
field_or_method,
is_override,
&mut methods,
&mut fields,
&birth_once_props,
)? {
last_method_name = Some(field_or_method.clone());
methods.insert(field_or_method, method);
} else {
// フィールド or 統一メンバcomputed/once/birth_once header-first
let fname = field_or_method;
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
self,
fname,
&mut methods,
&mut fields,
)? {
continue;
}
}
&mut last_method_name,
)? { continue; }
} else {
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),
@ -614,7 +214,7 @@ impl NyashParser {
self.consume(TokenType::RBRACE)?;
// 🚫 Disallow method named same as the box (constructor-like confusion)
self.validate_no_ctor_like_name(&name, &methods)?;
crate::parser::declarations::box_def::validators::validate_no_ctor_like_name(self, &name, &methods)?;
// 🔥 Override validation
for parent in &extends {
@ -622,7 +222,7 @@ impl NyashParser {
}
// birth_once 相互依存の簡易検出(宣言間の循環)
self.validate_birth_once_cycles(&methods)?;
crate::parser::declarations::box_def::validators::validate_birth_once_cycles(self, &methods)?;
Ok(ASTNode::BoxDeclaration {
name,
@ -645,169 +245,8 @@ impl NyashParser {
/// interface box宣言をパース: interface box Name { methods... }
pub fn parse_interface_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
self.consume(TokenType::INTERFACE)?;
self.consume(TokenType::BOX)?;
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
let name = name.clone();
self.advance();
name
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "identifier".to_string(),
line,
});
};
self.consume(TokenType::LBRACE)?;
self.skip_newlines(); // ブレース後の改行をスキップ
let mut methods = HashMap::new();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines(); // ループ開始時に改行をスキップ
if let TokenType::IDENTIFIER(method_name) = &self.current_token().token_type {
let method_name = method_name.clone();
self.advance();
// インターフェースメソッドはシグネチャのみ
if self.match_token(&TokenType::LPAREN) {
self.advance(); // consume '('
let mut params = Vec::new();
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
}
if self.match_token(&TokenType::COMMA) {
self.advance();
}
}
self.consume(TokenType::RPAREN)?;
// インターフェースメソッドは実装なし空のbody
let method_decl = ASTNode::FunctionDeclaration {
name: method_name.clone(),
params,
body: vec![], // 空の実装
is_static: false, // インターフェースメソッドは通常静的でない
is_override: false, // デフォルトは非オーバーライド
span: Span::unknown(),
};
methods.insert(method_name, method_decl);
// メソッド宣言後の改行をスキップ
self.skip_newlines();
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "(".to_string(),
line,
});
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "method name".to_string(),
line,
});
}
}
self.consume(TokenType::RBRACE)?;
Ok(ASTNode::BoxDeclaration {
name,
fields: vec![], // インターフェースはフィールドなし
public_fields: vec![],
private_fields: vec![],
methods,
constructors: HashMap::new(), // インターフェースにコンストラクタなし
init_fields: vec![], // インターフェースにinitブロックなし
weak_fields: vec![], // 🔗 インターフェースにweak fieldsなし
is_interface: true, // インターフェースフラグ
extends: vec![], // 🚀 Multi-delegation: Changed from None to vec![]
implements: vec![],
type_parameters: Vec::new(), // 🔥 インターフェースではジェネリクス未対応
is_static: false, // インターフェースは非static
static_init: None, // インターフェースにstatic initなし
span: Span::unknown(),
})
crate::parser::declarations::box_def::interface::parse_interface_box(self)
}
}
impl NyashParser {
/// Minimal scan: does body contain `me.<field>` access (direct self-cycle guard)
fn ast_contains_me_field(&self, nodes: &Vec<ASTNode>, field: &str) -> bool {
fn scan(nodes: &Vec<ASTNode>, field: &str) -> bool {
for n in nodes {
match n {
ASTNode::FieldAccess { object, field: f, .. } => {
if f == field {
if let ASTNode::Me { .. } = object.as_ref() {
return true;
}
}
}
ASTNode::Program { statements, .. } => {
if scan(statements, field) { return true; }
}
ASTNode::If { then_body, else_body, .. } => {
if scan(then_body, field) { return true; }
if let Some(eb) = else_body { if scan(eb, field) { return true; } }
}
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
if scan(try_body, field) { return true; }
for c in catch_clauses { if scan(&c.body, field) { return true; } }
if let Some(fb) = finally_body { if scan(fb, field) { return true; } }
}
ASTNode::FunctionDeclaration { body, .. } => {
if scan(body, field) { return true; }
}
_ => {}
}
}
false
}
scan(nodes, field)
}
/// Collect all `me.<field>` accessed in nodes (flat set)
fn ast_collect_me_fields(&self, nodes: &Vec<ASTNode>) -> std::collections::HashSet<String> {
use std::collections::HashSet;
fn scan(nodes: &Vec<ASTNode>, out: &mut HashSet<String>) {
for n in nodes {
match n {
ASTNode::FieldAccess { object, field, .. } => {
if let ASTNode::Me { .. } = object.as_ref() {
out.insert(field.clone());
}
}
ASTNode::Program { statements, .. } => scan(statements, out),
ASTNode::If { then_body, else_body, .. } => {
scan(then_body, out);
if let Some(eb) = else_body { scan(eb, out); }
}
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
scan(try_body, out);
for c in catch_clauses { scan(&c.body, out); }
if let Some(fb) = finally_body { scan(fb, out); }
}
ASTNode::FunctionDeclaration { body, .. } => scan(body, out),
_ => {}
}
}
}
let mut hs = HashSet::new();
scan(nodes, &mut hs);
hs
}
}
// ast_collect_me_fields moved into box_def::validators (private helper)

View File

@ -9,5 +9,6 @@ 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

@ -14,112 +14,8 @@ impl NyashParser {
/// static box宣言をパース: static box Name { ... }
pub fn parse_static_box(&mut self) -> Result<ASTNode, ParseError> {
self.consume(TokenType::BOX)?;
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
let name = name.clone();
self.advance();
name
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "identifier".to_string(),
line,
});
};
// 🔥 ジェネリクス型パラメータのパース (<T, U>)
let type_parameters = if self.match_token(&TokenType::LESS) {
self.advance(); // consume '<'
let mut params = Vec::new();
loop {
if let TokenType::IDENTIFIER(param_name) = &self.current_token().token_type {
params.push(param_name.clone());
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance(); // consume ','
} else {
break;
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "type parameter name".to_string(),
line,
});
}
}
self.consume(TokenType::GREATER)?; // consume '>'
params
} else {
Vec::new()
};
// from句のパースMulti-delegation- static boxでもデリゲーション可能 🚀
let extends = if self.match_token(&TokenType::FROM) {
self.advance(); // consume 'from'
let mut parent_list = Vec::new();
loop {
if let TokenType::IDENTIFIER(parent_name) = &self.current_token().token_type {
parent_list.push(parent_name.clone());
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance(); // consume ','
} else {
break;
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "parent class name".to_string(),
line,
});
}
}
parent_list
} else {
Vec::new()
};
// interface句のパースインターフェース実装- static boxでもinterface実装可能
let implements = if self.match_token(&TokenType::INTERFACE) {
self.advance(); // consume 'interface'
let mut interface_list = Vec::new();
loop {
if let TokenType::IDENTIFIER(interface_name) = &self.current_token().token_type {
interface_list.push(interface_name.clone());
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance(); // consume ','
} else {
break;
}
} else {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "interface name".to_string(),
line,
});
}
}
interface_list
} else {
vec![]
};
let (name, type_parameters, extends, implements) =
crate::parser::declarations::static_def::header::parse_static_header(self)?;
self.consume(TokenType::LBRACE)?;
self.skip_newlines(); // ブレース後の改行をスキップ
@ -129,7 +25,7 @@ impl NyashParser {
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 = None;
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;
@ -137,212 +33,35 @@ impl NyashParser {
self.skip_newlines(); // ループ開始時に改行をスキップ
// Fallback: method-level postfix catch/cleanup immediately following a method
if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() {
let mname = last_method_name.clone().unwrap();
// Parse optional catch then optional cleanup
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance();
self.consume(TokenType::LPAREN)?;
let (exc_ty, exc_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(crate::ast::CatchClause { exception_type: exc_ty, variable_name: exc_var, body: catch_body, span: crate::ast::Span::unknown() });
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "single catch only after method body".to_string(), line });
}
}
let finally_body = if self.match_token(&TokenType::CLEANUP) { self.advance(); Some(self.parse_block_statements()?) } else { None };
// Wrap existing method body
if let Some(mnode) = methods.get_mut(&mname) {
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
// If already TryCatch present, disallow duplicate postfix
let already = body.iter().any(|n| matches!(n, crate::ast::ASTNode::TryCatch{..}));
if already {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "duplicate postfix catch/cleanup after method".to_string(), line });
}
let old = std::mem::take(body);
*body = vec![crate::ast::ASTNode::TryCatch { try_body: old, catch_clauses, finally_body, span: crate::ast::Span::unknown() }];
continue;
}
}
}
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 初期化子の処理
// Gate: NYASH_PARSER_STATIC_INIT_STRICT=1 のとき、
// - 直後が '{' の場合のみ static 初期化子として扱う
// - 直後が 'box' or 'function' の場合は、トップレベル宣言の開始とみなし、この box 本体を閉じる
// 既定ゲートOFFは従来挙動常に static { ... } を期待)
if self.match_token(&TokenType::STATIC) {
let strict = std::env::var("NYASH_PARSER_STATIC_INIT_STRICT").ok().as_deref() == Some("1");
if strict {
match self.peek_token() {
TokenType::LBRACE => {
self.advance(); // consume 'static'
let static_body = self.parse_block_statements()?;
static_init = Some(static_body);
continue;
}
TokenType::BOX | TokenType::FUNCTION => {
// トップレベルの `static box|function` が続くシーム: ここで box を閉じる
break;
}
_ => {
// 不明な形は従来通り initializer として解釈(互換重視)
self.advance();
let static_body = self.parse_block_statements()?;
static_init = Some(static_body);
continue;
}
}
} else {
self.advance(); // consume 'static'
let static_body = self.parse_block_statements()?;
static_init = Some(static_body);
continue;
}
}
// initブロックの処理
if self.match_token(&TokenType::INIT) {
self.advance(); // consume 'init'
self.consume(TokenType::LBRACE)?;
// initブロック内のフィールド定義を読み込み
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
if self.match_token(&TokenType::RBRACE) {
break;
}
// Check for weak modifier
let is_weak = if self.match_token(&TokenType::WEAK) {
self.advance(); // consume 'weak'
true
} else {
false
};
if let TokenType::IDENTIFIER(field_name) = &self.current_token().token_type {
init_fields.push(field_name.clone());
if is_weak {
weak_fields.push(field_name.clone()); // 🔗 Add to weak fields list
}
self.advance();
// カンマがあればスキップ
if self.match_token(&TokenType::COMMA) {
self.advance();
}
} else {
// 不正なトークンがある場合はエラー
return Err(ParseError::UnexpectedToken {
expected: if is_weak {
"field name after 'weak'"
} else {
"field name"
}
.to_string(),
found: self.current_token().token_type.clone(),
line: self.current_token().line,
});
}
}
self.consume(TokenType::RBRACE)?;
// 🔥 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) {
// STRICT で top-level seam を検出した場合は while を抜ける
break;
}
// initブロックの処理共通ヘルパに委譲
if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
self, &mut init_fields, &mut weak_fields,
)? { continue; }
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
let field_or_method = field_or_method.clone();
self.advance();
// メソッド定義か?
if self.match_token(&TokenType::LPAREN) {
// メソッド定義
self.advance(); // consume '('
let mut params = Vec::new();
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
}
if self.match_token(&TokenType::COMMA) {
self.advance();
}
}
self.consume(TokenType::RPAREN)?;
let mut body = self.parse_block_statements()?;
self.skip_newlines();
// Method-level postfix catch/cleanup (gate)
if self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)
{
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance(); // consume 'catch'
self.consume(TokenType::LPAREN)?;
let (exc_ty, exc_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(crate::ast::CatchClause {
exception_type: exc_ty,
variable_name: exc_var,
body: catch_body,
span: crate::ast::Span::unknown(),
});
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "single catch only after method body".to_string(),
line,
});
}
}
let finally_body = if self.match_token(&TokenType::CLEANUP) {
self.advance();
Some(self.parse_block_statements()?)
} else {
None
};
// Wrap original body with TryCatch
body = vec![ASTNode::TryCatch {
try_body: body,
catch_clauses,
finally_body,
span: crate::ast::Span::unknown(),
}];
}
let method = ASTNode::FunctionDeclaration {
name: field_or_method.clone(),
params,
body,
is_static: false, // static box内のメソッドは通常メソッド
is_override: false, // デフォルトは非オーバーライド
span: Span::unknown(),
};
last_method_name = Some(field_or_method.clone());
methods.insert(field_or_method, method);
} else {
// フィールド定義
fields.push(field_or_method);
}
crate::parser::declarations::static_def::members::try_parse_method_or_field(
self, field_or_method, &mut methods, &mut fields, &mut last_method_name,
)?;
} else {
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),

View File

@ -0,0 +1,91 @@
//! Header parsing for `static box Name<T...> from Parent1, ... [interface ...]` (staged)
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
/// Parse the leading header of a static box and return
/// (name, type_params, extends, implements). Does not consume the opening '{'.
pub(crate) fn parse_static_header(
p: &mut NyashParser,
) -> Result<(String, Vec<String>, Vec<String>, Vec<String>), ParseError> {
// Name
let name = if let TokenType::IDENTIFIER(name) = &p.current_token().token_type {
let name = name.clone();
p.advance();
name
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "identifier".to_string(),
line,
});
};
// Generic type parameters: <T, U>
let type_parameters = if p.match_token(&TokenType::LESS) {
p.advance(); // consume '<'
let mut params = Vec::new();
loop {
if let TokenType::IDENTIFIER(param_name) = &p.current_token().token_type {
params.push(param_name.clone());
p.advance();
if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; }
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "type parameter name".to_string(),
line,
});
}
}
p.consume(TokenType::GREATER)?;
params
} else { Vec::new() };
// extends: from Parent1, Parent2
let extends = if p.match_token(&TokenType::FROM) {
p.advance(); // consume 'from'
let mut parents = Vec::new();
loop {
if let TokenType::IDENTIFIER(parent_name) = &p.current_token().token_type {
parents.push(parent_name.clone());
p.advance();
if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; }
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "parent class name".to_string(),
line,
});
}
}
parents
} else { Vec::new() };
// implements: `interface A, B` (optional)
let implements = if p.match_token(&TokenType::INTERFACE) {
p.advance(); // consume 'interface'
let mut list = Vec::new();
loop {
if let TokenType::IDENTIFIER(interface_name) = &p.current_token().token_type {
list.push(interface_name.clone());
p.advance();
if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; }
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "interface name".to_string(),
line,
});
}
}
list
} else { Vec::new() };
Ok((name, type_parameters, extends, implements))
}

View File

@ -0,0 +1,90 @@
//! Members helpers for static box (staged)
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::tokenizer::TokenType;
use std::collections::HashMap;
/// Parse a `static { ... }` initializer if present, honoring STRICT gate behavior.
/// Returns Ok(Some(body)) when consumed; Ok(None) otherwise.
pub(crate) fn parse_static_initializer_if_any(
p: &mut NyashParser,
) -> Result<Option<Vec<ASTNode>>, ParseError> {
if !p.match_token(&TokenType::STATIC) { return Ok(None); }
let strict = std::env::var("NYASH_PARSER_STATIC_INIT_STRICT").ok().as_deref() == Some("1");
if strict {
match p.peek_token() {
TokenType::LBRACE => {
p.advance(); // consume 'static'
let body = p.parse_block_statements()?;
return Ok(Some(body));
}
TokenType::BOX | TokenType::FUNCTION => {
// top-level seam: do not consume, let caller close the box
return Ok(None);
}
_ => {
// backward-compatible fallback: treat as initializer
p.advance();
let body = p.parse_block_statements()?;
return Ok(Some(body));
}
}
} else {
p.advance(); // consume 'static'
let body = p.parse_block_statements()?;
Ok(Some(body))
}
}
/// Parse a method body and apply optional postfix catch/cleanup inline (Stage3 gate).
/// Caller must have consumed `(` and collected `params` and parsed `body`.
pub(crate) fn wrap_method_body_with_postfix_if_any(
p: &mut NyashParser,
body: Vec<ASTNode>,
) -> Result<Vec<ASTNode>, ParseError> {
crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, body)
}
/// Parse either a method or a field in static box after consuming an identifier `name`.
/// - If next token is `(`, parses a method with optional postfix and inserts into `methods`.
/// - Otherwise, treats as a field name and pushes into `fields`.
pub(crate) fn try_parse_method_or_field(
p: &mut NyashParser,
name: String,
methods: &mut HashMap<String, ASTNode>,
fields: &mut Vec<String>,
last_method_name: &mut Option<String>,
) -> Result<bool, ParseError> {
if !p.match_token(&TokenType::LPAREN) {
// Field
fields.push(name);
return Ok(true);
}
// Method
p.advance(); // consume '('
let mut params = Vec::new();
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
crate::must_advance!(p, _unused, "static method parameter parsing");
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
params.push(param.clone());
p.advance();
}
if p.match_token(&TokenType::COMMA) { p.advance(); }
}
p.consume(TokenType::RPAREN)?;
let body = p.parse_block_statements()?;
let body = wrap_method_body_with_postfix_if_any(p, body)?;
// Construct method node
let method = ASTNode::FunctionDeclaration {
name: name.clone(),
params,
body,
is_static: false,
is_override: false,
span: crate::ast::Span::unknown(),
};
*last_method_name = Some(name.clone());
methods.insert(name, method);
Ok(true)
}

View File

@ -0,0 +1,23 @@
//! Static Box Definition (staged split)
#![allow(dead_code)]
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
pub mod header;
pub mod members;
pub mod validators;
/// Facade placeholder for static box parsing (to be wired gradually).
pub(crate) struct StaticDefFacade;
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,
})
}
}

View File

@ -0,0 +1,12 @@
//! Static box validators (placeholder for symmetry)
#![allow(dead_code)]
use crate::parser::{NyashParser, ParseError};
pub(crate) struct StaticValidators;
impl StaticValidators {
#[allow(dead_code)]
pub(crate) fn validate(_p: &mut NyashParser) -> Result<(), ParseError> { Ok(()) }
}

View File

@ -11,6 +11,78 @@ use crate::ast::{ASTNode, CatchClause, Span};
use crate::tokenizer::TokenType;
impl NyashParser {
/// Parse a standalone block `{ ... }` and optional postfix `catch/cleanup` sequence.
/// Returns Program(body) when no postfix keywords follow.
fn parse_standalone_block_statement(&mut self) -> Result<ASTNode, ParseError> {
// Parse the block body first
let try_body = self.parse_block_statements()?;
// Allow whitespace/newlines between block and postfix keywords
self.skip_newlines();
if crate::config::env::block_postfix_catch()
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
{
// Parse at most one catch, then optional cleanup
let mut catch_clauses: Vec<CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance(); // consume 'catch'
self.consume(TokenType::LPAREN)?;
let (exception_type, exception_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(CatchClause {
exception_type,
variable_name: exception_var,
body: catch_body,
span: Span::unknown(),
});
// Singlecatch policy (MVP): disallow multiple catch in postfix form
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "single catch only after standalone block".to_string(),
line,
});
}
}
// Optional cleanup
let finally_body = if self.match_token(&TokenType::CLEANUP) {
self.advance(); // consume 'cleanup'
Some(self.parse_block_statements()?)
} else {
None
};
Ok(ASTNode::TryCatch {
try_body,
catch_clauses,
finally_body,
span: Span::unknown(),
})
} else {
// No postfix keywords. If gate is on, enforce MVP static check:
// direct top-level `throw` inside the standalone block must be followed by catch
if crate::config::env::block_postfix_catch()
&& try_body.iter().any(|n| matches!(n, ASTNode::Throw { .. }))
{
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "block with direct 'throw' must be followed by 'catch'".to_string(),
line,
});
}
Ok(ASTNode::Program {
statements: try_body,
span: Span::unknown(),
})
}
}
/// Helper: parse a block `{ stmt* }` and return its statements
pub(super) fn parse_block_statements(&mut self) -> Result<Vec<ASTNode>, ParseError> {
self.consume(TokenType::LBRACE)?;
@ -25,6 +97,151 @@ impl NyashParser {
Ok(body)
}
/// Grouped: declarations (box/interface/global/function/static/import)
fn parse_declaration_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::BOX => self.parse_box_declaration(),
TokenType::IMPORT => self.parse_import(),
TokenType::INTERFACE => self.parse_interface_box_declaration(),
TokenType::GLOBAL => self.parse_global_var(),
TokenType::FUNCTION => self.parse_function_declaration(),
TokenType::STATIC => self.parse_static_declaration(),
_ => {
let line = self.current_token().line;
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "declaration statement".to_string(),
line,
})
}
}
}
/// Grouped: control flow (if/loop/break/continue/return)
fn parse_control_flow_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::IF => self.parse_if(),
TokenType::LOOP => self.parse_loop(),
TokenType::BREAK => self.parse_break(),
TokenType::CONTINUE => self.parse_continue(),
TokenType::RETURN => self.parse_return(),
_ => {
let line = self.current_token().line;
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "control-flow statement".to_string(),
line,
})
}
}
}
/// Grouped: IO/module-ish (print/nowait/include)
fn parse_io_module_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::PRINT => self.parse_print(),
TokenType::NOWAIT => self.parse_nowait(),
TokenType::INCLUDE => self.parse_include(),
_ => {
let line = self.current_token().line;
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "io/module statement".to_string(),
line,
})
}
}
}
/// Grouped: variable-related (local/outbox)
fn parse_variable_declaration_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::LOCAL => self.parse_local(),
TokenType::OUTBOX => self.parse_outbox(),
_ => {
let line = self.current_token().line;
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "variable declaration".to_string(),
line,
})
}
}
}
/// Grouped: exception (try/throw) with gate checks preserved
fn parse_exception_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::TRY => {
if crate::config::env::parser_stage3() {
self.parse_try_catch()
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_PARSER_STAGE3=1 to use 'try'".to_string(),
line: self.current_token().line,
})
}
}
TokenType::THROW => {
if crate::config::env::parser_stage3() {
self.parse_throw()
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_PARSER_STAGE3=1 to use 'throw'".to_string(),
line: self.current_token().line,
})
}
}
_ => {
let line = self.current_token().line;
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "try/throw".to_string(),
line,
})
}
}
}
/// Error helpers for standalone postfix keywords (catch/cleanup)
fn parse_postfix_catch_cleanup_error(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::CATCH => {
if crate::config::env::block_postfix_catch() {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "postfix 'catch' is only allowed immediately after a standalone block: { ... } catch (...) { ... } (wrap if/else/loop in a standalone block)".to_string(),
line: self.current_token().line,
})
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'catch' after a standalone block".to_string(),
line: self.current_token().line,
})
}
}
TokenType::CLEANUP => {
if crate::config::env::block_postfix_catch() {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "postfix 'cleanup' is only allowed immediately after a standalone block: { ... } cleanup { ... }".to_string(),
line: self.current_token().line,
})
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'cleanup' after a standalone block".to_string(),
line: self.current_token().line,
})
}
}
_ => unreachable!(),
}
}
/// Helper: parse catch parameter inside parentheses (after '(' consumed)
/// Forms: (Type ident) | (ident) | ()
pub(super) fn parse_catch_param(&mut self) -> Result<(Option<String>, Option<String>), ParseError> {
@ -66,149 +283,22 @@ impl NyashParser {
// For grammar diff: capture starting token to classify statement keyword
let start_tok = self.current_token().token_type.clone();
let result = match &start_tok {
TokenType::LBRACE => {
// Standalone block (Phase 15.5): may be followed by blockpostfix catch/finally
// Only enabled under gate; otherwise treat as error via expression fallback
// Parse the block body first
let try_body = self.parse_block_statements()?;
// Allow whitespace/newlines between block and postfix keywords
self.skip_newlines();
if crate::config::env::block_postfix_catch()
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
{
// Parse at most one catch, then optional cleanup
let mut catch_clauses: Vec<CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance(); // consume 'catch'
self.consume(TokenType::LPAREN)?;
let (exception_type, exception_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(CatchClause {
exception_type,
variable_name: exception_var,
body: catch_body,
span: Span::unknown(),
});
// Singlecatch policy (MVP): disallow multiple catch in postfix form
self.skip_newlines();
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "single catch only after standalone block".to_string(),
line,
});
}
}
// Optional cleanup
let finally_body = if self.match_token(&TokenType::CLEANUP) {
self.advance(); // consume 'cleanup'
Some(self.parse_block_statements()?)
} else {
None
};
Ok(ASTNode::TryCatch {
try_body,
catch_clauses,
finally_body,
span: Span::unknown(),
})
} else {
// No postfix keywords. If gate is on, enforce MVP static check:
// direct top-level `throw` inside the standalone block must be followed by catch
if crate::config::env::block_postfix_catch()
&& try_body.iter().any(|n| matches!(n, ASTNode::Throw { .. }))
{
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "block with direct 'throw' must be followed by 'catch'".to_string(),
line,
});
}
Ok(ASTNode::Program {
statements: try_body,
span: Span::unknown(),
})
}
}
TokenType::BOX => self.parse_box_declaration(),
TokenType::IMPORT => self.parse_import(),
TokenType::INTERFACE => self.parse_interface_box_declaration(),
TokenType::GLOBAL => self.parse_global_var(),
TokenType::FUNCTION => self.parse_function_declaration(),
TokenType::STATIC => {
self.parse_static_declaration() // 🔥 静的宣言 (function/box)
}
TokenType::IF => self.parse_if(),
TokenType::LOOP => self.parse_loop(),
TokenType::BREAK => self.parse_break(),
TokenType::CONTINUE => self.parse_continue(),
TokenType::RETURN => self.parse_return(),
TokenType::PRINT => self.parse_print(),
TokenType::NOWAIT => self.parse_nowait(),
TokenType::INCLUDE => self.parse_include(),
TokenType::LOCAL => self.parse_local(),
TokenType::OUTBOX => self.parse_outbox(),
TokenType::TRY => {
if crate::config::env::parser_stage3() {
self.parse_try_catch()
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_PARSER_STAGE3=1 to use 'try'".to_string(),
line: self.current_token().line,
})
}
}
TokenType::THROW => {
if crate::config::env::parser_stage3() {
self.parse_throw()
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_PARSER_STAGE3=1 to use 'throw'".to_string(),
line: self.current_token().line,
})
}
}
TokenType::CATCH => {
// Provide a friendlier error when someone writes: if { .. } catch { .. }
if crate::config::env::block_postfix_catch() {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "postfix 'catch' is only allowed immediately after a standalone block: { ... } catch (...) { ... } (wrap if/else/loop in a standalone block)".to_string(),
line: self.current_token().line,
})
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'catch' after a standalone block".to_string(),
line: self.current_token().line,
})
}
}
TokenType::CLEANUP => {
if crate::config::env::block_postfix_catch() {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "postfix 'cleanup' is only allowed immediately after a standalone block: { ... } cleanup { ... }".to_string(),
line: self.current_token().line,
})
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'cleanup' after a standalone block".to_string(),
line: self.current_token().line,
})
}
}
TokenType::LBRACE => self.parse_standalone_block_statement(),
TokenType::BOX
| TokenType::IMPORT
| TokenType::INTERFACE
| TokenType::GLOBAL
| TokenType::FUNCTION
| TokenType::STATIC => self.parse_declaration_statement(),
TokenType::IF
| TokenType::LOOP
| TokenType::BREAK
| TokenType::CONTINUE
| TokenType::RETURN => self.parse_control_flow_statement(),
TokenType::PRINT | TokenType::NOWAIT | TokenType::INCLUDE => self.parse_io_module_statement(),
TokenType::LOCAL | TokenType::OUTBOX => self.parse_variable_declaration_statement(),
TokenType::TRY | TokenType::THROW => self.parse_exception_statement(),
TokenType::CATCH | TokenType::CLEANUP => self.parse_postfix_catch_cleanup_error(),
TokenType::USING => self.parse_using(),
TokenType::FROM => {
// 🔥 from構文: from Parent.method(args) または from Parent.constructor(args)