fix(rewrite): toString normalization to BoxCall(slot #0) - Phase 287 P4

Root cause: toString/stringify/str were being rewritten to Global/Method calls
with class inference, causing Main.toString/0 to be called for primitives.

Fix (Box-First + Legacy Deletion):
1.  MIR Builder - toString normalization (special.rs)
   - ALWAYS emit BoxCall with method_id=0 for toString/stringify/str
   - Do NOT rewrite to Global(Class.str/0) or Method calls
   - DELETED 70+ lines of complex class inference logic
   - Primitive guard with method name filter (known.rs)

2.  JSON Serializer - method_id output (mir_json_emit.rs)
   - Include method_id field in BoxCall JSON for LLVM

3.  LLVM Backend - universal slot #0 support
   - Extract method_id from JSON (instruction_lower.py)
   - Box primitives via nyash.box.from_i64 (boxcall.py)
   - Invoke toString via plugin system with method_id=0
   - ⚠️ TODO: Add nyash.integer.tostring_h to kernel

Test Results:
 VM: local x = 1; print(x.toString()) → "1" (PASS)
 VM: array_length test (boxed Integer) → PASS
⚠️ LLVM: Compiles successfully, needs kernel function

SSOT: slot_registry - toString is ALWAYS universal slot #0

Legacy Deleted:
- special.rs: Complex class inference rewrite (~70 lines)
- special.rs: Unique suffix fallback for toString
- special.rs: Main box special handling

Files changed:
- src/mir/builder/rewrite/special.rs (try_early_str_like_to_dst)
- src/mir/builder/rewrite/known.rs (primitive guards x4)
- src/runner/mir_json_emit.rs (method_id serialization x2)
- src/llvm_py/builders/instruction_lower.py (method_id extraction)
- src/llvm_py/instructions/boxcall.py (slot #0 handler)
- docs/reference/language/quick-reference.md (toString SSOT)

🎊 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-25 11:38:05 +09:00
parent cab98e2fb4
commit c117a04035
12 changed files with 384 additions and 158 deletions

View File

@ -19,6 +19,7 @@ Imports and namespaces
Variables and scope
- See: reference/language/variables-and-scope.md — Block-scoped locals, assignment resolution, and strong/weak reference guidance.
- See: reference/language/lifecycle.md — Box lifetime, ownership (strong/weak), and finalization (`fini`) SSOT.
- See: reference/language/repl.md — REPL mode semantics (file mode vs REPL binding rules).
Type system (SSOT)
- See: reference/language/types.md — runtime truthiness, `+`/compare/equality semantics, and the role/limits of MIR type facts.

View File

@ -19,9 +19,8 @@ Expressions and Calls
- Member: `obj.field` or `obj.m`
Display & Conversion
- Humanreadable display: `str(x)`(推奨)/ `x.str()`
- 既存の `toString()` `str()` に正規化Builder早期リライト
- 互換: 既存の `stringify()` は当面エイリアス(内部で `str()` 相当へ誘導)。
- Humanreadable display: `x.toString()`(推奨)
- ユーザーBoxで表示をカスタムしたい場合は `str()` または `stringify()`互換を実装する。VMは `toString()` `str()/stringify()` に再ルーティングする
- Debug表示構造的・安定: `repr(x)`将来導入、devのみ
- JSONシリアライズ: `toJson(x)`(文字列)/ `toJsonNode(x)`(構造)
@ -46,13 +45,14 @@ Truthiness (boolean context)
Equality and Comparison
- SSOT: `reference/language/types.md``==`/`!=``< <= > >=` の runtime 仕様)
- `==` は一部の cross-kind`Integer↔Bool`, `Integer↔Float`)を best-effort で扱う。その他`false`
- `==` の cross-kind**`Integer↔Float` のみ**(精密ルール)。それ以外の mixed kinds `false`(エラーではない)
- `< <= > >=``Integer/Float/String` の **同型同士**のみ(異型は `TypeError`)。
String and Numeric `+`
- SSOT: `reference/language/types.md`runtime `+` 仕様)
- `Integer+Integer`, `Float+Float` は加算。片側が `String` なら文字列連結(相手は文字列化)
- それ以外(例: `Integer+Bool`, `Integer+Float``TypeError`Fail-Fast)。
- `Integer+Integer` は加算、`Float+Float` は加算`Integer↔Float``Float` に昇格して加算
- 文字列連結は **`String + String` のみ**。`"a"+1` / `1+"a"` `TypeError`暗黙 stringify なし)。
- 文字列化して連結したい場合は明示的に `x.toString()` を使う。
Blocks and Control
- `if (cond) { ... } [else { ... }]`
@ -73,5 +73,5 @@ Dev/Prod toggles (indicative)
- `NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1` — allow `main` as toplevel entry
Notes
- Keep the language small. Prefer explicit conversions (`int(x)`, `str(x)`, `bool(x)`) in standard helpers over implicit coercions.
- Keep the language small. Prefer explicit conversions (`x.toInteger()`, `x.toString()`, `x.toBool()`) over implicit coercions.
- Builder rewrites method calls to keep runtime dispatch simple and consistent across backends.

