461 lines
10 KiB
Markdown
461 lines
10 KiB
Markdown
# @match Macro Quick Start Guide
|
||
|
||
**✅ Status**: **Phase 19 完了(2025-10-08)** - match式 完全実装済み(@matchマクロではなく、正規match構文として)
|
||
|
||
**For**: ~~Developers implementing @match macro~~ **実装完了記録**
|
||
**Time**: ~~6 days~~ **✅ 完了(2025-10-08)**
|
||
**Difficulty**: Medium
|
||
**Prerequisites**: Understanding of Rust parser, AST, macros
|
||
|
||
---
|
||
|
||
## 🎉 実装完了状況(2025-10-08)
|
||
|
||
- ✅ **match式 Parser**: `src/parser/expr/match_expr.rs` (392行)
|
||
- ✅ **Literal patterns**: 整数・文字列・bool対応
|
||
- ✅ **Type patterns**: Box型パターン対応
|
||
- ✅ **Guards**: `if` ガード条件対応
|
||
- ⚠️ **注意**: `@match`マクロ**ではなく**、正規`match`構文として実装
|
||
|
||
**使用例** (実装済み):
|
||
```hakorune
|
||
match result {
|
||
Ok(value) if value > 0 => print("Positive")
|
||
Ok(value) => print("Non-positive")
|
||
Err(e) => print("Error: " + e)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 Quick Links
|
||
|
||
- **Complete Spec**: [@match-macro-implementation-spec.md](./@match-macro-implementation-spec.md)
|
||
- **VariantBox Design**: [DESIGN.md](./DESIGN.md)
|
||
- **Existing Boxes**:
|
||
- [apps/lib/boxes/result.hako](../../../../apps/lib/boxes/result.hako)
|
||
- [apps/lib/boxes/option.hako](../../../../apps/lib/boxes/option.hako)
|
||
|
||
---
|
||
|
||
## 🎯 What You're Building
|
||
|
||
Transform this:
|
||
```hakorune
|
||
@match result {
|
||
Ok(v) => print("Success: " + v)
|
||
Err(e) => print("Error: " + e)
|
||
}
|
||
```
|
||
|
||
Into this:
|
||
```hakorune
|
||
if result._ok == 1 {
|
||
local v
|
||
v = result._val
|
||
print("Success: " + v)
|
||
} else if result._ok == 0 {
|
||
local e
|
||
e = result._err
|
||
print("Error: " + e)
|
||
} else {
|
||
print("[PANIC] non-exhaustive match on Result: unknown state")
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 6-Day Implementation Plan
|
||
|
||
### Day 1: Parser Foundation
|
||
**Goal**: Parse @match syntax without errors
|
||
|
||
**Tasks**:
|
||
1. Add `MatchPattern` to `ASTNode` enum
|
||
2. Add `Pattern` and `MatchArm` structs
|
||
3. Add `FatArrow` and `Underscore` tokens
|
||
4. Implement `parse_match_pattern()` method
|
||
|
||
**Files to Modify**:
|
||
- `src/ast.rs` - AST nodes
|
||
- `src/tokenizer.rs` - Token types
|
||
- `src/parser/statements/mod.rs` - Parser methods
|
||
|
||
**Test**:
|
||
```bash
|
||
# Should parse without error
|
||
echo '@match x { Ok(v) => v }' | ./target/release/hako --dump-ast -
|
||
```
|
||
|
||
### Day 2: Tag Comparison Generation
|
||
**Goal**: Generate if-conditions for patterns
|
||
|
||
**Tasks**:
|
||
1. Create field mapping registry
|
||
2. Implement tag comparison code
|
||
3. Generate if-condition AST nodes
|
||
|
||
**Hardcoded Mappings**:
|
||
```rust
|
||
// Result
|
||
Ok → _ok==1, field: _val
|
||
Err → _ok==0, field: _err
|
||
|
||
// Option
|
||
Some → _is_some==1, field: _value
|
||
None → _is_some==0
|
||
|
||
// Generic VariantBox
|
||
Variant → _tag=="Variant", fields: _0, _1, ...
|
||
```
|
||
|
||
**Test**:
|
||
```hakorune
|
||
@match result { Ok(v) => v }
|
||
// Should generate: if result._ok == 1 { ... }
|
||
```
|
||
|
||
### Day 3: Variable Binding
|
||
**Goal**: Extract pattern variables to local declarations
|
||
|
||
**Tasks**:
|
||
1. Implement binding extraction
|
||
2. Generate `local` declarations
|
||
3. Generate field access code
|
||
4. Insert bindings at top of arm
|
||
|
||
**Test**:
|
||
```hakorune
|
||
@match point { Cartesian(x, y) => print(x + y) }
|
||
// Should generate:
|
||
// local x
|
||
// local y
|
||
// x = point._x
|
||
// y = point._y
|
||
```
|
||
|
||
### Day 4: Exhaustiveness Check
|
||
**Goal**: Add safety panic for unknown variants
|
||
|
||
**Tasks**:
|
||
1. Generate catch-all else clause
|
||
2. Generate panic message
|
||
3. Handle match-as-expression case
|
||
|
||
**Test**:
|
||
```hakorune
|
||
@match result { Ok(v) => v }
|
||
// Should generate final else:
|
||
// else { print("[PANIC] non-exhaustive match...") }
|
||
```
|
||
|
||
### Day 5: Basic Tests (8/15)
|
||
**Goal**: Verify core functionality
|
||
|
||
**Test Files**:
|
||
- Test 1-5: Basic matching
|
||
- Test 6-8: Variable binding
|
||
|
||
**Run**:
|
||
```bash
|
||
./target/release/hako apps/tests/match/test_result_basic.hako
|
||
./target/release/hako apps/tests/match/test_option_basic.hako
|
||
# ... etc
|
||
```
|
||
|
||
### Day 6: Edge Cases (7/15)
|
||
**Goal**: Handle corner cases
|
||
|
||
**Test Files**:
|
||
- Test 9-11: Exhaustiveness
|
||
- Test 12-15: Edge cases (loops, early return, etc.)
|
||
|
||
**Final Verification**:
|
||
```bash
|
||
# Run all match tests
|
||
bash tools/test_match_patterns.sh
|
||
|
||
# Run smoke tests
|
||
tools/smokes/v2/run.sh --profile quick
|
||
```
|
||
|
||
---
|
||
|
||
## 🔑 Key Implementation Points
|
||
|
||
### 1. AST Node Structure
|
||
|
||
```rust
|
||
// src/ast.rs
|
||
pub enum ASTNode {
|
||
// ... existing variants ...
|
||
|
||
MatchPattern {
|
||
scrutinee: Box<ASTNode>,
|
||
arms: Vec<MatchArm>,
|
||
span: Span,
|
||
},
|
||
}
|
||
|
||
pub struct MatchArm {
|
||
pub pattern: Pattern,
|
||
pub body: Vec<ASTNode>,
|
||
pub span: Span,
|
||
}
|
||
|
||
pub enum Pattern {
|
||
Variant {
|
||
name: String,
|
||
bindings: Vec<String>,
|
||
span: Span,
|
||
},
|
||
Wildcard {
|
||
span: Span,
|
||
},
|
||
}
|
||
```
|
||
|
||
### 2. Desugaring Algorithm (Simplified)
|
||
|
||
```rust
|
||
fn desugar_match(scrutinee: &ASTNode, arms: &[MatchArm]) -> ASTNode {
|
||
let mut if_chain = None;
|
||
|
||
// Build if/else chain (reverse order)
|
||
for arm in arms.iter().rev() {
|
||
let condition = generate_tag_check(scrutinee, &arm.pattern);
|
||
let bindings = generate_bindings(scrutinee, &arm.pattern);
|
||
|
||
let mut then_body = bindings;
|
||
then_body.extend(arm.body.clone());
|
||
|
||
if_chain = Some(ASTNode::If {
|
||
condition: Box::new(condition),
|
||
then_body,
|
||
else_body: if_chain.map(|node| vec![node]),
|
||
span: Span::unknown(),
|
||
});
|
||
}
|
||
|
||
// Add exhaustiveness panic
|
||
let panic = ASTNode::Print {
|
||
expression: Box::new(ASTNode::Literal {
|
||
value: LiteralValue::String("[PANIC] non-exhaustive match".to_string()),
|
||
span: Span::unknown(),
|
||
}),
|
||
span: Span::unknown(),
|
||
};
|
||
|
||
if let Some(ASTNode::If { else_body, .. }) = &mut if_chain {
|
||
*else_body = Some(vec![panic]);
|
||
}
|
||
|
||
if_chain.unwrap()
|
||
}
|
||
```
|
||
|
||
### 3. Field Mapping Function
|
||
|
||
```rust
|
||
fn get_field_mapping(variant: &str, binding_index: usize) -> String {
|
||
match variant {
|
||
"Ok" => "_val".to_string(),
|
||
"Err" => "_err".to_string(),
|
||
"Some" => "_value".to_string(),
|
||
"None" => panic!("None has no fields"),
|
||
_ => format!("_{}", binding_index), // Generic: _0, _1, ...
|
||
}
|
||
}
|
||
|
||
fn get_tag_check(variant: &str) -> (&str, &str) {
|
||
match variant {
|
||
"Ok" => ("_ok", "1"),
|
||
"Err" => ("_ok", "0"),
|
||
"Some" => ("_is_some", "1"),
|
||
"None" => ("_is_some", "0"),
|
||
_ => ("_tag", variant), // Generic: _tag == "Variant"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 Testing Strategy
|
||
|
||
### Smoke Test Template
|
||
|
||
```hakorune
|
||
// apps/tests/match/test_NAME.hako
|
||
using "../../lib/boxes/result.hako"
|
||
|
||
static box Main {
|
||
main() {
|
||
// Setup
|
||
local r = Result.ok(42)
|
||
|
||
// Test @match
|
||
local result = @match r {
|
||
Ok(v) => v * 2
|
||
Err(e) => -1
|
||
}
|
||
|
||
// Assert
|
||
if result != 84 {
|
||
print("FAIL: Expected 84, got " + result)
|
||
return 1
|
||
}
|
||
|
||
print("PASS: test_NAME")
|
||
return 0
|
||
}
|
||
}
|
||
```
|
||
|
||
### Running Tests
|
||
|
||
```bash
|
||
# Single test
|
||
./target/release/hako apps/tests/match/test_result_basic.hako
|
||
|
||
# With MIR dump (verify desugaring)
|
||
./target/release/hako --dump-mir apps/tests/match/test_result_basic.hako
|
||
|
||
# All tests
|
||
for f in apps/tests/match/*.hako; do
|
||
echo "Testing $f..."
|
||
./target/release/hako "$f" || echo "FAILED: $f"
|
||
done
|
||
```
|
||
|
||
---
|
||
|
||
## 🚨 Common Pitfalls
|
||
|
||
### 1. Parser Order
|
||
**Problem**: `=>` tokenized as `>` + `=`
|
||
**Solution**: Tokenize `=>` as single `FatArrow` token first
|
||
|
||
### 2. Variable Scope
|
||
**Problem**: Pattern variables leak to outer scope
|
||
**Solution**: Each if-arm creates new scope (via if block)
|
||
|
||
### 3. Expression vs Statement
|
||
**Problem**: Match used as expression (returns value)
|
||
**Solution**: Introduce temporary variable, assign in each arm
|
||
|
||
### 4. Exhaustiveness False Positives
|
||
**Problem**: All cases covered, but panic still added
|
||
**Solution**: Phase 1: Always add panic (safe). Phase 2: Static analysis
|
||
|
||
### 5. Field Name Conflicts
|
||
**Problem**: Multiple variants with same field name
|
||
**Solution**: Use variant-specific field names (`_val` vs `_err`)
|
||
|
||
---
|
||
|
||
## 📊 Progress Checklist
|
||
|
||
### Day 1: Parser
|
||
- [ ] `MatchPattern` AST node added
|
||
- [ ] `Pattern` enum added
|
||
- [ ] `FatArrow` token added
|
||
- [ ] `parse_match_pattern()` implemented
|
||
- [ ] Can parse basic @match without error
|
||
|
||
### Day 2: Tag Comparison
|
||
- [ ] Field mapping registry created
|
||
- [ ] Tag check generation works
|
||
- [ ] If-condition AST generation works
|
||
- [ ] Can generate if/else skeleton
|
||
|
||
### Day 3: Variable Binding
|
||
- [ ] Binding extraction works
|
||
- [ ] `local` declarations generated
|
||
- [ ] Field access generated
|
||
- [ ] Bindings inserted correctly
|
||
|
||
### Day 4: Exhaustiveness
|
||
- [ ] Catch-all else clause works
|
||
- [ ] Panic message generated
|
||
- [ ] Match-as-expression works
|
||
- [ ] Null assignment in panic case
|
||
|
||
### Day 5: Basic Tests
|
||
- [ ] Test 1: Result basic - PASS
|
||
- [ ] Test 2: Option basic - PASS
|
||
- [ ] Test 3: 3-way variant - PASS
|
||
- [ ] Test 4: Expression - PASS
|
||
- [ ] Test 5: Multi-statement - PASS
|
||
- [ ] Test 6: Single binding - PASS
|
||
- [ ] Test 7: Multi binding - PASS
|
||
- [ ] Test 8: Shadowing - PASS
|
||
|
||
### Day 6: Edge Cases
|
||
- [ ] Test 9: Exhaustive - PASS
|
||
- [ ] Test 10: Non-exhaustive - PASS
|
||
- [ ] Test 11: Wildcard - PASS
|
||
- [ ] Test 12: 0-field variant - PASS
|
||
- [ ] Test 13: Nested if - PASS
|
||
- [ ] Test 14: Loop control - PASS
|
||
- [ ] Test 15: Early return - PASS
|
||
|
||
### Final
|
||
- [ ] All 15 tests PASS
|
||
- [ ] Smoke tests PASS
|
||
- [ ] Documentation complete
|
||
- [ ] Ready for production
|
||
|
||
---
|
||
|
||
## 🎓 Learning Resources
|
||
|
||
### Rust Parser References
|
||
- `src/parser/expressions.rs` - Expression parsing patterns
|
||
- `src/parser/statements/mod.rs` - Statement parsing
|
||
- `src/ast.rs` - AST node definitions
|
||
|
||
### Existing Macro Examples
|
||
- `src/macro/macro_box.rs` - MacroBox trait
|
||
- `apps/macros/loop_normalize_macro.nyash` - Complex desugaring
|
||
|
||
### Hakorune Box Examples
|
||
- `apps/lib/boxes/result.hako` - Result implementation
|
||
- `apps/lib/boxes/option.hako` - Option implementation
|
||
|
||
---
|
||
|
||
## 💡 Tips for Success
|
||
|
||
1. **Start Simple**: Test with 2-arm match first (Ok/Err)
|
||
2. **Use --dump-mir**: Verify desugared code constantly
|
||
3. **Incremental Testing**: Test each day's work immediately
|
||
4. **Reference Phase 16**: @derive implementation is similar pattern
|
||
5. **Ask Questions**: Consult complete spec for details
|
||
|
||
---
|
||
|
||
## 🆘 When Stuck
|
||
|
||
### Debug Commands
|
||
```bash
|
||
# See parsed AST
|
||
./target/release/hako --dump-ast test.hako
|
||
|
||
# See desugared code
|
||
./target/release/hako --dump-mir test.hako
|
||
|
||
# Verbose diagnostics
|
||
NYASH_CLI_VERBOSE=1 ./target/release/hako test.hako
|
||
```
|
||
|
||
### Common Issues
|
||
- **Parse Error**: Check tokenizer output
|
||
- **Wrong Desugaring**: Check field mapping registry
|
||
- **Test Failure**: Use --dump-mir to compare expected vs actual
|
||
|
||
---
|
||
|
||
**Ready to Start?** Begin with Day 1 tasks and refer to the complete spec for detailed algorithms!
|
||
|
||
**Questions?** Check [@match-macro-implementation-spec.md](./@match-macro-implementation-spec.md) for comprehensive details.
|