feat(naming): Python NamingHelper実装 - Rust NamingBoxのミラー完成

Phase 25.4 メンテナンス: Python LLVM側のNamingBox SSOT統一

## 📦 実装内容

### 1. Python NamingHelper作成
- 新規作成: `src/llvm_py/naming_helper.py`
- Rust `src/mir/naming.rs` と完全同一の意味論を実装
- 3つの関数:
  - `encode_static_method(box_name, method, arity)` → "BoxName.method/arity"
  - `canonical_box_name(raw)` → "main" → "Main"
  - `normalize_static_global_name(func_name)` → "main._nop/0" → "Main._nop/0"
- doctest 9個全てPASS 

### 2. Python LLVM側の統一修正
- `instructions/boxcall.py:437` - f"Main.{method_name}/{arity}" → encode_static_method()
- `instructions/call.py:170-173` - traced_names タプル生成をNamingHelper経由に変更
- `pyvm/intrinsic.py:17, 50` - "Main.esc_json/1", "Main.dirname/1" → encode_static_method()
- `builders/entry.py:16` - 'Main.main/1' → encode_static_method("Main", "main", 1)

## 🎯 技術的成果
- **意味論一致**: Rust ↔ Python で完全同一の命名規則
- **保守性向上**: ハードコード4箇所 → NamingHelper一元管理
- **テスト完備**: doctest 9個でRust NamingBoxと同一動作を保証

## テスト結果
 python3 -m py_compile: 全ファイル構文OK
 python3 -m doctest naming_helper.py: 9 tests passed

## 参考
- Phase 25.4-A (Rust側): fa9cea51, bceb20ed
- Rust NamingBox SSOT: src/mir/naming.rs

🎉 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-21 09:38:49 +09:00
parent bceb20ed66
commit 419214a5a9
5 changed files with 114 additions and 7 deletions

View File

@ -1,5 +1,9 @@
from typing import Optional from typing import Optional
import os import os
import sys
# NamingBox SSOT: Add parent directory to path for naming_helper import
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from naming_helper import encode_static_method
def ensure_ny_main(builder) -> None: def ensure_ny_main(builder) -> None:
"""Ensure ny_main wrapper exists by delegating to Main.main/1 or main(). """Ensure ny_main wrapper exists by delegating to Main.main/1 or main().
@ -8,8 +12,10 @@ def ensure_ny_main(builder) -> None:
has_ny_main = any(f.name == 'ny_main' for f in builder.module.functions) has_ny_main = any(f.name == 'ny_main' for f in builder.module.functions)
fn_main_box = None fn_main_box = None
fn_main_plain = None fn_main_plain = None
# NamingBox SSOT: Use encode_static_method for name comparison
main_box_name = encode_static_method("Main", "main", 1)
for f in builder.module.functions: for f in builder.module.functions:
if f.name == 'Main.main/1': if f.name == main_box_name:
fn_main_box = f fn_main_box = f
elif f.name == 'main': elif f.name == 'main':
fn_main_plain = f fn_main_plain = f

View File

