chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更

Phase 25.1 完了成果:
-  LoopForm v2 テスト・ドキュメント・コメント完備
  - 4ケース(A/B/C/D)完全テストカバレッジ
  - 最小再現ケース作成(SSAバグ調査用)
  - SSOT文書作成(loopform_ssot.md)
  - 全ソースに [LoopForm] コメントタグ追加

-  Stage-1 CLI デバッグ環境構築
  - stage1_cli.hako 実装
  - stage1_bridge.rs ブリッジ実装
  - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh)
  - アーキテクチャ改善提案文書

-  環境変数削減計画策定
  - 25変数の完全調査・分類
  - 6段階削減ロードマップ(25→5、80%削減)
  - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG)

Phase 26-D からの累積変更:
- PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等)
- MIRビルダーリファクタリング
- 型伝播・最適化パス改善
- その他約300ファイルの累積変更

🎯 技術的成果:
- SSAバグ根本原因特定(条件分岐内loop変数変更)
- Region+next_iパターン適用完了(UsingCollectorBox等)
- LoopFormパターン文書化・テスト化完了
- セルフホスティング基盤強化

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <noreply@openai.com>
Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

@ -58,13 +58,17 @@ pub trait ParserUtils {
// 環境変数 NYASH_PARSER_TOKEN_CURSOR=1 の場合は Cursor 側で一元管理する。
let cursor_on = std::env::var("NYASH_PARSER_TOKEN_CURSOR").ok().as_deref() == Some("1");
if !cursor_on {
let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok().map(|v| {
let lv = v.to_ascii_lowercase();
!(lv == "0" || lv == "false" || lv == "off")
}).unwrap_or(true);
let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON")
.ok()
.map(|v| {
let lv = v.to_ascii_lowercase();
!(lv == "0" || lv == "false" || lv == "off")
})
.unwrap_or(true);
loop {
let is_nl = matches!(self.current_token().token_type, TokenType::NEWLINE);
let is_sc = allow_sc && matches!(self.current_token().token_type, TokenType::SEMICOLON);
let is_sc =
allow_sc && matches!(self.current_token().token_type, TokenType::SEMICOLON);
if (is_nl || is_sc) && !self.is_at_end() {
*self.current_mut() += 1; // 非再帰的に前進
continue;

View File

@ -92,7 +92,8 @@ impl<'a> TokenCursor<'a> {
/// 明示的に改行をスキップ
pub fn skip_newlines(&mut self) {
while self.idx < self.tokens.len()
&& matches!(self.tokens[self.idx].token_type, TokenType::NEWLINE) {
&& matches!(self.tokens[self.idx].token_type, TokenType::NEWLINE)
{
self.idx += 1;
}
}
@ -241,11 +242,31 @@ mod tests {
#[test]
fn test_basic_cursor_operations() {
let tokens = vec![
Token { token_type: TokenType::LOCAL, line: 1, column: 1 },
Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 1, column: 7 },
Token { token_type: TokenType::ASSIGN, line: 1, column: 9 },
Token { token_type: TokenType::NUMBER(42), line: 1, column: 11 },
Token { token_type: TokenType::EOF, line: 1, column: 13 },
Token {
token_type: TokenType::LOCAL,
line: 1,
column: 1,
},
Token {
token_type: TokenType::IDENTIFIER("x".to_string()),
line: 1,
column: 7,
},
Token {
token_type: TokenType::ASSIGN,
line: 1,
column: 9,
},
Token {
token_type: TokenType::NUMBER(42),
line: 1,
column: 11,
},
Token {
token_type: TokenType::EOF,
line: 1,
column: 13,
},
];
let mut cursor = TokenCursor::new(&tokens);
@ -253,7 +274,10 @@ mod tests {
assert!(cursor.match_token(&TokenType::LOCAL));
cursor.advance();
assert!(matches!(cursor.current().token_type, TokenType::IDENTIFIER(_)));
assert!(matches!(
cursor.current().token_type,
TokenType::IDENTIFIER(_)
));
cursor.advance();
assert!(cursor.match_token(&TokenType::ASSIGN));
@ -268,27 +292,70 @@ mod tests {
#[test]
fn test_newline_skipping_in_braces() {
let tokens = vec![
Token { token_type: TokenType::LBRACE, line: 1, column: 1 },
Token { token_type: TokenType::NEWLINE, line: 1, column: 2 },
Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 2, column: 1 },
Token { token_type: TokenType::RBRACE, line: 2, column: 2 },
Token { token_type: TokenType::EOF, line: 2, column: 3 },
Token {
token_type: TokenType::LBRACE,
line: 1,
column: 1,
},
Token {
token_type: TokenType::NEWLINE,
line: 1,
column: 2,
},
Token {
token_type: TokenType::IDENTIFIER("x".to_string()),
line: 2,
column: 1,
},
Token {
token_type: TokenType::RBRACE,
line: 2,
column: 2,
},
Token {
token_type: TokenType::EOF,
line: 2,
column: 3,
},
];
let mut cursor = TokenCursor::new(&tokens);
cursor.advance(); // consume LBRACE, should skip NEWLINE
assert!(matches!(cursor.current().token_type, TokenType::IDENTIFIER(_)));
assert!(matches!(
cursor.current().token_type,
TokenType::IDENTIFIER(_)
));
}
#[test]
fn test_expr_mode() {
let tokens = vec![
Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 1, column: 1 },
Token { token_type: TokenType::NEWLINE, line: 1, column: 2 },
Token { token_type: TokenType::PLUS, line: 2, column: 1 },
Token { token_type: TokenType::NUMBER(1), line: 2, column: 3 },
Token { token_type: TokenType::EOF, line: 2, column: 4 },
Token {
token_type: TokenType::IDENTIFIER("x".to_string()),
line: 1,
column: 1,
},
Token {
token_type: TokenType::NEWLINE,
line: 1,
column: 2,
},
Token {
token_type: TokenType::PLUS,
line: 2,
column: 1,
},
Token {
token_type: TokenType::NUMBER(1),
line: 2,
column: 3,
},
Token {
token_type: TokenType::EOF,
line: 2,
column: 4,
},
];
let mut cursor = TokenCursor::new(&tokens);

View File

@ -1,7 +1,7 @@
//! Header parsing: `Name<T...> from Parent1, Parent2`
//! Assumes the caller already consumed the leading `box` token.
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
/// Parse the leading header of a box declaration and return

View File

@ -1,7 +1,7 @@
//! Interface box parser: `interface box Name { methods... }`
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;

View File

@ -1,6 +1,6 @@
//! Shared helpers for members parsing (scaffold)
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
#[derive(Debug, Clone, PartialEq, Eq)]
@ -23,10 +23,14 @@ pub(crate) fn classify_member(p: &mut NyashParser) -> Result<MemberKind, ParseEr
// Constructors by keyword or name
match &p.current_token().token_type {
TokenType::PACK | TokenType::BIRTH => {
if p.peek_token() == &TokenType::LPAREN { return Ok(MemberKind::Constructor); }
if p.peek_token() == &TokenType::LPAREN {
return Ok(MemberKind::Constructor);
}
}
TokenType::IDENTIFIER(name) if (name == "init" || name == "birth" || name == "pack")
&& p.peek_token() == &TokenType::LPAREN => {
TokenType::IDENTIFIER(name)
if (name == "init" || name == "birth" || name == "pack")
&& p.peek_token() == &TokenType::LPAREN =>
{
return Ok(MemberKind::Constructor);
}
_ => {}

View File

@ -1,7 +1,7 @@
//! Constructors parsing (init/pack/birth)
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
/// Try to parse a constructor at current position.
@ -66,7 +66,12 @@ pub(crate) fn try_parse_constructor(
} else {
None
};
body = vec![ASTNode::TryCatch { try_body: body, catch_clauses, finally_body, span: Span::unknown() }];
body = vec![ASTNode::TryCatch {
try_body: body,
catch_clauses,
finally_body,
span: Span::unknown(),
}];
}
let node = ASTNode::FunctionDeclaration {
name: name.clone(),

View File

@ -1,7 +1,7 @@
//! Fields parsing (header-first: `name: Type` + unified members gates)
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;
@ -25,7 +25,7 @@ pub(crate) fn try_parse_header_first_field_or_property(
return Ok(true);
}
p.advance(); // consume ':'
// Optional type name (identifier). For now we accept and ignore.
// Optional type name (identifier). For now we accept and ignore.
if let TokenType::IDENTIFIER(_ty) = &p.current_token().token_type {
p.advance();
} else {
@ -65,7 +65,10 @@ pub(crate) fn try_parse_header_first_field_or_property(
// name: Type { ... } [postfix]
if p.match_token(&TokenType::LBRACE) {
let body = p.parse_block_statements()?;
let body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, body)?;
let body =
crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(
p, body,
)?;
let getter_name = format!("__get_{}", fname);
let method = ASTNode::FunctionDeclaration {
name: getter_name.clone(),
@ -108,10 +111,16 @@ pub(crate) fn try_parse_visibility_block_or_single(
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()); }
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(); }
if p.match_token(&TokenType::COMMA) {
p.advance();
}
continue;
}
return Err(ParseError::UnexpectedToken {
@ -127,11 +136,19 @@ pub(crate) fn try_parse_visibility_block_or_single(
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()); }
if visibility == "public" {
public_fields.push(fname.clone());
} else {
private_fields.push(fname.clone());
}
*last_method_name = None;
return Ok(true);
} else {
if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
if visibility == "public" {
public_fields.push(fname.clone());
} else {
private_fields.push(fname.clone());
}
fields.push(fname);
return Ok(true);
}
@ -167,10 +184,17 @@ pub(crate) fn parse_init_block_if_any(
weak_fields.push(field_name.clone());
}
p.advance();
if p.match_token(&TokenType::COMMA) { 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(),
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,
});

View File

@ -1,7 +1,7 @@
//! Methods parsing (name(params){ body }) with special birth() prologue
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
/// Try to parse a method declaration starting at `method_name` (already consumed identifier).
@ -35,7 +35,9 @@ pub(crate) fn try_parse_method(
if method_name == "birth" && !birth_once_props.is_empty() {
let mut injected: Vec<ASTNode> = Vec::new();
for pprop in birth_once_props.iter() {
let me_node = ASTNode::Me { span: Span::unknown() };
let me_node = ASTNode::Me {
span: Span::unknown(),
};
let compute_call = ASTNode::MethodCall {
object: Box::new(me_node.clone()),
method: format!("__compute_birth_{}", pprop),
@ -56,7 +58,10 @@ pub(crate) fn try_parse_method(
value: crate::ast::LiteralValue::String(format!("__birth_{}", pprop)),
span: Span::unknown(),
},
ASTNode::Variable { name: tmp, span: Span::unknown() },
ASTNode::Variable {
name: tmp,
span: Span::unknown(),
},
],
span: Span::unknown(),
};

View File

@ -1,7 +1,6 @@
pub mod common;
pub mod constructors;
pub mod fields;
pub mod methods;
pub mod constructors;
pub mod properties;
pub mod postfix;
pub mod properties;

View File

@ -1,7 +1,7 @@
//! Postfix handlers (catch/cleanup) utilities for unified members
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;
@ -60,7 +60,9 @@ pub(crate) fn try_parse_method_postfix_after_last_method(
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() {
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();
@ -94,7 +96,9 @@ pub(crate) fn try_parse_method_postfix_after_last_method(
};
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 { .. }));
let already = body
.iter()
.any(|n| matches!(n, crate::ast::ASTNode::TryCatch { .. }));
if already {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {

View File

@ -1,11 +1,10 @@
//! Properties parsing (once/birth_once, header-first)
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
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(crate) fn try_parse_unified_property(
@ -52,12 +51,18 @@ pub(crate) fn try_parse_unified_property(
let orig_body: Vec<ASTNode> = if p.match_token(&TokenType::FatArrow) {
p.advance(); // consume '=>'
let expr = p.parse_expression()?;
vec![ASTNode::Return { value: Some(Box::new(expr)), span: Span::unknown() }]
vec![ASTNode::Return {
value: Some(Box::new(expr)),
span: Span::unknown(),
}]
} else {
p.parse_block_statements()?
};
// Optional postfix handlers (Stage-3) directly after body
let final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, orig_body)?;
let final_body =
crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(
p, orig_body,
)?;
if kind_kw == "once" {
// once: synthesize compute + getter with poison/cache
let compute_name = format!("__compute_once_{}", name);
@ -76,43 +81,182 @@ pub(crate) fn try_parse_unified_property(
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 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() }],
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 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() };
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() };
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 me_node = ASTNode::Me {
span: Span::unknown(),
};
// getter: me.getField("__birth_name")
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 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() };
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)
}
@ -194,7 +338,10 @@ pub(crate) fn try_parse_block_first_property(
}
// 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)?;
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" {
@ -216,31 +363,143 @@ pub(crate) fn try_parse_block_first_property(
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 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() }],
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 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 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 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() };
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);
}
@ -248,13 +507,40 @@ pub(crate) fn try_parse_block_first_property(
// 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() };
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 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() };
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

@ -4,15 +4,15 @@
//! Nyashの中核概念「Everything is Box」を実現する重要モジュール
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;
pub mod header;
pub mod interface;
pub mod members;
pub mod validators;
pub mod interface;
/// Thin wrappers to keep the main loop tidy (behavior-preserving)
fn box_try_block_first_property(
@ -20,9 +20,7 @@ fn box_try_block_first_property(
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,
)
members::properties::try_parse_block_first_property(p, methods, birth_once_props)
}
fn box_try_method_postfix_after_last(
@ -30,9 +28,7 @@ fn box_try_method_postfix_after_last(
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,
)
members::postfix::try_parse_method_postfix_after_last_method(p, methods, last_method_name)
}
fn box_try_init_block(
@ -40,9 +36,7 @@ fn box_try_init_block(
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,
)
members::fields::parse_init_block_if_any(p, init_fields, weak_fields)
}
fn box_try_constructor(
@ -88,23 +82,15 @@ fn box_try_method_or_field(
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,
)? {
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,
)
members::fields::try_parse_header_first_field_or_property(p, name, methods, fields)
}
/// box宣言をパース: box Name { fields... methods... }
@ -118,8 +104,7 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError>
});
}
p.advance(); // consume BOX or FLOW
let (name, type_parameters, extends, implements) =
header::parse_header(p)?;
let (name, type_parameters, extends, implements) = header::parse_header(p)?;
p.consume(TokenType::LBRACE)?;
@ -130,7 +115,7 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError>
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()
// 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;
@ -143,10 +128,14 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError>
}
// nyashモードblock-first: { body } as (once|birth_once)? name : Type
if box_try_block_first_property(p, &mut methods, &mut birth_once_props)? { continue; }
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; }
if box_try_method_postfix_after_last(p, &mut methods, &last_method_name)? {
continue;
}
// RBRACEに到達していればループを抜ける
if p.match_token(&TokenType::RBRACE) {
@ -154,7 +143,9 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError>
}
// initブロックの処理initメソッドではない場合のみ
if box_try_init_block(p, &mut init_fields, &mut weak_fields)? { continue; }
if box_try_init_block(p, &mut init_fields, &mut weak_fields)? {
continue;
}
// overrideキーワードをチェック
let mut is_override = false;
@ -164,7 +155,9 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError>
}
// constructor parsing moved to members::constructors
if box_try_constructor(p, is_override, &mut constructors)? { continue; }
if box_try_constructor(p, is_override, &mut constructors)? {
continue;
}
// 🚨 birth()統一システム: Box名コンストラクタ無効化
validators::forbid_box_named_constructor(p, &name)?;
@ -183,10 +176,14 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError>
&mut public_fields,
&mut private_fields,
&mut last_method_name,
)? { continue; }
)? {
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::config::env::unified_members()
&& (field_or_method == "once" || field_or_method == "birth_once")
{
if members::properties::try_parse_unified_property(
p,
&field_or_method,
@ -207,7 +204,9 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result<ASTNode, ParseError>
&mut fields,
&birth_once_props,
&mut last_method_name,
)? { continue; }
)? {
continue;
}
} else {
return Err(ParseError::UnexpectedToken {
expected: "method or field name".to_string(),

View File

@ -1,7 +1,7 @@
//! Validators and light analysis for box members
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::{HashMap, HashSet};
@ -67,11 +67,17 @@ pub(crate) fn validate_birth_once_cycles(
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 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; }
if has_cycle(n, deps, temp, perm) {
return true;
}
}
}
temp.remove(node);
@ -94,7 +100,10 @@ pub(crate) fn validate_birth_once_cycles(
}
/// 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> {
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 {
@ -122,14 +131,29 @@ fn ast_collect_me_fields(nodes: &Vec<ASTNode>) -> std::collections::HashSet<Stri
}
}
ASTNode::Program { statements, .. } => scan(statements, out),
ASTNode::If { then_body, else_body, .. } => {
ASTNode::If {
then_body,
else_body,
..
} => {
scan(then_body, out);
if let Some(eb) = else_body { scan(eb, out); }
if let Some(eb) = else_body {
scan(eb, out);
}
}
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
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); }
for c in catch_clauses {
scan(&c.body, out);
}
if let Some(fb) = finally_body {
scan(fb, out);
}
}
ASTNode::FunctionDeclaration { body, .. } => scan(body, out),
_ => {}