View File

@ -0,0 +1,122 @@
# REPL Mode (ReadEvalPrint Loop) — Specification (SSOT)
Status: Draft (design locked; implementation may lag)
This document defines Nyash REPL mode semantics. The primary design goal is:
- **File mode** stays strict and box-oriented (no mutable globals at top-level).
- **REPL mode** is convenience-oriented (interactive, persistent session scope, implicit locals).
- **Parser stays the same**; the difference is primarily **binding (name resolution)** rules.
## Terms
- **Top-level**: Code that is not inside a function/method body.
- **File mode**: Normal execution of a `.hako`/Nyash file via the runner.
- **REPL mode**: Interactive execution session (future CLI: `--repl`).
- **Global variable (in this project)**: A mutable binding created at file top-level (e.g., `x = 1` or `local x = 1` at top-level).
## 1) File Mode vs REPL Mode (high-level contract)
### File mode (strict)
- Top-level allows **declarations only** (e.g., `box`, `static box`, `function`, `static function`, `using`).
- Top-level **statements are rejected** (Fail-Fast):
- assignment (`x = 1`)
- `local` declarations (`local x = 1`)
- expression statements (`f()`), `print(...)`, control flow statements, etc.
- Rationale: prevents mutable globals; keeps “state lives in boxes” discipline.
### REPL mode (convenient)
- The REPL has exactly one **persistent session scope**.
- Session scope is conceptually a **lexical frame that persists across inputs**.
- Assignments can create bindings implicitly (see §2).
- Reads of undefined names are errors (Fail-Fast; no silent `void`).
CLI entry (initial policy):
- Start explicitly with `hakorune --repl` (optional short alias: `-i`).
## 2) Binding Rules in REPL Mode
### 2.1 Implicit local on assignment (key feature)
When executing `name = expr` in REPL mode:
- If `name` already exists in the session scope, update it.
- If `name` does not exist, **create a new session binding** and assign to it.
This applies to compound assignments as well (if supported): `name += expr`, etc.
### 2.2 Reads are strict
When evaluating `name` in REPL mode:
- If `name` exists in the session scope, return its value.
- Otherwise, raise **NameError / undeclared variable** (Fail-Fast).
### 2.3 `local` is accepted but not required
REPL accepts `local name = expr` / `local name` as explicit declarations.
- Semantics: declare/update `name` in the session scope (same end result as implicit assignment).
- Guidance: `local` remains useful for clarity, but REPL users are not forced to write it.
## 3) Output Rules (REPL UX contract)
REPL output distinguishes expressions vs statements:
- If the input is an **expression**, print its value (pretty display) unless it is `void`.
- If the input is a **statement**, do not auto-print.
### 3.1 Convenience binding `_`
- `_` is bound to the **last auto-printed value** (expressions only).
- `_` is not updated when the value is `void`.
### 3.2 Output suppression (planned)
- A trailing `;` may suppress auto-print for expressions (planned; should be implemented without changing the core parser).
## 4) REPL Meta Commands
REPL supports dot-commands (not part of the language grammar):
- `.help` — show help
- `.exit` — exit the REPL
- `.reset` — clear the session scope (remove all bindings and definitions created in the session)
Additional commands may be added for debugging (e.g., `.ast`, `.mir`), but they must remain REPL-only and default-off for CI.
## 5) Compatibility and `strip_local_decl` policy
Historical compatibility code exists that can strip top-level `local` from certain inputs.
SSOT policy:
- **File mode must not “strip and accept” top-level `local`** (would violate the “no globals” rule).
- If compatibility behavior is kept, it must be **REPL-only** and/or explicitly gated (e.g., `--compat`), with a stable warning tag.
## 6) Error Messages (Fail-Fast wording)
Recommended file-mode errors:
- `Error: top-level statements are not allowed in file mode. Put code inside Main.main() or run with --repl.`
- `Error: 'local' is not allowed at top-level in file mode. Use Main.main() or REPL mode.`
## 7) Minimal Conformance Tests (spec lock)
### File mode
1. `x = 1` at top-level → error (top-level statements not allowed)
2. `local x = 1` at top-level → error (local not allowed at top-level)
3. `print("hi")` at top-level → error
4. Declarations at top-level → OK
5. Statements inside `Main.main()` or `main()` → OK
### REPL mode
1. `x = 1` then `x` → prints `1`
2. `y` (undefined) → NameError
3. `x = 1; x = 2; x` → prints `2`
4. `local z = 3; z` → prints `3`
5. `x = 1; .reset; x` → NameError