# Nyash Grammar (Stage‑2 EBNF)

Status: Fixed for Phase 15 Stage‑2. Parser implementations (Rust/Python/Nyash selfhost) should conform to this subset.

program   := stmt* EOF

stmt      := 'return' expr
           | 'local' IDENT '=' expr
           | 'if' expr block ('else' block)?
           | 'loop' '('? expr ')' ? block
           | expr                         ; expression statement

block     := '{' stmt* '}'

expr      := logic
logic     := compare (('&&' | '||') compare)*
compare   := sum (( '==' | '!=' | '<' | '>' | '<=' | '>=' ) sum)?
sum       := term (('+' | '-') term)*
term      := unary (('*' | '/') unary)*
unary     := ( '-' | '!' | 'not' | '~' ) unary
           | weak_unary
           | factor

; Phase 285W-Syntax-0.1: `weak(<expr>)` is invalid. The operand must not be a grouped
; expression starting with `(`. (Write `weak x`, not `weak(x)`.)
weak_unary := 'weak' unary_no_group
unary_no_group := ( '-' | '!' | 'not' | '~' ) unary_no_group
               | INT
               | STRING
               | IDENT call_tail*
               | 'new' IDENT '(' args? ')'
               | '[' args? ']'           ; Array literal (Stage‑1 sugar, gated)
               | '{' map_entries? '}'    ; Map literal (Stage‑2 sugar, gated)
               | match_expr              ; Pattern matching (replaces legacy peek)

factor    := INT
           | STRING
           | IDENT call_tail*
           | '(' expr ')'
           | '(' assignment_expr ')'  ; Stage‑3: grouped assignment as expression
           | 'new' IDENT '(' args? ')'
           | '[' args? ']'           ; Array literal (Stage‑1 sugar, gated)
           | '{' map_entries? '}'    ; Map literal (Stage‑2 sugar, gated)
           | match_expr              ; Pattern matching (replaces legacy peek)

match_expr := 'match' expr '{' match_arm+ default_arm? '}'
match_arm  := pattern guard? '=>' (expr | block) ','?
default_arm:= '_' '=>' (expr | block) ','?

pattern   := '_'
           | STRING | INT | 'true' | 'false' | 'null'
           | IDENT '(' IDENT? ')'           ; Type pattern e.g., StringBox(s)
           | '[' (IDENT (',' '..' IDENT)? )? ']'
           | '{' ( (STRING|IDENT) ':' IDENT (',' '..')? )? '}'
           | pattern '|' pattern            ; OR pattern (same arm)

guard     := 'if' expr

map_entries := (STRING | IDENT) ':' expr (',' (STRING | IDENT) ':' expr)* [',']

call_tail := '.' IDENT '(' args? ')'   ; method
           | '(' args? ')'             ; function call

args      := expr (',' expr)*

; Stage‑3: grouped assignment expression
; `(x = expr)` だけを式として認める。値と型は右辺 expr と同じ。
assignment_expr := IDENT '=' expr

Notes
- ASI: Newline is the primary statement separator. Do not insert a semicolon between a closed block and a following 'else'.
- Semicolon (optional): When `NYASH_PARSER_ALLOW_SEMICOLON=1` is set, `;` is accepted as an additional statement separator (equivalent to newline). It is not allowed between `}` and a following `else`.
- Do‑while: not supported by design. Prefer a single‑entry, pre‑condition loop normalized via sugar (e.g., `repeat N {}` / `until cond {}`) to a `loop` with clear break conditions.
- Short-circuit: '&&' and '||' must not evaluate the RHS when not needed.
- Unary minus has higher precedence than '*' and '/'.
- IDENT names consist of [A-Za-z_][A-Za-z0-9_]*
- Array literal is enabled when syntax sugar is on (NYASH_SYNTAX_SUGAR_LEVEL=basic|full) or when NYASH_ENABLE_ARRAY_LITERAL=1 is set.
- Map literal is enabled when syntax sugar is on (NYASH_SYNTAX_SUGAR_LEVEL=basic|full) or when NYASH_ENABLE_MAP_LITERAL=1 is set.
- Identifier keys (`{name: v}`) are Stage‑3 and require either NYASH_SYNTAX_SUGAR_LEVEL=full or NYASH_ENABLE_MAP_IDENT_KEY=1.
- Pattern matching: `match` replaces legacy `peek`. MVP supports wildcard `_`, literals, simple type patterns, fixed/variadic array heads `[hd, ..tl]`, simple map key extract `{ "k": v, .. }`, OR patterns, and guards `if`.

## Box Members (Phase‑15, env gate: NYASH_ENABLE_UNIFIED_MEMBERS; default ON)

This section adds a minimal grammar for Box members (a unified member model) without changing JSON v0/MIR. Parsing is controlled by env `NYASH_ENABLE_UNIFIED_MEMBERS` (default ON; set `0/false/off` to disable).