View File

@ -1,6 +1,6 @@
//! Header parsing for `static box Name<T...> from Parent1, ... [interface ...]` (staged)
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
/// Parse the leading header of a static box and return
@ -30,7 +30,11 @@ pub(crate) fn parse_static_header(
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; }
if p.match_token(&TokenType::COMMA) {
p.advance();
} else {
break;
}
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
@ -42,7 +46,9 @@ pub(crate) fn parse_static_header(
}
p.consume(TokenType::GREATER)?;
params
} else { Vec::new() };
} else {
Vec::new()
};
// extends: from Parent1, Parent2
let extends = if p.match_token(&TokenType::FROM) {
@ -52,7 +58,11 @@ pub(crate) fn parse_static_header(
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; }
if p.match_token(&TokenType::COMMA) {
p.advance();
} else {
break;
}
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
@ -63,7 +73,9 @@ pub(crate) fn parse_static_header(
}
}
parents
} else { Vec::new() };
} else {
Vec::new()
};
// implements: `interface A, B` (optional)
let implements = if p.match_token(&TokenType::INTERFACE) {
@ -73,7 +85,11 @@ pub(crate) fn parse_static_header(
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; }
if p.match_token(&TokenType::COMMA) {
p.advance();
} else {
break;
}
} else {
let line = p.current_token().line;
return Err(ParseError::UnexpectedToken {
@ -84,8 +100,9 @@ pub(crate) fn parse_static_header(
}
}
list
} else { Vec::new() };
} else {
Vec::new()
};
Ok((name, type_parameters, extends, implements))
}

