grammar: 문법 문서를 기계가 읽는 소스로 — LL(1)이 처음으로 검증된다

설명서와 파서가 어긋나 있었다. 그냥 어긋난 게 아니라, 그 설명서를 안 읽고
"else if가 안 된다"고 마찰 보고서에 잘못 적었다 — 설명서로서 제 역할을 한
번도 못 했다는 뜻이다.

lib/ebnf.ml: EBNF를 데이터로 읽는다. nullable, FIRST, FOLLOW를 계산하고
LL(1) 충돌을 보고한다. 매개변수 프로덕션(name<p>)을 지원한다 — expr_ns와
목록을 복제 없이 적기 위한 것이다.

문법 첫머리의 "설계 제약: LL(1)"은 지금까지 사람의 주장이었다. 기계로 재니
충돌 18건이 나왔다. 세 부류였다:
- 구조적 3건: stmt(assign/expr), primary(ident/struct_lit), pattern —
  파서는 왼쪽 인수분해를 손으로 했는데 문법에 안 적혀 있었다
- 후행 콤마 9건: X , { "," , X } , [ "," ]는 콤마를 본 시점에 갈리지 않는다.
  우재귀로 다시 적었다
- 줄바꿈 흡수 6건: 어느 쪽이 먹어도 파스 트리가 같다. greedy 규칙을 표기에
  명시하고 그렇게 해소되는 것만 따로 분류한다

지금은 진짜 충돌 0건이고, 테스트가 이를 고정한다.

병렬 조사에서 나온 드리프트도 모두 반영했다:
- named_type, name_pattern에 한정 이름(Shapes.Shape, Shapes.Circle)
- name_pattern이 인자 0개와 괄호 없는 한정 생성자를 받는다
- string_lit의 이스케이프
- cap_method의 gen_params (파서가 이미 허용하고 있었다)
- field/variant/field_init/arm 사이의 콤마는 필수다
- stmt 사이의 NEWLINE도 필수다 — 선택적으로 적었더니 그것 하나가 LL(1)
  충돌 셋을 만들었다
- { NEWLINE }을 전부 [ NEWLINE ]으로. 렉서가 연속 줄바꿈을 만들 수 없다

expr_ns는 산문 주석이었고 파서 상태 플래그로 구현돼 있었다. 매개변수
프로덕션으로 형식화해 문맥자유가 됐다.

