세 가지를 자율 결정으로 닫고 타입 검사까지 세웠다.
1. scope 부모 문법: scope 자식 = 부모 { ... }로 확정. 부모를 적지 않으면
자식의 부모가 "가장 가까운 스코프"가 되는데 그것이 정확히 ambient
authority다. 권한 사슬이 main의 루트 TaskScope부터 끊기지 않으려면 모든
자식이 부모를 이름으로 지목해야 한다. 이름 해소가 이 구멍을 잡아준 건이라,
주석 처리했던 중첩 예제를 되살렸다.
2. prelude: 타입 이름은 그 타입에 딸린 함수의 이름공간이다(String.len,
File.close). 정적 메서드를 위한 별도 문법을 두지 않는다.
3. 타입 검사: 이 모듈 안에서 아는 것만 검사한다. 외부 이름은 TUnknown이
되어 무엇과도 맞는다 — 모르는 것을 틀렸다고 말하지 않기 위해서다.
제네릭 해소는 호출 지점의 지역 unification이고 함수 하나를 넘지 않는다.
클로저 파라미터 타입은 기대 타입에서 읽어온다(양방향 검사, 로컬).
단계 소유권을 하나 정정했다. affinity는 타입 동등성의 일부가 아니다. 값이
affine인지는 무엇을 capture했는지로 정해지는 substructural 성질이고
move/affinity 검사가 소유한다. 타입 검사가 이걸 판정하려다 정당한 코드를
거부하는 것을 06에서 확인하고 unify에서 분리했다.
unify 버그 하나: 같은 미지수끼리 unify할 때 occurs check가 자기 자신을
발견해 실패하고 있었다. 02의 fold 호출에서 잡혔다.
09_type_errors.cool 추가 — 외부 타입이 하나도 없어 검사기가 TUnknown으로
빠져나갈 구석이 없는 파일이다. 18개 진단이 전부 잡히고, 첫 오류에서 멈추지
않고 모두 보고한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
216 lines
10 KiB
EBNF
216 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 , "=" , 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 } , [ "," ] , ")" ;
|
|
(* 가드 없음. 중첩은 제한 없음 (exhaustiveness 알고리즘이 처리한다) *)
|