grammar.ebnf의 프로덕션 하나에 함수 하나로 대응한다. LL(1)이므로 선읽기는
항상 한 토큰이고 backtracking이 없다. 모든 노드가 위치를 들고 다닌다.
문법과 얽히는 두 자리를 원칙대로 처리했다:
- NEWLINE 흡수는 문법에 { NEWLINE }으로 적힌 자리에서만 한다. 파서가
"여기선 줄바꿈 무시" 식으로 임의 판단하면 어디서 무시되는지 아무도 모르게
된다.
- struct 리터럴은 if/match/scope 머리에서 금지하고(no_struct), 괄호·인자
목록·블록에 들어가면 다시 허용한다.
문법에 새긴 제한이 실제로 파서에서 죽는 것을 확인했다. 파라미터 위치의
effect 합집합과 match 가드는 검사기가 아니라 파서가 거부하며, 진단이
원인을 직접 말한다. 후행 콤마 누락도 일반적인 "닫는 괄호 필요" 대신
"다중 줄 목록에는 후행 콤마가 필요합니다"로 보고한다.
샘플을 실제로 파싱해 두 가지를 잡았다:
- 02와 05가 own을 타입 위치에 쓰고 있었다. 확정한 규칙은 바인딩 수식어가
이름 앞이므로 샘플이 틀렸다. 수정.
- 05에 구문 오류와 검사기 오류가 섞여 있었다. 파서가 첫 오류에서 멈추면
검사기 케이스에 영영 도달하지 못하므로 08_syntax_errors.cool로 분리.
grammar.ebnf를 구현과 맞췄다: 제네릭 인자에 effect 집합 허용, 마지막 문의
구분자는 "}" 앞에서 생략, 중괄호 목록 안의 NEWLINE 흡수 위치 명시.
cool ast 추가. cool check는 이제 파서까지 돌리되 여전히 통과했다고 말하지
않는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
213 lines
10 KiB
EBNF
213 lines
10 KiB
EBNF
(* coollang v0 문법 — EBNF
|
|
*
|
|
* 표기 규약:
|
|
* = 정의
|
|
* | 선택
|
|
* [ ] 선택적 (0 또는 1)
|
|
* { } 반복 (0 이상)
|
|
* ( ) 묶음
|
|
* " " 단말
|
|
* (* *) 주석
|
|
*
|
|
* 설계 제약: LL(1). backtracking 없음, 렉서 피드백 없음.
|
|
* 어떤 프로덕션도 무한 선읽기를 요구해서는 안 된다 (철학 2).
|
|
*)
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* 어휘 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
(* 주석은 // 부터 줄 끝까지. 블록 주석 없음 (중첩 규칙이라는 변종을 만들지 않는다) *)
|
|
|
|
(* 문 구분자 NEWLINE은 렉서가 삽입한다:
|
|
* 줄의 마지막 토큰이 ident, 리터럴, ")", "]", "}", "?", "return" 중 하나이면
|
|
* 그 줄 끝에 NEWLINE 토큰을 넣는다. 그 외에는 넣지 않는다.
|
|
* 따라서 연산자나 여는 괄호로 끝나는 줄은 다음 줄로 이어진다.
|
|
* 빈 줄과 주석만 있는 줄은 NEWLINE을 만들지 않는다.
|
|
*
|
|
* 다중 줄 목록(파라미터, 인자, 필드, variant, 리스트 리터럴)은 후행 콤마가
|
|
* 필수다. 콤마로 끝난 줄은 NEWLINE을 만들지 않으므로 목록이 자연히 이어진다.
|
|
* 공식 formatter가 이를 강제한다.
|
|
*
|
|
* 중괄호 목록(struct 정의, enum 정의, struct 리터럴, effect 집합, match 팔)
|
|
* 안에서는 항목 구분자 주변의 NEWLINE이 무시된다. 문이 놓이는 자리가 아니므로
|
|
* 구분자로 쓰이지 않는다. 아래 프로덕션에 { NEWLINE }으로 적혀 있다.
|
|
*
|
|
* 시그니처 머리에서는 NEWLINE이 문법적으로 허용되고 무시된다. effects 절이
|
|
* 줄 끝에 오면 "}"가 값 종료 토큰이라 NEWLINE이 삽입되는데, 이 자리는 문이
|
|
* 끝날 수 있는 자리가 아니므로 아래 프로덕션이 { NEWLINE }으로 흡수한다.
|
|
* 흡수 위치를 프로덕션에 명시적으로 적는다 — 파서가 임의로 건너뛰지 않는다.
|
|
*)
|
|
|
|
ident = letter , { letter | digit | "_" } ;
|
|
int_lit = digit , { digit | "_" } ;
|
|
string_lit = '"' , { char - '"' } , '"' ;
|
|
bool_lit = "true" | "false" ;
|
|
literal = int_lit | string_lit | bool_lit ;
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* 모듈 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
module = { NEWLINE } , { item } ;
|
|
item = ( import | reexport | decl ) , { NEWLINE } ;
|
|
(* decl이 이미 NEWLINE을 흡수했을 수 있으므로 항목 구분자는 0개 이상이다.
|
|
* 문 수준에서는 그렇지 않다 — stmt는 NEWLINE 하나를 반드시 요구한다 *)
|
|
|
|
import = "import" , string_lit , "as" , ident ;
|
|
reexport = "reexport" , ident ;
|
|
|
|
decl = [ "pub" ] , ( fn_decl | struct_decl | enum_decl
|
|
| capability_decl | const_decl ) ;
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* 선언 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
fn_decl = "fn" , ident , [ gen_params ] , "(" , [ params ] , ")" ,
|
|
{ NEWLINE } ,
|
|
[ eff_result , { NEWLINE } ] ,
|
|
[ "->" , type , { NEWLINE } ] ,
|
|
[ block ] ;
|
|
(* block이 없으면 시그니처 선언. interface 파일과 capability 본문에서 쓴다 *)
|
|
|
|
struct_decl = [ "copyable" ] , "struct" , ident , [ gen_params ] ,
|
|
"{" , { field } , "}" ;
|
|
field = ident , ":" , type , { NEWLINE } ,
|
|
[ "," , { NEWLINE } ] ;
|
|
|
|
enum_decl = "enum" , ident , [ gen_params ] , "{" , { variant } , "}" ;
|
|
variant = ident , [ "(" , type_list , ")" ] , { NEWLINE } ,
|
|
[ "," , { NEWLINE } ] ;
|
|
|
|
capability_decl = "capability" , ident , "{" , { NEWLINE } , { cap_method } , "}" ;
|
|
cap_method = "fn" , ident , "(" , [ params ] , ")" , { NEWLINE } ,
|
|
[ eff_result , { NEWLINE } ] , [ "->" , type ] ,
|
|
{ NEWLINE } ;
|
|
|
|
const_decl = "const" , ident , ":" , type , "=" , expr ;
|
|
|
|
gen_params = "[" , gen_param , { "," , gen_param } , [ "," ] , "]" ;
|
|
gen_param = ident , [ ":" , "effects" ] ;
|
|
(* ident 단독 = 타입 파라미터, ": effects" = effect 파라미터 *)
|
|
|
|
params = param , { "," , param } , [ "," ] ;
|
|
param = [ "own" ] , [ "mut" ] , ident , ":" , type ;
|
|
(* 무표기 = use(빌림). own만이 소유 이전을 뜻한다.
|
|
* 수식어 순서 고정: 바인딩 수식어(own, mut)가 먼저, 타입 수식어(affine)는 type 안 *)
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* effect 절 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
(* 결과 위치 — 함수 선언 자신의 effect. 합집합 허용 *)
|
|
eff_result = "effects" , eff_union ;
|
|
eff_union = 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_name = ident , "." , ident ;
|
|
(* 타입 수준 이름만. capability 값의 identity는 정적 층에 등장하지 않는다 *)
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* 타입 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
type = fn_type | named_type ;
|
|
|
|
fn_type = [ "affine" ] , "fn" , "(" , [ type_list ] , ")" ,
|
|
[ eff_param ] , [ "->" , type ] ;
|
|
|
|
named_type = ident , [ type_args ] ;
|
|
type_args = "[" , targ , { "," , targ } , [ "," ] , "]" ;
|
|
targ = type | eff_set ;
|
|
(* 제네릭 인자는 타입 또는 effect다. 맨 이름은 둘 다일 수 있으므로 파서는
|
|
* 타입으로 읽고 이름 해소가 판정한다 — 구문 층에서 갈리지 않아도 된다 *)
|
|
type_list = type , { "," , type } , [ "," ] ;
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* 문과 블록 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
block = "{" , { NEWLINE } ,
|
|
[ stmt , { stmt_sep , stmt } , [ stmt_sep ] ] , "}" ;
|
|
stmt = let_stmt | return_stmt | assign_stmt | expr ;
|
|
stmt_sep = NEWLINE , { NEWLINE } ;
|
|
(* 마지막 문의 구분자는 "}" 앞에서 생략된다.
|
|
* fn(s) { String.concat(prefix, s) } 처럼 한 줄로 쓰는 자리가 있기 때문이다 *)
|
|
|
|
let_stmt = "let" , [ "mut" ] , pattern , [ ":" , type ] , "=" , expr ;
|
|
return_stmt = "return" , [ expr ] ;
|
|
assign_stmt = place , "=" , expr ;
|
|
place = ident , { "." , ident } ;
|
|
|
|
(* 블록의 값 = 마지막 stmt가 expr이면 그 값, 아니면 Unit.
|
|
* return은 조기 탈출 전용이며, 꼬리 위치의 return은 formatter가 지적한다 *)
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* 식 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
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 ;
|
|
|
|
postfix = primary , { call_sfx | field_sfx | inst_sfx | "?" } ;
|
|
call_sfx = "(" , [ args ] , ")" ;
|
|
field_sfx = "." , ident ;
|
|
inst_sfx = type_args ;
|
|
(* 후위 "[" = 명시적 인스턴스화, 전위 "[" = 리스트 리터럴. 위치가 결정한다 *)
|
|
args = expr , { "," , expr } , [ "," ] ;
|
|
|
|
primary = literal
|
|
| ident
|
|
| list_lit
|
|
| struct_lit
|
|
| closure
|
|
| if_expr
|
|
| match_expr
|
|
| scope_expr
|
|
| "(" , expr , ")" ;
|
|
|
|
list_lit = "[" , [ expr , { "," , expr } , [ "," ] ] , "]" ;
|
|
(* 빈 리스트는 타입 주석이 필요하다: 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_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 } ] ;
|
|
scope_expr = "scope" , ident , block ;
|
|
|
|
(* expr_ns = struct_lit로 시작하지 않는 expr.
|
|
* if/match/scope의 머리 자리에서 "{"가 블록의 시작인지 struct 리터럴인지
|
|
* 갈리지 않으므로, 그 자리의 struct 리터럴은 괄호로 감싼다 *)
|
|
|
|
(* ------------------------------------------------------------------ *)
|
|
(* 패턴 *)
|
|
(* ------------------------------------------------------------------ *)
|
|
|
|
pattern = "_" | literal | ctor_pattern | ident ;
|
|
ctor_pattern = ident , "(" , pattern , { "," , pattern } , [ "," ] , ")" ;
|
|
(* 가드 없음. 중첩은 제한 없음 (exhaustiveness 알고리즘이 처리한다) *)
|