selfhost/runtime: Stage 0-1 runner + MIR JSON loader (summary) with trace; compiler: scopebox/loopform prepass wiring (flags, child args); libs: add P1 standard boxes (console/string/array/map) as thin wrappers; runner: pass --box-pref via env; ops_calls dispatcher skeleton; docs: selfhost executor roadmap + scopebox/loopform notes; smokes: selfhost runner + identity prepasses; CURRENT_TASK: update plan and box lib schedule
This commit is contained in:
@ -6,7 +6,7 @@ use crate::tokenizer::TokenType;
|
||||
|
||||
/// Parse the leading header of a box declaration and return
|
||||
/// (name, type_params, extends, implements). Does not consume the opening '{'.
|
||||
pub fn parse_header(
|
||||
pub(crate) fn parse_header(
|
||||
p: &mut NyashParser,
|
||||
) -> Result<(String, Vec<String>, Vec<String>, Vec<String>), ParseError> {
|
||||
// Name
|
||||
|
||||
107
src/parser/declarations/box_def/interface.rs
Normal file
107
src/parser/declarations/box_def/interface.rs
Normal file
@ -0,0 +1,107 @@
|
||||
//! Interface box parser: `interface box Name { methods... }`
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Parse `interface box Name { methods... }` and return an AST BoxDeclaration.
|
||||
/// Caller must be positioned at the beginning of `interface box`.
|
||||
pub(crate) fn parse_interface_box(p: &mut NyashParser) -> Result<ASTNode, ParseError> {
|
||||
p.consume(TokenType::INTERFACE)?;
|
||||
p.consume(TokenType::BOX)?;
|
||||
|
||||
let name = if let TokenType::IDENTIFIER(name) = &p.current_token().token_type {
|
||||
let name = name.clone();
|
||||
p.advance();
|
||||
name
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
p.consume(TokenType::LBRACE)?;
|
||||
p.skip_newlines(); // ブレース後の改行をスキップ
|
||||
|
||||
let mut methods = HashMap::new();
|
||||
|
||||
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
|
||||
p.skip_newlines(); // ループ開始時に改行をスキップ
|
||||
if let TokenType::IDENTIFIER(method_name) = &p.current_token().token_type {
|
||||
let method_name = method_name.clone();
|
||||
p.advance();
|
||||
|
||||
// インターフェースメソッドはシグネチャのみ
|
||||
if p.match_token(&TokenType::LPAREN) {
|
||||
p.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
}
|
||||
|
||||
if p.match_token(&TokenType::COMMA) {
|
||||
p.advance();
|
||||
}
|
||||
}
|
||||
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
|
||||
// インターフェースメソッドは実装なし(空のbody)
|
||||
let method_decl = ASTNode::FunctionDeclaration {
|
||||
name: method_name.clone(),
|
||||
params,
|
||||
body: vec![], // 空の実装
|
||||
is_static: false, // インターフェースメソッドは通常静的でない
|
||||
is_override: false, // デフォルトは非オーバーライド
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
methods.insert(method_name, method_decl);
|
||||
|
||||
// メソッド宣言後の改行をスキップ
|
||||
p.skip_newlines();
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "(".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "method name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
p.consume(TokenType::RBRACE)?;
|
||||
|
||||
Ok(ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields: vec![], // インターフェースはフィールドなし
|
||||
public_fields: vec![],
|
||||
private_fields: vec![],
|
||||
methods,
|
||||
constructors: HashMap::new(), // インターフェースにコンストラクタなし
|
||||
init_fields: vec![], // インターフェースにinitブロックなし
|
||||
weak_fields: vec![], // インターフェースにweak fieldsなし
|
||||
is_interface: true, // インターフェースフラグ
|
||||
extends: vec![], // Multi-delegation: None → vec![] として表現
|
||||
implements: vec![],
|
||||
type_parameters: Vec::new(), // インターフェースではジェネリクス未対応
|
||||
is_static: false, // インターフェースは非static
|
||||
static_init: None, // インターフェースにstatic initなし
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
@ -4,7 +4,7 @@ use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MemberKind {
|
||||
pub(crate) enum MemberKind {
|
||||
Field,
|
||||
Method,
|
||||
Constructor,
|
||||
@ -14,7 +14,7 @@ pub enum MemberKind {
|
||||
}
|
||||
|
||||
/// Decide member kind via simple lookahead (scaffold placeholder)
|
||||
pub fn classify_member(p: &mut NyashParser) -> Result<MemberKind, ParseError> {
|
||||
pub(crate) fn classify_member(p: &mut NyashParser) -> Result<MemberKind, ParseError> {
|
||||
// block-first: { body } as (once|birth_once)? name : Type
|
||||
if crate::config::env::unified_members() && p.match_any_token(&[TokenType::LBRACE]) {
|
||||
return Ok(MemberKind::PropertyComputed);
|
||||
|
||||
@ -7,7 +7,7 @@ use crate::tokenizer::TokenType;
|
||||
/// Try to parse a constructor at current position.
|
||||
/// Supported: `init(...) {}`, `pack(...) {}`, `birth(...) {}`.
|
||||
/// Returns Ok(Some((key, node))) when a constructor was parsed and consumed.
|
||||
pub fn try_parse_constructor(
|
||||
pub(crate) fn try_parse_constructor(
|
||||
p: &mut NyashParser,
|
||||
is_override: bool,
|
||||
) -> Result<Option<(String, ASTNode)>, ParseError> {
|
||||
|
||||
@ -12,7 +12,7 @@ use std::collections::HashMap;
|
||||
/// - `name: Type => expr` → computed property (getter function generated)
|
||||
/// - `name: Type { ... } [catch|cleanup]` → computed property block with optional postfix handlers
|
||||
/// Returns Ok(true) when this function consumed and handled the construct; Ok(false) if not applicable.
|
||||
pub fn try_parse_header_first_field_or_property(
|
||||
pub(crate) fn try_parse_header_first_field_or_property(
|
||||
p: &mut NyashParser,
|
||||
fname: String,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
@ -87,3 +87,104 @@ pub fn try_parse_header_first_field_or_property(
|
||||
fields.push(fname);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Parse a visibility block or a single header-first property with visibility prefix.
|
||||
/// Handles:
|
||||
/// - `public { a, b, c }` → pushes to fields/public_fields
|
||||
/// - `private { ... }` → pushes to fields/private_fields
|
||||
/// - `public name: Type ...` (delegates to header-first field/property)
|
||||
/// Returns Ok(true) if consumed, Ok(false) if visibility keyword not matched.
|
||||
pub(crate) fn try_parse_visibility_block_or_single(
|
||||
p: &mut NyashParser,
|
||||
visibility: &str,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
fields: &mut Vec<String>,
|
||||
public_fields: &mut Vec<String>,
|
||||
private_fields: &mut Vec<String>,
|
||||
last_method_name: &mut Option<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if visibility != "public" && visibility != "private" {
|
||||
return Ok(false);
|
||||
}
|
||||
if p.match_token(&TokenType::LBRACE) {
|
||||
p.advance();
|
||||
p.skip_newlines();
|
||||
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
|
||||
if let TokenType::IDENTIFIER(fname) = &p.current_token().token_type {
|
||||
let fname = fname.clone();
|
||||
if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
|
||||
fields.push(fname);
|
||||
p.advance();
|
||||
if p.match_token(&TokenType::COMMA) { p.advance(); }
|
||||
p.skip_newlines();
|
||||
continue;
|
||||
}
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "identifier in visibility block".to_string(),
|
||||
found: p.current_token().token_type.clone(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
p.consume(TokenType::RBRACE)?;
|
||||
p.skip_newlines();
|
||||
return Ok(true);
|
||||
}
|
||||
if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
|
||||
let fname = n.clone();
|
||||
p.advance();
|
||||
if try_parse_header_first_field_or_property(p, fname.clone(), methods, fields)? {
|
||||
if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
|
||||
*last_method_name = None;
|
||||
p.skip_newlines();
|
||||
return Ok(true);
|
||||
} else {
|
||||
if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
|
||||
fields.push(fname);
|
||||
p.skip_newlines();
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Parse `init { ... }` non-call block to collect initializable fields and weak flags.
|
||||
/// Returns Ok(true) if consumed; Ok(false) if no `init {` at current position.
|
||||
pub(crate) fn parse_init_block_if_any(
|
||||
p: &mut NyashParser,
|
||||
init_fields: &mut Vec<String>,
|
||||
weak_fields: &mut Vec<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !(p.match_token(&TokenType::INIT) && p.peek_token() != &TokenType::LPAREN) {
|
||||
return Ok(false);
|
||||
}
|
||||
p.advance(); // consume 'init'
|
||||
p.consume(TokenType::LBRACE)?;
|
||||
while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() {
|
||||
p.skip_newlines();
|
||||
if p.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
}
|
||||
let is_weak = if p.match_token(&TokenType::WEAK) {
|
||||
p.advance();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if let TokenType::IDENTIFIER(field_name) = &p.current_token().token_type {
|
||||
init_fields.push(field_name.clone());
|
||||
if is_weak {
|
||||
weak_fields.push(field_name.clone());
|
||||
}
|
||||
p.advance();
|
||||
if p.match_token(&TokenType::COMMA) { p.advance(); }
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: if is_weak { "field name after 'weak'" } else { "field name" }.to_string(),
|
||||
found: p.current_token().token_type.clone(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
p.consume(TokenType::RBRACE)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::tokenizer::TokenType;
|
||||
|
||||
/// Try to parse a method declaration starting at `method_name` (already consumed identifier).
|
||||
/// Returns Some(method_node) when parsed; None when not applicable (i.e., next token is not '(').
|
||||
pub fn try_parse_method(
|
||||
pub(crate) fn try_parse_method(
|
||||
p: &mut NyashParser,
|
||||
method_name: String,
|
||||
is_override: bool,
|
||||
|
||||
@ -3,10 +3,11 @@ use crate::ast::{ASTNode, Span};
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// If Stage-3 gate allows, parse optional catch/cleanup after a block body and wrap it.
|
||||
/// Returns a (possibly) wrapped body.
|
||||
pub fn wrap_with_optional_postfix(
|
||||
pub(crate) fn wrap_with_optional_postfix(
|
||||
p: &mut NyashParser,
|
||||
body: Vec<ASTNode>,
|
||||
) -> Result<Vec<ASTNode>, ParseError> {
|
||||
@ -52,3 +53,66 @@ pub fn wrap_with_optional_postfix(
|
||||
span: Span::unknown(),
|
||||
}])
|
||||
}
|
||||
|
||||
/// Try to parse method-level postfix catch/cleanup after the last parsed method.
|
||||
/// Attaches a TryCatch wrapper around the last method body.
|
||||
pub(crate) fn try_parse_method_postfix_after_last_method(
|
||||
p: &mut NyashParser,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
last_method_name: &Option<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !(p.match_token(&TokenType::CATCH) || p.match_token(&TokenType::CLEANUP)) || last_method_name.is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mname = last_method_name.clone().unwrap();
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if p.match_token(&TokenType::CATCH) {
|
||||
p.advance();
|
||||
p.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = p.parse_catch_param()?;
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let catch_body = p.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause {
|
||||
exception_type: exc_ty,
|
||||
variable_name: exc_var,
|
||||
body: catch_body,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
p.skip_newlines();
|
||||
if p.match_token(&TokenType::CATCH) {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "single catch only after method body".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
let finally_body = if p.match_token(&TokenType::CLEANUP) {
|
||||
p.advance();
|
||||
Some(p.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(mnode) = methods.get_mut(&mname) {
|
||||
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
|
||||
let already = body.iter().any(|n| matches!(n, crate::ast::ASTNode::TryCatch { .. }));
|
||||
if already {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "duplicate postfix catch/cleanup after method".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
let old = std::mem::take(body);
|
||||
*body = vec![crate::ast::ASTNode::TryCatch {
|
||||
try_body: old,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
}];
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -5,9 +5,10 @@ use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
/// Try to parse a unified member property: `once name: Type ...` or `birth_once name: Type ...`
|
||||
/// Returns Ok(true) if consumed and handled; otherwise Ok(false).
|
||||
pub fn try_parse_unified_property(
|
||||
pub(crate) fn try_parse_unified_property(
|
||||
p: &mut NyashParser,
|
||||
kind_kw: &str,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
@ -115,3 +116,146 @@ pub fn try_parse_unified_property(
|
||||
methods.insert(getter_name, getter);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Try to parse a block-first unified member: `{ body } as [once|birth_once]? name : Type [postfix]`
|
||||
/// Returns Ok(true) if a member was parsed and emitted into `methods`.
|
||||
pub(crate) fn try_parse_block_first_property(
|
||||
p: &mut NyashParser,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
birth_once_props: &mut Vec<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !(crate::config::env::unified_members() && p.match_token(&TokenType::LBRACE)) {
|
||||
return Ok(false);
|
||||
}
|
||||
// 1) Parse block body first
|
||||
let mut final_body = p.parse_block_statements()?;
|
||||
p.skip_newlines();
|
||||
|
||||
// 2) Expect 'as'
|
||||
if let TokenType::IDENTIFIER(kw) = &p.current_token().token_type {
|
||||
if kw != "as" {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "'as' after block for block-first member".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "'as' after block for block-first member".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
p.advance(); // consume 'as'
|
||||
|
||||
// 3) Optional kind keyword: once | birth_once
|
||||
let mut kind = "computed".to_string();
|
||||
if let TokenType::IDENTIFIER(k) = &p.current_token().token_type {
|
||||
if k == "once" || k == "birth_once" {
|
||||
kind = k.clone();
|
||||
p.advance();
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Name : Type
|
||||
let name = if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
|
||||
let s = n.clone();
|
||||
p.advance();
|
||||
s
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "identifier for member name".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
if p.match_token(&TokenType::COLON) {
|
||||
p.advance();
|
||||
if let TokenType::IDENTIFIER(_ty) = &p.current_token().token_type {
|
||||
p.advance();
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "type name after ':'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: ": type".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
|
||||
// 5) Optional postfix handlers (Stage‑3) directly after block (shared helper)
|
||||
final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, final_body)?;
|
||||
|
||||
// 6) Generate methods per kind (fully equivalent to legacy inline branch)
|
||||
if kind == "once" {
|
||||
// __compute_once_<name>
|
||||
let compute_name = format!("__compute_once_{}", name);
|
||||
let compute = ASTNode::FunctionDeclaration {
|
||||
name: compute_name.clone(),
|
||||
params: vec![],
|
||||
body: final_body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
methods.insert(compute_name.clone(), compute);
|
||||
|
||||
// Getter with cache + poison handling
|
||||
let key = format!("__once_{}", name);
|
||||
let poison_key = format!("__once_poison_{}", name);
|
||||
let cached_local = format!("__ny_cached_{}", name);
|
||||
let poison_local = format!("__ny_poison_{}", name);
|
||||
let val_local = format!("__ny_val_{}", name);
|
||||
let me_node = ASTNode::Me { span: Span::unknown() };
|
||||
let get_cached = ASTNode::MethodCall {
|
||||
object: Box::new(me_node.clone()),
|
||||
method: "getField".to_string(),
|
||||
arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let local_cached = ASTNode::Local { variables: vec![cached_local.clone()], initial_values: vec![Some(Box::new(get_cached))], span: Span::unknown() };
|
||||
let cond_cached = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
|
||||
let then_ret_cached = vec![ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })), span: Span::unknown() }];
|
||||
let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() };
|
||||
|
||||
let get_poison = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }], span: Span::unknown() };
|
||||
let local_poison = ASTNode::Local { variables: vec![poison_local.clone()], initial_values: vec![Some(Box::new(get_poison))], span: Span::unknown() };
|
||||
let cond_poison = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
|
||||
let then_throw = vec![ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }), span: Span::unknown() }];
|
||||
let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() };
|
||||
|
||||
let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() };
|
||||
let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() };
|
||||
let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() };
|
||||
let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() };
|
||||
let getter_body = vec![local_cached, if_cached, local_poison, if_poison, local_val, set_call, ret_stmt];
|
||||
let getter_name = format!("__get_once_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// birth_once
|
||||
birth_once_props.push(name.clone());
|
||||
let compute_name = format!("__compute_birth_{}", name);
|
||||
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(compute_name.clone(), compute);
|
||||
let me_node = ASTNode::Me { span: Span::unknown() };
|
||||
let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("__birth_{}", name)), span: Span::unknown() }], span: Span::unknown() };
|
||||
let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }];
|
||||
let getter_name = format!("__get_birth_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -10,14 +10,16 @@ use crate::parser::{NyashParser, ParseError};
|
||||
|
||||
pub mod header;
|
||||
pub mod members;
|
||||
pub mod validators;
|
||||
pub mod interface;
|
||||
|
||||
/// Facade to host the staged migration.
|
||||
pub struct BoxDefParserFacade;
|
||||
pub(crate) struct BoxDefParserFacade;
|
||||
|
||||
impl BoxDefParserFacade {
|
||||
/// Entry planned: parse full box declaration (header + members).
|
||||
/// Not wired yet; use NyashParser::parse_box_declaration for now.
|
||||
pub fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
|
||||
pub(crate) fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: crate::tokenizer::TokenType::EOF,
|
||||
expected: "box declaration (facade not wired)".to_string(),
|
||||
|
||||
142
src/parser/declarations/box_def/validators.rs
Normal file
142
src/parser/declarations/box_def/validators.rs
Normal file
@ -0,0 +1,142 @@
|
||||
//! Validators and light analysis for box members
|
||||
use crate::ast::ASTNode;
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// Forbid user-defined methods named exactly as the box (constructor-like names).
|
||||
/// Nyash constructors are explicit: init/pack/birth. A method with the same name
|
||||
/// as the box is likely a mistaken constructor attempt; reject for clarity.
|
||||
pub(crate) fn validate_no_ctor_like_name(
|
||||
p: &mut NyashParser,
|
||||
box_name: &str,
|
||||
methods: &HashMap<String, ASTNode>,
|
||||
) -> Result<(), ParseError> {
|
||||
if methods.contains_key(box_name) {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: format!(
|
||||
"method name must not match box name '{}'; use init/pack/birth for constructors",
|
||||
box_name
|
||||
),
|
||||
line,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate that birth_once properties do not have cyclic dependencies via me.<prop> references
|
||||
pub(crate) fn validate_birth_once_cycles(
|
||||
p: &mut NyashParser,
|
||||
methods: &HashMap<String, ASTNode>,
|
||||
) -> Result<(), ParseError> {
|
||||
if !crate::config::env::unified_members() {
|
||||
return Ok(());
|
||||
}
|
||||
// Collect birth_once compute bodies
|
||||
let mut birth_bodies: HashMap<String, Vec<ASTNode>> = HashMap::new();
|
||||
for (mname, mast) in methods {
|
||||
if let Some(prop) = mname.strip_prefix("__compute_birth_") {
|
||||
if let ASTNode::FunctionDeclaration { body, .. } = mast {
|
||||
birth_bodies.insert(prop.to_string(), body.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if birth_bodies.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
// Build dependency graph: A -> {B | me.B used inside A}
|
||||
let mut deps: HashMap<String, HashSet<String>> = HashMap::new();
|
||||
let props: HashSet<String> = birth_bodies.keys().cloned().collect();
|
||||
for (pname, body) in &birth_bodies {
|
||||
let used = ast_collect_me_fields(body);
|
||||
let mut set = HashSet::new();
|
||||
for u in used {
|
||||
if props.contains(&u) && u != *pname {
|
||||
set.insert(u);
|
||||
}
|
||||
}
|
||||
deps.insert(pname.clone(), set);
|
||||
}
|
||||
// Detect cycle via DFS
|
||||
fn has_cycle(
|
||||
node: &str,
|
||||
deps: &HashMap<String, HashSet<String>>,
|
||||
temp: &mut HashSet<String>,
|
||||
perm: &mut HashSet<String>,
|
||||
) -> bool {
|
||||
if perm.contains(node) { return false; }
|
||||
if !temp.insert(node.to_string()) { return true; } // back-edge
|
||||
if let Some(ns) = deps.get(node) {
|
||||
for n in ns {
|
||||
if has_cycle(n, deps, temp, perm) { return true; }
|
||||
}
|
||||
}
|
||||
temp.remove(node);
|
||||
perm.insert(node.to_string());
|
||||
false
|
||||
}
|
||||
let mut perm = HashSet::new();
|
||||
let mut temp = HashSet::new();
|
||||
for pname in deps.keys() {
|
||||
if has_cycle(pname, &deps, &mut temp, &mut perm) {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "birth_once declarations must not have cyclic dependencies".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Forbid constructor call with the same name as the box; enforce `birth()` usage.
|
||||
pub(crate) fn forbid_box_named_constructor(p: &mut NyashParser, box_name: &str) -> Result<(), ParseError> {
|
||||
if let TokenType::IDENTIFIER(id) = &p.current_token().token_type {
|
||||
if id == box_name && p.peek_token() == &TokenType::LPAREN {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: format!(
|
||||
"birth() constructor instead of {}(). Nyash uses birth() for unified constructor syntax.",
|
||||
box_name
|
||||
),
|
||||
found: TokenType::IDENTIFIER(box_name.to_string()),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Collect all `me.<field>` accessed in nodes (flat set)
|
||||
fn ast_collect_me_fields(nodes: &Vec<ASTNode>) -> std::collections::HashSet<String> {
|
||||
use std::collections::HashSet;
|
||||
fn scan(nodes: &Vec<ASTNode>, out: &mut HashSet<String>) {
|
||||
for n in nodes {
|
||||
match n {
|
||||
ASTNode::FieldAccess { object, field, .. } => {
|
||||
if let ASTNode::Me { .. } = object.as_ref() {
|
||||
out.insert(field.clone());
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => scan(statements, out),
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
scan(then_body, out);
|
||||
if let Some(eb) = else_body { scan(eb, out); }
|
||||
}
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
scan(try_body, out);
|
||||
for c in catch_clauses { scan(&c.body, out); }
|
||||
if let Some(fb) = finally_body { scan(fb, out); }
|
||||
}
|
||||
ASTNode::FunctionDeclaration { body, .. } => scan(body, out),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut hs = HashSet::new();
|
||||
scan(nodes, &mut hs);
|
||||
hs
|
||||
}
|
||||
@ -13,407 +13,50 @@ use crate::tokenizer::TokenType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl NyashParser {
|
||||
/// Forbid user-defined methods named exactly as the box (constructor-like names).
|
||||
/// Nyash constructors are explicit: init/pack/birth. A method with the same name
|
||||
/// as the box is likely a mistaken constructor attempt; reject for clarity.
|
||||
fn validate_no_ctor_like_name(
|
||||
&mut self,
|
||||
box_name: &str,
|
||||
methods: &HashMap<String, ASTNode>,
|
||||
) -> Result<(), ParseError> {
|
||||
if methods.contains_key(box_name) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: format!(
|
||||
"method name must not match box name '{}'; use init/pack/birth for constructors",
|
||||
box_name
|
||||
),
|
||||
line,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Validate that birth_once properties do not have cyclic dependencies via me.<prop> references
|
||||
fn validate_birth_once_cycles(
|
||||
&mut self,
|
||||
methods: &HashMap<String, ASTNode>,
|
||||
) -> Result<(), ParseError> {
|
||||
if !crate::config::env::unified_members() {
|
||||
return Ok(());
|
||||
}
|
||||
use std::collections::{HashMap, HashSet};
|
||||
// Collect birth_once compute bodies
|
||||
let mut birth_bodies: HashMap<String, Vec<ASTNode>> = HashMap::new();
|
||||
for (mname, mast) in methods {
|
||||
if let Some(prop) = mname.strip_prefix("__compute_birth_") {
|
||||
if let ASTNode::FunctionDeclaration { body, .. } = mast {
|
||||
birth_bodies.insert(prop.to_string(), body.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if birth_bodies.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
// Build dependency graph: A -> {B | me.B used inside A}
|
||||
let mut deps: HashMap<String, HashSet<String>> = HashMap::new();
|
||||
let props: HashSet<String> = birth_bodies.keys().cloned().collect();
|
||||
for (p, body) in &birth_bodies {
|
||||
let used = self.ast_collect_me_fields(body);
|
||||
let mut set = HashSet::new();
|
||||
for u in used {
|
||||
if props.contains(&u) && u != *p {
|
||||
set.insert(u);
|
||||
}
|
||||
}
|
||||
deps.insert(p.clone(), set);
|
||||
}
|
||||
// Detect cycle via DFS
|
||||
fn has_cycle(
|
||||
node: &str,
|
||||
deps: &HashMap<String, HashSet<String>>,
|
||||
temp: &mut HashSet<String>,
|
||||
perm: &mut HashSet<String>,
|
||||
) -> bool {
|
||||
if perm.contains(node) {
|
||||
return false;
|
||||
}
|
||||
if !temp.insert(node.to_string()) {
|
||||
return true; // back-edge
|
||||
}
|
||||
if let Some(ns) = deps.get(node) {
|
||||
for n in ns {
|
||||
if has_cycle(n, deps, temp, perm) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
temp.remove(node);
|
||||
perm.insert(node.to_string());
|
||||
false
|
||||
}
|
||||
let mut perm = HashSet::new();
|
||||
let mut temp = HashSet::new();
|
||||
for p in deps.keys() {
|
||||
if has_cycle(p, &deps, &mut temp, &mut perm) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "birth_once declarations must not have cyclic dependencies".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Extracted: parse block-first unified member: `{ body } as [once|birth_once]? name : Type [postfix]`
|
||||
/// Returns true if a member was parsed and emitted into `methods`.
|
||||
fn parse_unified_member_block_first(
|
||||
/// Thin wrappers to keep the main loop tidy (behavior-preserving)
|
||||
fn box_try_block_first_property(
|
||||
&mut self,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
birth_once_props: &mut Vec<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !(crate::config::env::unified_members() && self.match_token(&TokenType::LBRACE)) {
|
||||
return Ok(false);
|
||||
}
|
||||
// 1) Parse block body first
|
||||
let mut final_body = self.parse_block_statements()?;
|
||||
self.skip_newlines();
|
||||
|
||||
// 2) Expect 'as'
|
||||
if let TokenType::IDENTIFIER(kw) = &self.current_token().token_type {
|
||||
if kw != "as" {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "'as' after block for block-first member".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "'as' after block for block-first member".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
self.advance(); // consume 'as'
|
||||
|
||||
// 3) Optional kind keyword: once | birth_once
|
||||
let mut kind = "computed".to_string();
|
||||
if let TokenType::IDENTIFIER(k) = &self.current_token().token_type {
|
||||
if k == "once" || k == "birth_once" {
|
||||
kind = k.clone();
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Name : Type
|
||||
let name = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
|
||||
let s = n.clone();
|
||||
self.advance();
|
||||
s
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier for member name".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
if self.match_token(&TokenType::COLON) {
|
||||
self.advance();
|
||||
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type {
|
||||
self.advance();
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "type name after ':'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: ": type".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
|
||||
// 5) Optional postfix handlers (Stage‑3) directly after block (shared helper)
|
||||
final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(self, final_body)?;
|
||||
|
||||
// 6) Generate methods per kind (fully equivalent to former inline branch)
|
||||
if kind == "once" {
|
||||
// __compute_once_<name>
|
||||
let compute_name = format!("__compute_once_{}", name);
|
||||
let compute = ASTNode::FunctionDeclaration {
|
||||
name: compute_name.clone(),
|
||||
params: vec![],
|
||||
body: final_body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
methods.insert(compute_name.clone(), compute);
|
||||
|
||||
// Getter with cache + poison handling
|
||||
let key = format!("__once_{}", name);
|
||||
let poison_key = format!("__once_poison_{}", name);
|
||||
let cached_local = format!("__ny_cached_{}", name);
|
||||
let poison_local = format!("__ny_poison_{}", name);
|
||||
let val_local = format!("__ny_val_{}", name);
|
||||
let me_node = ASTNode::Me { span: Span::unknown() };
|
||||
let get_cached = ASTNode::MethodCall {
|
||||
object: Box::new(me_node.clone()),
|
||||
method: "getField".to_string(),
|
||||
arguments: vec![ASTNode::Literal {
|
||||
value: crate::ast::LiteralValue::String(key.clone()),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let local_cached = ASTNode::Local {
|
||||
variables: vec![cached_local.clone()],
|
||||
initial_values: vec![Some(Box::new(get_cached))],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let cond_cached = ASTNode::BinaryOp {
|
||||
operator: crate::ast::BinaryOperator::NotEqual,
|
||||
left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }),
|
||||
right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let then_ret_cached = vec![ASTNode::Return {
|
||||
value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() };
|
||||
|
||||
let get_poison = ASTNode::MethodCall {
|
||||
object: Box::new(me_node.clone()),
|
||||
method: "getField".to_string(),
|
||||
arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let local_poison = ASTNode::Local {
|
||||
variables: vec![poison_local.clone()],
|
||||
initial_values: vec![Some(Box::new(get_poison))],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let cond_poison = ASTNode::BinaryOp {
|
||||
operator: crate::ast::BinaryOperator::NotEqual,
|
||||
left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }),
|
||||
right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let then_throw = vec![ASTNode::Throw {
|
||||
expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() };
|
||||
|
||||
let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() };
|
||||
let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() };
|
||||
let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() };
|
||||
let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() };
|
||||
let try_body = vec![local_val, set_call, ret_stmt];
|
||||
let catch_body = vec![
|
||||
ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }, ASTNode::Literal { value: crate::ast::LiteralValue::Bool(true), span: Span::unknown() }], span: Span::unknown() },
|
||||
ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' init failed", name)), span: Span::unknown() }), span: Span::unknown() }
|
||||
];
|
||||
let trycatch = ASTNode::TryCatch { try_body, catch_clauses: vec![crate::ast::CatchClause { exception_type: None, variable_name: None, body: catch_body, span: Span::unknown() }], finally_body: None, span: Span::unknown() };
|
||||
let getter_body = vec![local_cached, if_cached, local_poison, if_poison, trycatch];
|
||||
let getter_name = format!("__get_once_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
} else if kind == "birth_once" {
|
||||
// Self-cycle guard: birth_once cannot reference itself via me.<name>
|
||||
if self.ast_contains_me_field(&final_body, &name) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: format!("birth_once '{}' must not reference itself", name), line });
|
||||
}
|
||||
birth_once_props.push(name.clone());
|
||||
let compute_name = format!("__compute_birth_{}", name);
|
||||
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(compute_name.clone(), compute);
|
||||
let key = format!("__birth_{}", name);
|
||||
let me_node = ASTNode::Me { span: Span::unknown() };
|
||||
let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], span: Span::unknown() };
|
||||
let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }];
|
||||
let getter_name = format!("__get_birth_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
} else {
|
||||
// computed
|
||||
let getter_name = format!("__get_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
}
|
||||
|
||||
self.skip_newlines();
|
||||
Ok(true)
|
||||
crate::parser::declarations::box_def::members::properties::try_parse_block_first_property(
|
||||
self, methods, birth_once_props,
|
||||
)
|
||||
}
|
||||
|
||||
/// Extracted: method-level postfix catch/cleanup after a method
|
||||
fn parse_method_postfix_after_last_method(
|
||||
fn box_try_method_postfix_after_last(
|
||||
&mut self,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
last_method_name: &Option<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !(self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
|
||||
|| last_method_name.is_none()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
let mname = last_method_name.clone().unwrap();
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance();
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause {
|
||||
exception_type: exc_ty,
|
||||
variable_name: exc_var,
|
||||
body: catch_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
});
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after method body".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance();
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(mnode) = methods.get_mut(&mname) {
|
||||
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
|
||||
let already = body
|
||||
.iter()
|
||||
.any(|n| matches!(n, crate::ast::ASTNode::TryCatch { .. }));
|
||||
if already {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "duplicate postfix catch/cleanup after method".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
let old = std::mem::take(body);
|
||||
*body = vec![crate::ast::ASTNode::TryCatch {
|
||||
try_body: old,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
}];
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
|
||||
self, methods, last_method_name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Extracted: init { ... } block (non-call form)
|
||||
fn parse_init_block_if_any(
|
||||
fn box_try_init_block(
|
||||
&mut self,
|
||||
init_fields: &mut Vec<String>,
|
||||
weak_fields: &mut Vec<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !(self.match_token(&TokenType::INIT) && self.peek_token() != &TokenType::LPAREN) {
|
||||
return Ok(false);
|
||||
}
|
||||
self.advance(); // consume 'init'
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
}
|
||||
let is_weak = if self.match_token(&TokenType::WEAK) {
|
||||
self.advance();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if let TokenType::IDENTIFIER(field_name) = &self.current_token().token_type {
|
||||
init_fields.push(field_name.clone());
|
||||
if is_weak {
|
||||
weak_fields.push(field_name.clone());
|
||||
}
|
||||
self.advance();
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: if is_weak {
|
||||
"field name after 'weak'"
|
||||
} else {
|
||||
"field name"
|
||||
}
|
||||
.to_string(),
|
||||
found: self.current_token().token_type.clone(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
Ok(true)
|
||||
crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
|
||||
self, init_fields, weak_fields,
|
||||
)
|
||||
}
|
||||
|
||||
/// Extracted: visibility block or single property header-first
|
||||
fn parse_visibility_block_or_single(
|
||||
fn box_try_constructor(
|
||||
&mut self,
|
||||
is_override: bool,
|
||||
constructors: &mut HashMap<String, ASTNode>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if let Some((key, node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? {
|
||||
constructors.insert(key, node);
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn box_try_visibility(
|
||||
&mut self,
|
||||
visibility: &str,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
@ -422,69 +65,50 @@ impl NyashParser {
|
||||
private_fields: &mut Vec<String>,
|
||||
last_method_name: &mut Option<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if visibility != "public" && visibility != "private" {
|
||||
return Ok(false);
|
||||
}
|
||||
if self.match_token(&TokenType::LBRACE) {
|
||||
self.advance();
|
||||
self.skip_newlines();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
if let TokenType::IDENTIFIER(fname) = &self.current_token().token_type {
|
||||
let fname = fname.clone();
|
||||
if visibility == "public" {
|
||||
public_fields.push(fname.clone());
|
||||
} else {
|
||||
private_fields.push(fname.clone());
|
||||
}
|
||||
fields.push(fname);
|
||||
self.advance();
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
self.skip_newlines();
|
||||
continue;
|
||||
}
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "identifier in visibility block".to_string(),
|
||||
found: self.current_token().token_type.clone(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
self.skip_newlines();
|
||||
crate::parser::declarations::box_def::members::fields::try_parse_visibility_block_or_single(
|
||||
self,
|
||||
visibility,
|
||||
methods,
|
||||
fields,
|
||||
public_fields,
|
||||
private_fields,
|
||||
last_method_name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse either a method or a header-first field/property starting with `name`.
|
||||
/// Updates `methods`/`fields` and `last_method_name` as appropriate.
|
||||
fn box_try_method_or_field(
|
||||
&mut self,
|
||||
name: String,
|
||||
is_override: bool,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
fields: &mut Vec<String>,
|
||||
birth_once_props: &Vec<String>,
|
||||
last_method_name: &mut Option<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method(
|
||||
self,
|
||||
name.clone(),
|
||||
is_override,
|
||||
birth_once_props,
|
||||
)? {
|
||||
*last_method_name = Some(name.clone());
|
||||
methods.insert(name, method);
|
||||
return Ok(true);
|
||||
}
|
||||
if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
|
||||
let fname = n.clone();
|
||||
self.advance();
|
||||
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
|
||||
self,
|
||||
fname.clone(),
|
||||
methods,
|
||||
fields,
|
||||
)? {
|
||||
if visibility == "public" {
|
||||
public_fields.push(fname.clone());
|
||||
} else {
|
||||
private_fields.push(fname.clone());
|
||||
}
|
||||
*last_method_name = None;
|
||||
self.skip_newlines();
|
||||
return Ok(true);
|
||||
} else {
|
||||
// Fallback: record visibility + field name for compatibility
|
||||
if visibility == "public" {
|
||||
public_fields.push(fname.clone());
|
||||
} else {
|
||||
private_fields.push(fname.clone());
|
||||
}
|
||||
fields.push(fname);
|
||||
self.skip_newlines();
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
// Fallback: header-first field/property (computed/once/birth_once handled inside)
|
||||
crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
|
||||
self,
|
||||
name,
|
||||
methods,
|
||||
fields,
|
||||
)
|
||||
}
|
||||
// parse_unified_member_block_first moved to members::properties
|
||||
|
||||
// parse_method_postfix_after_last_method moved to members::postfix
|
||||
|
||||
/// box宣言をパース: box Name { fields... methods... }
|
||||
pub fn parse_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.consume(TokenType::BOX)?;
|
||||
@ -515,10 +139,10 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
// nyashモード(block-first): { body } as (once|birth_once)? name : Type
|
||||
if self.parse_unified_member_block_first(&mut methods, &mut birth_once_props)? { continue; }
|
||||
if self.box_try_block_first_property(&mut methods, &mut birth_once_props)? { continue; }
|
||||
|
||||
// Fallback: method-level postfix catch/cleanup after a method (non-static box)
|
||||
if self.parse_method_postfix_after_last_method(&mut methods, &last_method_name)? { continue; }
|
||||
if self.box_try_method_postfix_after_last(&mut methods, &last_method_name)? { continue; }
|
||||
|
||||
// RBRACEに到達していればループを抜ける
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
@ -526,7 +150,7 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
// initブロックの処理(initメソッドではない場合のみ)
|
||||
if self.parse_init_block_if_any(&mut init_fields, &mut weak_fields)? { continue; }
|
||||
if self.box_try_init_block(&mut init_fields, &mut weak_fields)? { continue; }
|
||||
|
||||
// overrideキーワードをチェック
|
||||
let mut is_override = false;
|
||||
@ -536,22 +160,10 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
// constructor parsing moved to members::constructors
|
||||
if let Some((ctor_key, ctor_node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? {
|
||||
constructors.insert(ctor_key, ctor_node);
|
||||
continue;
|
||||
}
|
||||
if self.box_try_constructor(is_override, &mut constructors)? { continue; }
|
||||
|
||||
// 🚨 birth()統一システム: Box名コンストラクタ無効化
|
||||
// Box名と同じ名前のコンストラクタは禁止(birth()のみ許可)
|
||||
if let TokenType::IDENTIFIER(id) = &self.current_token().token_type {
|
||||
if id == &name && self.peek_token() == &TokenType::LPAREN {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: format!("birth() constructor instead of {}(). Nyash uses birth() for unified constructor syntax.", name),
|
||||
found: TokenType::IDENTIFIER(name.clone()),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
crate::parser::declarations::box_def::validators::forbid_box_named_constructor(self, &name)?;
|
||||
|
||||
// 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り
|
||||
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
|
||||
@ -559,7 +171,7 @@ impl NyashParser {
|
||||
self.advance();
|
||||
|
||||
// 可視性: public/private ブロック/単行
|
||||
if self.parse_visibility_block_or_single(
|
||||
if self.box_try_visibility(
|
||||
&field_or_method,
|
||||
&mut methods,
|
||||
&mut fields,
|
||||
@ -583,26 +195,14 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
// メソッド or フィールド(委譲)
|
||||
if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method(
|
||||
self,
|
||||
field_or_method.clone(),
|
||||
if self.box_try_method_or_field(
|
||||
field_or_method,
|
||||
is_override,
|
||||
&mut methods,
|
||||
&mut fields,
|
||||
&birth_once_props,
|
||||
)? {
|
||||
last_method_name = Some(field_or_method.clone());
|
||||
methods.insert(field_or_method, method);
|
||||
} else {
|
||||
// フィールド or 統一メンバ(computed/once/birth_once header-first)
|
||||
let fname = field_or_method;
|
||||
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
|
||||
self,
|
||||
fname,
|
||||
&mut methods,
|
||||
&mut fields,
|
||||
)? {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
&mut last_method_name,
|
||||
)? { continue; }
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method or field name".to_string(),
|
||||
@ -614,7 +214,7 @@ impl NyashParser {
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
// 🚫 Disallow method named same as the box (constructor-like confusion)
|
||||
self.validate_no_ctor_like_name(&name, &methods)?;
|
||||
crate::parser::declarations::box_def::validators::validate_no_ctor_like_name(self, &name, &methods)?;
|
||||
|
||||
// 🔥 Override validation
|
||||
for parent in &extends {
|
||||
@ -622,7 +222,7 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
// birth_once 相互依存の簡易検出(宣言間の循環)
|
||||
self.validate_birth_once_cycles(&methods)?;
|
||||
crate::parser::declarations::box_def::validators::validate_birth_once_cycles(self, &methods)?;
|
||||
|
||||
Ok(ASTNode::BoxDeclaration {
|
||||
name,
|
||||
@ -645,169 +245,8 @@ impl NyashParser {
|
||||
|
||||
/// interface box宣言をパース: interface box Name { methods... }
|
||||
pub fn parse_interface_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.consume(TokenType::INTERFACE)?;
|
||||
self.consume(TokenType::BOX)?;
|
||||
|
||||
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
let name = name.clone();
|
||||
self.advance();
|
||||
name
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
self.skip_newlines(); // ブレース後の改行をスキップ
|
||||
|
||||
let mut methods = HashMap::new();
|
||||
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines(); // ループ開始時に改行をスキップ
|
||||
if let TokenType::IDENTIFIER(method_name) = &self.current_token().token_type {
|
||||
let method_name = method_name.clone();
|
||||
self.advance();
|
||||
|
||||
// インターフェースメソッドはシグネチャのみ
|
||||
if self.match_token(&TokenType::LPAREN) {
|
||||
self.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
// インターフェースメソッドは実装なし(空のbody)
|
||||
let method_decl = ASTNode::FunctionDeclaration {
|
||||
name: method_name.clone(),
|
||||
params,
|
||||
body: vec![], // 空の実装
|
||||
is_static: false, // インターフェースメソッドは通常静的でない
|
||||
is_override: false, // デフォルトは非オーバーライド
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
methods.insert(method_name, method_decl);
|
||||
|
||||
// メソッド宣言後の改行をスキップ
|
||||
self.skip_newlines();
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "(".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "method name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
|
||||
Ok(ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields: vec![], // インターフェースはフィールドなし
|
||||
public_fields: vec![],
|
||||
private_fields: vec![],
|
||||
methods,
|
||||
constructors: HashMap::new(), // インターフェースにコンストラクタなし
|
||||
init_fields: vec![], // インターフェースにinitブロックなし
|
||||
weak_fields: vec![], // 🔗 インターフェースにweak fieldsなし
|
||||
is_interface: true, // インターフェースフラグ
|
||||
extends: vec![], // 🚀 Multi-delegation: Changed from None to vec![]
|
||||
implements: vec![],
|
||||
type_parameters: Vec::new(), // 🔥 インターフェースではジェネリクス未対応
|
||||
is_static: false, // インターフェースは非static
|
||||
static_init: None, // インターフェースにstatic initなし
|
||||
span: Span::unknown(),
|
||||
})
|
||||
crate::parser::declarations::box_def::interface::parse_interface_box(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashParser {
|
||||
/// Minimal scan: does body contain `me.<field>` access (direct self-cycle guard)
|
||||
fn ast_contains_me_field(&self, nodes: &Vec<ASTNode>, field: &str) -> bool {
|
||||
fn scan(nodes: &Vec<ASTNode>, field: &str) -> bool {
|
||||
for n in nodes {
|
||||
match n {
|
||||
ASTNode::FieldAccess { object, field: f, .. } => {
|
||||
if f == field {
|
||||
if let ASTNode::Me { .. } = object.as_ref() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => {
|
||||
if scan(statements, field) { return true; }
|
||||
}
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
if scan(then_body, field) { return true; }
|
||||
if let Some(eb) = else_body { if scan(eb, field) { return true; } }
|
||||
}
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
if scan(try_body, field) { return true; }
|
||||
for c in catch_clauses { if scan(&c.body, field) { return true; } }
|
||||
if let Some(fb) = finally_body { if scan(fb, field) { return true; } }
|
||||
}
|
||||
ASTNode::FunctionDeclaration { body, .. } => {
|
||||
if scan(body, field) { return true; }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
scan(nodes, field)
|
||||
}
|
||||
|
||||
/// Collect all `me.<field>` accessed in nodes (flat set)
|
||||
fn ast_collect_me_fields(&self, nodes: &Vec<ASTNode>) -> std::collections::HashSet<String> {
|
||||
use std::collections::HashSet;
|
||||
fn scan(nodes: &Vec<ASTNode>, out: &mut HashSet<String>) {
|
||||
for n in nodes {
|
||||
match n {
|
||||
ASTNode::FieldAccess { object, field, .. } => {
|
||||
if let ASTNode::Me { .. } = object.as_ref() {
|
||||
out.insert(field.clone());
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => scan(statements, out),
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
scan(then_body, out);
|
||||
if let Some(eb) = else_body { scan(eb, out); }
|
||||
}
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
scan(try_body, out);
|
||||
for c in catch_clauses { scan(&c.body, out); }
|
||||
if let Some(fb) = finally_body { scan(fb, out); }
|
||||
}
|
||||
ASTNode::FunctionDeclaration { body, .. } => scan(body, out),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut hs = HashSet::new();
|
||||
scan(nodes, &mut hs);
|
||||
hs
|
||||
}
|
||||
}
|
||||
// ast_collect_me_fields moved into box_def::validators (private helper)
|
||||
|
||||
@ -9,5 +9,6 @@ pub mod box_definition;
|
||||
pub mod box_def;
|
||||
pub mod dependency_helpers;
|
||||
pub mod static_box;
|
||||
pub mod static_def;
|
||||
|
||||
// Re-export commonly used items
|
||||
|
||||
@ -14,112 +14,8 @@ impl NyashParser {
|
||||
/// static box宣言をパース: static box Name { ... }
|
||||
pub fn parse_static_box(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.consume(TokenType::BOX)?;
|
||||
|
||||
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
let name = name.clone();
|
||||
self.advance();
|
||||
name
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
// 🔥 ジェネリクス型パラメータのパース (<T, U>)
|
||||
let type_parameters = if self.match_token(&TokenType::LESS) {
|
||||
self.advance(); // consume '<'
|
||||
let mut params = Vec::new();
|
||||
|
||||
loop {
|
||||
if let TokenType::IDENTIFIER(param_name) = &self.current_token().token_type {
|
||||
params.push(param_name.clone());
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance(); // consume ','
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "type parameter name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::GREATER)?; // consume '>'
|
||||
params
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// from句のパース(Multi-delegation)- static boxでもデリゲーション可能 🚀
|
||||
let extends = if self.match_token(&TokenType::FROM) {
|
||||
self.advance(); // consume 'from'
|
||||
|
||||
let mut parent_list = Vec::new();
|
||||
|
||||
loop {
|
||||
if let TokenType::IDENTIFIER(parent_name) = &self.current_token().token_type {
|
||||
parent_list.push(parent_name.clone());
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance(); // consume ','
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "parent class name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
parent_list
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// interface句のパース(インターフェース実装)- static boxでもinterface実装可能
|
||||
let implements = if self.match_token(&TokenType::INTERFACE) {
|
||||
self.advance(); // consume 'interface'
|
||||
|
||||
let mut interface_list = Vec::new();
|
||||
|
||||
loop {
|
||||
if let TokenType::IDENTIFIER(interface_name) = &self.current_token().token_type {
|
||||
interface_list.push(interface_name.clone());
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance(); // consume ','
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "interface name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface_list
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let (name, type_parameters, extends, implements) =
|
||||
crate::parser::declarations::static_def::header::parse_static_header(self)?;
|
||||
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
self.skip_newlines(); // ブレース後の改行をスキップ
|
||||
@ -129,7 +25,7 @@ impl NyashParser {
|
||||
let constructors = HashMap::new();
|
||||
let mut init_fields = Vec::new();
|
||||
let mut weak_fields = Vec::new(); // 🔗 Track weak fields for static box
|
||||
let mut static_init = None;
|
||||
let mut static_init: Option<Vec<ASTNode>> = None;
|
||||
|
||||
// Track last inserted method name to allow postfix catch/cleanup fallback parsing
|
||||
let mut last_method_name: Option<String> = None;
|
||||
@ -137,212 +33,35 @@ impl NyashParser {
|
||||
self.skip_newlines(); // ループ開始時に改行をスキップ
|
||||
|
||||
// Fallback: method-level postfix catch/cleanup immediately following a method
|
||||
if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() {
|
||||
let mname = last_method_name.clone().unwrap();
|
||||
// Parse optional catch then optional cleanup
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance();
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause { exception_type: exc_ty, variable_name: exc_var, body: catch_body, span: crate::ast::Span::unknown() });
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "single catch only after method body".to_string(), line });
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) { self.advance(); Some(self.parse_block_statements()?) } else { None };
|
||||
// Wrap existing method body
|
||||
if let Some(mnode) = methods.get_mut(&mname) {
|
||||
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
|
||||
// If already TryCatch present, disallow duplicate postfix
|
||||
let already = body.iter().any(|n| matches!(n, crate::ast::ASTNode::TryCatch{..}));
|
||||
if already {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "duplicate postfix catch/cleanup after method".to_string(), line });
|
||||
}
|
||||
let old = std::mem::take(body);
|
||||
*body = vec![crate::ast::ASTNode::TryCatch { try_body: old, catch_clauses, finally_body, span: crate::ast::Span::unknown() }];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
|
||||
self, &mut methods, &last_method_name,
|
||||
)? { continue; }
|
||||
|
||||
// RBRACEに到達していればループを抜ける
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 🔥 static 初期化子の処理
|
||||
// Gate: NYASH_PARSER_STATIC_INIT_STRICT=1 のとき、
|
||||
// - 直後が '{' の場合のみ static 初期化子として扱う
|
||||
// - 直後が 'box' or 'function' の場合は、トップレベル宣言の開始とみなし、この box 本体を閉じる
|
||||
// 既定(ゲートOFF)は従来挙動(常に static { ... } を期待)
|
||||
if self.match_token(&TokenType::STATIC) {
|
||||
let strict = std::env::var("NYASH_PARSER_STATIC_INIT_STRICT").ok().as_deref() == Some("1");
|
||||
if strict {
|
||||
match self.peek_token() {
|
||||
TokenType::LBRACE => {
|
||||
self.advance(); // consume 'static'
|
||||
let static_body = self.parse_block_statements()?;
|
||||
static_init = Some(static_body);
|
||||
continue;
|
||||
}
|
||||
TokenType::BOX | TokenType::FUNCTION => {
|
||||
// トップレベルの `static box|function` が続くシーム: ここで box を閉じる
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
// 不明な形は従来通り initializer として解釈(互換重視)
|
||||
self.advance();
|
||||
let static_body = self.parse_block_statements()?;
|
||||
static_init = Some(static_body);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.advance(); // consume 'static'
|
||||
let static_body = self.parse_block_statements()?;
|
||||
static_init = Some(static_body);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// initブロックの処理
|
||||
if self.match_token(&TokenType::INIT) {
|
||||
self.advance(); // consume 'init'
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
// initブロック内のフィールド定義を読み込み
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for weak modifier
|
||||
let is_weak = if self.match_token(&TokenType::WEAK) {
|
||||
self.advance(); // consume 'weak'
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if let TokenType::IDENTIFIER(field_name) = &self.current_token().token_type {
|
||||
init_fields.push(field_name.clone());
|
||||
if is_weak {
|
||||
weak_fields.push(field_name.clone()); // 🔗 Add to weak fields list
|
||||
}
|
||||
self.advance();
|
||||
|
||||
// カンマがあればスキップ
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
} else {
|
||||
// 不正なトークンがある場合はエラー
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: if is_weak {
|
||||
"field name after 'weak'"
|
||||
} else {
|
||||
"field name"
|
||||
}
|
||||
.to_string(),
|
||||
found: self.current_token().token_type.clone(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
// 🔥 static 初期化子の処理(厳密ゲート互換)
|
||||
if let Some(body) = crate::parser::declarations::static_def::members::parse_static_initializer_if_any(self)? {
|
||||
static_init = Some(body);
|
||||
continue;
|
||||
} else if self.match_token(&TokenType::STATIC) {
|
||||
// STRICT で top-level seam を検出した場合は while を抜ける
|
||||
break;
|
||||
}
|
||||
|
||||
// initブロックの処理(共通ヘルパに委譲)
|
||||
if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any(
|
||||
self, &mut init_fields, &mut weak_fields,
|
||||
)? { continue; }
|
||||
|
||||
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
|
||||
let field_or_method = field_or_method.clone();
|
||||
self.advance();
|
||||
|
||||
// メソッド定義か?
|
||||
if self.match_token(&TokenType::LPAREN) {
|
||||
// メソッド定義
|
||||
self.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let mut body = self.parse_block_statements()?;
|
||||
self.skip_newlines();
|
||||
|
||||
// Method-level postfix catch/cleanup (gate)
|
||||
if self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)
|
||||
{
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause {
|
||||
exception_type: exc_ty,
|
||||
variable_name: exc_var,
|
||||
body: catch_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
});
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after method body".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance();
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Wrap original body with TryCatch
|
||||
body = vec![ASTNode::TryCatch {
|
||||
try_body: body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
}];
|
||||
}
|
||||
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
params,
|
||||
body,
|
||||
is_static: false, // static box内のメソッドは通常メソッド
|
||||
is_override: false, // デフォルトは非オーバーライド
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
last_method_name = Some(field_or_method.clone());
|
||||
methods.insert(field_or_method, method);
|
||||
} else {
|
||||
// フィールド定義
|
||||
fields.push(field_or_method);
|
||||
}
|
||||
crate::parser::declarations::static_def::members::try_parse_method_or_field(
|
||||
self, field_or_method, &mut methods, &mut fields, &mut last_method_name,
|
||||
)?;
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method or field name".to_string(),
|
||||
|
||||
91
src/parser/declarations/static_def/header.rs
Normal file
91
src/parser/declarations/static_def/header.rs
Normal file
@ -0,0 +1,91 @@
|
||||
//! Header parsing for `static box Name<T...> from Parent1, ... [interface ...]` (staged)
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
/// Parse the leading header of a static box and return
|
||||
/// (name, type_params, extends, implements). Does not consume the opening '{'.
|
||||
pub(crate) fn parse_static_header(
|
||||
p: &mut NyashParser,
|
||||
) -> Result<(String, Vec<String>, Vec<String>, Vec<String>), ParseError> {
|
||||
// Name
|
||||
let name = if let TokenType::IDENTIFIER(name) = &p.current_token().token_type {
|
||||
let name = name.clone();
|
||||
p.advance();
|
||||
name
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
// Generic type parameters: <T, U>
|
||||
let type_parameters = if p.match_token(&TokenType::LESS) {
|
||||
p.advance(); // consume '<'
|
||||
let mut params = Vec::new();
|
||||
loop {
|
||||
if let TokenType::IDENTIFIER(param_name) = &p.current_token().token_type {
|
||||
params.push(param_name.clone());
|
||||
p.advance();
|
||||
if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; }
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "type parameter name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
p.consume(TokenType::GREATER)?;
|
||||
params
|
||||
} else { Vec::new() };
|
||||
|
||||
// extends: from Parent1, Parent2
|
||||
let extends = if p.match_token(&TokenType::FROM) {
|
||||
p.advance(); // consume 'from'
|
||||
let mut parents = Vec::new();
|
||||
loop {
|
||||
if let TokenType::IDENTIFIER(parent_name) = &p.current_token().token_type {
|
||||
parents.push(parent_name.clone());
|
||||
p.advance();
|
||||
if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; }
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "parent class name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
parents
|
||||
} else { Vec::new() };
|
||||
|
||||
// implements: `interface A, B` (optional)
|
||||
let implements = if p.match_token(&TokenType::INTERFACE) {
|
||||
p.advance(); // consume 'interface'
|
||||
let mut list = Vec::new();
|
||||
loop {
|
||||
if let TokenType::IDENTIFIER(interface_name) = &p.current_token().token_type {
|
||||
list.push(interface_name.clone());
|
||||
p.advance();
|
||||
if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; }
|
||||
} else {
|
||||
let line = p.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "interface name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
list
|
||||
} else { Vec::new() };
|
||||
|
||||
Ok((name, type_parameters, extends, implements))
|
||||
}
|
||||
|
||||
90
src/parser/declarations/static_def/members.rs
Normal file
90
src/parser/declarations/static_def/members.rs
Normal file
@ -0,0 +1,90 @@
|
||||
//! Members helpers for static box (staged)
|
||||
use crate::ast::ASTNode;
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Parse a `static { ... }` initializer if present, honoring STRICT gate behavior.
|
||||
/// Returns Ok(Some(body)) when consumed; Ok(None) otherwise.
|
||||
pub(crate) fn parse_static_initializer_if_any(
|
||||
p: &mut NyashParser,
|
||||
) -> Result<Option<Vec<ASTNode>>, ParseError> {
|
||||
if !p.match_token(&TokenType::STATIC) { return Ok(None); }
|
||||
let strict = std::env::var("NYASH_PARSER_STATIC_INIT_STRICT").ok().as_deref() == Some("1");
|
||||
if strict {
|
||||
match p.peek_token() {
|
||||
TokenType::LBRACE => {
|
||||
p.advance(); // consume 'static'
|
||||
let body = p.parse_block_statements()?;
|
||||
return Ok(Some(body));
|
||||
}
|
||||
TokenType::BOX | TokenType::FUNCTION => {
|
||||
// top-level seam: do not consume, let caller close the box
|
||||
return Ok(None);
|
||||
}
|
||||
_ => {
|
||||
// backward-compatible fallback: treat as initializer
|
||||
p.advance();
|
||||
let body = p.parse_block_statements()?;
|
||||
return Ok(Some(body));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.advance(); // consume 'static'
|
||||
let body = p.parse_block_statements()?;
|
||||
Ok(Some(body))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a method body and apply optional postfix catch/cleanup inline (Stage‑3 gate).
|
||||
/// Caller must have consumed `(` and collected `params` and parsed `body`.
|
||||
pub(crate) fn wrap_method_body_with_postfix_if_any(
|
||||
p: &mut NyashParser,
|
||||
body: Vec<ASTNode>,
|
||||
) -> Result<Vec<ASTNode>, ParseError> {
|
||||
crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, body)
|
||||
}
|
||||
|
||||
/// Parse either a method or a field in static box after consuming an identifier `name`.
|
||||
/// - If next token is `(`, parses a method with optional postfix and inserts into `methods`.
|
||||
/// - Otherwise, treats as a field name and pushes into `fields`.
|
||||
pub(crate) fn try_parse_method_or_field(
|
||||
p: &mut NyashParser,
|
||||
name: String,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
fields: &mut Vec<String>,
|
||||
last_method_name: &mut Option<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !p.match_token(&TokenType::LPAREN) {
|
||||
// Field
|
||||
fields.push(name);
|
||||
return Ok(true);
|
||||
}
|
||||
// Method
|
||||
p.advance(); // consume '('
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "static method parameter parsing");
|
||||
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
}
|
||||
if p.match_token(&TokenType::COMMA) { p.advance(); }
|
||||
}
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let body = p.parse_block_statements()?;
|
||||
let body = wrap_method_body_with_postfix_if_any(p, body)?;
|
||||
// Construct method node
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: name.clone(),
|
||||
params,
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
*last_method_name = Some(name.clone());
|
||||
methods.insert(name, method);
|
||||
Ok(true)
|
||||
}
|
||||
23
src/parser/declarations/static_def/mod.rs
Normal file
23
src/parser/declarations/static_def/mod.rs
Normal file
@ -0,0 +1,23 @@
|
||||
//! Static Box Definition (staged split)
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
|
||||
pub mod header;
|
||||
pub mod members;
|
||||
pub mod validators;
|
||||
|
||||
/// Facade placeholder for static box parsing (to be wired gradually).
|
||||
pub(crate) struct StaticDefFacade;
|
||||
|
||||
impl StaticDefFacade {
|
||||
pub(crate) fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: crate::tokenizer::TokenType::EOF,
|
||||
expected: "static box declaration (facade not wired)".to_string(),
|
||||
line: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
12
src/parser/declarations/static_def/validators.rs
Normal file
12
src/parser/declarations/static_def/validators.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! Static box validators (placeholder for symmetry)
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
|
||||
pub(crate) struct StaticValidators;
|
||||
|
||||
impl StaticValidators {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn validate(_p: &mut NyashParser) -> Result<(), ParseError> { Ok(()) }
|
||||
}
|
||||
|
||||
@ -11,6 +11,78 @@ use crate::ast::{ASTNode, CatchClause, Span};
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
impl NyashParser {
|
||||
/// Parse a standalone block `{ ... }` and optional postfix `catch/cleanup` sequence.
|
||||
/// Returns Program(body) when no postfix keywords follow.
|
||||
fn parse_standalone_block_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
// Parse the block body first
|
||||
let try_body = self.parse_block_statements()?;
|
||||
|
||||
// Allow whitespace/newlines between block and postfix keywords
|
||||
self.skip_newlines();
|
||||
|
||||
if crate::config::env::block_postfix_catch()
|
||||
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
|
||||
{
|
||||
// Parse at most one catch, then optional cleanup
|
||||
let mut catch_clauses: Vec<CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exception_type, exception_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(CatchClause {
|
||||
exception_type,
|
||||
variable_name: exception_var,
|
||||
body: catch_body,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
|
||||
// Single‑catch policy (MVP): disallow multiple catch in postfix form
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after standalone block".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Optional cleanup
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance(); // consume 'cleanup'
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ASTNode::TryCatch {
|
||||
try_body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
} else {
|
||||
// No postfix keywords. If gate is on, enforce MVP static check:
|
||||
// direct top-level `throw` inside the standalone block must be followed by catch
|
||||
if crate::config::env::block_postfix_catch()
|
||||
&& try_body.iter().any(|n| matches!(n, ASTNode::Throw { .. }))
|
||||
{
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "block with direct 'throw' must be followed by 'catch'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
Ok(ASTNode::Program {
|
||||
statements: try_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
}
|
||||
/// Helper: parse a block `{ stmt* }` and return its statements
|
||||
pub(super) fn parse_block_statements(&mut self) -> Result<Vec<ASTNode>, ParseError> {
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
@ -25,6 +97,151 @@ impl NyashParser {
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
/// Grouped: declarations (box/interface/global/function/static/import)
|
||||
fn parse_declaration_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::BOX => self.parse_box_declaration(),
|
||||
TokenType::IMPORT => self.parse_import(),
|
||||
TokenType::INTERFACE => self.parse_interface_box_declaration(),
|
||||
TokenType::GLOBAL => self.parse_global_var(),
|
||||
TokenType::FUNCTION => self.parse_function_declaration(),
|
||||
TokenType::STATIC => self.parse_static_declaration(),
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "declaration statement".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Grouped: control flow (if/loop/break/continue/return)
|
||||
fn parse_control_flow_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::IF => self.parse_if(),
|
||||
TokenType::LOOP => self.parse_loop(),
|
||||
TokenType::BREAK => self.parse_break(),
|
||||
TokenType::CONTINUE => self.parse_continue(),
|
||||
TokenType::RETURN => self.parse_return(),
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "control-flow statement".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Grouped: IO/module-ish (print/nowait/include)
|
||||
fn parse_io_module_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::PRINT => self.parse_print(),
|
||||
TokenType::NOWAIT => self.parse_nowait(),
|
||||
TokenType::INCLUDE => self.parse_include(),
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "io/module statement".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Grouped: variable-related (local/outbox)
|
||||
fn parse_variable_declaration_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::LOCAL => self.parse_local(),
|
||||
TokenType::OUTBOX => self.parse_outbox(),
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "variable declaration".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Grouped: exception (try/throw) with gate checks preserved
|
||||
fn parse_exception_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::TRY => {
|
||||
if crate::config::env::parser_stage3() {
|
||||
self.parse_try_catch()
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_PARSER_STAGE3=1 to use 'try'".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::THROW => {
|
||||
if crate::config::env::parser_stage3() {
|
||||
self.parse_throw()
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_PARSER_STAGE3=1 to use 'throw'".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "try/throw".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error helpers for standalone postfix keywords (catch/cleanup)
|
||||
fn parse_postfix_catch_cleanup_error(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::CATCH => {
|
||||
if crate::config::env::block_postfix_catch() {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "postfix 'catch' is only allowed immediately after a standalone block: { ... } catch (...) { ... } (wrap if/else/loop in a standalone block)".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'catch' after a standalone block".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::CLEANUP => {
|
||||
if crate::config::env::block_postfix_catch() {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "postfix 'cleanup' is only allowed immediately after a standalone block: { ... } cleanup { ... }".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'cleanup' after a standalone block".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper: parse catch parameter inside parentheses (after '(' consumed)
|
||||
/// Forms: (Type ident) | (ident) | ()
|
||||
pub(super) fn parse_catch_param(&mut self) -> Result<(Option<String>, Option<String>), ParseError> {
|
||||
@ -66,149 +283,22 @@ impl NyashParser {
|
||||
// For grammar diff: capture starting token to classify statement keyword
|
||||
let start_tok = self.current_token().token_type.clone();
|
||||
let result = match &start_tok {
|
||||
TokenType::LBRACE => {
|
||||
// Standalone block (Phase 15.5): may be followed by block‑postfix catch/finally
|
||||
// Only enabled under gate; otherwise treat as error via expression fallback
|
||||
// Parse the block body first
|
||||
let try_body = self.parse_block_statements()?;
|
||||
|
||||
// Allow whitespace/newlines between block and postfix keywords
|
||||
self.skip_newlines();
|
||||
|
||||
if crate::config::env::block_postfix_catch()
|
||||
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
|
||||
{
|
||||
// Parse at most one catch, then optional cleanup
|
||||
let mut catch_clauses: Vec<CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exception_type, exception_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(CatchClause {
|
||||
exception_type,
|
||||
variable_name: exception_var,
|
||||
body: catch_body,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
|
||||
// Single‑catch policy (MVP): disallow multiple catch in postfix form
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after standalone block".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Optional cleanup
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance(); // consume 'cleanup'
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ASTNode::TryCatch {
|
||||
try_body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
} else {
|
||||
// No postfix keywords. If gate is on, enforce MVP static check:
|
||||
// direct top-level `throw` inside the standalone block must be followed by catch
|
||||
if crate::config::env::block_postfix_catch()
|
||||
&& try_body.iter().any(|n| matches!(n, ASTNode::Throw { .. }))
|
||||
{
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "block with direct 'throw' must be followed by 'catch'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
Ok(ASTNode::Program {
|
||||
statements: try_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::BOX => self.parse_box_declaration(),
|
||||
TokenType::IMPORT => self.parse_import(),
|
||||
TokenType::INTERFACE => self.parse_interface_box_declaration(),
|
||||
TokenType::GLOBAL => self.parse_global_var(),
|
||||
TokenType::FUNCTION => self.parse_function_declaration(),
|
||||
TokenType::STATIC => {
|
||||
self.parse_static_declaration() // 🔥 静的宣言 (function/box)
|
||||
}
|
||||
TokenType::IF => self.parse_if(),
|
||||
TokenType::LOOP => self.parse_loop(),
|
||||
TokenType::BREAK => self.parse_break(),
|
||||
TokenType::CONTINUE => self.parse_continue(),
|
||||
TokenType::RETURN => self.parse_return(),
|
||||
TokenType::PRINT => self.parse_print(),
|
||||
TokenType::NOWAIT => self.parse_nowait(),
|
||||
TokenType::INCLUDE => self.parse_include(),
|
||||
TokenType::LOCAL => self.parse_local(),
|
||||
TokenType::OUTBOX => self.parse_outbox(),
|
||||
TokenType::TRY => {
|
||||
if crate::config::env::parser_stage3() {
|
||||
self.parse_try_catch()
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_PARSER_STAGE3=1 to use 'try'".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::THROW => {
|
||||
if crate::config::env::parser_stage3() {
|
||||
self.parse_throw()
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_PARSER_STAGE3=1 to use 'throw'".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::CATCH => {
|
||||
// Provide a friendlier error when someone writes: if { .. } catch { .. }
|
||||
if crate::config::env::block_postfix_catch() {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "postfix 'catch' is only allowed immediately after a standalone block: { ... } catch (...) { ... } (wrap if/else/loop in a standalone block)".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'catch' after a standalone block".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::CLEANUP => {
|
||||
if crate::config::env::block_postfix_catch() {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "postfix 'cleanup' is only allowed immediately after a standalone block: { ... } cleanup { ... }".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'cleanup' after a standalone block".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::LBRACE => self.parse_standalone_block_statement(),
|
||||
TokenType::BOX
|
||||
| TokenType::IMPORT
|
||||
| TokenType::INTERFACE
|
||||
| TokenType::GLOBAL
|
||||
| TokenType::FUNCTION
|
||||
| TokenType::STATIC => self.parse_declaration_statement(),
|
||||
TokenType::IF
|
||||
| TokenType::LOOP
|
||||
| TokenType::BREAK
|
||||
| TokenType::CONTINUE
|
||||
| TokenType::RETURN => self.parse_control_flow_statement(),
|
||||
TokenType::PRINT | TokenType::NOWAIT | TokenType::INCLUDE => self.parse_io_module_statement(),
|
||||
TokenType::LOCAL | TokenType::OUTBOX => self.parse_variable_declaration_statement(),
|
||||
TokenType::TRY | TokenType::THROW => self.parse_exception_statement(),
|
||||
TokenType::CATCH | TokenType::CLEANUP => self.parse_postfix_catch_cleanup_error(),
|
||||
TokenType::USING => self.parse_using(),
|
||||
TokenType::FROM => {
|
||||
// 🔥 from構文: from Parent.method(args) または from Parent.constructor(args)
|
||||
|
||||
Reference in New Issue
Block a user