feat(parser): Phase 285A1.4 & A1.5 - Weak field sugar + Parser hang fix
A1.4: Add sugar syntax `public weak parent` ≡ `public { weak parent }`
A1.5: Fix parser hang on unsupported `param: Type` syntax
Key changes:
- A1.4: Extend visibility parser to handle weak modifier (fields.rs)
- A1.5: Shared helper `parse_param_name_list()` with progress-zero detection
- A1.5: Fix 6 vulnerable parameter parsing loops (methods, constructors, functions)
- Tests: Sugar syntax (OK/NG), parser hang (timeout-based)
- Docs: lifecycle.md, EBNF.md, phase-285a1-boxification.md
Additional changes:
- weak() builtin implementation (handlers/weak.rs)
- Leak tracking improvements (leak_tracker.rs)
- Documentation updates (lifecycle, types, memory-finalization, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -5,6 +5,8 @@
|
||||
* Extracted from parser/mod.rs as part of modularization
|
||||
*/
|
||||
|
||||
pub(crate) mod params;
|
||||
|
||||
use super::ParseError;
|
||||
use crate::ast::Span;
|
||||
use crate::tokenizer::{Token, TokenType};
|
||||
92
src/parser/common/params.rs
Normal file
92
src/parser/common/params.rs
Normal file
@ -0,0 +1,92 @@
|
||||
/*!
|
||||
* Parameter Parsing Utilities
|
||||
*
|
||||
* Phase 285A1.5: Common helper for parameter name list parsing
|
||||
* Prevents infinite parser hangs on unsupported parameter type annotations
|
||||
*/
|
||||
|
||||
use super::ParserUtils;
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
/// Parse parameter name list with Fail-Fast on unexpected tokens
|
||||
///
|
||||
/// Parses: IDENT (',' IDENT)*
|
||||
/// Rejects: Type annotations, unexpected tokens, malformed comma sequences
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `p` - Mutable reference to NyashParser
|
||||
/// * `context` - Context string for error messages ("method", "constructor", "function", etc.)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(Vec<String>)` - List of parameter names
|
||||
/// * `Err(ParseError)` - Parse error with context-aware message
|
||||
///
|
||||
/// # Features
|
||||
/// * Progress-zero detection: Tracks token position, errors if stuck
|
||||
/// * Explicit token handling: All token types explicitly matched
|
||||
/// * Fail-Fast: Either advances or errors (no infinite loop possible)
|
||||
/// * Unified error messages: Single source of truth for error text
|
||||
pub(crate) fn parse_param_name_list(
|
||||
p: &mut NyashParser,
|
||||
context: &str,
|
||||
) -> Result<Vec<String>, ParseError> {
|
||||
let mut params = Vec::new();
|
||||
let mut last_token_position: Option<(usize, usize)> = None;
|
||||
|
||||
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
|
||||
// Progress-zero detection: same position twice → infinite loop risk
|
||||
let current_position = p.current_position();
|
||||
if let Some(last_pos) = last_token_position {
|
||||
if current_position == last_pos {
|
||||
return Err(ParseError::InfiniteLoop {
|
||||
location: format!("{} parameter list", context),
|
||||
token: p.current_token().token_type.clone(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
last_token_position = Some(current_position);
|
||||
|
||||
match &p.current_token().token_type {
|
||||
TokenType::IDENTIFIER(param) => {
|
||||
params.push(param.clone());
|
||||
p.advance();
|
||||
|
||||
if p.match_token(&TokenType::COMMA) {
|
||||
p.advance();
|
||||
} else if !p.match_token(&TokenType::RPAREN) {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: format!(
|
||||
"',' or ')' in {} parameter list. \
|
||||
Note: Parameter type annotations are not supported.",
|
||||
context
|
||||
),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
TokenType::COMMA => {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: TokenType::COMMA,
|
||||
expected: format!("parameter name in {} parameter list", context),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: p.current_token().token_type.clone(),
|
||||
expected: format!(
|
||||
"parameter name or ')' in {} parameter list. \
|
||||
Note: Parameter type annotations are not supported.",
|
||||
context
|
||||
),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
@ -23,17 +23,10 @@ pub(crate) fn try_parse_constructor(
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (init)")?;
|
||||
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let mut body = p.parse_block_statements()?;
|
||||
// Optional postfix catch/cleanup (method-level gate)
|
||||
@ -97,17 +90,10 @@ pub(crate) fn try_parse_constructor(
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (pack)")?;
|
||||
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let body = p.parse_block_statements()?;
|
||||
let node = ASTNode::FunctionDeclaration {
|
||||
@ -134,17 +120,10 @@ pub(crate) fn try_parse_constructor(
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (birth)")?;
|
||||
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let body = p.parse_block_statements()?;
|
||||
let node = ASTNode::FunctionDeclaration {
|
||||
|
||||
@ -151,6 +151,35 @@ pub(crate) fn try_parse_visibility_block_or_single(
|
||||
p.consume(TokenType::RBRACE)?;
|
||||
return Ok(true);
|
||||
}
|
||||
// Phase 285A1.4: Sugar syntax - public weak parent, private weak parent
|
||||
if p.match_token(&TokenType::WEAK) {
|
||||
p.advance(); // consume WEAK only
|
||||
|
||||
// Read field name (reuse existing pattern)
|
||||
if let TokenType::IDENTIFIER(fname) = &p.current_token().token_type {
|
||||
let fname = fname.clone();
|
||||
p.advance(); // consume IDENTIFIER
|
||||
|
||||
// Delegate to existing weak field parser (handles type annotation, etc.)
|
||||
parse_weak_field(p, fname.clone(), methods, fields, weak_fields)?;
|
||||
|
||||
// Register with visibility tracking
|
||||
if visibility == "public" {
|
||||
public_fields.push(fname);
|
||||
} else {
|
||||
private_fields.push(fname);
|
||||
}
|
||||
|
||||
*last_method_name = None; // Reset method context (Phase 285A1.4)
|
||||
return Ok(true);
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "field name after 'weak' in visibility context".to_string(),
|
||||
found: p.current_token().token_type.clone(),
|
||||
line: p.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
|
||||
let fname = n.clone();
|
||||
p.advance();
|
||||
|
||||
@ -17,17 +17,8 @@ pub(crate) fn try_parse_method(
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(p, "method")?;
|
||||
p.consume(TokenType::RPAREN)?;
|
||||
let mut body = p.parse_block_statements()?;
|
||||
|
||||
|
||||
@ -313,6 +313,19 @@ impl NyashParser {
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
// Phase 285A0: weak(expr) → WeakRef creation
|
||||
// SSOT: docs/reference/language/lifecycle.md:171
|
||||
TokenType::WEAK => {
|
||||
self.advance();
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let argument = self.parse_expression()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
Ok(ASTNode::FunctionCall {
|
||||
name: "weak".to_string(),
|
||||
arguments: vec![argument],
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::InvalidExpression { line })
|
||||
|
||||
@ -29,27 +29,9 @@ impl NyashParser {
|
||||
|
||||
// パラメータリストをパース
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "function declaration parameter parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
} else if !self.match_token(&TokenType::RPAREN) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "parameter name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(self, "function")?;
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
|
||||
@ -67,27 +67,9 @@ impl NyashParser {
|
||||
|
||||
// パラメータリストをパース
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let mut params = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "static function parameter parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
} else if !self.match_token(&TokenType::RPAREN) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "parameter name".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
|
||||
let params = crate::parser::common::params::parse_param_name_list(self, "static method")?;
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user