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:
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 (Stage‑3) 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)
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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),
|
||||
_ => {}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
|
||||
@ -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()?
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) */
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user