```
box_decl       := 'box' IDENT '{' member* '}'

member         := visibility_block
                | weak_stored
                | stored
                | computed
                | once_decl
                | birth_once_decl
                | method_decl
                | block_as_role      ; nyash-mode (block-first) equivalent

visibility_block := ( 'public' | 'private' ) '{' member* '}'
                  ; member visibility grouping (Phase 285A1.3). `weak` is allowed inside.

weak_stored    := 'weak' IDENT ( ':' TYPE )?
                  ; weak field declaration (Phase 285A1.2). Enforces WeakRef type at compile-time.

visibility_weak_sugar := ('public'|'private') 'weak' IDENT ( ':' TYPE )?
                  ; sugar syntax (Phase 285A1.4). Equivalent to visibility block form.
                  ; e.g., `public weak parent` ≡ `public { weak parent }`

stored         := IDENT ':' TYPE ( '=' expr )?
                  ; stored property (read/write). No handlers supported.

computed       := IDENT ':' TYPE ( '=>' expr | block ) handler_tail?
                  ; computed property (read‑only). Recomputes on each read.

once_decl      := 'once' IDENT ':' TYPE ( '=>' expr | block ) handler_tail?
                  ; lazy once. First read computes and caches; later reads return cached value.

birth_once_decl:= 'birth_once' IDENT ':' TYPE ( '=>' expr | block ) handler_tail?
                  ; eager once. Computed during construction (before user birth), in declaration order.

method_decl    := IDENT '(' params? ')' ( ':' TYPE )? block handler_tail?

params         := IDENT (',' IDENT)*
                  ; parameter name list (Phase 285A1.5)
                  ; Note: Parameter type annotations (e.g., `name: Type`) are not supported.

; nyash-mode (block-first) variant — gated with NYASH_ENABLE_UNIFIED_MEMBERS=1
block_as_role  := block 'as' ( 'once' | 'birth_once' )? IDENT ':' TYPE

handler_tail   := ( catch_block )? ( cleanup_block )?
catch_block    := 'catch' ( '(' ( IDENT IDENT | IDENT )? ')' )? block
cleanup_block  := 'cleanup' block

; Stage‑3 (Phase 1 via normalization gate NYASH_CATCH_NEW=1)
; Postfix handlers for expressions and calls (cleanup may appear without catch)
postfix_catch      := primary_expr 'catch' ( '(' ( IDENT IDENT | IDENT )? ')' )? block
postfix_cleanup    := primary_expr 'cleanup' block
```

Semantics (summary)
- stored: O(1) slot read; write via assignment. Initializer (if present) evaluates at construction once.
- computed: read‑only; each read evaluates the block; assignment is an error unless a setter is explicitly defined.
- once: first read evaluates the block and caches the value; subsequent reads return the cached value. On exception without a `catch`, the property becomes poisoned and rethrows on later reads (no retries).
- birth_once: evaluated before the user `birth` body, in declaration order; exceptions without a `catch` abort construction; cycles between `birth_once` members are an error.
- handlers: `catch/cleanup` are permitted for computed/once/birth_once/method blocks (Stage‑3), not for stored.

Lowering (no JSON v0 change)
- stored → slot
- computed → synthesize `__get_name():T { try body; catch; finally }`; reads of `obj.name` become `obj.__get_name()`
- once → add `__name: Option<T>` and emit `__get_name()` with first‑read initialization; on uncaught exception mark poisoned and rethrow on subsequent reads
- birth_once → add `__name: T` and insert initialization just before user `birth` in declaration order; handlers apply to each initializer
- method → existing method forms; optional postfix handlers lower to try/catch/finally

## Legacy: `init { ... }` field list (compatibility)

Some docs and older code use an `init { a, b, c }` list inside a `box` body. This is a legacy compatibility form to declare stored slots.

Semantics (SSOT):
- `init { a, b, c }` declares **untyped stored slots** named `a`, `b`, `c` (equivalent to writing `a` / `b` / `c` as stored members without type).
- `init { weak x, weak y }` declares **weak fields** (equivalent to writing `weak x` / `weak y` as members).
- It does not execute code. Initialization logic belongs in `birth(...) { ... }` and assignments.
- **New code** should prefer the direct syntax: `weak field_name` (Phase 285A1.2) or the unified member model (`stored/computed/once/birth_once`).
  - Legacy `init { weak field }` syntax still works for backward compatibility but is superseded by `weak field`.

## Stage‑3 (Gated) Additions

Enabled when `NYASH_PARSER_STAGE3=1` for the Rust parser (and via `--stage3`/`NYASH_NY_COMPILER_STAGE3=1` for the selfhost parser):

- try/catch/cleanup
  - `try_stmt := 'try' block ('catch' '(' (IDENT IDENT | IDENT | ε) ')' block)? ('cleanup' block)?`
    - MVP policy: single `catch` per `try`。
    - `(Type var)` or `(var)` or `()` are accepted for the catch parameter。

- Block‑postfix catch/cleanup（Phase 15.5）
  - `block_catch := '{' stmt* '}' ('catch' '(' (IDENT IDENT | IDENT | ε) ')' block)? ('cleanup' block)?`
  - Applies to standalone block statements. Do not attach to `if/else/loop` structural blocks (wrap with a standalone block when needed).
  - Gate: `NYASH_BLOCK_CATCH=1` (or `NYASH_PARSER_STAGE3=1`).
- throw
  - `throw_stmt := 'throw' expr`

- Method‑level postfix catch/cleanup（Phase 15.6, gated）
  - `method_decl := 'method' IDENT '(' params? ')' block ('catch' '(' (IDENT IDENT | IDENT | ε) ')' block)? ('cleanup' block)?`
  - Gate: `NYASH_METHOD_CATCH=1`（または `NYASH_PARSER_STAGE3=1` と同梱）

- Member‑level postfix catch/cleanup（Phase 15.6, gated）
  - Applies to computed/once/birth_once in the unified member model: see “Box Members”.
  - Gate: `NYASH_PARSER_STAGE3=1` (shared). Stored members do not accept handlers.

These constructs remain experimental; behaviour may degrade to no‑op in some backends until runtime support lands, as tracked in CURRENT_TASK.md.