View File

@ -1,7 +1,7 @@
//! Members helpers for static box (staged)
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;
@ -10,8 +10,13 @@ use std::collections::HashMap;
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 !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 => {
@ -61,18 +66,26 @@ pub(crate) fn try_parse_method_or_field(
if !p.match_token(&TokenType::LPAREN) {
// Lookahead skipping NEWLINE to see if a '(' follows → treat as method head
let mut k = 0usize;
while matches!(p.peek_nth_token(k), TokenType::NEWLINE) { k += 1; }
while matches!(p.peek_nth_token(k), TokenType::NEWLINE) {
k += 1;
}
if matches!(p.peek_nth_token(k), TokenType::LPAREN) {
// Consume intervening NEWLINEs so current becomes '('
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
while p.match_token(&TokenType::NEWLINE) {
p.advance();
}
} else {
if trace { eprintln!("[parser][static-box] field detected: {}", name); }
if trace {
eprintln!("[parser][static-box] field detected: {}", name);
}
// Field
fields.push(name);
return Ok(true);
}
}
if trace { eprintln!("[parser][static-box] method head detected: {}(..)", name); }
if trace {
eprintln!("[parser][static-box] method head detected: {}(..)", name);
}
// Method
p.advance(); // consume '('
let mut params = Vec::new();
@ -95,7 +108,11 @@ pub(crate) fn try_parse_method_or_field(
}
_ => {
// Unexpected token handling
if std::env::var("NYASH_PARSER_METHOD_PARAM_STRICT").ok().as_deref() == Some("1") {
if std::env::var("NYASH_PARSER_METHOD_PARAM_STRICT")
.ok()
.as_deref()
== Some("1")
{
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: "parameter identifier, comma, or )".to_string(),
@ -110,9 +127,15 @@ pub(crate) fn try_parse_method_or_field(
}
p.consume(TokenType::RPAREN)?;
// Allow NEWLINE(s) between ')' and '{' of method body
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
while p.match_token(&TokenType::NEWLINE) {
p.advance();
}
// Parse method body; optionally use strict method-body guard when enabled
let body = if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT").ok().as_deref() == Some("1") {
let body = if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT")
.ok()
.as_deref()
== Some("1")
{
p.parse_method_body_statements()?
} else {
p.parse_block_statements()?

View File

@ -2,8 +2,8 @@
#![allow(dead_code)]
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
use std::collections::HashMap;
@ -14,8 +14,7 @@ pub mod validators;
/// 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)?;
let (name, type_parameters, extends, implements) = header::parse_static_header(p)?;
p.consume(TokenType::LBRACE)?;
@ -30,7 +29,9 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
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(); }
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!(
@ -56,7 +57,11 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
} 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_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)");
}
@ -66,14 +71,23 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
// initブロックの処理共通ヘルパに委譲
if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
p, &mut init_fields, &mut weak_fields,
)? { continue; }
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 => {
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);
@ -87,13 +101,15 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
// 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; }
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");
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!(
@ -120,7 +136,11 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
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,
p,
field_or_method,
&mut methods,
&mut fields,
&mut last_method_name,
)?;
}
_ => {
@ -134,7 +154,9 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
}
// Tolerate trailing NEWLINE(s) before the closing '}' of the static box
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
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={:?}",
@ -146,14 +168,16 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
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);
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);
p.static_box_dependencies.insert(name.clone(), dependencies);
} else {
p.static_box_dependencies
.insert(name.clone(), std::collections::HashSet::new());

View File

@ -7,6 +7,7 @@ pub(crate) struct StaticValidators;
impl StaticValidators {
#[allow(dead_code)]
pub(crate) fn validate(_p: &mut NyashParser) -> Result<(), ParseError> { Ok(()) }
pub(crate) fn validate(_p: &mut NyashParser) -> Result<(), ParseError> {
Ok(())
}
}

View File

@ -8,13 +8,22 @@ impl NyashParser {
/// MVP: リテラルパターンORデフォルト(_) のみ。アーム本体は式またはブロック。
pub(crate) fn expr_parse_match(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'match'
// Scrutinee: 通常の式を受理(演算子優先順位を含む)
// Scrutinee: 通常の式を受理(演算子優先順位を含む)
let scrutinee = self.parse_expression()?;
self.consume(TokenType::LBRACE)?;
enum MatchArm {
Lit { lits: Vec<LiteralValue>, guard: Option<ASTNode>, body: ASTNode },
Type { ty: String, bind: String, guard: Option<ASTNode>, body: ASTNode },
Lit {
lits: Vec<LiteralValue>,
guard: Option<ASTNode>,
body: ASTNode,
},
Type {
ty: String,
bind: String,
guard: Option<ASTNode>,
body: ASTNode,
},
Default,
}
@ -34,10 +43,11 @@ impl NyashParser {
}
// default '_' or type/literal arm
let is_default = matches!(self.current_token().token_type, TokenType::IDENTIFIER(ref s) if s == "_");
let is_default =
matches!(self.current_token().token_type, TokenType::IDENTIFIER(ref s) if s == "_");
if is_default {
self.advance(); // consume '_'
// MVP: default '_' does not accept guard
// MVP: default '_' does not accept guard
if self.match_token(&TokenType::IF) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
@ -106,7 +116,9 @@ impl NyashParser {
let g = self.parse_expression()?;
saw_guard = true;
Some(g)
} else { None };
} else {
None
};
self.consume(TokenType::FatArrow)?;
let body = if self.match_token(&TokenType::LBRACE) {
if self.is_object_literal() {
@ -123,14 +135,22 @@ impl NyashParser {
}
}
self.consume(TokenType::RBRACE)?;
ASTNode::Program { statements: stmts, span: Span::unknown() }
ASTNode::Program {
statements: stmts,
span: Span::unknown(),
}
}
} else {
// 値アームは通常の式全体を受理
self.parse_expression()?
};
// type arm parsed
arms_any.push(MatchArm::Type { ty, bind, guard, body });
arms_any.push(MatchArm::Type {
ty,
bind,
guard,
body,
});
saw_type_arm = true;
handled = true;
}
@ -151,7 +171,9 @@ impl NyashParser {
let g = self.parse_expression()?;
saw_guard = true;
Some(g)
} else { None };
} else {
None
};
self.consume(TokenType::FatArrow)?;
let expr = if self.match_token(&TokenType::LBRACE) {
if self.is_object_literal() {
@ -168,13 +190,20 @@ impl NyashParser {
}
}
self.consume(TokenType::RBRACE)?;
ASTNode::Program { statements: stmts, span: Span::unknown() }
ASTNode::Program {
statements: stmts,
span: Span::unknown(),
}
}
} else {
// 値アームは通常の式全体を受理
self.parse_expression()?
};
arms_any.push(MatchArm::Lit { lits, guard, body: expr });
arms_any.push(MatchArm::Lit {
lits,
guard,
body: expr,
});
}
}
@ -197,7 +226,11 @@ impl NyashParser {
let mut lit_arms: Vec<(LiteralValue, ASTNode)> = Vec::new();
for arm in arms_any.into_iter() {
match arm {
MatchArm::Lit { lits, guard: _, body } => {
MatchArm::Lit {
lits,
guard: _,
body,
} => {
for lit in lits.into_iter() {
lit_arms.push((lit, body.clone()));
}
@ -226,7 +259,10 @@ impl NyashParser {
// 2) アーム順に If 連鎖を構築
let mut else_node: ASTNode = else_expr;
// Wrap else body in Program for uniformity
else_node = ASTNode::Program { statements: vec![else_node], span: Span::unknown() };
else_node = ASTNode::Program {
statements: vec![else_node],
span: Span::unknown(),
};
// Process arms in reverse to build nested If
for arm in arms_any.into_iter().rev() {
@ -240,8 +276,14 @@ impl NyashParser {
for lit in lits.into_iter() {
let eq = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }),
right: Box::new(ASTNode::Literal { value: lit, span: Span::unknown() }),
left: Box::new(ASTNode::Variable {
name: scr_var.clone(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: lit,
span: Span::unknown(),
}),
span: Span::unknown(),
};
cond = Some(match cond {
@ -254,7 +296,10 @@ impl NyashParser {
},
});
}
let else_statements = match else_node.clone() { ASTNode::Program { statements, .. } => statements, other => vec![other] };
let else_statements = match else_node.clone() {
ASTNode::Program { statements, .. } => statements,
other => vec![other],
};
let then_body_statements = if let Some(g) = guard {
// Nested guard: if g then body else else_node
let guard_if = ASTNode::If {
@ -268,25 +313,44 @@ impl NyashParser {
vec![body]
};
else_node = ASTNode::If {
condition: Box::new(cond.expect("literal arm must have at least one literal")),
condition: Box::new(
cond.expect("literal arm must have at least one literal"),
),
then_body: then_body_statements,
else_body: Some(else_statements),
span: Span::unknown(),
};
}
MatchArm::Type { ty, bind, guard, body } => {
MatchArm::Type {
ty,
bind,
guard,
body,
} => {
// condition: scr.is("Type")
let is_call = ASTNode::MethodCall {
object: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }),
object: Box::new(ASTNode::Variable {
name: scr_var.clone(),
span: Span::unknown(),
}),
method: "is".to_string(),
arguments: vec![ASTNode::Literal { value: LiteralValue::String(ty.clone()), span: Span::unknown() }],
arguments: vec![ASTNode::Literal {
value: LiteralValue::String(ty.clone()),
span: Span::unknown(),
}],
span: Span::unknown(),
};
// then: local bind = scr.as("Type"); <body>
let cast = ASTNode::MethodCall {
object: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }),
object: Box::new(ASTNode::Variable {
name: scr_var.clone(),
span: Span::unknown(),
}),
method: "as".to_string(),
arguments: vec![ASTNode::Literal { value: LiteralValue::String(ty.clone()), span: Span::unknown() }],
arguments: vec![ASTNode::Literal {
value: LiteralValue::String(ty.clone()),
span: Span::unknown(),
}],
span: Span::unknown(),
};
let bind_local = ASTNode::Local {
@ -294,7 +358,10 @@ impl NyashParser {
initial_values: vec![Some(Box::new(cast))],
span: Span::unknown(),
};
let else_statements = match else_node.clone() { ASTNode::Program { statements, .. } => statements, other => vec![other] };
let else_statements = match else_node.clone() {
ASTNode::Program { statements, .. } => statements,
other => vec![other],
};
let then_body_statements = if let Some(g) = guard {
// After binding, check guard then branch to body else fallthrough to else_node
let guard_if = ASTNode::If {
@ -344,7 +411,7 @@ impl NyashParser {
}
matches!(self.peek_nth_token(lookahead_idx), TokenType::COLON)
}
_ => false
_ => false,
}
}

View File

@ -4,9 +4,9 @@ pub(crate) mod coalesce;
pub(crate) mod compare;
pub(crate) mod factor;
pub(crate) mod logic;
pub(crate) mod match_expr;
pub(crate) mod primary;
pub(crate) mod range;
pub(crate) mod shift;
pub(crate) mod term;
pub(crate) mod ternary;
pub(crate) mod match_expr;

View File

@ -46,8 +46,8 @@ impl NyashParser {
// デフォルトでIDENTIFIERキーを許可basicが明示的に指定された場合のみ無効
let ident_key_on = std::env::var("NYASH_ENABLE_MAP_IDENT_KEY").ok().as_deref()
== Some("1")
|| sugar_level.as_deref() != Some("basic"); // basic以外は全て許可デフォルト含む
// skip_newlines削除: brace_depth > 0なので自動スキップされる
|| sugar_level.as_deref() != Some("basic"); // basic以外は全て許可デフォルト含む
// skip_newlines削除: brace_depth > 0なので自動スキップされる
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
// skip_newlines削除: brace_depth > 0なので自動スキップされる
let key = match &self.current_token().token_type {

View File

@ -1,8 +1,8 @@
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::parser::cursor::TokenCursor;
use crate::parser::sugar_gate;
use crate::parser::ParseError;
use crate::tokenizer::TokenType;
use crate::parser::sugar_gate;
/// TokenCursorを使用した式パーサー実験的実装
pub struct ExprParserWithCursor;
@ -11,9 +11,7 @@ impl ExprParserWithCursor {
/// 式をパースTokenCursor版
pub fn parse_expression(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
// 式モードで実行(改行を自動的にスキップ)
cursor.with_expr_mode(|c| {
Self::parse_or_expr(c)
})
cursor.with_expr_mode(|c| Self::parse_or_expr(c))
}
/// OR式をパース
@ -300,7 +298,10 @@ impl ExprParserWithCursor {
}
}
cursor.consume(TokenType::RBRACK)?;
Ok(ASTNode::ArrayLiteral { elements, span: Span::unknown() })
Ok(ASTNode::ArrayLiteral {
elements,
span: Span::unknown(),
})
}
TokenType::NUMBER(n) => {
let value = *n;
@ -409,10 +410,17 @@ impl ExprParserWithCursor {
while !cursor.match_token(&TokenType::RPAREN) && !cursor.is_at_end() {
let arg = Self::parse_expression(cursor)?;
arguments.push(arg);
if cursor.match_token(&TokenType::COMMA) { cursor.advance(); }
if cursor.match_token(&TokenType::COMMA) {
cursor.advance();
}
}
cursor.consume(TokenType::RPAREN)?;
Ok(ASTNode::New { class, arguments, type_arguments, span: Span::unknown() })
Ok(ASTNode::New {
class,
arguments,
type_arguments,
span: Span::unknown(),
})
}
_ => {
let line = cursor.current().line;

View File

@ -9,9 +9,9 @@
use super::common::ParserUtils;
use super::{NyashParser, ParseError};
use crate::ast::{ASTNode, Span, UnaryOperator};
use crate::tokenizer::TokenType;
use crate::parser::cursor::TokenCursor;
use crate::parser::expr_cursor::ExprParserWithCursor;
use crate::tokenizer::TokenType;
// Debug macros are now imported from the parent module via #[macro_export]
use crate::must_advance;

View File

@ -182,31 +182,76 @@ impl NyashParser {
while let Some(c) = it.next() {
if in_line {
out.push(c);
if c == '\n' { in_line = false; }
if c == '\n' {
in_line = false;
}
continue;
}
if in_block {
out.push(c);
if c == '*' && matches!(it.peek(), Some('/')) { out.push('/'); it.next(); in_block = false; }
if c == '*' && matches!(it.peek(), Some('/')) {
out.push('/');
it.next();
in_block = false;
}
continue;
}
if in_str {
out.push(c);
if c == '\\' { if let Some(nc) = it.next() { out.push(nc); } continue; }
if c == '"' { in_str = false; }
if c == '\\' {
if let Some(nc) = it.next() {
out.push(nc);
}
continue;
}
if c == '"' {
in_str = false;
}
continue;
}
match c {
'"' => { in_str = true; out.push(c); }
'/' => {
match it.peek() { Some('/') => { out.push('/'); out.push('/'); it.next(); in_line = true; }, Some('*') => { out.push('/'); out.push('*'); it.next(); in_block = true; }, _ => out.push('/') }
'"' => {
in_str = true;
out.push(c);
}
'/' => match it.peek() {
Some('/') => {
out.push('/');
out.push('/');
it.next();
in_line = true;
}
Some('*') => {
out.push('/');
out.push('*');
it.next();
in_block = true;
}
_ => out.push('/'),
},
'#' => {
in_line = true;
out.push('#');
}
'#' => { in_line = true; out.push('#'); }
'|' => {
if matches!(it.peek(), Some('|')) { out.push_str(" or "); it.next(); } else if matches!(it.peek(), Some('>')) { out.push('|'); out.push('>'); it.next(); } else { out.push('|'); }
if matches!(it.peek(), Some('|')) {
out.push_str(" or ");
it.next();
} else if matches!(it.peek(), Some('>')) {
out.push('|');
out.push('>');
it.next();
} else {
out.push('|');
}
}
'&' => {
if matches!(it.peek(), Some('&')) { out.push_str(" and "); it.next(); } else { out.push('&'); }
if matches!(it.peek(), Some('&')) {
out.push_str(" and ");
it.next();
} else {
out.push('&');
}
}
_ => out.push(c),
}
@ -247,10 +292,13 @@ impl NyashParser {
let mut statements = Vec::new();
let mut _statement_count = 0;
let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok().map(|v| {
let lv = v.to_ascii_lowercase();
!(lv == "0" || lv == "false" || lv == "off")
}).unwrap_or(true);
let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON")
.ok()
.map(|v| {
let lv = v.to_ascii_lowercase();
!(lv == "0" || lv == "false" || lv == "off")
})
.unwrap_or(true);
while !self.is_at_end() {
// EOF tokenはスキップ
@ -293,13 +341,13 @@ impl NyashParser {
// 左辺が代入可能な形式かチェック
match &expr {
ASTNode::Variable { .. }
| ASTNode::FieldAccess { .. }
| ASTNode::Index { .. } => Ok(ASTNode::Assignment {
target: Box::new(expr),
value,
span: Span::unknown(),
}),
ASTNode::Variable { .. } | ASTNode::FieldAccess { .. } | ASTNode::Index { .. } => {
Ok(ASTNode::Assignment {
target: Box::new(expr),
value,
span: Span::unknown(),
})
}
_ => {
let line = self.current_token().line;
Err(ParseError::InvalidStatement { line })
@ -365,9 +413,17 @@ impl NyashParser {
// ---- Minimal ParserUtils impl (depth-less; TokenCursor handles newline policy) ----
impl common::ParserUtils for NyashParser {
fn tokens(&self) -> &Vec<Token> { &self.tokens }
fn current(&self) -> usize { self.current }
fn current_mut(&mut self) -> &mut usize { &mut self.current }
fn update_depth_before_advance(&mut self) { /* no-op (legacy removed) */ }
fn update_depth_after_advance(&mut self) { /* no-op (legacy removed) */ }
fn tokens(&self) -> &Vec<Token> {
&self.tokens
}
fn current(&self) -> usize {
self.current
}
fn current_mut(&mut self) -> &mut usize {
&mut self.current
}
fn update_depth_before_advance(&mut self) { /* no-op (legacy removed) */
}
fn update_depth_after_advance(&mut self) { /* no-op (legacy removed) */
}
}

View File

@ -9,9 +9,9 @@
*/
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::cursor::TokenCursor;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {

View File

@ -6,8 +6,8 @@
*/
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {
@ -17,7 +17,9 @@ impl NyashParser {
TokenType::BOX => crate::parser::declarations::box_def::parse_box_declaration(self),
TokenType::FLOW => crate::parser::declarations::box_def::parse_box_declaration(self), // flow is syntactic sugar for static box
TokenType::IMPORT => self.parse_import(),
TokenType::INTERFACE => crate::parser::declarations::box_def::parse_interface_box_declaration(self),
TokenType::INTERFACE => {
crate::parser::declarations::box_def::parse_interface_box_declaration(self)
}
TokenType::GLOBAL => self.parse_global_var(),
TokenType::FUNCTION => self.parse_function_declaration(),
TokenType::STATIC => self.parse_static_declaration(),
@ -28,4 +30,4 @@ impl NyashParser {
}),
}
}
}
}

View File

@ -8,8 +8,8 @@
*/
use crate::ast::{ASTNode, CatchClause, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {
@ -76,7 +76,9 @@ impl NyashParser {
}
/// Parse catch parameter: (ExceptionType varName) or (varName) or ()
pub(crate) fn parse_catch_param(&mut self) -> Result<(Option<String>, Option<String>), ParseError> {
pub(crate) fn parse_catch_param(
&mut self,
) -> Result<(Option<String>, Option<String>), ParseError> {
match &self.current_token().token_type {
TokenType::IDENTIFIER(first) => {
let first_str = first.clone();
@ -121,4 +123,4 @@ impl NyashParser {
line: self.current_token().line,
})
}
}
}

View File

@ -5,9 +5,9 @@
*/
use crate::ast::ASTNode;
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::cursor::TokenCursor;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
/// Check if token cursor is enabled
@ -16,7 +16,6 @@ pub(super) fn cursor_enabled() -> bool {
}
impl NyashParser {
/// Thin adapter: when Cursor route is enabled, align statement start position
/// by letting TokenCursor apply its statement-mode newline policy
pub(super) fn with_stmt_cursor<F>(&mut self, f: F) -> Result<ASTNode, ParseError>

View File

@ -7,9 +7,9 @@
*/
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::cursor::TokenCursor;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {
@ -71,4 +71,4 @@ impl NyashParser {
span: Span::unknown(),
})
}
}
}

View File

@ -27,8 +27,8 @@ pub mod exceptions;
pub mod modules;
use crate::ast::{ASTNode, CatchClause, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {
@ -94,14 +94,20 @@ impl NyashParser {
// Critical: Skip any leading NEWLINE tokens immediately after '{'
// This ensures the first statement starts at the correct position
while self.match_token(&TokenType::NEWLINE) { self.advance(); }
while self.match_token(&TokenType::NEWLINE) {
self.advance();
}
let mut statements = Vec::new();
// Be tolerant to blank lines within blocks: skip NEWLINE tokens between statements
while !self.is_at_end() {
while self.match_token(&TokenType::NEWLINE) { self.advance(); }
if self.match_token(&TokenType::RBRACE) { break; }
while self.match_token(&TokenType::NEWLINE) {
self.advance();
}
if self.match_token(&TokenType::RBRACE) {
break;
}
statements.push(self.parse_statement()?);
}
if trace_blocks {
@ -137,21 +143,33 @@ impl NyashParser {
TokenType::IDENTIFIER(_) => {
// Expect '(' after optional NEWLINE
let mut k = 1usize;
while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { k += 1; }
if !matches!(this.peek_nth_token(k), TokenType::LPAREN) { return false; }
while matches!(this.peek_nth_token(k), TokenType::NEWLINE) {
k += 1;
}
if !matches!(this.peek_nth_token(k), TokenType::LPAREN) {
return false;
}
// Walk to matching ')'
k += 1; // after '('
let mut depth: i32 = 1;
while !matches!(this.peek_nth_token(k), TokenType::EOF) {
match this.peek_nth_token(k) {
TokenType::LPAREN => depth += 1,
TokenType::RPAREN => { depth -= 1; if depth == 0 { k += 1; break; } },
TokenType::RPAREN => {
depth -= 1;
if depth == 0 {
k += 1;
break;
}
}
_ => {}
}
k += 1;
}
// Allow NEWLINE(s) between ')' and '{'
while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { k += 1; }
while matches!(this.peek_nth_token(k), TokenType::NEWLINE) {
k += 1;
}
matches!(this.peek_nth_token(k), TokenType::LBRACE)
}
_ => false,
@ -160,14 +178,22 @@ impl NyashParser {
while !self.is_at_end() {
// Skip blank lines at method body top-level
while self.match_token(&TokenType::NEWLINE) { self.advance(); }
while self.match_token(&TokenType::NEWLINE) {
self.advance();
}
// Stop at end of current method body
if self.match_token(&TokenType::RBRACE) { break; }
if self.match_token(&TokenType::RBRACE) {
break;
}
// Optional seam guard: if the upcoming tokens form a method head
// like `ident '(' ... ')' NEWLINE* '{'`, bail out so the caller
// (static box member parser) can handle it as a declaration, not
// as a function call expression inside this body.
if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT").ok().as_deref() == Some("1") {
if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT")
.ok()
.as_deref()
== Some("1")
{
if looks_like_method_head(self) {
break;
}

View File

@ -8,8 +8,8 @@
*/
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {
@ -123,7 +123,10 @@ impl NyashParser {
}
}
Ok(ASTNode::UsingStatement { namespace_name: namespace, span: Span::unknown() })
Ok(ASTNode::UsingStatement {
namespace_name: namespace,
span: Span::unknown(),
})
}
/// Parse from statement: from Parent.method(args)

View File

@ -8,9 +8,9 @@
*/
use crate::ast::{ASTNode, Span};
use crate::parser::{NyashParser, ParseError};
use crate::parser::common::ParserUtils;
use crate::parser::cursor::TokenCursor;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {
@ -29,10 +29,14 @@ impl NyashParser {
/// Parse local variable declaration: local var1, var2, var3 or local x = 10
pub(super) fn parse_local(&mut self) -> Result<ASTNode, ParseError> {
let debug_parse_local = std::env::var("NYASH_DEBUG_PARSE_LOCAL").ok().as_deref() == Some("1");
let debug_parse_local =
std::env::var("NYASH_DEBUG_PARSE_LOCAL").ok().as_deref() == Some("1");
if debug_parse_local {
eprintln!("[parse_local] entry: current_token={:?} at line {}",
self.current_token().token_type, self.current_token().line);
eprintln!(
"[parse_local] entry: current_token={:?} at line {}",
self.current_token().token_type,
self.current_token().line
);
}
// Always skip leading NEWLINEs before consuming 'local' keyword
@ -54,8 +58,11 @@ impl NyashParser {
}
if debug_parse_local {
eprintln!("[parse_local] after advance: current_token={:?} at line {}",
self.current_token().token_type, self.current_token().line);
eprintln!(
"[parse_local] after advance: current_token={:?} at line {}",
self.current_token().token_type,
self.current_token().line
);
}
let mut names = Vec::new();
@ -107,8 +114,11 @@ impl NyashParser {
} else {
// Enhanced error message for debugging
if debug_parse_local {
eprintln!("[parse_local] ERROR: Expected IDENTIFIER, found {:?} at line {}",
self.current_token().token_type, self.current_token().line);
eprintln!(
"[parse_local] ERROR: Expected IDENTIFIER, found {:?} at line {}",
self.current_token().token_type,
self.current_token().line
);
eprintln!("[parse_local] ERROR: Previous 3 tokens:");
for i in 1..=3 {
if self.current >= i {
@ -168,4 +178,4 @@ impl NyashParser {
})
}
}
}
}