parser(match): split match parser to expr/match_expr.rs; keep MVP (literal | literal ... and default _) and reuse PeekExpr lowering

- Routing: expressions.rs -> expr_parse_match()
- Module: src/parser/expr/match_expr.rs (+OR pattern, comma/newline separators)
- No MIR changes; existing PeekExpr lower handles match result via decision tree
This commit is contained in:
Selfhosting Dev
2025-09-19 08:12:40 +09:00
parent a6f28a8980
commit f4e340da08
3 changed files with 152 additions and 94 deletions

View File

@ -0,0 +1,149 @@
use crate::ast::{ASTNode, Span};
use crate::parser::common::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
impl NyashParser {
/// match式: match <expr> { lit[ '|' lit ]* => <expr|block>, ..., _ => <expr|block> }
/// MVP: リテラルパターンORデフォルト(_) のみ。アーム本体は式またはブロック。
pub(crate) fn expr_parse_match(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'match'
// Scrutinee: MVPでは primary/call に限定(表現力は十分)
let scrutinee = self.expr_parse_primary()?;
self.consume(TokenType::LBRACE)?;
let mut arms: Vec<(crate::ast::LiteralValue, ASTNode)> = Vec::new();
let mut default_expr: Option<ASTNode> = None;
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
while self.match_token(&TokenType::COMMA) || self.match_token(&TokenType::NEWLINE) {
self.advance();
self.skip_newlines();
}
if self.match_token(&TokenType::RBRACE) {
break;
}
// default '_' or literal arm
let is_default = matches!(self.current_token().token_type, TokenType::IDENTIFIER(ref s) if s == "_");
if is_default {
self.advance(); // consume '_'
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 {
// MVP: アームは primary/call を優先
self.expr_parse_primary()?
};
default_expr = Some(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);
}
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()));
}
}
// 区切り(カンマや改行を許可)
while self.match_token(&TokenType::COMMA) || self.match_token(&TokenType::NEWLINE) {
self.advance();
}
self.skip_newlines();
}
self.consume(TokenType::RBRACE)?;
let else_expr = default_expr.ok_or(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "_ => <expr> in match".to_string(),
line: self.current_token().line,
})?;
// 既存の Lower を活用するため PeekExpr に落とす
Ok(ASTNode::PeekExpr {
scrutinee: Box::new(scrutinee),
arms,
else_expr: Box::new(else_expr),
span: Span::unknown(),
})
}
// match 用の最小リテラルパーサ(式は受け付けない)
fn lit_only_for_match(&mut self) -> Result<crate::ast::LiteralValue, ParseError> {
match &self.current_token().token_type {
TokenType::STRING(s) => {
let v = crate::ast::LiteralValue::String(s.clone());
self.advance();
Ok(v)
}
TokenType::NUMBER(n) => {
let v = crate::ast::LiteralValue::Integer(*n);
self.advance();
Ok(v)
}
TokenType::FLOAT(f) => {
let v = crate::ast::LiteralValue::Float(*f);
self.advance();
Ok(v)
}
TokenType::TRUE => {
self.advance();
Ok(crate::ast::LiteralValue::Bool(true))
}
TokenType::FALSE => {
self.advance();
Ok(crate::ast::LiteralValue::Bool(false))
}
TokenType::NULL => {
self.advance();
Ok(crate::ast::LiteralValue::Null)
}
_ => {
let line = self.current_token().line;
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "literal".to_string(),
line,
})
}
}
}
}

View File

@ -9,3 +9,4 @@ pub(crate) mod range;
pub(crate) mod shift;
pub(crate) mod term;
pub(crate) mod ternary;
pub(crate) mod match_expr;

View File

@ -187,7 +187,7 @@ impl NyashParser {
pub(crate) fn parse_unary(&mut self) -> Result<ASTNode, ParseError> {
// match式peek置換の先読み
if self.match_token(&TokenType::MATCH) {
return self.parse_match_expr();
return self.expr_parse_match();
}
if self.match_token(&TokenType::MINUS) {
self.advance(); // consume '-'
@ -221,99 +221,7 @@ impl NyashParser {
self.parse_call()
}
/// match式: match <expr> { pat => arm ... _ => arm }
/// MVP: pat はリテラルのみOR/型/構造は後段)。アームは式またはブロック(最後の式が値)。
fn parse_match_expr(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'match'
// Scrutinee: keep MVP simple and accept a primary/call expression
let scrutinee = self.parse_primary()?;
self.consume(TokenType::LBRACE)?;
let mut arms: Vec<(crate::ast::LiteralValue, ASTNode)> = Vec::new();
let mut default_expr: Option<ASTNode> = None;
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
self.skip_newlines();
while self.match_token(&TokenType::COMMA) || self.match_token(&TokenType::NEWLINE) {
self.advance();
self.skip_newlines();
}
if self.match_token(&TokenType::RBRACE) {
break;
}
// default '_' or literal arm
let is_default = matches!(self.current_token().token_type, TokenType::IDENTIFIER(ref s) if s == "_");
if is_default {
self.advance(); // consume '_'
self.consume(TokenType::FatArrow)?;
// else アーム: ブロック or 式
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 {
// MVP: accept a primary/call expression for arm body
self.parse_primary()?
};
default_expr = Some(expr);
} else {
// リテラルのみ許可P0
let lit = self.parse_literal_only()?;
self.consume(TokenType::FatArrow)?;
// アーム: ブロック or 式
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.parse_expression()?
};
arms.push((lit, expr));
}
// 区切り(カンマや改行を許可)
while self.match_token(&TokenType::COMMA) || self.match_token(&TokenType::NEWLINE) {
self.advance();
}
self.skip_newlines();
}
self.consume(TokenType::RBRACE)?;
let else_expr = default_expr.ok_or(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "_ => <expr> in match".to_string(),
line: self.current_token().line,
})?;
Ok(ASTNode::PeekExpr {
scrutinee: Box::new(scrutinee),
arms,
else_expr: Box::new(else_expr),
span: Span::unknown(),
})
}
// parse_match_expr moved to expr/match_expr.rs as expr_parse_match
fn parse_literal_only(&mut self) -> Result<crate::ast::LiteralValue, ParseError> {
match &self.current_token().token_type {