Files
hakorune/docs/reference/language/repl.md
tomoaki c117a04035 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>
2025-12-25 11:38:05 +09:00

4.6 KiB
Raw Blame History

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