# Phase 171: JsonParserBox 実装 & hako_check への導入 ## 0. ゴール **Phase 170 で決めた API 草案どおりに .hako 純正 JSON パーサ Box(JsonParserBox)を実装する。** 目的: - JSON 文字列をメモリ内オブジェクトに変換する Box を実装 - hako_check (HC020) で Phase 156 の手書き JSON パーサ(289行)を置き換え - 将来の selfhost/Stage-B/解析ツールから再利用可能に --- ## 1. 背景と戦略 ### Phase 156 の課題 - hako_check の `analysis_consumer.hako` に 289行の手書き JSON パーサ - 他の HC ルール(HC021+)でも JSON 解析が必要になる - 共通ライブラリ化が急務 ### Phase 170 での決定 - **API 草案**: `parse()`, `parse_object()`, `parse_array()` - **型定義**: JsonValueBox(union 相当), JsonObjectBox, JsonArrayBox - **MVP スコープ**: MIR/CFG JSON のみ対応(Program JSON v0 は Phase 172+) ### Phase 171 の戦略 1. **最小MVP実装**: MIR/CFG JSON が使う型のみ 2. **段階的導入**: hako_check (HC020) が最初のユーザー 3. **共通ライブラリ化**: `tools/hako_shared/` で shared 設計 --- ## 2. Scope / Non-scope ### ✅ やること 1. **JsonParserBox の実装** - `parse(json_str) -> JsonValue?` - `parse_object(json_str) -> JsonObjectBox?` - `parse_array(json_str) -> JsonArrayBox?` 2. **内部型の定義** - JsonValueBox(kind で型判定) - JsonObjectBox(key-value map) - JsonArrayBox(value list) 3. **エスケープシーケンス対応** - MIR JSON が実際に使う範囲:`\"`, `\\`, `\n`, `\t` など - Unicode (`\uXXXX`) は Phase 172+ 4. **hako_check への導入** - `analysis_consumer.hako` の 289行手書きパーサを削除 - JsonParserBox を使用した 10行規模実装に置き換え - HC019/HC020 の出力が変わらないことを確認 5. **テスト** - JsonParserBox 単体テスト(正常系・エラー系) - hako_check スモークテスト(回帰なし) ### ❌ やらないこと - to_json() / serialization(Phase 172+) - スキーマ検証(Phase 172+) - Program JSON v0 対応(Phase 172+) - ストリーミングパーサ(Phase 173+) --- ## 3. Task 1: 設計ドキュメントの確認・MVP の切り出し ### やること 1. **Phase 170 設計の再確認** - `docs/private/roadmap2/phases/phase-170-hako-json-library/README.md` を読む - API 草案を確認(parse, parse_object, parse_array) - 型定義案を確認(JsonValueBox, JsonObjectBox, JsonArrayBox) 2. **MVP スコープの確定** - MIR/CFG JSON のサブセット: - 型: null, bool, number, string, object, array - number: 整数のみ(浮動小数点は未対応) - string: 基本的なエスケープのみ - object: キーは文字列固定 - エラーハンドリング: null を返す 3. **実装方針の決定** - Phase 156 の手書きパーサを参照しつつ - StringBox.substring/indexOf で文字列走査 - 状態機械を最小化(シンプル > 最適化) ### 成果物 - MVP の詳細仕様書(Task 2 への引き継ぎ) --- ## 4. Task 2: JsonParserBox の実装 (.hako) ### ファイル位置 ``` tools/hako_shared/ ├── json_parser.hako ← 新規作成 └── tests/ └── json_parser_test.hako ← 新規作成 ``` ### やること 1. **Box 構造の定義** ```hako // JsonValueBox: union 相当の型 static box JsonValueBox { kind: StringBox # "null", "bool", "number", "string", "array", "object" boolVal: BoolBox numVal: IntegerBox strVal: StringBox arrVal: JsonArrayBox objVal: JsonObjectBox method is_null() { return me.kind == "null" } method is_bool() { return me.kind == "bool" } method is_number() { return me.kind == "number" } method is_string() { return me.kind == "string" } method is_array() { return me.kind == "array" } method is_object() { return me.kind == "object" } } // JsonObjectBox: key-value map static box JsonObjectBox { pairs: ArrayBox # [{"key": String, "value": JsonValue}, ...] method get(key: String) -> JsonValue? { ... } method keys() -> ArrayBox { ... } method values() -> ArrayBox { ... } } // JsonArrayBox: value list static box JsonArrayBox { elements: ArrayBox # [JsonValue, ...] method get(index: Integer) -> JsonValue? { ... } method length() -> Integer { ... } } // JsonParserBox: メインパーサ static box JsonParserBox { method parse(json_str: String) -> JsonValue? { ... } method parse_object(json_str: String) -> JsonObjectBox? { ... } method parse_array(json_str: String) -> JsonArrayBox? { ... } } ``` 2. **JSON パーサ実装** ```hako static box JsonParserBox { method parse(json_str: String) -> JsonValue? { // 前後の空白を削除 local s = me._trim(json_str) if s.length() == 0 { return null } local result = null local pos = me._parse_value(s, 0, result) if pos < 0 { return null } return result } method parse_object(json_str: String) -> JsonObjectBox? { local val = me.parse(json_str) if val == null or not val.is_object() { return null } return val.objVal } method parse_array(json_str: String) -> JsonArrayBox? { local val = me.parse(json_str) if val == null or not val.is_array() { return null } return val.arrVal } // 内部ヘルパー method _parse_value(s: String, pos: Integer, out: JsonValue) -> Integer { ... } method _parse_null(s: String, pos: Integer, out: JsonValue) -> Integer { ... } method _parse_bool(s: String, pos: Integer, out: JsonValue) -> Integer { ... } method _parse_number(s: String, pos: Integer, out: JsonValue) -> Integer { ... } method _parse_string(s: String, pos: Integer, out: JsonValue) -> Integer { ... } method _parse_array(s: String, pos: Integer, out: JsonValue) -> Integer { ... } method _parse_object(s: String, pos: Integer, out: JsonValue) -> Integer { ... } method _skip_whitespace(s: String, pos: Integer) -> Integer { ... } method _trim(s: String) -> String { ... } method _unescape_string(s: String) -> String { ... } } ``` 3. **エスケープシーケンス対応** MIR JSON で使われるもの: - `\"` → `"` - `\\` → `\` - `\n` → newline - `\t` → tab - `\r` → carriage return - `\b`, `\f` → 対応可(Phase 171) - `\uXXXX` → 未対応(Phase 172+) ### 成果物 - JsonParserBox 実装(約300-400行推定) - 単体テストケース --- ## 5. Task 3: hako_check (HC020) から JsonParserBox を使うように置き換え ### 対象ファイル - `tools/hako_check/analysis_consumer.hako` (Lines 206-494: 手書きパーサ) - `tools/hako_check/rules/rule_dead_blocks.hako` ### やること 1. **JSON パーサ部分の削除** - `analysis_consumer.hako` の手書きパーサ関数を全削除(~289行) - 代わりに JsonParserBox を import 2. **CFG 処理の修正** ```hako // 修正前(Phase 156) local mir_json_text = ir.get("_mir_json_text") local cfg_obj = me._parse_json_object(mir_json_text) // 手写 // 修正後(Phase 171) local mir_json_text = ir.get("_mir_json_text") local cfg_val = JsonParserBox.parse(mir_json_text) local cfg_obj = cfg_val.objVal // JsonObjectBox を取得 ``` 3. **HC020 ルール側の確認** - `rule_dead_blocks.hako` が CFG を正しく解析できるか確認 - JsonObjectBox/.get() メソッドとの互換性確認 4. **目標** - `analysis_consumer.hako` の行数を 500+ → 210 に削減(約60%削減) - Phase 156 の 289行手書きパーサの削除 ### 成果物 - 修正済み `analysis_consumer.hako` - 修正済み `tools/hako_check/` 関連ファイル --- ## 6. Task 4: 単体テスト & スモークテスト ### JsonParserBox 単体テスト 新規作成: `tools/hako_shared/tests/json_parser_test.hako` テストケース: ```hako static box JsonParserTest { main() { me.test_null() me.test_bool() me.test_number() me.test_string() me.test_array() me.test_object() me.test_error_cases() print("All tests passed!") } test_null() { local val = JsonParserBox.parse("null") assert(val != null && val.is_null()) } test_bool() { local t = JsonParserBox.parse("true") local f = JsonParserBox.parse("false") assert(t.is_bool() && t.boolVal == true) assert(f.is_bool() && f.boolVal == false) } test_number() { local n = JsonParserBox.parse("123") assert(n.is_number() && n.numVal == 123) } test_string() { local s = JsonParserBox.parse('"hello"') assert(s.is_string() && s.strVal == "hello") } test_array() { local arr = JsonParserBox.parse("[1, 2, 3]") assert(arr.is_array() && arr.arrVal.length() == 3) } test_object() { local obj = JsonParserBox.parse('{"key": "value"}') assert(obj.is_object()) local val = obj.objVal.get("key") assert(val != null && val.is_string()) } test_error_cases() { local inv1 = JsonParserBox.parse("{") assert(inv1 == null) local inv2 = JsonParserBox.parse("[1,2,]") assert(inv2 == null) } } ``` ### hako_check スモークテスト 既存のスモークテストを実行: ```bash # HC020 スモーク ./tools/hako_check_deadblocks_smoke.sh # HC019 スモーク(回帰なし) ./tools/hako_check_deadcode_smoke.sh ``` 期待: - HC020 出力が Phase 156 と変わらない - HC019 出力が変わらない - エラーが出ない ### 成果物 - JsonParserBox テストファイル - スモークテスト成功確認 --- ## 7. Task 5: ドキュメント & CURRENT_TASK 更新 ### ドキュメント更新 1. **phase170_hako_json_library_design.md に追記** ```markdown ## Phase 171 実装結果 ✅ JsonParserBox 実装完了 - API: parse(), parse_object(), parse_array() - サポート型: null, bool, number, string, array, object - エスケープ: \", \\, \n, \t, \r, \b, \f ✅ hako_check への導入完了 - analysis_consumer.hako: 500+ → 210 行(~60%削減) - Phase 156 の 289行手書きパーサを削除 - HC020 が JsonParserBox を使用 📊 削減実績: - hako_check パーサ: 289行 → JsonParserBox 呼び出し(~10行) - 共通ライブラリ化: hako_check/selfhost/ツールから再利用可能 ``` 2. **hako_check_design.md を更新** ```markdown ### JSON 解析 - Phase 156 まで: analysis_consumer.hako に手書きパーサ(289行) - Phase 171 から: JsonParserBox を使用 - 場所: tools/hako_shared/json_parser.hako ``` 3. **CURRENT_TASK.md に Phase 171 セクション追加** ```markdown ### Phase 171: JsonParserBox 実装 & hako_check 導入 ✅ **完了内容**: - JsonParserBox 実装(tools/hako_shared/json_parser.hako) - hako_check (HC020) が JsonParserBox を使用 - 手書きパーサ 289行 → 共通ライブラリに統合 **成果**: - hako_check 行数削減: 60% (500+ → 210 行) - 再利用性: HC021+, selfhost, Stage-B で活用可 - 箱化: .hako 純正 JSON パーサ Box 確立 **次フェーズ**: Phase 172 で selfhost/Stage-B への導入予定 ``` ### git commit ``` feat(hako_check): Phase 171 JsonParserBox implementation ✨ .hako 純正 JSON パーサ Box 実装完了! 🎯 実装内容: - JsonParserBox: parse/parse_object/parse_array メソッド - JsonValueBox, JsonObjectBox, JsonArrayBox 型定義 - エスケープシーケンス対応(\", \\, \n, \t など) 📊 hako_check 統合: - analysis_consumer.hako: 289行手書きパーサを削除 - JsonParserBox を使用した軽量実装に置き換え - HC019/HC020 の出力は変わらず(回帰なし) ✅ テスト: - JsonParserBox 単体テスト全 PASS - hako_check スモークテスト全 PASS - Phase 156 との後方互換性確認 🏗️ 次ステップ: - Phase 172: selfhost/Stage-B への導入 - Phase 172+: Program JSON v0 対応 - Phase 173+: to_json() 逆変換実装 ``` --- ## ✅ 完成チェックリスト(Phase 171) - [ ] Task 1: 設計ドキュメント確認・MVP 切り出し - [ ] Phase 170 設計再確認 - [ ] MVP スコープ確定 - [ ] 実装方針決定 - [ ] Task 2: JsonParserBox 実装 - [ ] Box 構造定義 - [ ] パーサ実装 - [ ] エスケープシーケンス対応 - [ ] Task 3: hako_check への導入 - [ ] analysis_consumer.hako 修正 - [ ] 手書きパーサ削除 - [ ] 行数削減確認(500+ → 210) - [ ] Task 4: テスト & スモーク - [ ] JsonParserBox 単体テスト - [ ] hako_check スモークテスト - [ ] 回帰なし確認 - [ ] Task 5: ドキュメント更新 - [ ] phase170 に追記 - [ ] hako_check_design.md 更新 - [ ] CURRENT_TASK.md 追加 - [ ] git commit --- ## 技術的ポイント ### JSON 解析の状態機械 ``` parse_value: | "null" → parse_null | "true"|"false" → parse_bool | digit → parse_number | '"' → parse_string | '[' → parse_array | '{' → parse_object | else → error (null) ``` ### ArrayBox / MapBox との互換性 ```hako // JsonArrayBox.get() は ArrayBox.get() と同じ感覚で local elem = json_array.get(0) // JsonObjectBox.get() は MapBox.get() と同じ感覚で local val = json_obj.get("key") ``` ### エラーハンドリング - 構文エラー → null を返す(Nyash 的な nil) - 期待値と異なる型 → null を返す ### パフォーマンス Phase 171 MVP は正確性 > 速度を優先。最適化は Phase 173+ へ。 --- **作成日**: 2025-12-04 **Phase**: 171(JsonParserBox 実装 & hako_check 導入) **予定工数**: 3-4 時間 **難易度**: 中(JSON パーサ実装 + .hako での Box 設計) **期待削減**: hako_check 行数 60%、コード共通化 100%