@ -6,6 +6,7 @@ Core of Nyash's "Everything is Box" philosophy
import llvmlite.ir as ir import llvmlite.ir as ir
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from instructions.safepoint import insert_automatic_safepoint from instructions.safepoint import insert_automatic_safepoint
from naming_helper import encode_static_method
def _declare(module: ir.Module, name: str, ret, args): def _declare(module: ir.Module, name: str, ret, args):
for f in module.functions: for f in module.functions:
@ -431,9 +432,9 @@ def lower_boxcall(
except Exception: except Exception:
pass pass
if is_me and cur_fn_name.startswith('Main.'): if is_me and cur_fn_name.startswith('Main.'):
# Build target function name with arity # NamingBox SSOT: Build target function name with arity
arity = len(args) arity = len(args)
target = f"Main.{method_name}/{arity}" target = encode_static_method("Main", method_name, arity)
# If module already has such function, prefer direct call # If module already has such function, prefer direct call
callee = None callee = None
for f in module.functions: for f in module.functions:

View File

@ -7,6 +7,7 @@ import llvmlite.ir as ir
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from trace import debug as trace_debug from trace import debug as trace_debug
from instructions.safepoint import insert_automatic_safepoint from instructions.safepoint import insert_automatic_safepoint
from naming_helper import encode_static_method
def lower_call( def lower_call(
builder: ir.IRBuilder, builder: ir.IRBuilder,
@ -165,8 +166,13 @@ def lower_call(
# Make the call # Make the call
result = builder.call(func, call_args, name=f"call_{func_name}") result = builder.call(func, call_args, name=f"call_{func_name}")
# Optional trace for final debugging # NamingBox SSOT: Optional trace for final debugging
if isinstance(actual_name, str) and actual_name in ("Main.node_json/3", "Main.esc_json/1", "main"): traced_names = (
encode_static_method("Main", "node_json", 3),
encode_static_method("Main", "esc_json", 1),
"main"
)
if isinstance(actual_name, str) and actual_name in traced_names:
trace_debug(f"[TRACE] call {actual_name} args={len(call_args)}") trace_debug(f"[TRACE] call {actual_name} args={len(call_args)}")
# Store result if needed # Store result if needed

View File

@ -0,0 +1,88 @@
"""
MIR NamingHelper — Python mirror of Rust src/mir/naming.rs
Responsibility:
- Encode/decode static box methods to MIR function names.
- Centralize naming rules (e.g., Main._nop/0) for Builder/PyVM consistency.
- Minimal: handle `main.*` → `Main.*` cases safely.
Non-responsibility:
- Dynamic dispatch or BoxFactory name resolution.
- Entry point selection policy (NYASH_ENTRY).
"""
def encode_static_method(box_name: str, method: str, arity: int) -> str:
"""
Encode a static box method into a MIR function name: `BoxName.method/arity`.
Args:
box_name: Raw box name (e.g., "main", "Main", "Calculator")
method: Method name (e.g., "main", "_nop", "add")
arity: Number of parameters (e.g., 0, 1, 3)
Returns:
Canonical MIR function name (e.g., "Main.main/1", "Main._nop/0")
Examples:
>>> encode_static_method("main", "main", 1)
'Main.main/1'
>>> encode_static_method("Main", "_nop", 0)
'Main._nop/0'
>>> encode_static_method("Calculator", "add", 2)
'Calculator.add/2'
"""
return f"{canonical_box_name(box_name)}.{method}/{arity}"
def canonical_box_name(raw: str) -> str:
"""
Canonicalize a static box name for MIR-level usage.
Current rules:
- "main""Main" (minimal correction)
- Others: return as-is (avoid wide-scope spec changes)
Args:
raw: Raw box name (e.g., "main", "Main", "Calculator")
Returns:
Canonical box name (e.g., "Main", "Calculator")
Examples:
>>> canonical_box_name("main")
'Main'
>>> canonical_box_name("Main")
'Main'
>>> canonical_box_name("Calculator")
'Calculator'
"""
return "Main" if raw == "main" else raw
def normalize_static_global_name(func_name: str) -> str:
"""
If `func_name` looks like a static box method like `main._nop/0`,
normalize the box part (`main` → `Main`) and return canonical form.
Args:
func_name: MIR function name (e.g., "main._nop/0", "Main.main/1", "print")
Returns:
Normalized function name (e.g., "Main._nop/0", "Main.main/1", "print")
Examples:
>>> normalize_static_global_name("main._nop/0")
'Main._nop/0'
>>> normalize_static_global_name("Main._nop/0")
'Main._nop/0'
>>> normalize_static_global_name("print")
'print'
"""
if '.' in func_name:
box_part, rest = func_name.split('.', 1)
# rest contains "method/arity"
canon = canonical_box_name(box_part)
if canon != box_part:
return f"{canon}.{rest}"
return func_name

View File

@ -5,11 +5,16 @@ from __future__ import annotations
from typing import Any, List, Tuple from typing import Any, List, Tuple
import os import os
import sys
# NamingBox SSOT: Add parent directory to path for naming_helper import
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from naming_helper import encode_static_method
def try_intrinsic(name: str, args: List[Any]) -> Tuple[bool, Any]: def try_intrinsic(name: str, args: List[Any]) -> Tuple[bool, Any]:
try: try:
if name == "Main.esc_json/1": # NamingBox SSOT: Use encode_static_method for name comparison
if name == encode_static_method("Main", "esc_json", 1):
s = "" if not args else ("" if args[0] is None else str(args[0])) s = "" if not args else ("" if args[0] is None else str(args[0]))
out = [] out = []
for ch in s: for ch in s:
@ -41,7 +46,8 @@ def try_intrinsic(name: str, args: List[Any]) -> Tuple[bool, Any]:
start = idx + len(key) start = idx + len(key)
ok, digits = try_intrinsic("MiniVm.read_digits/2", [js, start]) ok, digits = try_intrinsic("MiniVm.read_digits/2", [js, start])
return True, digits return True, digits
if name == "Main.dirname/1": # NamingBox SSOT: Use encode_static_method for name comparison
if name == encode_static_method("Main", "dirname", 1):
p = "" if not args else ("" if args[0] is None else str(args[0])) p = "" if not args else ("" if args[0] is None else str(args[0]))
d = os.path.dirname(p) d = os.path.dirname(p)
if d == "": if d == "":