## 🎉 Step 2: peek→match完全統一アーキテクチャクリーンアップ完了 - ✅ 15ファイルで PeekExpr → MatchExpr 一括置換完了 - ✅ lowering/peek.rs → match_expr.rs 完全移行 - ✅ AI理解性・コードベース一貫性・保守性大幅向上 ## 🔍 Step 3: 複数行パース問題調査完了 - ✅ Task先生による根本原因特定完了 - 原因: オブジェクトリテラルパーサーの改行スキップ不足 - 修正: src/parser/expr/primary.rs の skip_newlines() 追加 ## 🚨 重大発見: PHI命令処理バグ - 問題: gemini_test_case.nyash で期待値2→実際0 - 原因: フェーズM+M.2のPHI統一作業でループ後変数マージに回帰バグ - 詳細: PHI命令は正常だが、print時に間違ったPHI参照 - 影響: Phase 15セルフホスティング基盤の重大バグ ## 📝 CLAUDE.md更新 - 全進捗状況の詳細記録 - 次のアクション: ChatGPT相談でMIRビルダー修正戦略立案 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
221 lines
9.1 KiB
Rust
221 lines
9.1 KiB
Rust
use crate::ast::{ASTNode, Span};
|
|
use crate::must_advance;
|
|
use crate::parser::common::ParserUtils;
|
|
use crate::parser::{NyashParser, ParseError};
|
|
use crate::tokenizer::TokenType;
|
|
|
|
#[inline]
|
|
fn is_sugar_enabled() -> bool {
|
|
crate::parser::sugar_gate::is_enabled()
|
|
}
|
|
|
|
impl NyashParser {
|
|
pub(crate) fn expr_parse_call(&mut self) -> Result<ASTNode, ParseError> {
|
|
let mut expr = self.expr_parse_primary()?;
|
|
|
|
loop {
|
|
// Phase 2: expression-level postfix catch/cleanup
|
|
// Example: foo(bar) catch(Type e) { ... } cleanup { ... }
|
|
// Guarded by Stage-3 gate to avoid surprising Stage-2 programs.
|
|
if crate::config::env::expr_postfix_catch()
|
|
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
|
|
{
|
|
use crate::ast::{CatchClause, Span};
|
|
// Parse optional single 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(),
|
|
});
|
|
}
|
|
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
|
self.advance(); // consume 'cleanup'
|
|
Some(self.parse_block_statements()?)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
expr = ASTNode::TryCatch {
|
|
try_body: vec![expr],
|
|
catch_clauses,
|
|
finally_body,
|
|
span: Span::unknown(),
|
|
};
|
|
// Postfix catch/cleanup binds at the end of a call/chain. Stop further chaining.
|
|
break;
|
|
}
|
|
if self.match_token(&TokenType::DOT) {
|
|
self.advance(); // consume '.'
|
|
|
|
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) {
|
|
// メソッド呼び出し: obj.method(args)
|
|
self.advance(); // consume '('
|
|
let mut arguments = Vec::new();
|
|
let mut _arg_count = 0;
|
|
|
|
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
|
must_advance!(self, _unused, "method call argument parsing");
|
|
|
|
arguments.push(self.parse_expression()?);
|
|
_arg_count += 1;
|
|
|
|
if self.match_token(&TokenType::COMMA) {
|
|
self.advance();
|
|
}
|
|
}
|
|
|
|
self.consume(TokenType::RPAREN)?;
|
|
|
|
expr = ASTNode::MethodCall {
|
|
object: Box::new(expr),
|
|
method: method_name,
|
|
arguments,
|
|
span: Span::unknown(),
|
|
};
|
|
} else {
|
|
// フィールドアクセス: obj.field
|
|
expr = ASTNode::FieldAccess {
|
|
object: Box::new(expr),
|
|
field: method_name,
|
|
span: Span::unknown(),
|
|
};
|
|
}
|
|
} else {
|
|
let line = self.current_token().line;
|
|
return Err(ParseError::UnexpectedToken {
|
|
found: self.current_token().token_type.clone(),
|
|
expected: "identifier".to_string(),
|
|
line,
|
|
});
|
|
}
|
|
} else if self.match_token(&TokenType::QmarkDot) {
|
|
if !is_sugar_enabled() {
|
|
let line = self.current_token().line;
|
|
return Err(ParseError::UnexpectedToken {
|
|
found: self.current_token().token_type.clone(),
|
|
expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full for '?.'".to_string(),
|
|
line,
|
|
});
|
|
}
|
|
self.advance(); // consume '?.'
|
|
// ident then optional call
|
|
let name = match &self.current_token().token_type {
|
|
TokenType::IDENTIFIER(s) => {
|
|
let v = s.clone();
|
|
self.advance();
|
|
v
|
|
}
|
|
_ => {
|
|
let line = self.current_token().line;
|
|
return Err(ParseError::UnexpectedToken {
|
|
found: self.current_token().token_type.clone(),
|
|
expected: "identifier after '?.'".to_string(),
|
|
line,
|
|
});
|
|
}
|
|
};
|
|
let access = if self.match_token(&TokenType::LPAREN) {
|
|
// method call
|
|
self.advance();
|
|
let mut arguments = Vec::new();
|
|
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
|
must_advance!(self, _unused, "safe method call arg parsing");
|
|
arguments.push(self.parse_expression()?);
|
|
if self.match_token(&TokenType::COMMA) {
|
|
self.advance();
|
|
}
|
|
}
|
|
self.consume(TokenType::RPAREN)?;
|
|
ASTNode::MethodCall {
|
|
object: Box::new(expr.clone()),
|
|
method: name,
|
|
arguments,
|
|
span: Span::unknown(),
|
|
}
|
|
} else {
|
|
// field access
|
|
ASTNode::FieldAccess {
|
|
object: Box::new(expr.clone()),
|
|
field: name,
|
|
span: Span::unknown(),
|
|
}
|
|
};
|
|
|
|
// Wrap with peek: peek expr { null => null, else => access(expr) }
|
|
expr = ASTNode::MatchExpr {
|
|
scrutinee: Box::new(expr.clone()),
|
|
arms: vec![(
|
|
crate::ast::LiteralValue::Null,
|
|
ASTNode::Literal {
|
|
value: crate::ast::LiteralValue::Null,
|
|
span: Span::unknown(),
|
|
},
|
|
)],
|
|
else_expr: Box::new(access),
|
|
span: Span::unknown(),
|
|
};
|
|
} else if self.match_token(&TokenType::LPAREN) {
|
|
// 関数呼び出し: function(args) または 一般式呼び出し: (callee)(args)
|
|
self.advance(); // consume '('
|
|
let mut arguments = Vec::new();
|
|
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
|
must_advance!(self, _unused, "function call argument parsing");
|
|
arguments.push(self.parse_expression()?);
|
|
if self.match_token(&TokenType::COMMA) {
|
|
self.advance();
|
|
}
|
|
}
|
|
self.consume(TokenType::RPAREN)?;
|
|
|
|
if let ASTNode::Variable { name, .. } = expr.clone() {
|
|
expr = ASTNode::FunctionCall {
|
|
name,
|
|
arguments,
|
|
span: Span::unknown(),
|
|
};
|
|
} else {
|
|
expr = ASTNode::Call {
|
|
callee: Box::new(expr),
|
|
arguments,
|
|
span: Span::unknown(),
|
|
};
|
|
}
|
|
} else if self.match_token(&TokenType::QUESTION) {
|
|
let nt = self.peek_token();
|
|
let is_ender = matches!(
|
|
nt,
|
|
TokenType::NEWLINE
|
|
| TokenType::EOF
|
|
| TokenType::RPAREN
|
|
| TokenType::COMMA
|
|
| TokenType::RBRACE
|
|
);
|
|
if !is_ender {
|
|
break;
|
|
}
|
|
self.advance();
|
|
expr = ASTNode::QMarkPropagate {
|
|
expression: Box::new(expr),
|
|
span: Span::unknown(),
|
|
};
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(expr)
|
|
}
|
|
}
|