parser(match): add MVP type patterns (IntegerBox(x)/StringBox(s)) via AST If-chain; keep literal-only path using PeekExpr; add smoke app (apps/tests/match_type_pattern_basic.nyash); build + stage-2 smokes green
This commit is contained in:
90
src/parser/declarations/box_def/header.rs
Normal file
90
src/parser/declarations/box_def/header.rs
Normal file
@ -0,0 +1,90 @@
|
||||
//! Header parsing: `Name<T...> from Parent1, Parent2`
|
||||
//! Assumes the caller already consumed the leading `box` token.
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::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(
|
||||
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();
|
||||
while !p.match_token(&TokenType::GREATER) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "generic type 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.skip_newlines();
|
||||
}
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "type parameter name".to_string(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
p.consume(TokenType::GREATER)?; // consume '>'
|
||||
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();
|
||||
if let TokenType::IDENTIFIER(parent) = &p.current_token().token_type {
|
||||
parents.push(parent.clone());
|
||||
p.advance();
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "parent box name after 'from'".to_string(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
while p.match_token(&TokenType::COMMA) {
|
||||
p.advance(); // consume ','
|
||||
p.skip_newlines();
|
||||
if let TokenType::IDENTIFIER(parent) = &p.current_token().token_type {
|
||||
parents.push(parent.clone());
|
||||
p.advance();
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "parent box name after comma".to_string(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
parents
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// implements: not supported (TokenType::IMPLEMENTS not defined yet)
|
||||
let implements: Vec<String> = Vec::new();
|
||||
|
||||
Ok((name, type_parameters, extends, implements))
|
||||
}
|
||||
57
src/parser/declarations/box_def/members/common.rs
Normal file
57
src/parser/declarations/box_def/members/common.rs
Normal file
@ -0,0 +1,57 @@
|
||||
//! Shared helpers for members parsing (scaffold)
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MemberKind {
|
||||
Field,
|
||||
Method,
|
||||
Constructor,
|
||||
PropertyComputed,
|
||||
PropertyOnce,
|
||||
PropertyBirthOnce,
|
||||
}
|
||||
|
||||
/// Decide member kind via simple lookahead (scaffold placeholder)
|
||||
pub 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);
|
||||
}
|
||||
|
||||
// Constructors by keyword or name
|
||||
match &p.current_token().token_type {
|
||||
TokenType::PACK | TokenType::BIRTH => {
|
||||
if p.peek_token() == &TokenType::LPAREN { return Ok(MemberKind::Constructor); }
|
||||
}
|
||||
TokenType::IDENTIFIER(name) if (name == "init" || name == "birth" || name == "pack")
|
||||
&& p.peek_token() == &TokenType::LPAREN => {
|
||||
return Ok(MemberKind::Constructor);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Method: ident '(' ...
|
||||
if matches!(&p.current_token().token_type, TokenType::IDENTIFIER(_))
|
||||
&& p.peek_token() == &TokenType::LPAREN
|
||||
{
|
||||
return Ok(MemberKind::Method);
|
||||
}
|
||||
|
||||
// Field: [weak] ident ':' Type
|
||||
if p.match_any_token(&[TokenType::WEAK]) {
|
||||
// weak IDENT ':'
|
||||
// do not consume; use peek via offset: current is WEAK, next should be IDENT, then ':'
|
||||
// We only classify; the main parser will handle errors.
|
||||
return Ok(MemberKind::Field);
|
||||
}
|
||||
if matches!(&p.current_token().token_type, TokenType::IDENTIFIER(_))
|
||||
&& p.peek_token() == &TokenType::COLON
|
||||
{
|
||||
return Ok(MemberKind::Field);
|
||||
}
|
||||
|
||||
// Default: treat as method for graceful recovery
|
||||
Ok(MemberKind::Method)
|
||||
}
|
||||
160
src/parser/declarations/box_def/members/constructors.rs
Normal file
160
src/parser/declarations/box_def/members/constructors.rs
Normal file
@ -0,0 +1,160 @@
|
||||
//! Constructors parsing (init/pack/birth)
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
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(
|
||||
p: &mut NyashParser,
|
||||
is_override: bool,
|
||||
) -> Result<Option<(String, ASTNode)>, ParseError> {
|
||||
// init(...)
|
||||
if p.match_token(&TokenType::INIT) && p.peek_token() == &TokenType::LPAREN {
|
||||
if is_override {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method definition, not constructor after override keyword".to_string(),
|
||||
found: TokenType::INIT,
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
let name = "init".to_string();
|
||||
p.advance(); // consume 'init'
|
||||
p.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "constructor 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 mut body = p.parse_block_statements()?;
|
||||
p.skip_newlines();
|
||||
// Optional postfix catch/cleanup (method-level gate)
|
||||
if p.match_token(&TokenType::CATCH) || p.match_token(&TokenType::CLEANUP) {
|
||||
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
|
||||
};
|
||||
body = vec![ASTNode::TryCatch { try_body: body, catch_clauses, finally_body, span: Span::unknown() }];
|
||||
}
|
||||
let node = ASTNode::FunctionDeclaration {
|
||||
name: name.clone(),
|
||||
params: params.clone(),
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let key = format!("{}/{}", name, params.len());
|
||||
return Ok(Some((key, node)));
|
||||
}
|
||||
|
||||
// pack(...)
|
||||
if p.match_token(&TokenType::PACK) && p.peek_token() == &TokenType::LPAREN {
|
||||
if is_override {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method definition, not constructor after override keyword".to_string(),
|
||||
found: TokenType::PACK,
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
let name = "pack".to_string();
|
||||
p.advance(); // consume 'pack'
|
||||
p.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "pack 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 node = ASTNode::FunctionDeclaration {
|
||||
name: name.clone(),
|
||||
params: params.clone(),
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let key = format!("{}/{}", name, params.len());
|
||||
return Ok(Some((key, node)));
|
||||
}
|
||||
|
||||
// birth(...)
|
||||
if p.match_token(&TokenType::BIRTH) && p.peek_token() == &TokenType::LPAREN {
|
||||
if is_override {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method definition, not constructor after override keyword".to_string(),
|
||||
found: TokenType::BIRTH,
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
let name = "birth".to_string();
|
||||
p.advance(); // consume 'birth'
|
||||
p.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "birth 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 node = ASTNode::FunctionDeclaration {
|
||||
name: name.clone(),
|
||||
params: params.clone(),
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let key = format!("{}/{}", name, params.len());
|
||||
return Ok(Some((key, node)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
89
src/parser/declarations/box_def/members/fields.rs
Normal file
89
src/parser/declarations/box_def/members/fields.rs
Normal file
@ -0,0 +1,89 @@
|
||||
//! Fields parsing (header-first: `name: Type` + unified members gates)
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Parse a header-first field or property that starts with an already parsed identifier `fname`.
|
||||
/// Handles:
|
||||
/// - `name: Type` → field
|
||||
/// - `name: Type = expr` → field with initializer (initializer is parsed then discarded at P0)
|
||||
/// - `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(
|
||||
p: &mut NyashParser,
|
||||
fname: String,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
fields: &mut Vec<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
// Expect ':' Type after name
|
||||
if !p.match_token(&TokenType::COLON) {
|
||||
// No type annotation: treat as bare stored field
|
||||
fields.push(fname);
|
||||
return Ok(true);
|
||||
}
|
||||
p.advance(); // consume ':'
|
||||
// Optional type name (identifier). For now we accept and ignore.
|
||||
if let TokenType::IDENTIFIER(_ty) = &p.current_token().token_type {
|
||||
p.advance();
|
||||
} else {
|
||||
// If no type present, still proceed (tolerant parsing), but only when unified_members gate is off
|
||||
// Keep behavior aligned with existing parser (it allowed missing type in some branches)
|
||||
}
|
||||
|
||||
// Unified members gate behavior
|
||||
if crate::config::env::unified_members() {
|
||||
// name: Type = expr → field with initializer (store as field, initializer discarded at P0)
|
||||
if p.match_token(&TokenType::ASSIGN) {
|
||||
p.advance();
|
||||
let _init_expr = p.parse_expression()?; // P0: parse and discard
|
||||
fields.push(fname);
|
||||
p.skip_newlines();
|
||||
return Ok(true);
|
||||
}
|
||||
// name: Type => expr → computed property (getter method with return expr)
|
||||
if p.match_token(&TokenType::FatArrow) {
|
||||
p.advance();
|
||||
let expr = p.parse_expression()?;
|
||||
let body = vec![ASTNode::Return {
|
||||
value: Some(Box::new(expr)),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
let getter_name = format!("__get_{}", fname);
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: getter_name.clone(),
|
||||
params: vec![],
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
methods.insert(getter_name, method);
|
||||
p.skip_newlines();
|
||||
return Ok(true);
|
||||
}
|
||||
// name: Type { ... } [postfix]
|
||||
if p.match_token(&TokenType::LBRACE) {
|
||||
let body = p.parse_block_statements()?;
|
||||
let body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, body)?;
|
||||
let getter_name = format!("__get_{}", fname);
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: getter_name.clone(),
|
||||
params: vec![],
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
methods.insert(getter_name, method);
|
||||
p.skip_newlines();
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Default: treat as a plain field when unified-members gate didn't match any special form
|
||||
fields.push(fname);
|
||||
Ok(true)
|
||||
}
|
||||
80
src/parser/declarations/box_def/members/methods.rs
Normal file
80
src/parser/declarations/box_def/members/methods.rs
Normal file
@ -0,0 +1,80 @@
|
||||
//! Methods parsing (name(params){ body }) with special birth() prologue
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::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(
|
||||
p: &mut NyashParser,
|
||||
method_name: String,
|
||||
is_override: bool,
|
||||
birth_once_props: &Vec<String>,
|
||||
) -> Result<Option<ASTNode>, ParseError> {
|
||||
if !p.match_token(&TokenType::LPAREN) {
|
||||
return Ok(None);
|
||||
}
|
||||
p.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
crate::must_advance!(p, _unused, "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 mut body = p.parse_block_statements()?;
|
||||
|
||||
// Inject eager init for birth_once at the very beginning of user birth()
|
||||
if method_name == "birth" && !birth_once_props.is_empty() {
|
||||
let mut injected: Vec<ASTNode> = Vec::new();
|
||||
for pprop in birth_once_props.iter() {
|
||||
let me_node = ASTNode::Me { span: Span::unknown() };
|
||||
let compute_call = ASTNode::MethodCall {
|
||||
object: Box::new(me_node.clone()),
|
||||
method: format!("__compute_birth_{}", pprop),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let tmp = format!("__ny_birth_{}", pprop);
|
||||
let local_tmp = ASTNode::Local {
|
||||
variables: vec![tmp.clone()],
|
||||
initial_values: vec![Some(Box::new(compute_call))],
|
||||
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(format!("__birth_{}", pprop)),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Variable { name: tmp, span: Span::unknown() },
|
||||
],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
injected.push(local_tmp);
|
||||
injected.push(set_call);
|
||||
}
|
||||
let mut new_body = injected;
|
||||
new_body.extend(body.into_iter());
|
||||
body = new_body;
|
||||
}
|
||||
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: method_name.clone(),
|
||||
params,
|
||||
body,
|
||||
is_static: false,
|
||||
is_override,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
Ok(Some(method))
|
||||
}
|
||||
7
src/parser/declarations/box_def/members/mod.rs
Normal file
7
src/parser/declarations/box_def/members/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod common;
|
||||
pub mod fields;
|
||||
pub mod methods;
|
||||
pub mod constructors;
|
||||
pub mod properties;
|
||||
pub mod postfix;
|
||||
|
||||
54
src/parser/declarations/box_def/members/postfix.rs
Normal file
54
src/parser/declarations/box_def/members/postfix.rs
Normal file
@ -0,0 +1,54 @@
|
||||
//! Postfix handlers (catch/cleanup) utilities for unified members
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
/// 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(
|
||||
p: &mut NyashParser,
|
||||
body: Vec<ASTNode>,
|
||||
) -> Result<Vec<ASTNode>, ParseError> {
|
||||
if !(crate::config::env::parser_stage3()
|
||||
&& (p.match_token(&TokenType::CATCH) || p.match_token(&TokenType::CLEANUP)))
|
||||
{
|
||||
return Ok(body);
|
||||
}
|
||||
|
||||
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 member body".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
let finally_body = if p.match_token(&TokenType::CLEANUP) {
|
||||
p.advance();
|
||||
Some(p.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(vec![ASTNode::TryCatch {
|
||||
try_body: body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: Span::unknown(),
|
||||
}])
|
||||
}
|
||||
117
src/parser/declarations/box_def/members/properties.rs
Normal file
117
src/parser/declarations/box_def/members/properties.rs
Normal file
@ -0,0 +1,117 @@
|
||||
//! Properties parsing (once/birth_once, header-first)
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
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(
|
||||
p: &mut NyashParser,
|
||||
kind_kw: &str,
|
||||
methods: &mut HashMap<String, ASTNode>,
|
||||
birth_once_props: &mut Vec<String>,
|
||||
) -> Result<bool, ParseError> {
|
||||
if !(kind_kw == "once" || kind_kw == "birth_once") {
|
||||
return Ok(false);
|
||||
}
|
||||
// Name
|
||||
let name = if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
|
||||
let n2 = n.clone();
|
||||
p.advance();
|
||||
n2
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "identifier after once/birth_once".to_string(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
};
|
||||
// ':' TYPE (type is accepted and ignored for now)
|
||||
if p.match_token(&TokenType::COLON) {
|
||||
p.advance();
|
||||
if let TokenType::IDENTIFIER(_ty) = &p.current_token().token_type {
|
||||
p.advance();
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: "type name".to_string(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: ": type".to_string(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
// Body: either fat arrow expr or block
|
||||
let orig_body: Vec<ASTNode> = if p.match_token(&TokenType::FatArrow) {
|
||||
p.advance(); // consume '=>'
|
||||
let expr = p.parse_expression()?;
|
||||
vec![ASTNode::Return { value: Some(Box::new(expr)), span: Span::unknown() }]
|
||||
} else {
|
||||
p.parse_block_statements()?
|
||||
};
|
||||
// Optional postfix handlers (Stage-3) directly after body
|
||||
let final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, orig_body)?;
|
||||
if kind_kw == "once" {
|
||||
// once: synthesize compute + getter with poison/cache
|
||||
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);
|
||||
// Build complex getter wrapper identical to legacy impl
|
||||
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() };
|
||||
// getter: me.getField("__birth_name")
|
||||
let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("__birth_{}", name)), span: Span::unknown() }], span: Span::unknown() };
|
||||
let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }];
|
||||
let 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)
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
pub mod box_definition;
|
||||
pub mod box_def;
|
||||
pub mod dependency_helpers;
|
||||
pub mod static_box;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user