375 lines
11 KiB
Markdown
375 lines
11 KiB
Markdown
|
|
# Phase 20: Rust → Hakorune マクロ移行ガイド
|
|||
|
|
|
|||
|
|
Date: 2025-10-06
|
|||
|
|
Status: **REFERENCE** - Phase 20実装時に参照
|
|||
|
|
|
|||
|
|
## 🎯 **移行の目的**
|
|||
|
|
|
|||
|
|
Phase 16で実装したRust版マクロシステムの主要機能を、
|
|||
|
|
Hakoruneでセルフホスト実装に書き直す。
|
|||
|
|
|
|||
|
|
## 📋 **移行対象機能一覧**
|
|||
|
|
|
|||
|
|
### **優先度 HIGH: 即座に便利**
|
|||
|
|
| 機能 | Rust実装場所 | 移行先(案) | 難易度 |
|
|||
|
|
|------|-------------|-------------|--------|
|
|||
|
|
| @derive(Equals) | `src/macro/engine.rs:151-174` | `apps/macros/derive/derive_equals.hako` | ⭐⭐ |
|
|||
|
|
| @derive(ToString) | `src/macro/engine.rs:176-194` | `apps/macros/derive/derive_tostring.hako` | ⭐⭐ |
|
|||
|
|
| @test ランナー | `src/macro/mod.rs:61-401` | `apps/macros/test/test_runner.hako` | ⭐⭐⭐ |
|
|||
|
|
|
|||
|
|
### **優先度 MEDIUM: 複雑なマクロ用基盤**
|
|||
|
|
| 機能 | Rust実装場所 | 移行先(案) | 難易度 |
|
|||
|
|
|------|-------------|-------------|--------|
|
|||
|
|
| TemplatePattern | `src/macro/pattern.rs:116-238` | `apps/macros/pattern/template_pattern.hako` | ⭐⭐⭐⭐ |
|
|||
|
|
| Quote/Unquote | `src/macro/pattern.rs:9-114` | `apps/macros/quote/quote_system.hako` | ⭐⭐⭐⭐ |
|
|||
|
|
| MacroEngine | `src/macro/engine.rs:11-101` | `apps/macros/engine/macro_engine.hako` | ⭐⭐⭐ |
|
|||
|
|
|
|||
|
|
### **優先度 LOW: 構文サポート(Parser側)**
|
|||
|
|
| 機能 | Rust実装場所 | 移行判断 |
|
|||
|
|
|------|-------------|----------|
|
|||
|
|
| Match式構文 | `src/parser/expr/match_expr.rs` | **移行不要**(Rust Parserのまま維持) |
|
|||
|
|
| PatternAst | `src/ast.rs` | **移行不要**(AST定義はRust) |
|
|||
|
|
|
|||
|
|
## 🔄 **移行戦略: 3つのアプローチ**
|
|||
|
|
|
|||
|
|
### **Strategy 1: JSON AST直接操作(推奨)**
|
|||
|
|
|
|||
|
|
**使用場面**: @derive, @test
|
|||
|
|
**難易度**: ⭐⭐
|
|||
|
|
|
|||
|
|
```hakorune
|
|||
|
|
// apps/macros/derive/derive_equals.hako
|
|||
|
|
static box DeriveEqualsMacro {
|
|||
|
|
expand(json_ast, ctx) {
|
|||
|
|
// 1. JSON文字列をパース
|
|||
|
|
local ast, box_decl, fields, new_method
|
|||
|
|
ast = JSON.parse(json_ast)
|
|||
|
|
|
|||
|
|
// 2. BoxDeclaration検出
|
|||
|
|
if ast.kind != "Program" {
|
|||
|
|
return json_ast
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. public_fieldsを取得
|
|||
|
|
fields = box_decl.public_fields
|
|||
|
|
|
|||
|
|
// 4. equals()メソッドJSON生成
|
|||
|
|
new_method = me.build_equals_method(fields)
|
|||
|
|
|
|||
|
|
// 5. methodsに追加
|
|||
|
|
box_decl.methods.set("equals", new_method)
|
|||
|
|
|
|||
|
|
// 6. JSON文字列化
|
|||
|
|
return JSON.stringify(ast)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
build_equals_method(fields) {
|
|||
|
|
// JSON AST構築(詳細は後述)
|
|||
|
|
return method_json
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**利点**:
|
|||
|
|
- ✅ 現在の暫定実装(JSON文字列操作)の延長
|
|||
|
|
- ✅ Hakoruneの機能だけで完結
|
|||
|
|
- ✅ バグが少ない
|
|||
|
|
|
|||
|
|
**欠点**:
|
|||
|
|
- ❌ JSON操作が冗長
|
|||
|
|
- ❌ 複雑なマクロは困難
|
|||
|
|
|
|||
|
|
### **Strategy 2: AstBuilderBox実装(中間層)**
|
|||
|
|
|
|||
|
|
**使用場面**: Pattern Matching, Quote/Unquote
|
|||
|
|
**難易度**: ⭐⭐⭐⭐
|
|||
|
|
|
|||
|
|
```hakorune
|
|||
|
|
// apps/macros/ast/ast_builder.hako
|
|||
|
|
box AstBuilderBox {
|
|||
|
|
// JSON ASTをHakoruneオブジェクトにラップ
|
|||
|
|
root: MapBox
|
|||
|
|
|
|||
|
|
birth(json_str) {
|
|||
|
|
from MapBox.birth()
|
|||
|
|
me.root = JSON.parse(json_str)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ヘルパーメソッド
|
|||
|
|
find_box_decl(name) {
|
|||
|
|
// AST走査してBoxDeclaration検出
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
add_method(box_name, method_name, body_ast) {
|
|||
|
|
// メソッド追加
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
to_json() {
|
|||
|
|
return JSON.stringify(me.root)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**利点**:
|
|||
|
|
- ✅ API設計でRust版に近い書き方が可能
|
|||
|
|
- ✅ 複雑なマクロ実装が楽
|
|||
|
|
|
|||
|
|
**欠点**:
|
|||
|
|
- ❌ AstBuilderBox実装コストが大きい
|
|||
|
|
- ❌ JSON↔オブジェクト変換オーバーヘッド
|
|||
|
|
|
|||
|
|
### **Strategy 3: Rust Hybrid(非推奨)**
|
|||
|
|
|
|||
|
|
Rust側にヘルパー関数を残し、Hakoruneから呼ぶ。
|
|||
|
|
|
|||
|
|
**使用場面**: なし(セルフホスティングの意義を損なう)
|
|||
|
|
**難易度**: ⭐
|
|||
|
|
|
|||
|
|
❌ **推奨しない理由**:
|
|||
|
|
- Phase 20の目的は「Rust依存を減らす」こと
|
|||
|
|
- ハイブリッドは複雑性が増すだけ
|
|||
|
|
|
|||
|
|
## 📝 **移行実装例: @derive(Equals)**
|
|||
|
|
|
|||
|
|
### **Rust版(参考)**
|
|||
|
|
```rust
|
|||
|
|
// src/macro/engine.rs:151-174
|
|||
|
|
fn build_equals_method(_box_name: &str, fields: &Vec<String>) -> ASTNode {
|
|||
|
|
let cond = if fields.is_empty() {
|
|||
|
|
ASTNode::Literal { value: LiteralValue::Bool(true), span: Span::unknown() }
|
|||
|
|
} else {
|
|||
|
|
let mut it = fields.iter();
|
|||
|
|
let first = it.next().unwrap();
|
|||
|
|
let mut expr = bin_eq(me_field(first), var_field("__ny_other", first));
|
|||
|
|
for f in it {
|
|||
|
|
expr = bin_and(expr, bin_eq(me_field(f), var_field("__ny_other", f)));
|
|||
|
|
}
|
|||
|
|
expr
|
|||
|
|
};
|
|||
|
|
ASTNode::FunctionDeclaration {
|
|||
|
|
name: "equals".to_string(),
|
|||
|
|
params: vec!["__ny_other".to_string()],
|
|||
|
|
body: vec![ASTNode::Return { value: Some(Box::new(cond)), span: Span::unknown() }],
|
|||
|
|
is_static: false,
|
|||
|
|
is_override: false,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **Hakorune版(Strategy 1: JSON直接操作)**
|
|||
|
|
```hakorune
|
|||
|
|
// apps/macros/derive/derive_equals.hako
|
|||
|
|
static box DeriveEqualsMacro {
|
|||
|
|
expand(json_str, ctx) {
|
|||
|
|
local ast, stmts, i, modified
|
|||
|
|
ast = JSON.parse(json_str)
|
|||
|
|
|
|||
|
|
if ast.kind != "Program" {
|
|||
|
|
return json_str
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
stmts = ast.statements
|
|||
|
|
modified = false
|
|||
|
|
i = 0
|
|||
|
|
|
|||
|
|
loop(i < stmts.length()) {
|
|||
|
|
local stmt
|
|||
|
|
stmt = stmts.get(i)
|
|||
|
|
|
|||
|
|
if stmt.kind == "BoxDeclaration" {
|
|||
|
|
// public_fieldsチェック
|
|||
|
|
if stmt.public_fields && stmt.public_fields.length() > 0 {
|
|||
|
|
// equals()メソッドが存在しないか確認
|
|||
|
|
if !stmt.methods || !stmt.methods.has("equals") {
|
|||
|
|
// equals()生成
|
|||
|
|
local equals_method
|
|||
|
|
equals_method = me.build_equals_json(stmt.name, stmt.public_fields)
|
|||
|
|
|
|||
|
|
// methodsに追加
|
|||
|
|
if !stmt.methods {
|
|||
|
|
stmt.methods = json({})
|
|||
|
|
}
|
|||
|
|
stmt.methods.set("equals", equals_method)
|
|||
|
|
modified = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if modified {
|
|||
|
|
return JSON.stringify(ast)
|
|||
|
|
} else {
|
|||
|
|
return json_str
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
build_equals_json(box_name, fields) {
|
|||
|
|
// JSON AST for: equals(__ny_other) { return me.f1 == __ny_other.f1 && ... }
|
|||
|
|
local body, cond, i
|
|||
|
|
|
|||
|
|
if fields.length() == 0 {
|
|||
|
|
cond = json({
|
|||
|
|
"kind": "Literal",
|
|||
|
|
"value": true,
|
|||
|
|
"span": json({})
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
i = 0
|
|||
|
|
cond = null
|
|||
|
|
|
|||
|
|
loop(i < fields.length()) {
|
|||
|
|
local field_name, eq_expr
|
|||
|
|
field_name = fields.get(i)
|
|||
|
|
|
|||
|
|
// me.field == __ny_other.field
|
|||
|
|
eq_expr = json({
|
|||
|
|
"kind": "BinaryOp",
|
|||
|
|
"operator": "Equal",
|
|||
|
|
"left": json({
|
|||
|
|
"kind": "FieldAccess",
|
|||
|
|
"object": json({ "kind": "Me", "span": json({}) }),
|
|||
|
|
"field": field_name,
|
|||
|
|
"span": json({})
|
|||
|
|
}),
|
|||
|
|
"right": json({
|
|||
|
|
"kind": "FieldAccess",
|
|||
|
|
"object": json({ "kind": "Variable", "name": "__ny_other", "span": json({}) }),
|
|||
|
|
"field": field_name,
|
|||
|
|
"span": json({})
|
|||
|
|
}),
|
|||
|
|
"span": json({})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if !cond {
|
|||
|
|
cond = eq_expr
|
|||
|
|
} else {
|
|||
|
|
// cond && eq_expr
|
|||
|
|
cond = json({
|
|||
|
|
"kind": "BinaryOp",
|
|||
|
|
"operator": "And",
|
|||
|
|
"left": cond,
|
|||
|
|
"right": eq_expr,
|
|||
|
|
"span": json({})
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// FunctionDeclaration
|
|||
|
|
return json({
|
|||
|
|
"kind": "FunctionDeclaration",
|
|||
|
|
"name": "equals",
|
|||
|
|
"params": arr(["__ny_other"]),
|
|||
|
|
"body": arr([
|
|||
|
|
json({
|
|||
|
|
"kind": "Return",
|
|||
|
|
"value": cond,
|
|||
|
|
"span": json({})
|
|||
|
|
})
|
|||
|
|
]),
|
|||
|
|
"is_static": false,
|
|||
|
|
"is_override": false,
|
|||
|
|
"span": json({})
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🧪 **テスト戦略**
|
|||
|
|
|
|||
|
|
### **1. ユニットテスト(Hakorune版)**
|
|||
|
|
```hakorune
|
|||
|
|
// apps/macros/derive/test_derive_equals.hako
|
|||
|
|
static box TestDeriveEquals {
|
|||
|
|
test_simple_box() {
|
|||
|
|
local input, expected, actual
|
|||
|
|
|
|||
|
|
input = json({
|
|||
|
|
"kind": "Program",
|
|||
|
|
"statements": arr([
|
|||
|
|
json({
|
|||
|
|
"kind": "BoxDeclaration",
|
|||
|
|
"name": "Person",
|
|||
|
|
"public_fields": arr(["name", "age"]),
|
|||
|
|
"methods": json({})
|
|||
|
|
})
|
|||
|
|
])
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
actual = DeriveEqualsMacro.expand(JSON.stringify(input), "{}")
|
|||
|
|
|
|||
|
|
// equals()メソッドが追加されていることを確認
|
|||
|
|
local result
|
|||
|
|
result = JSON.parse(actual)
|
|||
|
|
assert(result.statements.get(0).methods.has("equals"))
|
|||
|
|
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **2. 統合テスト(スモークテスト)**
|
|||
|
|
```bash
|
|||
|
|
# tools/smokes/v2/profiles/quick/macro/derive_equals_vm.sh
|
|||
|
|
#!/usr/bin/env bash
|
|||
|
|
# Smoke: @derive(Equals) basic functionality
|
|||
|
|
|
|||
|
|
NYASH_MACRO_ENABLE=1 \
|
|||
|
|
NYASH_MACRO_PATHS=apps/macros/derive/derive_equals.hako \
|
|||
|
|
./target/release/hako apps/tests/macro_derive_person.hako
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **3. パリティテスト(Rust版 vs Hakorune版)**
|
|||
|
|
```bash
|
|||
|
|
# 同じ入力に対して同じ出力を生成するか確認
|
|||
|
|
diff <(NYASH_MACRO_DERIVE_BACKEND=rust ./hako test.hako --dump-ast) \
|
|||
|
|
<(NYASH_MACRO_DERIVE_BACKEND=hako ./hako test.hako --dump-ast)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📊 **進捗管理**
|
|||
|
|
|
|||
|
|
### **Phase 20.1: @derive(Equals)実装**
|
|||
|
|
- [ ] DeriveEqualsMacro.build_equals_json() 実装
|
|||
|
|
- [ ] ユニットテスト作成・通過
|
|||
|
|
- [ ] スモークテスト作成・通過
|
|||
|
|
- [ ] パリティテスト通過(Rust版と同一出力)
|
|||
|
|
- [ ] ドキュメント作成
|
|||
|
|
|
|||
|
|
### **Phase 20.2: @derive拡張**
|
|||
|
|
- [ ] DeriveToStringMacro 実装
|
|||
|
|
- [ ] DeriveCloneMacro 実装
|
|||
|
|
- [ ] DeriveDebugMacro 実装
|
|||
|
|
|
|||
|
|
### **Phase 20.3: @test ランナー**
|
|||
|
|
- [ ] TestCollectorBox 実装
|
|||
|
|
- [ ] TestRunnerBox 実装
|
|||
|
|
- [ ] JSON引数注入対応
|
|||
|
|
|
|||
|
|
## 🚨 **リスク管理**
|
|||
|
|
|
|||
|
|
### **Risk 1: JSON操作の複雑性**
|
|||
|
|
**対策**: AstBuilderBoxヘルパーを段階的に実装
|
|||
|
|
|
|||
|
|
### **Risk 2: パフォーマンス劣化**
|
|||
|
|
**対策**: ベンチマーク必須(目標: Rust版の2倍以内)
|
|||
|
|
|
|||
|
|
### **Risk 3: バグ再発**
|
|||
|
|
**対策**: Rust版の既知バグを事前に洗い出し、テストケース化
|
|||
|
|
|
|||
|
|
## 🔗 **参考資料**
|
|||
|
|
|
|||
|
|
- [Phase 16 README](../phase-16-macro-revolution/README.md) - Rust版設計書
|
|||
|
|
- [Phase 16 IMPLEMENTATION](../phase-16-macro-revolution/IMPLEMENTATION.md) - Rust版実装ガイド
|
|||
|
|
- `src/macro/engine.rs` - derive実装参考
|
|||
|
|
- `src/macro/pattern.rs` - Pattern/Quote実装参考
|
|||
|
|
- `src/macro/mod.rs` - @test実装参考
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Next**: Phase 15.7完了後、Phase 20.1開始
|