렉서 쪽: Token.next_kind가 모든 토큰을 한 번씩 잇는다. 나열이 빠지면
컴파일러가 지적한다. 문법 문서의 키워드 표와 줄 끝 판정 목록은 이제
lib/lexical_doc.ml이 거기서 생성하고 테스트가 대조한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
2026-08-30 17:03:25 +09:00
co-authored by Claude Opus 5
parent 6c0d08b6b0
commit bde940eda3
8 changed files with 1049 additions and 80 deletions
+141 -80
View File
@@ -7,10 +7,24 @@
* { } (0 )
* ( )
* " "
* < > ( )
* (* *)
*
* : LL(1). backtracking 없음, .
* ( 2).
* tools/ebnf_tool.exe가 이 파일을 읽어
* FIRST/FOLLOW를 계산하고 충돌을 보고하며, .
*
* :
* name<p> = ... p ... name<arg> .
* .
* (name<yes> = ... ; name<no> = ... ;).
* expr_ns를 복제 없이 적기 위한 것이다.
*
* :
* [ NEWLINE ] (greedy).
* .
* , .
*)
(* ------------------------------------------------------------------ *)
@@ -20,39 +34,71 @@
(* // . ( ) *)
(* NEWLINE :
* ident, , ")", "]", "}", "?", "return"
* NEWLINE . .
* .
* NEWLINE .
* .
* . .
* NEWLINE .
*
* NEWLINE . " "
* NEWLINE .
* { NEWLINE } [ NEWLINE ].
*
(* lib/token.ml *)
* .
* NEWLINE이 삽입된다. Token.can_end_statement가 원본이다.
*
* ident | int_lit | string_lit | "return" | "true" | "false" | ")" |
* "}" | "]" | "?"
*
* . . Token.keyword가 원본이다.
*
* "pub" | "fn" | "struct" | "enum" | "capability" | "const" |
* "import" | "as" | "reexport" | "let" | "mut" | "own" | "affine" |
* "copyable" | "effects" | "return" | "if" | "else" | "match" |
* "scope" | "true" | "false"
(* *)
*
* (, , , variant, )
* . NEWLINE을 만들지 않으므로 목록이 자연히 이어진다.
* formatter가 이를 강제한다.
*
* (struct , enum , struct , effect , match )
* NEWLINE .
* . { NEWLINE } .
*
* NEWLINE이 문법적으로 허용되고 무시된다. effects 절이
* "}" NEWLINE이 삽입되는데,
* { NEWLINE } .
* [ NEWLINE ] .
* .
*)
ident = letter , { letter | digit | "_" } ;
int_lit = digit , { digit | "_" } ;
string_lit = '"' , { char - '"' } , '"' ;
string_lit = '"' , { str_char } , '"' ;
str_char = ( char - '"' - "\" ) | escape ;
escape = "\" , ( "n" | "t" | "\" | '"' ) ;
bool_lit = "true" | "false" ;
literal = int_lit | string_lit | bool_lit ;
(* ------------------------------------------------------------------ *)
(* *)
(* ------------------------------------------------------------------ *)
(* . LL(1)
* X , { "," , X } , [ "," ] " "
* " " .
* , . *)
list<item> = item , list_rest<item> ;
list_rest<item> = [ "," , [ list<item> ] ] ;
(* . .
* . *)
brace_list<item> = [ NEWLINE ] , [ item , brace_rest<item> ] ;
brace_rest<item> = [ NEWLINE ] ,
[ "," , [ NEWLINE ] , [ item , brace_rest<item> ] ] ;
(* ------------------------------------------------------------------ *)
(* *)
(* ------------------------------------------------------------------ *)
module = { NEWLINE } , { item } ;
item = ( import | reexport | decl ) , { NEWLINE } ;
(* decl NEWLINE 0 .
* stmt NEWLINE *)
module = [ NEWLINE ] , { item } ;
item = ( import | reexport | decl ) , [ NEWLINE ] ;
import = "import" , string_lit , "as" , ident ;
reexport = "reexport" , ident ;
@@ -65,53 +111,47 @@ decl = [ "pub" ] , ( fn_decl | struct_decl | enum_decl
(* ------------------------------------------------------------------ *)
fn_decl = "fn" , ident , [ gen_params ] , "(" , [ params ] , ")" ,
{ NEWLINE } ,
[ eff_result , { NEWLINE } ] ,
[ "->" , type , { NEWLINE } ] ,
[ NEWLINE ] ,
[ eff_result , [ NEWLINE ] ] ,
[ "->" , type , [ NEWLINE ] ] ,
[ block ] ;
(* block . interface capability *)
(* block . std/*.cool capability *)
struct_decl = [ "copyable" ] , "struct" , ident , [ gen_params ] ,
"{" , { field } , "}" ;
field = ident , ":" , type , { NEWLINE } ,
[ "," , { NEWLINE } ] ;
"{" , brace_list<field> , "}" ;
field = ident , ":" , type ;
enum_decl = "enum" , ident , [ gen_params ] , "{" , { variant } , "}" ;
variant = ident , [ "(" , type_list , ")" ] , { NEWLINE } ,
[ "," , { NEWLINE } ] ;
enum_decl = "enum" , ident , [ gen_params ] ,
"{" , brace_list<variant> , "}" ;
variant = ident , [ "(" , type_list , ")" ] ;
capability_decl = "capability" , ident , "{" , { NEWLINE } , { cap_method } , "}" ;
cap_method = "fn" , ident , "(" , [ params ] , ")" , { NEWLINE } ,
[ eff_result , { NEWLINE } ] , [ "->" , type ] ,
{ NEWLINE } ;
capability_decl = "capability" , ident , "{" , [ NEWLINE ] , { cap_method } , "}" ;
cap_method = "fn" , ident , [ gen_params ] , "(" , [ params ] , ")" ,
[ NEWLINE ] ,
[ eff_result , [ NEWLINE ] ] ,
[ "->" , type ] ,
[ NEWLINE ] ;
const_decl = "const" , ident , ":" , type , "=" , expr ;
gen_params = "[" , gen_param , { "," , gen_param } , [ "," ] , "]" ;
gen_params = "[" , list<gen_param> , "]" ;
gen_param = ident , [ ":" , "effects" ] ;
(* ident = , ": effects" = effect *)
params = param , { "," , param } , [ "," ] ;
params = list<param> ;
param = [ "own" ] , [ "mut" ] , ident , ":" , type ;
(* = use(). own .
* : (own, mut) , (affine) type *)
(* ------------------------------------------------------------------ *)
(* effect *)
(* effect *)
(* ------------------------------------------------------------------ *)
(* effect. *)
eff_result = "effects" , eff_union ;
eff_union = eff_atom , { "|" , eff_atom } ;
(* *)
eff_result = "effects" , eff_atom , { "|" , eff_atom } ;
(* effect. .
* " " " " *)
(* . .
* " " " " *)
eff_param = "effects" , eff_atom ;
eff_atom = ident | eff_set ;
eff_set = "{" , { NEWLINE } ,
[ eff_name , { { NEWLINE } , "," , { NEWLINE } , eff_name } ,
{ NEWLINE } , [ "," , { NEWLINE } ] ] , "}" ;
eff_set = "{" , brace_list<eff_name> , "}" ;
eff_name = ident , "." , ident ;
(* . capability identity *)
@@ -124,28 +164,35 @@ type = fn_type | named_type ;
fn_type = [ "affine" ] , "fn" , "(" , [ type_list ] , ")" ,
[ eff_param ] , [ "->" , type ] ;
named_type = ident , [ type_args ] ;
type_args = "[" , targ , { "," , targ } , [ "," ] , "]" ;
(* : Shapes.Shape.
* *)
named_type = ident , [ "." , ident ] , [ type_args ] ;
type_args = "[" , list<targ> , "]" ;
targ = type | eff_set ;
(* effect.
* *)
type_list = type , { "," , type } , [ "," ] ;
type_list = list<type> ;
(* ------------------------------------------------------------------ *)
(* *)
(* ------------------------------------------------------------------ *)
block = "{" , { NEWLINE } ,
[ stmt , { stmt_sep , stmt } , [ stmt_sep ] ] , "}" ;
stmt = let_stmt | return_stmt | assign_stmt | expr ;
stmt_sep = NEWLINE , { NEWLINE } ;
(* "}" .
block = "{" , [ NEWLINE ] , [ stmt , stmt_rest ] , "}" ;
stmt_rest = [ NEWLINE , [ stmt , stmt_rest ] ] ;
(* NEWLINE .
* , "-" "("
* .
* "}" .
* fn(s) { String.concat(prefix, s) } *)
stmt = let_stmt | return_stmt | expr_stmt ;
let_stmt = "let" , [ "mut" ] , pattern , [ ":" , type ] , "=" , expr ;
return_stmt = "return" , [ expr ] ;
assign_stmt = place , "=" , expr ;
place = ident , { "." , ident } ;
(* NEWLINE "}" *)
expr_stmt = expr , [ "=" , expr ] ;
(* ( )
* . ident *)
(* = stmt expr , Unit.
* return , return formatter *)
@@ -154,62 +201,76 @@ place = ident , { "." , ident } ;
(* *)
(* ------------------------------------------------------------------ *)
expr = or_expr ;
or_expr = and_expr , { "||" , and_expr } ;
and_expr = cmp_expr , { "&&" , cmp_expr } ;
cmp_expr = add_expr , [ cmp_op , add_expr ] ;
cmp_op = "==" | "!=" | "<" | "<=" | ">" | ">=" ;
add_expr = mul_expr , { ( "+" | "-" ) , mul_expr } ;
mul_expr = unary , { ( "*" | "/" | "%" ) , unary } ;
unary = [ "!" | "-" ] , postfix ;
(* s struct .
* expr = ( )
* expr_ns = (if/match )
* if/match "{" struct
* , struct .
* , , ,
* . primary<no>
* expr(= yes).
* scope ( ) *)
expr = or_expr<yes> ;
expr_ns = or_expr<no> ;
postfix = primary , { call_sfx | field_sfx | inst_sfx | "?" } ;
or_expr<s> = and_expr<s> , { "||" , and_expr<s> } ;
and_expr<s> = cmp_expr<s> , { "&&" , cmp_expr<s> } ;
cmp_expr<s> = add_expr<s> , [ cmp_op , add_expr<s> ] ;
cmp_op = "==" | "!=" | "<" | "<=" | ">" | ">=" ;
add_expr<s> = mul_expr<s> , { ( "+" | "-" ) , mul_expr<s> } ;
mul_expr<s> = unary<s> , { ( "*" | "/" | "%" ) , unary<s> } ;
unary<s> = [ "!" | "-" ] , postfix<s> ;
postfix<s> = primary<s> , { call_sfx | field_sfx | inst_sfx | "?" } ;
call_sfx = "(" , [ args ] , ")" ;
field_sfx = "." , ident ;
inst_sfx = type_args ;
(* "[" = , "[" = . *)
args = expr , { "," , expr } , [ "," ] ;
args = list<expr> ;
primary = literal
| ident
primary<s> = literal
| list_lit
| struct_lit
| closure
| if_expr
| match_expr
| scope_expr
| "(" , expr , ")" ;
| "(" , expr , ")"
| name_or_struct<s> ;
list_lit = "[" , [ expr , { "," , expr } , [ "," ] ] , "]" ;
(* ident struct . "{"
* *)
name_or_struct<yes> = ident , [ struct_body ] ;
name_or_struct<no> = ident ;
struct_body = "{" , brace_list<field_init> , "}" ;
field_init = ident , ":" , expr ;
list_lit = "[" , [ args ] , "]" ;
(* : let xs: List[Int] = [] *)
struct_lit = ident , "{" , { field_init } , "}" ;
field_init = ident , ":" , expr , { NEWLINE } , [ "," , { NEWLINE } ] ;
closure = "fn" , "(" , [ cl_params ] , ")" ,
[ eff_param ] , [ "->" , type ] , block ;
cl_params = cl_param , { "," , cl_param } , [ "," ] ;
cl_params = list<cl_param> ;
cl_param = ident , [ ":" , type ] ;
(* . .
* error *)
if_expr = "if" , expr_ns , block , [ "else" , ( block | if_expr ) ] ;
match_expr = "match" , expr_ns , "{" , { arm } , "}" ;
arm = pattern , "=>" , ( expr | block ) , { NEWLINE } ,
[ "," , { NEWLINE } ] ;
match_expr = "match" , expr_ns , "{" , brace_list<arm> , "}" ;
arm = pattern , "=>" , ( expr | block ) ;
scope_expr = "scope" , ident , "=" , ident , block ;
(* scope = { ... }
* . " "
* ambient authority *)
(* expr_ns = struct_lit expr.
* if/match/scope "{" struct
* , struct *)
(* ------------------------------------------------------------------ *)
(* *)
(* ------------------------------------------------------------------ *)
pattern = "_" | literal | ctor_pattern | ident ;
ctor_pattern = ident , "(" , pattern , { "," , pattern } , [ "," ] , ")" ;
pattern = "_" | literal | name_pattern ;
(* . :
* (Shapes.Dot)
*
* , *)
name_pattern = ident , [ "." , ident ] , [ "(" , [ list<pattern> ] , ")" ] ;
(* . (exhaustiveness ) *)