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>
4.6 KiB
4.6 KiB
REPL Mode (Read–Eval–Print 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 = 1orlocal x = 1at 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) localdeclarations (local x = 1)- expression statements (
f()),print(...), control flow statements, etc.
- assignment (
- 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
namealready exists in the session scope, update it. - If
namedoes 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
nameexists 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
namein the session scope (same end result as implicit assignment). - Guidance:
localremains 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 isvoid.
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
x = 1at top-level → error (top-level statements not allowed)local x = 1at top-level → error (local not allowed at top-level)print("hi")at top-level → error- Declarations at top-level → OK
- Statements inside
Main.main()ormain()→ OK
REPL mode
x = 1thenx→ prints1y(undefined) → NameErrorx = 1; x = 2; x→ prints2local z = 3; z→ prints3x = 1; .reset; x→ NameError