Updated implementation status to reflect actual working features: - ✅ print() output displays - ✅ .reset command works - ✅ Implicit local binding compiles - ⏳ Variable persistence (deferred to Phase 288.1+) Known limitations documented: - Variable access across lines not yet working - _ variable stored but not accessible yet - Expression auto-display deferred Accurate current behavior examples added. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
11 KiB
REPL Mode (Read–Eval–Print Loop) — Specification (SSOT)
Status: Phase 288 MVP Implementation (2025-12-25)
Philosophy: Two Execution Contexts, One Language
Nyash provides two execution contexts for the same language:
-
File Mode - Strict, box-oriented, production-ready
- Forces explicit structure (
static box Main { main() { ... } }) - Prevents mutable globals at top-level
- Optimized for large, maintainable codebases
- Forces explicit structure (
-
REPL Mode - Interactive, exploratory, development-friendly
- Implicit local variables for rapid iteration
- Persistent session scope across inputs
- Optimized for learning, debugging, and experimentation
Key Principle: Same parser, same language semantics. The difference is binding rules and execution context.
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)
Starting REPL Mode
hakorune --repl # Full flag
hakorune -i # Short alias (interactive)
File Mode (strict) - Code Example
// File: program.hako
// ❌ ERROR: Top-level executable statements forbidden
x = 1
print("Hello")
local temp = 42
// ✅ ALLOWED: Declarations only
static box Main {
main() {
local x // ✅ Explicit declaration REQUIRED
x = 1
print(x)
return 0
}
}
File Mode Rules:
- Top-level allows declarations only (
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, etc.
- Assignment (
- Rationale: Prevents mutable globals; maintains "state lives in boxes" discipline
REPL Mode (convenient) - Interactive Example
>>> x = 1 // ✅ Implicitly creates session-level local
>>>
>>> print(x) // ✅ Reads from session scope
1
>>> y // ❌ ERROR: Undefined variable (Fail-Fast)
Error: Undefined variable 'y'
Hint: Variable not defined. Assign a value first.
>>> local z = 3 // ✅ Explicit declaration also works
>>>
>>> print(z)
3
>>> 1 + 1 // ✅ Expression auto-display (Phase 288.1+)
2
>>> _ // ✅ Last value stored in _ variable
2
>>> .reset // ✅ Clear session
Session reset
>>> print(x) // ❌ ERROR: Session cleared
Error: Undefined variable 'x'
REPL Mode Rules:
- 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) - Implementation: Session stores runtime values (
VMValue), not MIR IDs
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) Implementation Contract (Phase 288 SSOT)
5.1 Architecture: Box-First Modularization
┌─────────────────────────────────────┐
│ Runner (src/runner/mod.rs) │
│ ┌───────────────────────────────┐ │
│ │ ReplSessionBox │ │ ← REPL専用state隔離
│ │ - variables: BTreeMap< │ │
│ │ String, VMValue> │ │ ← 実行時の値を永続化
│ │ - last_value: Option<VMValue> │ │
│ │ - eval_count: usize │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ MirBuilder (src/mir/builder.rs) │
│ - repl_mode: bool │ ← mode 分岐フラグのみ
│ (assignment_resolver.rs は不変) │ ← file mode 専用に保つ
└─────────────────────────────────────┘
5.2 Mode Flags
| Flag | File Mode | REPL Mode | Purpose |
|---|---|---|---|
builder.repl_mode |
false |
true |
Implicit local 許可フラグ |
top_level_exec_allowed |
false |
N/A | File mode: top-level 実行禁止 |
5.3 Session Persistence Contract
File Mode:
- Each file execution is independent
- No state persists across runs
- All variables are scoped to functions/boxes
REPL Mode:
- Session persists across line inputs
- Variables stored as
VMValue(runtime values) - Critical: NOT
ValueId(MIR-compilation-specific, invalidated per line)
Why VMValue?
// ❌ WRONG: ValueId is per-compilation
session.variables: BTreeMap<String, ValueId> // Invalid across lines!
// ✅ CORRECT: VMValue is runtime value
session.variables: BTreeMap<String, VMValue> // Persists across compilations
5.4 Evaluation Pipeline (REPL)
User Input (line)
↓
Parse → AST
↓
MirCompiler (repl_mode=true)
↓
MIR Module
↓
MirInterpreter::execute_module()
↓
VMValue (return value)
↓
session.set_last_value(vm_value) // Store in session
↓
Output (Phase 288.1: expression auto-display)
5.5 Fail-Fast Guarantee
Both Modes:
- Undefined variable reads → Immediate error
- No silent
voidor fallback values - Clear error messages with hints
File Mode Hint:
Error: Undefined variable 'x'
Hint: Nyash requires explicit local declaration. Use `local <name>` before assignment.
REPL Mode Hint:
Error: Undefined variable 'x'
Hint: Variable not defined. Assign a value first.
6) 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.
7) 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.
8) 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
9) Phase 288 MVP Implementation Status
Completed (Phase 288 P0-P3) - 2025-12-25
✅ CLI Entry (--repl / -i flags)
✅ REPL Loop (.help, .exit, .reset commands)
✅ ReplSessionBox (VMValue-based session state)
✅ Implicit Local Binding (x = 1 compiles without error)
✅ print() Output (ExternCall output displays correctly)
✅ _ Variable (last value stored in session)
✅ Session Reset (.reset command clears state)
✅ Main Box Entry Point (VM execution fixed)
Deferred to Phase 288.1+
⏳ Variable Persistence (session variables accessible across lines)
⏳ Expression Auto-Display (1+1 → 2 automatic output)
⏳ Complex Expression Detection (distinguish expression vs statement)
⏳ REPL Variable Injection (session → compilation context bridge)
Current Behavior (Phase 288 P3 MVP)
>>> .help
Commands:
.exit / .quit - Exit REPL
.reset - Clear session
.help - Show this help
>>> x = 42
>>> # Silent (implicit local creation)
>>> print("Hello REPL!")
Hello REPL! # print() output displays
>>> print(42 + 1)
43 # Arithmetic works
>>> 1 + 1
>>> # Silent (expression auto-display in Phase 288.1)
>>> .reset
Session reset
>>> print("After reset")
After reset # REPL continues after reset
Known Limitations (Phase 288 MVP):
- Variable persistence not yet implemented (
x = 1thenprint(x)errors) _variable stored but not accessible in user code yet- No expression auto-display (deferred to Phase 288.1)
Design Philosophy: Follow 80/20 rule - deliver working REPL first, polish UX later.
Document Version: Phase 288 P0 (2025-12-25) Implementation: Phase 288 P1-P3 (CLI + Session + Basic UX) Next Phase: Phase 288.1 (Expression Auto-Display)