Files
hakorune/docs/reference/language/EBNF.md
tomoaki cc05c37ae3 feat(phase285w): Phase 285W-Syntax-0.1 - Reject weak(...) syntax (Parser-level Fail-Fast)
- Parser guard: Reject weak(...) with LPAREN check in parse_unary()
  - Error: "Use 'weak expr', not 'weak(expr)'" (helpful message)
  - Location: src/parser/expressions.rs:248-256
- MIR builder guard: Defense-in-depth for any bypassed cases
  - Location: src/mir/builder/calls/build.rs:37-46
- Rejection test: apps/tests/phase285w_weak_call_rejected.hako
- Smoke test: phase285w_weak_call_rejected_vm.sh (PASS )
- Documentation:
  - EBNF.md: Add ~ (BitNot) to unary operators
  - lifecycle.md: Document weak(expr) as invalid syntax
  - phase-285/README.md: Add Phase 285W-Syntax-0.1 entry

Test results: 5/6 phase285 tests PASS (1 unrelated failure)
SSOT: docs/reference/language/lifecycle.md

Closes: Phase 285W-Syntax-0.1
2025-12-25 00:04:55 +09:00

175 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Nyash Grammar (Stage2 EBNF)
Status: Fixed for Phase 15 Stage2. 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' | '~' | 'weak') unary | factor
factor := INT
| STRING
| IDENT call_tail*
| '(' expr ')'
| '(' assignment_expr ')' ; Stage3: grouped assignment as expression
| 'new' IDENT '(' args? ')'
| '[' args? ']' ; Array literal (Stage1 sugar, gated)
| '{' map_entries? '}' ; Map literal (Stage2 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)*
; Stage3: 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`.
- Dowhile: not supported by design. Prefer a singleentry, precondition 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 Stage3 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 (Phase15, 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 (readonly). 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
; Stage3 (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: readonly; 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 (Stage3), 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 firstread 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`.
## Stage3 (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。
- Blockpostfix catch/cleanupPhase 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`
- Methodlevel postfix catch/cleanupPhase 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` と同梱)
- Memberlevel postfix catch/cleanupPhase 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 noop in some backends until runtime support lands, as tracked in CURRENT_TASK.md.