- MainDetectionHelper で main() 検出ロジックを箱化
- Legacy "static method main" と Modern "static box Main { main() }" の両パターン対応
- stage1_run_min.hako を modern 形式に統一
- ドキュメント更新(quickstart 等で static box スタイルに統一)
- パーサ新構文追加なし(仕様統一性保持)
- 後方互換性維持(Stage-B ヘルパーで legacy もサポート)
- テスト結果: 全スモーク PASS
Phase 152-B: Static Method 宣言の整理(Stage-3 仕様統一)
実装パターン: 箱化モジュール化(Phase 133/134 継承)
修正ファイル:
- lang/src/compiler/entry/compiler_stageb.hako: MainDetectionHelper (+103 lines)
- lang/src/compiler/entry/compiler.hako: Legacy Stage-A コメント (+3 lines)
- apps/tests/stage1_run_min.hako: Modern syntax に統一 (-1 line)
- docs/development/selfhosting/quickstart.md: サンプルコード更新
- CURRENT_TASK.md: Phase 152-B 完了記録
MainDetectionHelper 設計:
- findMainBody(): Entry point
- tryLegacyPattern(): "static method main" detection
- tryModernPattern(): "static box Main { main() }" detection
- findPattern(): Pattern search helper
- extractBodyFromPosition(): Brace matching extraction
利点:
✅ 明確な責任分離(各パターン検出が独立モジュール)
✅ テスタビリティ(各メソッド個別テスト可能)
✅ 拡張性(新パターン追加時は新メソッド追加のみ)
✅ 後方互換性(Legacy パターン削除は tryLegacyPattern 削除のみ)
テスト結果:
- stage1_run_min.hako: RC 0
- Selfhost depth-1: RC 0
- 全スモーク: 30/31 PASS (1 timeout は無関係)
7.7 KiB
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 | factor
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=1is set,;is accepted as an additional statement separator (equivalent to newline). It is not allowed between}and a followingelse. - Do‑while: not supported by design. Prefer a single‑entry, pre‑condition loop normalized via sugar (e.g.,
repeat N {}/until cond {}) to aloopwith 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:
matchreplaces legacypeek. MVP supports wildcard_, literals, simple type patterns, fixed/variadic array heads[hd, ..tl], simple map key extract{ "k": v, .. }, OR patterns, and guardsif.
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 := stored
| computed
| once_decl
| birth_once_decl
| method_decl
| block_as_role ; nyash-mode (block-first) equivalent
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?
; 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
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
birthbody, in declaration order; exceptions without acatchabort construction; cycles betweenbirth_oncemembers are an error. - handlers:
catch/cleanupare 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 ofobj.namebecomeobj.__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: Tand insert initialization just before userbirthin declaration order; handlers apply to each initializer - method → existing method forms; optional postfix handlers lower to try/catch/finally
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
catchpertry。 (Type var)or(var)or()are accepted for the catch parameter。
- MVP policy: single
-
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/loopstructural blocks (wrap with a standalone block when needed). - Gate:
NYASH_BLOCK_CATCH=1(orNYASH_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.