742 lines
23 KiB
Markdown
742 lines
23 KiB
Markdown
|
|
# Phase 16 Implementation Guide: Box-Based Macro System
|
|||
|
|
|
|||
|
|
Date: 2025-09-19
|
|||
|
|
Version: 0.1.0
|
|||
|
|
Status: **READY** - 実装開始準備完了
|
|||
|
|
|
|||
|
|
## 🚀 **実装開始チェックリスト**
|
|||
|
|
|
|||
|
|
### **前提条件確認**
|
|||
|
|
- [x] CLI統合完了(--expand, --run-tests既に実装済み)
|
|||
|
|
- [x] 環境変数対応(NYASH_MACRO_ENABLE等)
|
|||
|
|
- [x] AST基盤(既存のASTNode構造体)
|
|||
|
|
- [x] パーサー基盤(NyashParser実装済み)
|
|||
|
|
- [ ] **開始準備**: Phase 1実装開始
|
|||
|
|
|
|||
|
|
## 🎯 **Phase 1: AST Pattern Matching 実装**
|
|||
|
|
|
|||
|
|
### **Step 1.1: AST構造体拡張**
|
|||
|
|
|
|||
|
|
#### **ファイル**: `src/ast.rs`
|
|||
|
|
```rust
|
|||
|
|
// 既存のASTNodeに追加
|
|||
|
|
#[derive(Debug, Clone)]
|
|||
|
|
pub enum ASTNode {
|
|||
|
|
// ... 既存のVariant ...
|
|||
|
|
|
|||
|
|
// 新規追加: パターンマッチング
|
|||
|
|
Match {
|
|||
|
|
target: Box<ASTNode>,
|
|||
|
|
arms: Vec<MatchArm>,
|
|||
|
|
span: Span,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// パターン表現
|
|||
|
|
Pattern(PatternAst),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 新規追加: パターンAST
|
|||
|
|
#[derive(Debug, Clone)]
|
|||
|
|
pub enum PatternAst {
|
|||
|
|
// 基本パターン
|
|||
|
|
Wildcard { span: Span },
|
|||
|
|
Identifier { name: String, span: Span },
|
|||
|
|
Literal { value: LiteralValue, span: Span },
|
|||
|
|
|
|||
|
|
// 構造パターン
|
|||
|
|
BoxPattern {
|
|||
|
|
name: String,
|
|||
|
|
fields: Vec<FieldPattern>,
|
|||
|
|
rest: Option<String>, // ..rest
|
|||
|
|
span: Span,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 配列パターン
|
|||
|
|
ArrayPattern {
|
|||
|
|
elements: Vec<PatternAst>,
|
|||
|
|
rest: Option<String>, // ...rest
|
|||
|
|
span: Span,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// OR パターン
|
|||
|
|
OrPattern {
|
|||
|
|
patterns: Vec<PatternAst>,
|
|||
|
|
span: Span,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// バインドパターン
|
|||
|
|
BindPattern {
|
|||
|
|
name: String, // @variable
|
|||
|
|
pattern: Box<PatternAst>,
|
|||
|
|
span: Span,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Debug, Clone)]
|
|||
|
|
pub struct MatchArm {
|
|||
|
|
pub pattern: PatternAst,
|
|||
|
|
pub guard: Option<Box<ASTNode>>,
|
|||
|
|
pub body: Vec<ASTNode>,
|
|||
|
|
pub span: Span,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Debug, Clone)]
|
|||
|
|
pub struct FieldPattern {
|
|||
|
|
pub name: String,
|
|||
|
|
pub pattern: PatternAst,
|
|||
|
|
pub span: Span,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**実装タスク**:
|
|||
|
|
```bash
|
|||
|
|
# 1. ast.rsに上記の定義を追加
|
|||
|
|
# 2. 既存のコンパイルエラーを修正
|
|||
|
|
# 3. 基本的なDebug traitの動作確認
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **Step 1.2: Tokenizer拡張**
|
|||
|
|
|
|||
|
|
#### **ファイル**: `src/tokenizer.rs`
|
|||
|
|
```rust
|
|||
|
|
// TokenTypeに追加
|
|||
|
|
#[derive(Debug, Clone, PartialEq)]
|
|||
|
|
pub enum TokenType {
|
|||
|
|
// ... 既存のToken ...
|
|||
|
|
|
|||
|
|
// パターンマッチング用
|
|||
|
|
MATCH, // match
|
|||
|
|
PIPE, // |
|
|||
|
|
AT, // @
|
|||
|
|
DOTDOT, // ..
|
|||
|
|
DOTDOTDOT, // ...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Tokenizerに追加
|
|||
|
|
impl Tokenizer {
|
|||
|
|
fn read_word(&mut self) -> TokenType {
|
|||
|
|
match word.as_str() {
|
|||
|
|
// ... 既存のキーワード ...
|
|||
|
|
"match" => TokenType::MATCH,
|
|||
|
|
_ => TokenType::IDENTIFIER(word),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn read_symbol(&mut self) -> TokenType {
|
|||
|
|
match self.current_char() {
|
|||
|
|
// ... 既存のシンボル ...
|
|||
|
|
'|' => TokenType::PIPE,
|
|||
|
|
'@' => TokenType::AT,
|
|||
|
|
'.' => {
|
|||
|
|
if self.peek_char() == Some('.') {
|
|||
|
|
self.advance(); // consume second '.'
|
|||
|
|
if self.peek_char() == Some('.') {
|
|||
|
|
self.advance(); // consume third '.'
|
|||
|
|
TokenType::DOTDOTDOT
|
|||
|
|
} else {
|
|||
|
|
TokenType::DOTDOT
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
TokenType::DOT
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_ => // ... 既存の処理 ...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**実装タスク**:
|
|||
|
|
```bash
|
|||
|
|
# 1. TokenTypeに新しいトークンを追加
|
|||
|
|
# 2. Tokenizerの辞書登録
|
|||
|
|
# 3. 基本的なトークン化テスト
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **Step 1.3: Parser拡張(match式)**
|
|||
|
|
|
|||
|
|
#### **ファイル**: `src/parser/expressions.rs` (新規作成)
|
|||
|
|
```rust
|
|||
|
|
use crate::ast::{ASTNode, PatternAst, MatchArm, FieldPattern};
|
|||
|
|
use crate::parser::{NyashParser, ParseError};
|
|||
|
|
use crate::tokenizer::TokenType;
|
|||
|
|
|
|||
|
|
impl NyashParser {
|
|||
|
|
/// match式のパース
|
|||
|
|
pub fn parse_match_expression(&mut self) -> Result<ASTNode, ParseError> {
|
|||
|
|
let start_line = self.current_token().line;
|
|||
|
|
self.consume(TokenType::MATCH)?;
|
|||
|
|
|
|||
|
|
// match対象の式
|
|||
|
|
let target = Box::new(self.parse_expression()?);
|
|||
|
|
|
|||
|
|
self.consume(TokenType::LBRACE)?;
|
|||
|
|
self.skip_newlines();
|
|||
|
|
|
|||
|
|
// match arms
|
|||
|
|
let mut arms = Vec::new();
|
|||
|
|
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
|||
|
|
arms.push(self.parse_match_arm()?);
|
|||
|
|
self.skip_newlines();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if arms.is_empty() {
|
|||
|
|
return Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "at least one match arm".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: start_line,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.consume(TokenType::RBRACE)?;
|
|||
|
|
|
|||
|
|
Ok(ASTNode::Match {
|
|||
|
|
target,
|
|||
|
|
arms,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// マッチアームのパース
|
|||
|
|
fn parse_match_arm(&mut self) -> Result<MatchArm, ParseError> {
|
|||
|
|
// パターン
|
|||
|
|
let pattern = self.parse_pattern()?;
|
|||
|
|
|
|||
|
|
// ガード(if condition)
|
|||
|
|
let guard = if self.match_token(&TokenType::IF) {
|
|||
|
|
self.advance(); // consume 'if'
|
|||
|
|
Some(Box::new(self.parse_expression()?))
|
|||
|
|
} else {
|
|||
|
|
None
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
self.consume(TokenType::ARROW)?; // =>
|
|||
|
|
|
|||
|
|
// ボディ
|
|||
|
|
let body = if self.match_token(&TokenType::LBRACE) {
|
|||
|
|
self.parse_block_statements()?
|
|||
|
|
} else {
|
|||
|
|
vec![self.parse_statement()?]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
Ok(MatchArm {
|
|||
|
|
pattern,
|
|||
|
|
guard,
|
|||
|
|
body,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// パターンのパース
|
|||
|
|
pub fn parse_pattern(&mut self) -> Result<PatternAst, ParseError> {
|
|||
|
|
self.parse_or_pattern()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// ORパターン(最低優先度)
|
|||
|
|
fn parse_or_pattern(&mut self) -> Result<PatternAst, ParseError> {
|
|||
|
|
let mut pattern = self.parse_bind_pattern()?;
|
|||
|
|
|
|||
|
|
if self.match_token(&TokenType::PIPE) {
|
|||
|
|
let mut patterns = vec![pattern];
|
|||
|
|
|
|||
|
|
while self.match_token(&TokenType::PIPE) {
|
|||
|
|
self.advance(); // consume '|'
|
|||
|
|
patterns.push(self.parse_bind_pattern()?);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pattern = PatternAst::OrPattern {
|
|||
|
|
patterns,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Ok(pattern)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// バインドパターン(@variable pattern)
|
|||
|
|
fn parse_bind_pattern(&mut self) -> Result<PatternAst, ParseError> {
|
|||
|
|
if self.match_token(&TokenType::AT) {
|
|||
|
|
self.advance(); // consume '@'
|
|||
|
|
|
|||
|
|
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
|||
|
|
let name = name.clone();
|
|||
|
|
self.advance();
|
|||
|
|
name
|
|||
|
|
} else {
|
|||
|
|
return Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "identifier after '@'".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: self.current_token().line,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let pattern = Box::new(self.parse_primary_pattern()?);
|
|||
|
|
|
|||
|
|
Ok(PatternAst::BindPattern {
|
|||
|
|
name,
|
|||
|
|
pattern,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
self.parse_primary_pattern()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 基本パターン
|
|||
|
|
fn parse_primary_pattern(&mut self) -> Result<PatternAst, ParseError> {
|
|||
|
|
match &self.current_token().token_type {
|
|||
|
|
// ワイルドカード
|
|||
|
|
TokenType::UNDERSCORE => {
|
|||
|
|
self.advance();
|
|||
|
|
Ok(PatternAst::Wildcard {
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 識別子(変数バインドまたは構造体パターン)
|
|||
|
|
TokenType::IDENTIFIER(name) => {
|
|||
|
|
let name = name.clone();
|
|||
|
|
self.advance();
|
|||
|
|
|
|||
|
|
if self.match_token(&TokenType::LBRACE) {
|
|||
|
|
// 構造パターン: TypeName { field1, field2, .. }
|
|||
|
|
self.parse_box_pattern(name)
|
|||
|
|
} else {
|
|||
|
|
// 変数バインド
|
|||
|
|
Ok(PatternAst::Identifier {
|
|||
|
|
name,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// リテラル
|
|||
|
|
TokenType::INTEGER(value) => {
|
|||
|
|
let value = *value;
|
|||
|
|
self.advance();
|
|||
|
|
Ok(PatternAst::Literal {
|
|||
|
|
value: crate::ast::LiteralValue::Integer(value),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TokenType::STRING(value) => {
|
|||
|
|
let value = value.clone();
|
|||
|
|
self.advance();
|
|||
|
|
Ok(PatternAst::Literal {
|
|||
|
|
value: crate::ast::LiteralValue::String(value),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 配列パターン
|
|||
|
|
TokenType::LBRACKET => self.parse_array_pattern(),
|
|||
|
|
|
|||
|
|
_ => Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "pattern".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: self.current_token().line,
|
|||
|
|
}),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Box構造パターン: TypeName { field1, field2, .. }
|
|||
|
|
fn parse_box_pattern(&mut self, type_name: String) -> Result<PatternAst, ParseError> {
|
|||
|
|
self.consume(TokenType::LBRACE)?;
|
|||
|
|
self.skip_newlines();
|
|||
|
|
|
|||
|
|
let mut fields = Vec::new();
|
|||
|
|
let mut rest = None;
|
|||
|
|
|
|||
|
|
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
|||
|
|
// rest pattern: ..rest
|
|||
|
|
if self.match_token(&TokenType::DOTDOT) {
|
|||
|
|
self.advance(); // consume '..'
|
|||
|
|
|
|||
|
|
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
|||
|
|
rest = Some(name.clone());
|
|||
|
|
self.advance();
|
|||
|
|
} else {
|
|||
|
|
return Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "identifier after '..'".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: self.current_token().line,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break; // rest patternは最後でなければならない
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// フィールドパターン
|
|||
|
|
let field_name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
|||
|
|
let name = name.clone();
|
|||
|
|
self.advance();
|
|||
|
|
name
|
|||
|
|
} else {
|
|||
|
|
return Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "field name".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: self.current_token().line,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let pattern = if self.match_token(&TokenType::COLON) {
|
|||
|
|
self.advance(); // consume ':'
|
|||
|
|
self.parse_pattern()?
|
|||
|
|
} else {
|
|||
|
|
// 短縮形: field は field: field と同じ
|
|||
|
|
PatternAst::Identifier {
|
|||
|
|
name: field_name.clone(),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fields.push(FieldPattern {
|
|||
|
|
name: field_name,
|
|||
|
|
pattern,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if self.match_token(&TokenType::COMMA) {
|
|||
|
|
self.advance();
|
|||
|
|
self.skip_newlines();
|
|||
|
|
} else if !self.match_token(&TokenType::RBRACE) {
|
|||
|
|
return Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "',' or '}'".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: self.current_token().line,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.consume(TokenType::RBRACE)?;
|
|||
|
|
|
|||
|
|
Ok(PatternAst::BoxPattern {
|
|||
|
|
name: type_name,
|
|||
|
|
fields,
|
|||
|
|
rest,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 配列パターン: [first, second, ...rest]
|
|||
|
|
fn parse_array_pattern(&mut self) -> Result<PatternAst, ParseError> {
|
|||
|
|
self.consume(TokenType::LBRACKET)?;
|
|||
|
|
self.skip_newlines();
|
|||
|
|
|
|||
|
|
let mut elements = Vec::new();
|
|||
|
|
let mut rest = None;
|
|||
|
|
|
|||
|
|
while !self.match_token(&TokenType::RBRACKET) && !self.is_at_end() {
|
|||
|
|
// rest pattern: ...rest
|
|||
|
|
if self.match_token(&TokenType::DOTDOTDOT) {
|
|||
|
|
self.advance(); // consume '...'
|
|||
|
|
|
|||
|
|
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
|||
|
|
rest = Some(name.clone());
|
|||
|
|
self.advance();
|
|||
|
|
} else {
|
|||
|
|
return Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "identifier after '...'".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: self.current_token().line,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break; // rest patternは最後でなければならない
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
elements.push(self.parse_pattern()?);
|
|||
|
|
|
|||
|
|
if self.match_token(&TokenType::COMMA) {
|
|||
|
|
self.advance();
|
|||
|
|
self.skip_newlines();
|
|||
|
|
} else if !self.match_token(&TokenType::RBRACKET) {
|
|||
|
|
return Err(ParseError::UnexpectedToken {
|
|||
|
|
expected: "',' or ']'".to_string(),
|
|||
|
|
found: self.current_token().token_type.clone(),
|
|||
|
|
line: self.current_token().line,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.consume(TokenType::RBRACKET)?;
|
|||
|
|
|
|||
|
|
Ok(PatternAst::ArrayPattern {
|
|||
|
|
elements,
|
|||
|
|
rest,
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**実装タスク**:
|
|||
|
|
```bash
|
|||
|
|
# 1. src/parser/expressions.rs を作成
|
|||
|
|
# 2. src/parser/mod.rs に追加
|
|||
|
|
# 3. 基本的なmatch式のパーステスト
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **Step 1.4: パターンマッチング実行エンジン**
|
|||
|
|
|
|||
|
|
#### **ファイル**: `src/macro_system/pattern_matcher.rs` (新規作成)
|
|||
|
|
```rust
|
|||
|
|
use crate::ast::{ASTNode, PatternAst, FieldPattern, LiteralValue};
|
|||
|
|
use std::collections::HashMap;
|
|||
|
|
|
|||
|
|
#[derive(Debug)]
|
|||
|
|
pub struct PatternMatcher {
|
|||
|
|
bindings: HashMap<String, ASTNode>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Debug)]
|
|||
|
|
pub enum MatchResult {
|
|||
|
|
Success,
|
|||
|
|
Failure,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
impl PatternMatcher {
|
|||
|
|
pub fn new() -> Self {
|
|||
|
|
Self {
|
|||
|
|
bindings: HashMap::new(),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// パターンマッチング実行
|
|||
|
|
pub fn match_pattern(&mut self, pattern: &PatternAst, value: &ASTNode) -> MatchResult {
|
|||
|
|
match (pattern, value) {
|
|||
|
|
// ワイルドカード: 常に成功
|
|||
|
|
(PatternAst::Wildcard { .. }, _) => MatchResult::Success,
|
|||
|
|
|
|||
|
|
// 識別子: 変数バインド
|
|||
|
|
(PatternAst::Identifier { name, .. }, _) => {
|
|||
|
|
self.bindings.insert(name.clone(), value.clone());
|
|||
|
|
MatchResult::Success
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// リテラル: 値比較
|
|||
|
|
(PatternAst::Literal { value: pattern_val, .. },
|
|||
|
|
ASTNode::Literal { value: node_val, .. }) => {
|
|||
|
|
if self.literal_equals(pattern_val, node_val) {
|
|||
|
|
MatchResult::Success
|
|||
|
|
} else {
|
|||
|
|
MatchResult::Failure
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Box構造パターン
|
|||
|
|
(PatternAst::BoxPattern { name: pattern_name, fields: pattern_fields, rest, .. },
|
|||
|
|
ASTNode::BoxDeclaration { name: box_name, fields: box_fields, .. }) => {
|
|||
|
|
if pattern_name == box_name {
|
|||
|
|
self.match_box_fields(pattern_fields, box_fields, rest)
|
|||
|
|
} else {
|
|||
|
|
MatchResult::Failure
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 配列パターン
|
|||
|
|
(PatternAst::ArrayPattern { elements, rest, .. },
|
|||
|
|
ASTNode::Array { elements: array_elements, .. }) => {
|
|||
|
|
self.match_array_elements(elements, array_elements, rest)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ORパターン: いずれかが成功すれば成功
|
|||
|
|
(PatternAst::OrPattern { patterns, .. }, value) => {
|
|||
|
|
for pattern in patterns {
|
|||
|
|
let mut temp_matcher = PatternMatcher::new();
|
|||
|
|
if let MatchResult::Success = temp_matcher.match_pattern(pattern, value) {
|
|||
|
|
// 成功したバインディングをマージ
|
|||
|
|
self.bindings.extend(temp_matcher.bindings);
|
|||
|
|
return MatchResult::Success;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
MatchResult::Failure
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// バインドパターン: 内部パターンマッチ + 変数バインド
|
|||
|
|
(PatternAst::BindPattern { name, pattern, .. }, value) => {
|
|||
|
|
if let MatchResult::Success = self.match_pattern(pattern, value) {
|
|||
|
|
self.bindings.insert(name.clone(), value.clone());
|
|||
|
|
MatchResult::Success
|
|||
|
|
} else {
|
|||
|
|
MatchResult::Failure
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// その他: 失敗
|
|||
|
|
_ => MatchResult::Failure,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// リテラル値の比較
|
|||
|
|
fn literal_equals(&self, a: &LiteralValue, b: &LiteralValue) -> bool {
|
|||
|
|
match (a, b) {
|
|||
|
|
(LiteralValue::Integer(a), LiteralValue::Integer(b)) => a == b,
|
|||
|
|
(LiteralValue::String(a), LiteralValue::String(b)) => a == b,
|
|||
|
|
(LiteralValue::Bool(a), LiteralValue::Bool(b)) => a == b,
|
|||
|
|
(LiteralValue::Null, LiteralValue::Null) => true,
|
|||
|
|
_ => false,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Boxフィールドのマッチング
|
|||
|
|
fn match_box_fields(
|
|||
|
|
&mut self,
|
|||
|
|
pattern_fields: &[FieldPattern],
|
|||
|
|
box_fields: &[String],
|
|||
|
|
rest: &Option<String>,
|
|||
|
|
) -> MatchResult {
|
|||
|
|
// TODO: 実装
|
|||
|
|
// 現在は簡単のため、フィールド名のマッチングのみ
|
|||
|
|
if pattern_fields.len() <= box_fields.len() {
|
|||
|
|
MatchResult::Success
|
|||
|
|
} else {
|
|||
|
|
MatchResult::Failure
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 配列要素のマッチング
|
|||
|
|
fn match_array_elements(
|
|||
|
|
&mut self,
|
|||
|
|
pattern_elements: &[PatternAst],
|
|||
|
|
array_elements: &[ASTNode],
|
|||
|
|
rest: &Option<String>,
|
|||
|
|
) -> MatchResult {
|
|||
|
|
// TODO: 実装
|
|||
|
|
// 現在は簡単のため、要素数のチェックのみ
|
|||
|
|
if pattern_elements.len() <= array_elements.len() {
|
|||
|
|
MatchResult::Success
|
|||
|
|
} else {
|
|||
|
|
MatchResult::Failure
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// バインディング取得
|
|||
|
|
pub fn get_binding(&self, name: &str) -> Option<&ASTNode> {
|
|||
|
|
self.bindings.get(name)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// すべてのバインディング取得
|
|||
|
|
pub fn bindings(&self) -> &HashMap<String, ASTNode> {
|
|||
|
|
&self.bindings
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**実装タスク**:
|
|||
|
|
```bash
|
|||
|
|
# 1. src/macro_system ディレクトリ作成
|
|||
|
|
# 2. pattern_matcher.rs 作成
|
|||
|
|
# 3. src/lib.rs にmacro_systemモジュール追加
|
|||
|
|
# 4. 基本的なパターンマッチテスト
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **Step 1.5: 基本テスト**
|
|||
|
|
|
|||
|
|
#### **ファイル**: `src/tests/pattern_matching_tests.rs` (新規作成)
|
|||
|
|
```rust
|
|||
|
|
#[cfg(test)]
|
|||
|
|
mod tests {
|
|||
|
|
use super::*;
|
|||
|
|
use crate::ast::{ASTNode, PatternAst, LiteralValue};
|
|||
|
|
use crate::macro_system::pattern_matcher::{PatternMatcher, MatchResult};
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn test_wildcard_pattern() {
|
|||
|
|
let mut matcher = PatternMatcher::new();
|
|||
|
|
let pattern = PatternAst::Wildcard { span: crate::ast::Span::unknown() };
|
|||
|
|
let value = ASTNode::Literal {
|
|||
|
|
value: LiteralValue::Integer(42),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let result = matcher.match_pattern(&pattern, &value);
|
|||
|
|
assert!(matches!(result, MatchResult::Success));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn test_identifier_pattern() {
|
|||
|
|
let mut matcher = PatternMatcher::new();
|
|||
|
|
let pattern = PatternAst::Identifier {
|
|||
|
|
name: "x".to_string(),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
let value = ASTNode::Literal {
|
|||
|
|
value: LiteralValue::Integer(42),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let result = matcher.match_pattern(&pattern, &value);
|
|||
|
|
assert!(matches!(result, MatchResult::Success));
|
|||
|
|
|
|||
|
|
// バインディング確認
|
|||
|
|
let binding = matcher.get_binding("x");
|
|||
|
|
assert!(binding.is_some());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn test_literal_pattern_success() {
|
|||
|
|
let mut matcher = PatternMatcher::new();
|
|||
|
|
let pattern = PatternAst::Literal {
|
|||
|
|
value: LiteralValue::Integer(42),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
let value = ASTNode::Literal {
|
|||
|
|
value: LiteralValue::Integer(42),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let result = matcher.match_pattern(&pattern, &value);
|
|||
|
|
assert!(matches!(result, MatchResult::Success));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn test_literal_pattern_failure() {
|
|||
|
|
let mut matcher = PatternMatcher::new();
|
|||
|
|
let pattern = PatternAst::Literal {
|
|||
|
|
value: LiteralValue::Integer(42),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
let value = ASTNode::Literal {
|
|||
|
|
value: LiteralValue::Integer(99),
|
|||
|
|
span: crate::ast::Span::unknown(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let result = matcher.match_pattern(&pattern, &value);
|
|||
|
|
assert!(matches!(result, MatchResult::Failure));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**実装タスク**:
|
|||
|
|
```bash
|
|||
|
|
# 1. テストファイル作成
|
|||
|
|
# 2. cargo test で動作確認
|
|||
|
|
# 3. CI通過確認
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎯 **Phase 1 完成目標**
|
|||
|
|
|
|||
|
|
### **動作する最小例**
|
|||
|
|
```nyash
|
|||
|
|
// パース可能なmatch式
|
|||
|
|
match some_value {
|
|||
|
|
42 => print("Found answer")
|
|||
|
|
x => print("Found: " + x.toString())
|
|||
|
|
_ => print("Unknown")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// パース可能なパターン
|
|||
|
|
BoxDeclaration { name: "Person", fields: [first, ...rest] }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **Phase 1 完了条件**
|
|||
|
|
- [ ] すべてのパターン構文がパース可能
|
|||
|
|
- [ ] 基本的なパターンマッチングが動作
|
|||
|
|
- [ ] テストが緑色(通過)
|
|||
|
|
- [ ] 既存機能に影響なし(回帰テスト通過)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Phase 1が完了したら、Phase 2(Quote/Unquote)の実装に進む!** 🚀
|
|||
|
|
|
|||
|
|
**Next**: [Phase 2実装ガイド](./PHASE2_IMPLEMENTATION.md) | [テスト戦略](./TESTING_STRATEGY.md)
|