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:
Selfhosting Dev
2025-09-19 08:34:29 +09:00
parent f4e340da08
commit 9142476484
348 changed files with 2539 additions and 281 deletions

View 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))
}

View 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)
}

View 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)
}

View 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)
}

View 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))
}

View File

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

View 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(),
}])
}

View 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)
}

View File

@ -6,6 +6,7 @@
*/
pub mod box_definition;
pub mod box_def;
pub mod dependency_helpers;
pub mod static_box;

View File

@ -1,4 +1,4 @@
use crate::ast::{ASTNode, Span};
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
@ -12,7 +12,14 @@ impl NyashParser {
let scrutinee = self.expr_parse_primary()?;
self.consume(TokenType::LBRACE)?;
let mut arms: Vec<(crate::ast::LiteralValue, ASTNode)> = Vec::new();
enum MatchArm {
Lit(Vec<LiteralValue>, ASTNode),
Type { ty: String, bind: String, body: ASTNode },
Default(ASTNode),
}
let mut arms_any: Vec<MatchArm> = Vec::new();
let mut saw_type_arm = false;
let mut default_expr: Option<ASTNode> = None;
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
@ -25,7 +32,7 @@ impl NyashParser {
break;
}
// default '_' or literal arm
// default '_' or type/literal arm
let is_default = matches!(self.current_token().token_type, TokenType::IDENTIFIER(ref s) if s == "_");
if is_default {
self.advance(); // consume '_'
@ -49,37 +56,81 @@ impl NyashParser {
// MVP: アームは primary/call を優先
self.expr_parse_primary()?
};
default_expr = Some(expr);
default_expr = Some(expr.clone());
arms_any.push(MatchArm::Default(expr));
} else {
// リテラルOR結合可
let mut lits: Vec<crate::ast::LiteralValue> = Vec::new();
let first = self.lit_only_for_match()?;
lits.push(first);
while self.match_token(&TokenType::BitOr) {
self.advance(); // consume '|'
let nxt = self.lit_only_for_match()?;
lits.push(nxt);
// Type pattern? IDENT '(' IDENT ')'
let mut handled = false;
if let TokenType::IDENTIFIER(type_name) = self.current_token().token_type.clone() {
if self.peek_token() == &TokenType::LPAREN
&& matches!(self.peek_nth_token(2), TokenType::IDENTIFIER(_))
&& self.peek_nth_token(3) == &TokenType::RPAREN
{
// consume TypeName ( IDENT ), capture binding name
let ty = type_name.clone();
self.advance(); // TypeName
self.consume(TokenType::LPAREN)?;
let bind = match self.current_token().token_type.clone() {
TokenType::IDENTIFIER(s) => {
self.advance();
s
}
other => {
return Err(ParseError::UnexpectedToken {
found: other,
expected: "identifier".to_string(),
line: self.current_token().line,
})
}
};
self.consume(TokenType::RPAREN)?;
self.consume(TokenType::FatArrow)?;
let body = if self.match_token(&TokenType::LBRACE) {
self.advance(); // consume '{'
let mut stmts: Vec<ASTNode> = Vec::new();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
if !self.match_token(&TokenType::RBRACE) {
stmts.push(self.parse_statement()?);
}
}
self.consume(TokenType::RBRACE)?;
ASTNode::Program { statements: stmts, span: Span::unknown() }
} else {
self.expr_parse_primary()?
};
// type arm parsed
arms_any.push(MatchArm::Type { ty, bind, body });
saw_type_arm = true;
handled = true;
}
}
self.consume(TokenType::FatArrow)?;
let expr = if self.match_token(&TokenType::LBRACE) {
self.advance(); // consume '{'
let mut stmts: Vec<ASTNode> = Vec::new();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
if !self.match_token(&TokenType::RBRACE) {
stmts.push(self.parse_statement()?);
if !handled {
// リテラルOR結合可
let mut lits: Vec<crate::ast::LiteralValue> = Vec::new();
let first = self.lit_only_for_match()?;
lits.push(first);
while self.match_token(&TokenType::BitOr) {
self.advance(); // consume '|'
let nxt = self.lit_only_for_match()?;
lits.push(nxt);
}
self.consume(TokenType::FatArrow)?;
let expr = if self.match_token(&TokenType::LBRACE) {
self.advance(); // consume '{'
let mut stmts: Vec<ASTNode> = Vec::new();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
if !self.match_token(&TokenType::RBRACE) {
stmts.push(self.parse_statement()?);
}
}
}
self.consume(TokenType::RBRACE)?;
ASTNode::Program {
statements: stmts,
span: Span::unknown(),
}
} else {
self.expr_parse_primary()?
};
for lit in lits {
arms.push((lit.clone(), expr.clone()));
self.consume(TokenType::RBRACE)?;
ASTNode::Program { statements: stmts, span: Span::unknown() }
} else {
self.expr_parse_primary()?
};
arms_any.push(MatchArm::Lit(lits, expr));
}
}
@ -97,11 +148,110 @@ impl NyashParser {
line: self.current_token().line,
})?;
// 既存の Lower を活用するため PeekExpr に落とす
Ok(ASTNode::PeekExpr {
scrutinee: Box::new(scrutinee),
arms,
else_expr: Box::new(else_expr),
if !saw_type_arm {
// 既存の Lower を活用するため PeekExpr に落とす(型パターンが無い場合のみ)
let mut lit_arms: Vec<(LiteralValue, ASTNode)> = Vec::new();
for arm in arms_any.into_iter() {
match arm {
MatchArm::Lit(lits, expr) => {
for lit in lits.into_iter() {
lit_arms.push((lit, expr.clone()));
}
}
MatchArm::Default(_) => { /* handled via else_expr above */ }
MatchArm::Type { .. } => unreachable!(),
}
}
return Ok(ASTNode::PeekExpr {
scrutinee: Box::new(scrutinee),
arms: lit_arms,
else_expr: Box::new(else_expr),
span: Span::unknown(),
});
}
// 型パターンを含む: ASTで if 連鎖へ合成
// 1) scrutinee を一度だけ評価しローカルに束縛
let scr_var = "__ny_match_scrutinee".to_string();
let scr_local = ASTNode::Local {
variables: vec![scr_var.clone()],
initial_values: vec![Some(Box::new(scrutinee))],
span: Span::unknown(),
};
// 2) アーム順に If 連鎖を構築
let mut else_node: ASTNode = else_expr;
// Wrap else body in Program for uniformity
else_node = ASTNode::Program { statements: vec![else_node], span: Span::unknown() };
// Process arms in reverse to build nested If
for arm in arms_any.into_iter().rev() {
match arm {
MatchArm::Default(_) => {
// already handled as else_node
}
MatchArm::Lit(lits, body) => {
// condition: (scr == lit1) || (scr == lit2) || ...
let mut cond: Option<ASTNode> = None;
for lit in lits.into_iter() {
let eq = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }),
right: Box::new(ASTNode::Literal { value: lit, span: Span::unknown() }),
span: Span::unknown(),
};
cond = Some(match cond {
None => eq,
Some(prev) => ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(prev),
right: Box::new(eq),
span: Span::unknown(),
},
});
}
let then_prog = ASTNode::Program { statements: vec![body], span: Span::unknown() };
else_node = ASTNode::If {
condition: Box::new(cond.expect("literal arm must have at least one literal")),
then_body: match then_prog { ASTNode::Program { statements, .. } => statements, _ => unreachable!() },
else_body: Some(match else_node.clone() { ASTNode::Program { statements, .. } => statements, other => vec![other] }),
span: Span::unknown(),
};
}
MatchArm::Type { ty, bind, body } => {
// condition: scr.is("Type")
let is_call = ASTNode::MethodCall {
object: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }),
method: "is".to_string(),
arguments: vec![ASTNode::Literal { value: LiteralValue::String(ty.clone()), span: Span::unknown() }],
span: Span::unknown(),
};
// then: local bind = scr.as("Type"); <body>
let cast = ASTNode::MethodCall {
object: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }),
method: "as".to_string(),
arguments: vec![ASTNode::Literal { value: LiteralValue::String(ty.clone()), span: Span::unknown() }],
span: Span::unknown(),
};
let bind_local = ASTNode::Local {
variables: vec![bind.clone()],
initial_values: vec![Some(Box::new(cast))],
span: Span::unknown(),
};
let then_prog = ASTNode::Program { statements: vec![bind_local, body], span: Span::unknown() };
else_node = ASTNode::If {
condition: Box::new(is_call),
then_body: match then_prog { ASTNode::Program { statements, .. } => statements, _ => unreachable!() },
else_body: Some(match else_node.clone() { ASTNode::Program { statements, .. } => statements, other => vec![other] }),
span: Span::unknown(),
};
}
}
}
// 3) 全体を Program で包み、scrutinee の一回評価を保証
Ok(ASTNode::Program {
statements: vec![scr_local, else_node],
span: Span::unknown(),
})
}