Files
hakorune/docs/reference/language/EBNF.md
tomoaki ab76e39036 feat(parser): Phase 285A1.4 & A1.5 - Weak field sugar + Parser hang fix
A1.4: Add sugar syntax `public weak parent` ≡ `public { weak parent }`
A1.5: Fix parser hang on unsupported `param: Type` syntax

Key changes:
- A1.4: Extend visibility parser to handle weak modifier (fields.rs)
- A1.5: Shared helper `parse_param_name_list()` with progress-zero detection
- A1.5: Fix 6 vulnerable parameter parsing loops (methods, constructors, functions)
- Tests: Sugar syntax (OK/NG), parser hang (timeout-based)
- Docs: lifecycle.md, EBNF.md, phase-285a1-boxification.md

Additional changes:
- weak() builtin implementation (handlers/weak.rs)
- Leak tracking improvements (leak_tracker.rs)
- Documentation updates (lifecycle, types, memory-finalization, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-24 07:44:50 +09:00

9.4 KiB
Raw Blame History

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') 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.