123 lines
4.6 KiB
Markdown
123 lines
4.6 KiB
Markdown
|
|
# 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 = 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
|