parser: 재귀 하강 구문 분석기와 AST
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
This commit is contained in:
+11
@@ -5,6 +5,7 @@ let usage =
|
||||
cool check <file.cool>... 타입/effect/capability 검사 (fast path)
|
||||
cool run <file.cool> typed IR 인터프리터로 실행
|
||||
cool tokens <file.cool> 토큰 덤프 (렉서 디버깅)
|
||||
cool ast <file.cool> 구문 트리 덤프 (파서 디버깅)
|
||||
cool version 버전 출력
|
||||
|}
|
||||
|
||||
@@ -21,6 +22,15 @@ let dump_tokens file =
|
||||
List.iter (fun t -> print_endline (Coollang.Token.show t)) tokens;
|
||||
0
|
||||
|
||||
let dump_ast file =
|
||||
match Coollang.Driver.ast file with
|
||||
| Error errors -> report_errors errors
|
||||
| Ok m ->
|
||||
List.iter
|
||||
(fun it -> print_endline (Coollang.Ast.show_item it))
|
||||
m.Coollang.Ast.items;
|
||||
0
|
||||
|
||||
let () =
|
||||
let argv = Array.to_list Sys.argv in
|
||||
let code =
|
||||
@@ -28,6 +38,7 @@ let () =
|
||||
| "check" :: files -> report (Coollang.Driver.check files)
|
||||
| [ "run"; file ] -> report (Coollang.Driver.run file)
|
||||
| [ "tokens"; file ] -> dump_tokens file
|
||||
| [ "ast"; file ] -> dump_ast file
|
||||
| [ "version" ] ->
|
||||
print_endline Coollang.Version.string;
|
||||
0
|
||||
|
||||
+27
-11
@@ -29,6 +29,10 @@
|
||||
* 필수다. 콤마로 끝난 줄은 NEWLINE을 만들지 않으므로 목록이 자연히 이어진다.
|
||||
* 공식 formatter가 이를 강제한다.
|
||||
*
|
||||
* 중괄호 목록(struct 정의, enum 정의, struct 리터럴, effect 집합, match 팔)
|
||||
* 안에서는 항목 구분자 주변의 NEWLINE이 무시된다. 문이 놓이는 자리가 아니므로
|
||||
* 구분자로 쓰이지 않는다. 아래 프로덕션에 { NEWLINE }으로 적혀 있다.
|
||||
*
|
||||
* 시그니처 머리에서는 NEWLINE이 문법적으로 허용되고 무시된다. effects 절이
|
||||
* 줄 끝에 오면 "}"가 값 종료 토큰이라 NEWLINE이 삽입되는데, 이 자리는 문이
|
||||
* 끝날 수 있는 자리가 아니므로 아래 프로덕션이 { NEWLINE }으로 흡수한다.
|
||||
@@ -69,14 +73,17 @@ fn_decl = "fn" , ident , [ gen_params ] , "(" , [ params ] , ")" ,
|
||||
|
||||
struct_decl = [ "copyable" ] , "struct" , ident , [ gen_params ] ,
|
||||
"{" , { field } , "}" ;
|
||||
field = ident , ":" , type , "," , { NEWLINE } ;
|
||||
field = ident , ":" , type , { NEWLINE } ,
|
||||
[ "," , { NEWLINE } ] ;
|
||||
|
||||
enum_decl = "enum" , ident , [ gen_params ] , "{" , { variant } , "}" ;
|
||||
variant = ident , [ "(" , type_list , ")" ] , "," , { NEWLINE } ;
|
||||
variant = ident , [ "(" , type_list , ")" ] , { NEWLINE } ,
|
||||
[ "," , { NEWLINE } ] ;
|
||||
|
||||
capability_decl = "capability" , ident , "{" , { cap_method } , "}" ;
|
||||
capability_decl = "capability" , ident , "{" , { NEWLINE } , { cap_method } , "}" ;
|
||||
cap_method = "fn" , ident , "(" , [ params ] , ")" , { NEWLINE } ,
|
||||
[ eff_result , { NEWLINE } ] , [ "->" , type ] , NEWLINE ;
|
||||
[ eff_result , { NEWLINE } ] , [ "->" , type ] ,
|
||||
{ NEWLINE } ;
|
||||
|
||||
const_decl = "const" , ident , ":" , type , "=" , expr ;
|
||||
|
||||
@@ -102,7 +109,9 @@ eff_union = eff_atom , { "|" , eff_atom } ;
|
||||
eff_param = "effects" , eff_atom ;
|
||||
|
||||
eff_atom = ident | eff_set ;
|
||||
eff_set = "{" , [ eff_name , { "," , eff_name } , [ "," ] ] , "}" ;
|
||||
eff_set = "{" , { NEWLINE } ,
|
||||
[ eff_name , { { NEWLINE } , "," , { NEWLINE } , eff_name } ,
|
||||
{ NEWLINE } , [ "," , { NEWLINE } ] ] , "}" ;
|
||||
eff_name = ident , "." , ident ;
|
||||
(* 타입 수준 이름만. capability 값의 identity는 정적 층에 등장하지 않는다 *)
|
||||
|
||||
@@ -116,16 +125,22 @@ fn_type = [ "affine" ] , "fn" , "(" , [ type_list ] , ")" ,
|
||||
[ eff_param ] , [ "->" , type ] ;
|
||||
|
||||
named_type = ident , [ type_args ] ;
|
||||
type_args = "[" , type , { "," , type } , [ "," ] , "]" ;
|
||||
type_args = "[" , targ , { "," , targ } , [ "," ] , "]" ;
|
||||
targ = type | eff_set ;
|
||||
(* 제네릭 인자는 타입 또는 effect다. 맨 이름은 둘 다일 수 있으므로 파서는
|
||||
* 타입으로 읽고 이름 해소가 판정한다 — 구문 층에서 갈리지 않아도 된다 *)
|
||||
type_list = type , { "," , type } , [ "," ] ;
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 문과 블록 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
block = "{" , { NEWLINE } , { stmt } , "}" ;
|
||||
stmt = ( let_stmt | return_stmt | assign_stmt | expr ) ,
|
||||
NEWLINE , { NEWLINE } ;
|
||||
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 ] ;
|
||||
@@ -169,7 +184,7 @@ list_lit = "[" , [ expr , { "," , expr } , [ "," ] ] , "]" ;
|
||||
(* 빈 리스트는 타입 주석이 필요하다: let xs: List[Int] = [] *)
|
||||
|
||||
struct_lit = ident , "{" , { field_init } , "}" ;
|
||||
field_init = ident , ":" , expr , "," , { NEWLINE } ;
|
||||
field_init = ident , ":" , expr , { NEWLINE } , [ "," , { NEWLINE } ] ;
|
||||
|
||||
closure = "fn" , "(" , [ cl_params ] , ")" ,
|
||||
[ eff_param ] , [ "->" , type ] , block ;
|
||||
@@ -180,7 +195,8 @@ cl_param = ident , [ ":" , type ] ;
|
||||
|
||||
if_expr = "if" , expr_ns , block , [ "else" , ( block | if_expr ) ] ;
|
||||
match_expr = "match" , expr_ns , "{" , { arm } , "}" ;
|
||||
arm = pattern , "=>" , ( expr | block ) , "," , { NEWLINE } ;
|
||||
arm = pattern , "=>" , ( expr | block ) , { NEWLINE } ,
|
||||
[ "," , { NEWLINE } ] ;
|
||||
scope_expr = "scope" , ident , block ;
|
||||
|
||||
(* expr_ns = struct_lit로 시작하지 않는 expr.
|
||||
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
(* 구문 트리. grammar.ebnf의 프로덕션과 일대일로 대응한다.
|
||||
모든 노드가 위치를 들고 다닌다. *)
|
||||
|
||||
type pos = Token.pos
|
||||
type lit = L_int of string | L_str of string | L_bool of bool
|
||||
type eff_name = { cap : string; meth : string }
|
||||
|
||||
(* eff_atom: 변수 단독 또는 리터럴 집합. 합집합은 여기 없다 —
|
||||
결과 위치에서만 eff_atom의 목록으로 나타난다. *)
|
||||
type eff_atom = Eff_var of string | Eff_set of eff_name list
|
||||
type eff_result = eff_atom list (* 합집합. 길이 1이면 단일 *)
|
||||
|
||||
type ty =
|
||||
| T_named of { name : string; args : targ list; pos : pos }
|
||||
| T_fn of {
|
||||
affine : bool;
|
||||
params : ty list;
|
||||
eff : eff_atom option;
|
||||
ret : ty option;
|
||||
pos : pos;
|
||||
}
|
||||
|
||||
(* 제네릭 인자는 타입 또는 effect다. 맨 이름은 둘 다일 수 있으므로
|
||||
파서는 타입으로 읽고 이름 해소가 판정한다. *)
|
||||
and targ = TA_ty of ty | TA_eff of eff_atom
|
||||
|
||||
type pattern =
|
||||
| P_wild of pos
|
||||
| P_lit of lit * pos
|
||||
| P_bind of string * pos
|
||||
| P_ctor of { name : string; args : pattern list; pos : pos }
|
||||
|
||||
type unop = U_not | U_neg
|
||||
|
||||
type binop =
|
||||
| B_or
|
||||
| B_and
|
||||
| B_eq
|
||||
| B_ne
|
||||
| B_lt
|
||||
| B_le
|
||||
| B_gt
|
||||
| B_ge
|
||||
| B_add
|
||||
| B_sub
|
||||
| B_mul
|
||||
| B_div
|
||||
| B_rem
|
||||
|
||||
type expr =
|
||||
| E_lit of lit * pos
|
||||
| E_ident of string * pos
|
||||
| E_list of expr list * pos
|
||||
| E_struct of { name : string; fields : (string * expr) list; pos : pos }
|
||||
| E_closure of closure
|
||||
| E_if of { cond : expr; then_ : block; else_ : expr option; pos : pos }
|
||||
| E_match of { scrutinee : expr; arms : arm list; pos : pos }
|
||||
| E_scope of { name : string; body : block; pos : pos }
|
||||
| E_block of block
|
||||
| E_call of { callee : expr; args : expr list; pos : pos }
|
||||
| E_field of { obj : expr; name : string; pos : pos }
|
||||
| E_inst of { callee : expr; args : targ list; pos : pos }
|
||||
| E_try of { inner : expr; pos : pos }
|
||||
| E_unary of { op : unop; operand : expr; pos : pos }
|
||||
| E_binary of { op : binop; lhs : expr; rhs : expr; pos : pos }
|
||||
|
||||
and closure = {
|
||||
cl_params : (string * ty option) list;
|
||||
cl_eff : eff_atom option;
|
||||
cl_ret : ty option;
|
||||
cl_body : block;
|
||||
cl_pos : pos;
|
||||
}
|
||||
|
||||
and arm = { arm_pat : pattern; arm_body : expr; arm_pos : pos }
|
||||
and block = { stmts : stmt list; block_pos : pos }
|
||||
|
||||
and stmt =
|
||||
| S_let of {
|
||||
mut_ : bool;
|
||||
pat : pattern;
|
||||
ty : ty option;
|
||||
value : expr;
|
||||
pos : pos;
|
||||
}
|
||||
| S_return of { value : expr option; pos : pos }
|
||||
| S_assign of { place : expr; value : expr; pos : pos }
|
||||
| S_expr of expr
|
||||
|
||||
type gen_param = { gp_name : string; gp_effect : bool; gp_pos : pos }
|
||||
|
||||
type param = {
|
||||
p_own : bool;
|
||||
p_mut : bool;
|
||||
p_name : string;
|
||||
p_ty : ty;
|
||||
p_pos : pos;
|
||||
}
|
||||
|
||||
type fn_decl = {
|
||||
fn_name : string;
|
||||
fn_gen : gen_param list;
|
||||
fn_params : param list;
|
||||
fn_eff : eff_result option;
|
||||
fn_ret : ty option;
|
||||
fn_body : block option;
|
||||
fn_pos : pos;
|
||||
}
|
||||
|
||||
type field = { f_name : string; f_ty : ty; f_pos : pos }
|
||||
type variant = { v_name : string; v_args : ty list; v_pos : pos }
|
||||
|
||||
type item =
|
||||
| I_import of { path : string; alias : string; pos : pos }
|
||||
| I_reexport of { name : string; pos : pos }
|
||||
| I_fn of { pub : bool; decl : fn_decl }
|
||||
| I_struct of {
|
||||
pub : bool;
|
||||
copyable : bool;
|
||||
name : string;
|
||||
gen : gen_param list;
|
||||
fields : field list;
|
||||
pos : pos;
|
||||
}
|
||||
| I_enum of {
|
||||
pub : bool;
|
||||
name : string;
|
||||
gen : gen_param list;
|
||||
variants : variant list;
|
||||
pos : pos;
|
||||
}
|
||||
| I_capability of {
|
||||
pub : bool;
|
||||
name : string;
|
||||
methods : fn_decl list;
|
||||
pos : pos;
|
||||
}
|
||||
| I_const of { pub : bool; name : string; ty : ty; value : expr; pos : pos }
|
||||
|
||||
type modul = { items : item list }
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 출력 — 디버깅과 테스트용 s-식 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
let buf_lit b = function
|
||||
| L_int s -> Buffer.add_string b s
|
||||
| L_str s -> Buffer.add_string b (Printf.sprintf "%S" s)
|
||||
| L_bool v -> Buffer.add_string b (if v then "true" else "false")
|
||||
|
||||
let buf_list b f sep xs =
|
||||
List.iteri
|
||||
(fun i x ->
|
||||
if i > 0 then Buffer.add_string b sep;
|
||||
f x)
|
||||
xs
|
||||
|
||||
let buf_eff_atom b = function
|
||||
| Eff_var v -> Buffer.add_string b ("evar " ^ v)
|
||||
| Eff_set names ->
|
||||
Buffer.add_string b "eset";
|
||||
List.iter
|
||||
(fun { cap; meth } -> Buffer.add_string b (" " ^ cap ^ "." ^ meth))
|
||||
names
|
||||
|
||||
let rec buf_ty b = function
|
||||
| T_named { name; args; _ } ->
|
||||
if args = [] then Buffer.add_string b name
|
||||
else (
|
||||
Buffer.add_string b ("(" ^ name);
|
||||
List.iter
|
||||
(fun a ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_targ b a)
|
||||
args;
|
||||
Buffer.add_char b ')')
|
||||
| T_fn { affine; params; eff; ret; _ } ->
|
||||
Buffer.add_string b (if affine then "(affine-fn (" else "(fn (");
|
||||
buf_list b (buf_ty b) " " params;
|
||||
Buffer.add_char b ')';
|
||||
(match eff with
|
||||
| None -> ()
|
||||
| Some e ->
|
||||
Buffer.add_string b " [";
|
||||
buf_eff_atom b e;
|
||||
Buffer.add_char b ']');
|
||||
(match ret with
|
||||
| None -> ()
|
||||
| Some t ->
|
||||
Buffer.add_string b " -> ";
|
||||
buf_ty b t);
|
||||
Buffer.add_char b ')'
|
||||
|
||||
and buf_targ b = function
|
||||
| TA_ty t -> buf_ty b t
|
||||
| TA_eff e ->
|
||||
Buffer.add_char b '[';
|
||||
buf_eff_atom b e;
|
||||
Buffer.add_char b ']'
|
||||
|
||||
let rec buf_pattern b = function
|
||||
| P_wild _ -> Buffer.add_char b '_'
|
||||
| P_lit (l, _) -> buf_lit b l
|
||||
| P_bind (n, _) -> Buffer.add_string b n
|
||||
| P_ctor { name; args; _ } ->
|
||||
Buffer.add_string b ("(" ^ name);
|
||||
List.iter
|
||||
(fun p ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_pattern b p)
|
||||
args;
|
||||
Buffer.add_char b ')'
|
||||
|
||||
let unop_name = function U_not -> "!" | U_neg -> "-"
|
||||
|
||||
let binop_name = function
|
||||
| B_or -> "||"
|
||||
| B_and -> "&&"
|
||||
| B_eq -> "=="
|
||||
| B_ne -> "!="
|
||||
| B_lt -> "<"
|
||||
| B_le -> "<="
|
||||
| B_gt -> ">"
|
||||
| B_ge -> ">="
|
||||
| B_add -> "+"
|
||||
| B_sub -> "-"
|
||||
| B_mul -> "*"
|
||||
| B_div -> "/"
|
||||
| B_rem -> "%"
|
||||
|
||||
let rec buf_expr b = function
|
||||
| E_lit (l, _) -> buf_lit b l
|
||||
| E_ident (n, _) -> Buffer.add_string b n
|
||||
| E_list (xs, _) ->
|
||||
Buffer.add_string b "(list";
|
||||
List.iter
|
||||
(fun e ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b e)
|
||||
xs;
|
||||
Buffer.add_char b ')'
|
||||
| E_struct { name; fields; _ } ->
|
||||
Buffer.add_string b ("(struct " ^ name);
|
||||
List.iter
|
||||
(fun (n, e) ->
|
||||
Buffer.add_string b (" (" ^ n ^ " ");
|
||||
buf_expr b e;
|
||||
Buffer.add_char b ')')
|
||||
fields;
|
||||
Buffer.add_char b ')'
|
||||
| E_closure c ->
|
||||
Buffer.add_string b "(closure (";
|
||||
buf_list b
|
||||
(fun (n, t) ->
|
||||
Buffer.add_string b n;
|
||||
match t with
|
||||
| None -> ()
|
||||
| Some t ->
|
||||
Buffer.add_char b ':';
|
||||
buf_ty b t)
|
||||
" " c.cl_params;
|
||||
Buffer.add_string b ") ";
|
||||
buf_block b c.cl_body;
|
||||
Buffer.add_char b ')'
|
||||
| E_if { cond; then_; else_; _ } ->
|
||||
Buffer.add_string b "(if ";
|
||||
buf_expr b cond;
|
||||
Buffer.add_char b ' ';
|
||||
buf_block b then_;
|
||||
(match else_ with
|
||||
| None -> ()
|
||||
| Some e ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b e);
|
||||
Buffer.add_char b ')'
|
||||
| E_match { scrutinee; arms; _ } ->
|
||||
Buffer.add_string b "(match ";
|
||||
buf_expr b scrutinee;
|
||||
List.iter
|
||||
(fun a ->
|
||||
Buffer.add_string b " (";
|
||||
buf_pattern b a.arm_pat;
|
||||
Buffer.add_string b " => ";
|
||||
buf_expr b a.arm_body;
|
||||
Buffer.add_char b ')')
|
||||
arms;
|
||||
Buffer.add_char b ')'
|
||||
| E_scope { name; body; _ } ->
|
||||
Buffer.add_string b ("(scope " ^ name ^ " ");
|
||||
buf_block b body;
|
||||
Buffer.add_char b ')'
|
||||
| E_block bl -> buf_block b bl
|
||||
| E_call { callee; args; _ } ->
|
||||
Buffer.add_string b "(call ";
|
||||
buf_expr b callee;
|
||||
List.iter
|
||||
(fun e ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b e)
|
||||
args;
|
||||
Buffer.add_char b ')'
|
||||
| E_field { obj; name; _ } ->
|
||||
Buffer.add_string b "(. ";
|
||||
buf_expr b obj;
|
||||
Buffer.add_string b (" " ^ name ^ ")")
|
||||
| E_inst { callee; args; _ } ->
|
||||
Buffer.add_string b "(inst ";
|
||||
buf_expr b callee;
|
||||
List.iter
|
||||
(fun a ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_targ b a)
|
||||
args;
|
||||
Buffer.add_char b ')'
|
||||
| E_try { inner; _ } ->
|
||||
Buffer.add_string b "(? ";
|
||||
buf_expr b inner;
|
||||
Buffer.add_char b ')'
|
||||
| E_unary { op; operand; _ } ->
|
||||
Buffer.add_string b ("(" ^ unop_name op ^ " ");
|
||||
buf_expr b operand;
|
||||
Buffer.add_char b ')'
|
||||
| E_binary { op; lhs; rhs; _ } ->
|
||||
Buffer.add_string b ("(" ^ binop_name op ^ " ");
|
||||
buf_expr b lhs;
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b rhs;
|
||||
Buffer.add_char b ')'
|
||||
|
||||
and buf_block b { stmts; _ } =
|
||||
Buffer.add_string b "(block";
|
||||
List.iter
|
||||
(fun s ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_stmt b s)
|
||||
stmts;
|
||||
Buffer.add_char b ')'
|
||||
|
||||
and buf_stmt b = function
|
||||
| S_let { mut_; pat; ty; value; _ } ->
|
||||
Buffer.add_string b (if mut_ then "(let-mut " else "(let ");
|
||||
buf_pattern b pat;
|
||||
(match ty with
|
||||
| None -> ()
|
||||
| Some t ->
|
||||
Buffer.add_char b ':';
|
||||
buf_ty b t);
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b value;
|
||||
Buffer.add_char b ')'
|
||||
| S_return { value; _ } -> (
|
||||
Buffer.add_string b "(return";
|
||||
match value with
|
||||
| None -> Buffer.add_char b ')'
|
||||
| Some e ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b e;
|
||||
Buffer.add_char b ')')
|
||||
| S_assign { place; value; _ } ->
|
||||
Buffer.add_string b "(set ";
|
||||
buf_expr b place;
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b value;
|
||||
Buffer.add_char b ')'
|
||||
| S_expr e -> buf_expr b e
|
||||
|
||||
let buf_gen b gen =
|
||||
if gen <> [] then (
|
||||
Buffer.add_string b " <";
|
||||
buf_list b
|
||||
(fun g ->
|
||||
Buffer.add_string b
|
||||
(if g.gp_effect then g.gp_name ^ ":effects" else g.gp_name))
|
||||
" " gen;
|
||||
Buffer.add_char b '>')
|
||||
|
||||
let buf_fn b (d : fn_decl) =
|
||||
Buffer.add_string b ("(fn " ^ d.fn_name);
|
||||
buf_gen b d.fn_gen;
|
||||
Buffer.add_string b " (";
|
||||
buf_list b
|
||||
(fun p ->
|
||||
if p.p_own then Buffer.add_string b "own ";
|
||||
if p.p_mut then Buffer.add_string b "mut ";
|
||||
Buffer.add_string b (p.p_name ^ ":");
|
||||
buf_ty b p.p_ty)
|
||||
" " d.fn_params;
|
||||
Buffer.add_char b ')';
|
||||
(match d.fn_eff with
|
||||
| None -> ()
|
||||
| Some atoms ->
|
||||
Buffer.add_string b " [";
|
||||
buf_list b (fun a -> buf_eff_atom b a) " | " atoms;
|
||||
Buffer.add_char b ']');
|
||||
(match d.fn_ret with
|
||||
| None -> ()
|
||||
| Some t ->
|
||||
Buffer.add_string b " -> ";
|
||||
buf_ty b t);
|
||||
(match d.fn_body with
|
||||
| None -> Buffer.add_string b " decl"
|
||||
| Some bl ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_block b bl);
|
||||
Buffer.add_char b ')'
|
||||
|
||||
let buf_item b = function
|
||||
| I_import { path; alias; _ } ->
|
||||
Buffer.add_string b (Printf.sprintf "(import %S as %s)" path alias)
|
||||
| I_reexport { name; _ } -> Buffer.add_string b ("(reexport " ^ name ^ ")")
|
||||
| I_fn { pub; decl } ->
|
||||
if pub then Buffer.add_string b "pub ";
|
||||
buf_fn b decl
|
||||
| I_struct { pub; copyable; name; gen; fields; _ } ->
|
||||
if pub then Buffer.add_string b "pub ";
|
||||
if copyable then Buffer.add_string b "copyable ";
|
||||
Buffer.add_string b ("(struct " ^ name);
|
||||
buf_gen b gen;
|
||||
List.iter
|
||||
(fun f ->
|
||||
Buffer.add_string b (" (" ^ f.f_name ^ " ");
|
||||
buf_ty b f.f_ty;
|
||||
Buffer.add_char b ')')
|
||||
fields;
|
||||
Buffer.add_char b ')'
|
||||
| I_enum { pub; name; gen; variants; _ } ->
|
||||
if pub then Buffer.add_string b "pub ";
|
||||
Buffer.add_string b ("(enum " ^ name);
|
||||
buf_gen b gen;
|
||||
List.iter
|
||||
(fun v ->
|
||||
Buffer.add_string b (" (" ^ v.v_name);
|
||||
List.iter
|
||||
(fun t ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_ty b t)
|
||||
v.v_args;
|
||||
Buffer.add_char b ')')
|
||||
variants;
|
||||
Buffer.add_char b ')'
|
||||
| I_capability { pub; name; methods; _ } ->
|
||||
if pub then Buffer.add_string b "pub ";
|
||||
Buffer.add_string b ("(capability " ^ name);
|
||||
List.iter
|
||||
(fun m ->
|
||||
Buffer.add_char b ' ';
|
||||
buf_fn b m)
|
||||
methods;
|
||||
Buffer.add_char b ')'
|
||||
| I_const { pub; name; ty; value; _ } ->
|
||||
if pub then Buffer.add_string b "pub ";
|
||||
Buffer.add_string b ("(const " ^ name ^ ":");
|
||||
buf_ty b ty;
|
||||
Buffer.add_char b ' ';
|
||||
buf_expr b value;
|
||||
Buffer.add_char b ')'
|
||||
|
||||
let show_item item =
|
||||
let b = Buffer.create 256 in
|
||||
buf_item b item;
|
||||
Buffer.contents b
|
||||
|
||||
let show_expr e =
|
||||
let b = Buffer.create 128 in
|
||||
buf_expr b e;
|
||||
Buffer.contents b
|
||||
|
||||
let show_ty t =
|
||||
let b = Buffer.create 64 in
|
||||
buf_ty b t;
|
||||
Buffer.contents b
|
||||
+16
-4
@@ -2,7 +2,7 @@
|
||||
parse -> name resolution -> type check -> effect/capability check
|
||||
-> interface artifact + hash -> (cool run 시) 얇은 typed IR -> interpreter
|
||||
|
||||
현재 구현된 단계: 어휘 분석. *)
|
||||
현재 구현된 단계: 어휘 분석, 구문 분석. *)
|
||||
|
||||
type error = { file : string; line : int; col : int; message : string }
|
||||
|
||||
@@ -30,6 +30,18 @@ let lex_file file =
|
||||
let tokens (file : string) : (Token.t list, error list) result =
|
||||
match lex_file file with Ok ts -> Ok ts | Error e -> Error [ e ]
|
||||
|
||||
let parse_file file =
|
||||
match lex_file file with
|
||||
| Error e -> Error e
|
||||
| Ok tokens -> (
|
||||
match Parser.parse_result tokens with
|
||||
| Ok m -> Ok m
|
||||
| Error { pos; msg } ->
|
||||
Error { file; line = pos.line; col = pos.col; message = msg })
|
||||
|
||||
let ast (file : string) : (Ast.modul, error list) result =
|
||||
match parse_file file with Ok m -> Ok m | Error e -> Error [ e ]
|
||||
|
||||
let check (files : string list) : (unit, error list) result =
|
||||
match files with
|
||||
| [] ->
|
||||
@@ -37,12 +49,12 @@ let check (files : string list) : (unit, error list) result =
|
||||
| _ ->
|
||||
let errors =
|
||||
List.filter_map
|
||||
(fun f -> match lex_file f with Ok _ -> None | Error e -> Some e)
|
||||
(fun f -> match parse_file f with Ok _ -> None | Error e -> Some e)
|
||||
files
|
||||
in
|
||||
if errors <> [] then Error errors
|
||||
else
|
||||
(* 어휘 분석은 통과했다. 통과했다고 말하지 않는다 — 파이프라인의
|
||||
(* 구문 분석은 통과했다. 통과했다고 말하지 않는다 — 파이프라인의
|
||||
나머지가 아직 없으므로 검사되지 않은 것이다. *)
|
||||
Error
|
||||
(List.map
|
||||
@@ -51,7 +63,7 @@ let check (files : string list) : (unit, error list) result =
|
||||
file = f;
|
||||
line = 0;
|
||||
col = 0;
|
||||
message = "어휘 분석까지 통과. 파서가 아직 구현되지 않았습니다";
|
||||
message = "구문 분석까지 통과. 이름 해소가 아직 구현되지 않았습니다";
|
||||
})
|
||||
files)
|
||||
|
||||
|
||||
+786
@@ -0,0 +1,786 @@
|
||||
(* 재귀 하강 파서. grammar.ebnf의 프로덕션 하나에 함수 하나로 대응한다.
|
||||
LL(1)이므로 선읽기는 항상 한 토큰이고 backtracking은 없다.
|
||||
|
||||
문법과 얽히는 두 자리:
|
||||
- NEWLINE 흡수는 문법에 { NEWLINE }으로 적힌 자리에서만 한다.
|
||||
파서가 임의로 건너뛰지 않는다.
|
||||
- struct 리터럴은 if/match/scope 머리에서 금지된다 (no_struct). *)
|
||||
|
||||
open Ast
|
||||
|
||||
type error = { pos : Token.pos; msg : string }
|
||||
|
||||
exception Error of error
|
||||
|
||||
type state = { toks : Token.t array; mutable i : int; mutable no_struct : bool }
|
||||
|
||||
let cur st = st.toks.(st.i)
|
||||
let kind st = (cur st).Token.kind
|
||||
let pos st = (cur st).Token.pos
|
||||
let adv st = if st.i < Array.length st.toks - 1 then st.i <- st.i + 1
|
||||
let err st msg = raise (Error { pos = pos st; msg })
|
||||
|
||||
let err_expect st what =
|
||||
err st
|
||||
(Printf.sprintf "%s이(가) 필요합니다 — %s 발견" what (Token.show_kind (kind st)))
|
||||
|
||||
let accept st k =
|
||||
if kind st = k then (
|
||||
adv st;
|
||||
true)
|
||||
else false
|
||||
|
||||
let expect st k what = if not (accept st k) then err_expect st what
|
||||
|
||||
let skip_nl st =
|
||||
while kind st = Token.Newline do
|
||||
adv st
|
||||
done
|
||||
|
||||
(* 목록을 닫을 때 줄바꿈이 보이면 후행 콤마 누락이다. 일반 메시지보다
|
||||
원인을 직접 말해준다. *)
|
||||
let expect_close st k what =
|
||||
if kind st = Token.Newline then err st "다중 줄 목록에는 후행 콤마가 필요합니다"
|
||||
else expect st k what
|
||||
|
||||
let ident st what =
|
||||
match kind st with
|
||||
| Token.Ident n ->
|
||||
adv st;
|
||||
n
|
||||
| _ -> err_expect st what
|
||||
|
||||
let with_struct_ok st f =
|
||||
let saved = st.no_struct in
|
||||
st.no_struct <- false;
|
||||
let r = f () in
|
||||
st.no_struct <- saved;
|
||||
r
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* effect 절 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
let parse_eff_name st =
|
||||
let cap = ident st "capability 이름" in
|
||||
expect st Token.Dot ".";
|
||||
let meth = ident st "메서드 이름" in
|
||||
{ cap; meth }
|
||||
|
||||
let parse_eff_set st =
|
||||
expect st Token.LBrace "{";
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.RBrace then List.rev acc
|
||||
else
|
||||
let n = parse_eff_name st in
|
||||
skip_nl st;
|
||||
if accept st Token.Comma then (
|
||||
skip_nl st;
|
||||
loop (n :: acc))
|
||||
else (
|
||||
skip_nl st;
|
||||
List.rev (n :: acc))
|
||||
in
|
||||
let names = loop [] in
|
||||
expect st Token.RBrace "}";
|
||||
Eff_set names
|
||||
|
||||
let parse_eff_atom st =
|
||||
match kind st with
|
||||
| Token.LBrace -> parse_eff_set st
|
||||
| Token.Ident n ->
|
||||
adv st;
|
||||
Eff_var n
|
||||
| _ -> err_expect st "effect 변수 또는 { ... } 집합"
|
||||
|
||||
(* 결과 위치: 합집합 허용 *)
|
||||
let parse_eff_result st =
|
||||
expect st Token.Kw_effects "effects";
|
||||
let rec loop acc =
|
||||
let a = parse_eff_atom st in
|
||||
if accept st Token.Pipe then loop (a :: acc) else List.rev (a :: acc)
|
||||
in
|
||||
loop []
|
||||
|
||||
(* 파라미터 위치: 합집합이 문법에 없다 *)
|
||||
let parse_eff_param st =
|
||||
expect st Token.Kw_effects "effects";
|
||||
let a = parse_eff_atom st in
|
||||
if kind st = Token.Pipe then
|
||||
err st "파라미터 위치의 effects 절에는 합집합을 쓸 수 없습니다 (변수 단독 또는 리터럴 집합만 가능)";
|
||||
a
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 타입 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
let rec parse_ty st =
|
||||
match kind st with
|
||||
| Token.Kw_affine ->
|
||||
let p = pos st in
|
||||
adv st;
|
||||
expect st Token.Kw_fn "fn";
|
||||
parse_fn_ty st true p
|
||||
| Token.Kw_fn ->
|
||||
let p = pos st in
|
||||
adv st;
|
||||
parse_fn_ty st false p
|
||||
| Token.Ident n ->
|
||||
let p = pos st in
|
||||
adv st;
|
||||
let args = if kind st = Token.LBracket then parse_targs st else [] in
|
||||
T_named { name = n; args; pos = p }
|
||||
| _ -> err_expect st "타입"
|
||||
|
||||
and parse_fn_ty st affine p =
|
||||
expect st Token.LParen "(";
|
||||
let params =
|
||||
if kind st = Token.RParen then []
|
||||
else
|
||||
let rec loop acc =
|
||||
let t = parse_ty st in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RParen then List.rev (t :: acc) else loop (t :: acc)
|
||||
else List.rev (t :: acc)
|
||||
in
|
||||
loop []
|
||||
in
|
||||
expect_close st Token.RParen ")";
|
||||
let eff =
|
||||
if kind st = Token.Kw_effects then Some (parse_eff_param st) else None
|
||||
in
|
||||
let ret = if accept st Token.Arrow then Some (parse_ty st) else None in
|
||||
T_fn { affine; params; eff; ret; pos = p }
|
||||
|
||||
and parse_targs st =
|
||||
expect st Token.LBracket "[";
|
||||
let rec loop acc =
|
||||
let a =
|
||||
if kind st = Token.LBrace then TA_eff (parse_eff_set st)
|
||||
else TA_ty (parse_ty st)
|
||||
in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RBracket then List.rev (a :: acc) else loop (a :: acc)
|
||||
else List.rev (a :: acc)
|
||||
in
|
||||
let args = loop [] in
|
||||
expect_close st Token.RBracket "]";
|
||||
args
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 패턴 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
let rec parse_pattern st =
|
||||
let p = pos st in
|
||||
match kind st with
|
||||
| Token.Underscore ->
|
||||
adv st;
|
||||
P_wild p
|
||||
| Token.Int s ->
|
||||
adv st;
|
||||
P_lit (L_int s, p)
|
||||
| Token.Str s ->
|
||||
adv st;
|
||||
P_lit (L_str s, p)
|
||||
| Token.Kw_true ->
|
||||
adv st;
|
||||
P_lit (L_bool true, p)
|
||||
| Token.Kw_false ->
|
||||
adv st;
|
||||
P_lit (L_bool false, p)
|
||||
| Token.Ident n ->
|
||||
adv st;
|
||||
if kind st = Token.LParen then (
|
||||
adv st;
|
||||
let rec loop acc =
|
||||
let x = parse_pattern st in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RParen then List.rev (x :: acc)
|
||||
else loop (x :: acc)
|
||||
else List.rev (x :: acc)
|
||||
in
|
||||
let args = if kind st = Token.RParen then [] else loop [] in
|
||||
expect_close st Token.RParen ")";
|
||||
P_ctor { name = n; args; pos = p })
|
||||
else P_bind (n, p)
|
||||
| _ -> err_expect st "패턴"
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 식 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
let rec parse_expr st = parse_or st
|
||||
|
||||
and parse_or st =
|
||||
let lhs = ref (parse_and st) in
|
||||
while kind st = Token.PipePipe do
|
||||
let p = pos st in
|
||||
adv st;
|
||||
lhs := E_binary { op = B_or; lhs = !lhs; rhs = parse_and st; pos = p }
|
||||
done;
|
||||
!lhs
|
||||
|
||||
and parse_and st =
|
||||
let lhs = ref (parse_cmp st) in
|
||||
while kind st = Token.AmpAmp do
|
||||
let p = pos st in
|
||||
adv st;
|
||||
lhs := E_binary { op = B_and; lhs = !lhs; rhs = parse_cmp st; pos = p }
|
||||
done;
|
||||
!lhs
|
||||
|
||||
and parse_cmp st =
|
||||
let lhs = parse_add st in
|
||||
let op =
|
||||
match kind st with
|
||||
| Token.EqEq -> Some B_eq
|
||||
| Token.BangEq -> Some B_ne
|
||||
| Token.Lt -> Some B_lt
|
||||
| Token.Le -> Some B_le
|
||||
| Token.Gt -> Some B_gt
|
||||
| Token.Ge -> Some B_ge
|
||||
| _ -> None
|
||||
in
|
||||
match op with
|
||||
| None -> lhs
|
||||
| Some op ->
|
||||
let p = pos st in
|
||||
adv st;
|
||||
E_binary { op; lhs; rhs = parse_add st; pos = p }
|
||||
|
||||
and parse_add st =
|
||||
let lhs = ref (parse_mul st) in
|
||||
let rec go () =
|
||||
let op =
|
||||
match kind st with
|
||||
| Token.Plus -> Some B_add
|
||||
| Token.Minus -> Some B_sub
|
||||
| _ -> None
|
||||
in
|
||||
match op with
|
||||
| None -> ()
|
||||
| Some op ->
|
||||
let p = pos st in
|
||||
adv st;
|
||||
lhs := E_binary { op; lhs = !lhs; rhs = parse_mul st; pos = p };
|
||||
go ()
|
||||
in
|
||||
go ();
|
||||
!lhs
|
||||
|
||||
and parse_mul st =
|
||||
let lhs = ref (parse_unary st) in
|
||||
let rec go () =
|
||||
let op =
|
||||
match kind st with
|
||||
| Token.Star -> Some B_mul
|
||||
| Token.Slash -> Some B_div
|
||||
| Token.Percent -> Some B_rem
|
||||
| _ -> None
|
||||
in
|
||||
match op with
|
||||
| None -> ()
|
||||
| Some op ->
|
||||
let p = pos st in
|
||||
adv st;
|
||||
lhs := E_binary { op; lhs = !lhs; rhs = parse_unary st; pos = p };
|
||||
go ()
|
||||
in
|
||||
go ();
|
||||
!lhs
|
||||
|
||||
and parse_unary st =
|
||||
let p = pos st in
|
||||
match kind st with
|
||||
| Token.Bang ->
|
||||
adv st;
|
||||
E_unary { op = U_not; operand = parse_unary st; pos = p }
|
||||
| Token.Minus ->
|
||||
adv st;
|
||||
E_unary { op = U_neg; operand = parse_unary st; pos = p }
|
||||
| _ -> parse_postfix st
|
||||
|
||||
and parse_postfix st =
|
||||
let e = ref (parse_primary st) in
|
||||
let rec go () =
|
||||
let p = pos st in
|
||||
match kind st with
|
||||
| Token.LParen ->
|
||||
adv st;
|
||||
let args =
|
||||
if kind st = Token.RParen then []
|
||||
else
|
||||
with_struct_ok st (fun () ->
|
||||
let rec loop acc =
|
||||
let a = parse_expr st in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RParen then List.rev (a :: acc)
|
||||
else loop (a :: acc)
|
||||
else List.rev (a :: acc)
|
||||
in
|
||||
loop [])
|
||||
in
|
||||
expect_close st Token.RParen ")";
|
||||
e := E_call { callee = !e; args; pos = p };
|
||||
go ()
|
||||
| Token.Dot ->
|
||||
adv st;
|
||||
let n = ident st "필드 또는 메서드 이름" in
|
||||
e := E_field { obj = !e; name = n; pos = p };
|
||||
go ()
|
||||
| Token.LBracket ->
|
||||
let args = with_struct_ok st (fun () -> parse_targs st) in
|
||||
e := E_inst { callee = !e; args; pos = p };
|
||||
go ()
|
||||
| Token.Question ->
|
||||
adv st;
|
||||
e := E_try { inner = !e; pos = p };
|
||||
go ()
|
||||
| _ -> ()
|
||||
in
|
||||
go ();
|
||||
!e
|
||||
|
||||
and parse_primary st =
|
||||
let p = pos st in
|
||||
match kind st with
|
||||
| Token.Int s ->
|
||||
adv st;
|
||||
E_lit (L_int s, p)
|
||||
| Token.Str s ->
|
||||
adv st;
|
||||
E_lit (L_str s, p)
|
||||
| Token.Kw_true ->
|
||||
adv st;
|
||||
E_lit (L_bool true, p)
|
||||
| Token.Kw_false ->
|
||||
adv st;
|
||||
E_lit (L_bool false, p)
|
||||
| Token.LParen ->
|
||||
adv st;
|
||||
let e = with_struct_ok st (fun () -> parse_expr st) in
|
||||
expect_close st Token.RParen ")";
|
||||
e
|
||||
| Token.LBracket ->
|
||||
adv st;
|
||||
let xs =
|
||||
if kind st = Token.RBracket then []
|
||||
else
|
||||
with_struct_ok st (fun () ->
|
||||
let rec loop acc =
|
||||
let x = parse_expr st in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RBracket then List.rev (x :: acc)
|
||||
else loop (x :: acc)
|
||||
else List.rev (x :: acc)
|
||||
in
|
||||
loop [])
|
||||
in
|
||||
expect_close st Token.RBracket "]";
|
||||
E_list (xs, p)
|
||||
| Token.Kw_fn -> parse_closure st
|
||||
| Token.Kw_if -> parse_if st
|
||||
| Token.Kw_match -> parse_match st
|
||||
| Token.Kw_scope ->
|
||||
adv st;
|
||||
let name = ident st "scope 이름" in
|
||||
let body = parse_block st in
|
||||
E_scope { name; body; pos = p }
|
||||
| Token.Ident n ->
|
||||
adv st;
|
||||
if (not st.no_struct) && kind st = Token.LBrace then
|
||||
let fields = parse_struct_lit_fields st in
|
||||
E_struct { name = n; fields; pos = p }
|
||||
else E_ident (n, p)
|
||||
| _ -> err_expect st "식"
|
||||
|
||||
and parse_struct_lit_fields st =
|
||||
expect st Token.LBrace "{";
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.RBrace then List.rev acc
|
||||
else begin
|
||||
let n = ident st "필드 이름" in
|
||||
expect st Token.Colon ":";
|
||||
let v = with_struct_ok st (fun () -> parse_expr st) in
|
||||
skip_nl st;
|
||||
if accept st Token.Comma then (
|
||||
skip_nl st;
|
||||
loop ((n, v) :: acc))
|
||||
else (
|
||||
skip_nl st;
|
||||
List.rev ((n, v) :: acc))
|
||||
end
|
||||
in
|
||||
let fields = loop [] in
|
||||
expect st Token.RBrace "}";
|
||||
fields
|
||||
|
||||
and parse_closure st =
|
||||
let p = pos st in
|
||||
expect st Token.Kw_fn "fn";
|
||||
expect st Token.LParen "(";
|
||||
let params =
|
||||
if kind st = Token.RParen then []
|
||||
else
|
||||
let rec loop acc =
|
||||
let n = ident st "파라미터 이름" in
|
||||
let t = if accept st Token.Colon then Some (parse_ty st) else None in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RParen then List.rev ((n, t) :: acc)
|
||||
else loop ((n, t) :: acc)
|
||||
else List.rev ((n, t) :: acc)
|
||||
in
|
||||
loop []
|
||||
in
|
||||
expect_close st Token.RParen ")";
|
||||
let eff =
|
||||
if kind st = Token.Kw_effects then Some (parse_eff_param st) else None
|
||||
in
|
||||
let ret = if accept st Token.Arrow then Some (parse_ty st) else None in
|
||||
let body = parse_block st in
|
||||
E_closure
|
||||
{
|
||||
cl_params = params;
|
||||
cl_eff = eff;
|
||||
cl_ret = ret;
|
||||
cl_body = body;
|
||||
cl_pos = p;
|
||||
}
|
||||
|
||||
and parse_if st =
|
||||
let p = pos st in
|
||||
expect st Token.Kw_if "if";
|
||||
let saved = st.no_struct in
|
||||
st.no_struct <- true;
|
||||
let cond = parse_expr st in
|
||||
st.no_struct <- saved;
|
||||
let then_ = parse_block st in
|
||||
let else_ =
|
||||
if accept st Token.Kw_else then
|
||||
if kind st = Token.Kw_if then Some (parse_if st)
|
||||
else Some (E_block (parse_block st))
|
||||
else None
|
||||
in
|
||||
E_if { cond; then_; else_; pos = p }
|
||||
|
||||
and parse_match st =
|
||||
let p = pos st in
|
||||
expect st Token.Kw_match "match";
|
||||
let saved = st.no_struct in
|
||||
st.no_struct <- true;
|
||||
let scrutinee = parse_expr st in
|
||||
st.no_struct <- saved;
|
||||
expect st Token.LBrace "{";
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.RBrace then List.rev acc
|
||||
else begin
|
||||
let ap = pos st in
|
||||
let pat = parse_pattern st in
|
||||
if kind st = Token.Kw_if then
|
||||
err st "match 가드는 v0에 없습니다 (분기 본문에서 if를 쓰십시오)";
|
||||
expect st Token.FatArrow "=>";
|
||||
let body =
|
||||
with_struct_ok st (fun () ->
|
||||
if kind st = Token.LBrace then E_block (parse_block st)
|
||||
else parse_expr st)
|
||||
in
|
||||
skip_nl st;
|
||||
if accept st Token.Comma then (
|
||||
skip_nl st;
|
||||
loop ({ arm_pat = pat; arm_body = body; arm_pos = ap } :: acc))
|
||||
else (
|
||||
skip_nl st;
|
||||
List.rev ({ arm_pat = pat; arm_body = body; arm_pos = ap } :: acc))
|
||||
end
|
||||
in
|
||||
let arms = loop [] in
|
||||
expect st Token.RBrace "}";
|
||||
E_match { scrutinee; arms; pos = p }
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 문과 블록 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
and parse_block st =
|
||||
let p = pos st in
|
||||
expect st Token.LBrace "{";
|
||||
let saved = st.no_struct in
|
||||
st.no_struct <- false;
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.RBrace then List.rev acc
|
||||
else begin
|
||||
let s = parse_stmt st in
|
||||
if kind st = Token.Newline then (
|
||||
skip_nl st;
|
||||
loop (s :: acc))
|
||||
else if kind st = Token.RBrace then List.rev (s :: acc)
|
||||
else err st "문 끝에 줄바꿈이 필요합니다"
|
||||
end
|
||||
in
|
||||
let stmts = loop [] in
|
||||
expect st Token.RBrace "}";
|
||||
st.no_struct <- saved;
|
||||
{ stmts; block_pos = p }
|
||||
|
||||
and parse_stmt st =
|
||||
let p = pos st in
|
||||
match kind st with
|
||||
| Token.Kw_let ->
|
||||
adv st;
|
||||
let mut_ = accept st Token.Kw_mut in
|
||||
let pat = parse_pattern st in
|
||||
let ty = if accept st Token.Colon then Some (parse_ty st) else None in
|
||||
expect st Token.Eq "=";
|
||||
let value = parse_expr st in
|
||||
S_let { mut_; pat; ty; value; pos = p }
|
||||
| Token.Kw_return ->
|
||||
adv st;
|
||||
let value =
|
||||
if kind st = Token.Newline || kind st = Token.RBrace then None
|
||||
else Some (parse_expr st)
|
||||
in
|
||||
S_return { value; pos = p }
|
||||
| _ ->
|
||||
let e = parse_expr st in
|
||||
if accept st Token.Eq then begin
|
||||
let rec is_place = function
|
||||
| E_ident _ -> true
|
||||
| E_field { obj; _ } -> is_place obj
|
||||
| _ -> false
|
||||
in
|
||||
if not (is_place e) then err st "대입 왼쪽에는 변수나 필드만 올 수 있습니다";
|
||||
let value = parse_expr st in
|
||||
S_assign { place = e; value; pos = p }
|
||||
end
|
||||
else S_expr e
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 선언 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
let parse_gen_params st =
|
||||
expect st Token.LBracket "[";
|
||||
let rec loop acc =
|
||||
let p = pos st in
|
||||
let n = ident st "타입 또는 effect 파라미터 이름" in
|
||||
let is_eff =
|
||||
if accept st Token.Colon then (
|
||||
expect st Token.Kw_effects "effects";
|
||||
true)
|
||||
else false
|
||||
in
|
||||
let g = { gp_name = n; gp_effect = is_eff; gp_pos = p } in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RBracket then List.rev (g :: acc) else loop (g :: acc)
|
||||
else List.rev (g :: acc)
|
||||
in
|
||||
let gs = loop [] in
|
||||
expect_close st Token.RBracket "]";
|
||||
gs
|
||||
|
||||
let parse_params st =
|
||||
if kind st = Token.RParen then []
|
||||
else
|
||||
let rec loop acc =
|
||||
let p = pos st in
|
||||
let own = accept st Token.Kw_own in
|
||||
let mut_ = accept st Token.Kw_mut in
|
||||
let name = ident st "파라미터 이름" in
|
||||
expect st Token.Colon ":";
|
||||
let ty = parse_ty st in
|
||||
let prm =
|
||||
{ p_own = own; p_mut = mut_; p_name = name; p_ty = ty; p_pos = p }
|
||||
in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RParen then List.rev (prm :: acc)
|
||||
else loop (prm :: acc)
|
||||
else List.rev (prm :: acc)
|
||||
in
|
||||
loop []
|
||||
|
||||
(* fn_decl = "fn" ident [gen] "(" [params] ")" {NL}
|
||||
[eff_result {NL}] ["->" type {NL}] [block]
|
||||
{ NEWLINE } 흡수 위치는 문법에 적힌 그대로다. *)
|
||||
let parse_fn_decl st ~allow_body =
|
||||
let p = pos st in
|
||||
expect st Token.Kw_fn "fn";
|
||||
let name = ident st "함수 이름" in
|
||||
let gen = if kind st = Token.LBracket then parse_gen_params st else [] in
|
||||
expect st Token.LParen "(";
|
||||
let params = parse_params st in
|
||||
expect_close st Token.RParen ")";
|
||||
skip_nl st;
|
||||
let eff =
|
||||
if kind st = Token.Kw_effects then (
|
||||
let e = parse_eff_result st in
|
||||
skip_nl st;
|
||||
Some e)
|
||||
else None
|
||||
in
|
||||
let ret =
|
||||
if accept st Token.Arrow then (
|
||||
let t = parse_ty st in
|
||||
skip_nl st;
|
||||
Some t)
|
||||
else None
|
||||
in
|
||||
let body =
|
||||
if allow_body && kind st = Token.LBrace then Some (parse_block st) else None
|
||||
in
|
||||
{
|
||||
fn_name = name;
|
||||
fn_gen = gen;
|
||||
fn_params = params;
|
||||
fn_eff = eff;
|
||||
fn_ret = ret;
|
||||
fn_body = body;
|
||||
fn_pos = p;
|
||||
}
|
||||
|
||||
let parse_struct st ~pub ~copyable =
|
||||
let p = pos st in
|
||||
expect st Token.Kw_struct "struct";
|
||||
let name = ident st "타입 이름" in
|
||||
let gen = if kind st = Token.LBracket then parse_gen_params st else [] in
|
||||
expect st Token.LBrace "{";
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.RBrace then List.rev acc
|
||||
else begin
|
||||
let fp = pos st in
|
||||
let n = ident st "필드 이름" in
|
||||
expect st Token.Colon ":";
|
||||
let t = parse_ty st in
|
||||
let f = { f_name = n; f_ty = t; f_pos = fp } in
|
||||
skip_nl st;
|
||||
if accept st Token.Comma then (
|
||||
skip_nl st;
|
||||
loop (f :: acc))
|
||||
else (
|
||||
skip_nl st;
|
||||
List.rev (f :: acc))
|
||||
end
|
||||
in
|
||||
let fields = loop [] in
|
||||
expect st Token.RBrace "}";
|
||||
I_struct { pub; copyable; name; gen; fields; pos = p }
|
||||
|
||||
let parse_enum st ~pub =
|
||||
let p = pos st in
|
||||
expect st Token.Kw_enum "enum";
|
||||
let name = ident st "타입 이름" in
|
||||
let gen = if kind st = Token.LBracket then parse_gen_params st else [] in
|
||||
expect st Token.LBrace "{";
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.RBrace then List.rev acc
|
||||
else begin
|
||||
let vp = pos st in
|
||||
let n = ident st "variant 이름" in
|
||||
let args =
|
||||
if accept st Token.LParen then begin
|
||||
let rec go acc =
|
||||
let t = parse_ty st in
|
||||
if accept st Token.Comma then
|
||||
if kind st = Token.RParen then List.rev (t :: acc)
|
||||
else go (t :: acc)
|
||||
else List.rev (t :: acc)
|
||||
in
|
||||
let ts = if kind st = Token.RParen then [] else go [] in
|
||||
expect_close st Token.RParen ")";
|
||||
ts
|
||||
end
|
||||
else []
|
||||
in
|
||||
let v = { v_name = n; v_args = args; v_pos = vp } in
|
||||
skip_nl st;
|
||||
if accept st Token.Comma then (
|
||||
skip_nl st;
|
||||
loop (v :: acc))
|
||||
else (
|
||||
skip_nl st;
|
||||
List.rev (v :: acc))
|
||||
end
|
||||
in
|
||||
let variants = loop [] in
|
||||
expect st Token.RBrace "}";
|
||||
I_enum { pub; name; gen; variants; pos = p }
|
||||
|
||||
let parse_capability st ~pub =
|
||||
let p = pos st in
|
||||
expect st Token.Kw_capability "capability";
|
||||
let name = ident st "capability 이름" in
|
||||
expect st Token.LBrace "{";
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.RBrace then List.rev acc
|
||||
else
|
||||
let m = parse_fn_decl st ~allow_body:false in
|
||||
skip_nl st;
|
||||
loop (m :: acc)
|
||||
in
|
||||
let methods = loop [] in
|
||||
expect st Token.RBrace "}";
|
||||
I_capability { pub; name; methods; pos = p }
|
||||
|
||||
let parse_item st =
|
||||
let p = pos st in
|
||||
match kind st with
|
||||
| Token.Kw_import ->
|
||||
adv st;
|
||||
let path =
|
||||
match kind st with
|
||||
| Token.Str s ->
|
||||
adv st;
|
||||
s
|
||||
| _ -> err_expect st "모듈 경로 문자열"
|
||||
in
|
||||
expect st Token.Kw_as "as";
|
||||
let alias = ident st "별칭" in
|
||||
I_import { path; alias; pos = p }
|
||||
| Token.Kw_reexport ->
|
||||
adv st;
|
||||
let name = ident st "재수출할 이름" in
|
||||
I_reexport { name; pos = p }
|
||||
| _ -> (
|
||||
let pub = accept st Token.Kw_pub in
|
||||
match kind st with
|
||||
| Token.Kw_fn -> I_fn { pub; decl = parse_fn_decl st ~allow_body:true }
|
||||
| Token.Kw_copyable ->
|
||||
adv st;
|
||||
parse_struct st ~pub ~copyable:true
|
||||
| Token.Kw_struct -> parse_struct st ~pub ~copyable:false
|
||||
| Token.Kw_enum -> parse_enum st ~pub
|
||||
| Token.Kw_capability -> parse_capability st ~pub
|
||||
| Token.Kw_const ->
|
||||
adv st;
|
||||
let name = ident st "상수 이름" in
|
||||
expect st Token.Colon ":";
|
||||
let ty = parse_ty st in
|
||||
expect st Token.Eq "=";
|
||||
let value = parse_expr st in
|
||||
I_const { pub; name; ty; value; pos = p }
|
||||
| _ -> err_expect st "선언 (fn, struct, enum, capability, const)")
|
||||
|
||||
let parse_module st =
|
||||
skip_nl st;
|
||||
let rec loop acc =
|
||||
if kind st = Token.Eof then List.rev acc
|
||||
else
|
||||
let it = parse_item st in
|
||||
skip_nl st;
|
||||
loop (it :: acc)
|
||||
in
|
||||
{ items = loop [] }
|
||||
|
||||
let parse (tokens : Token.t list) : modul =
|
||||
let st = { toks = Array.of_list tokens; i = 0; no_struct = false } in
|
||||
parse_module st
|
||||
|
||||
let parse_result tokens =
|
||||
match parse tokens with m -> Ok m | exception Error e -> Error e
|
||||
@@ -29,7 +29,7 @@ pub fn map_then_each[a, b, e1: effects, e2: effects](
|
||||
}
|
||||
|
||||
// 클로저를 저장하는 sink는 own으로 유표기한다. 빌린 값을 넘길 수 없다.
|
||||
pub fn register(handler: own fn()) effects {Registry.add}
|
||||
pub fn register(own handler: fn()) effects {Registry.add}
|
||||
|
||||
// 호출 지점: e는 인자의 시그니처에서 읽어온다. 추론이 아니라 읽기.
|
||||
pub fn total_size(
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn store_capability(pay: PaymentGateway) -> Holder {
|
||||
}
|
||||
|
||||
// [E-use-escape] 빌린 값을 capture한 클로저를 own 자리에 전달
|
||||
pub fn register(handler: own fn()) effects {Registry.add}
|
||||
pub fn register(own handler: fn()) effects {Registry.add}
|
||||
|
||||
pub fn escape_via_closure(pay: PaymentGateway) effects {Registry.add} {
|
||||
register(fn() { pay.refund(OrderId(1)) })
|
||||
@@ -74,19 +74,3 @@ pub fn spawn_mutable(sc: TaskScope, mut counter: Int) effects {TaskScope.spawn}
|
||||
pub fn silent_write(log: Logger) {
|
||||
log.write("hi") // ERROR: effect Logger.write가 시그니처에 선언되지 않음
|
||||
}
|
||||
|
||||
// [E-syntax-effect-union] 파라미터 위치의 합집합은 문법에 존재하지 않는다.
|
||||
// 검사기가 아니라 파서가 거부한다 (eff_param 프로덕션에 "|"가 없다).
|
||||
pub fn peel[a, e: effects](
|
||||
f: fn(a) effects e | {Logger.write},
|
||||
) effects e -> fn(a)
|
||||
// ERROR (parse): 파라미터 위치의 effects 절은 변수 단독 또는 리터럴 집합만 허용
|
||||
|
||||
// [E-syntax-match-guard] match 가드는 v0 문법에 없다
|
||||
pub fn classify(e: PayError) -> String {
|
||||
match e {
|
||||
Network(r) if r.retryable => "retry",
|
||||
// ERROR (parse): match 가드는 지원되지 않음. 분기 본문에서 if를 쓸 것
|
||||
_ => "other",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// 08. 파서가 거부해야 하는 코드
|
||||
//
|
||||
// 05와 목적이 다르다. 05는 구문은 맞지만 검사기가 거부해야 하는 파일이고,
|
||||
// 이 파일은 파서가 거부해야 하는 파일이다.
|
||||
// 아직 오류 복구가 없으므로 파서는 첫 오류에서 멈춘다 — 한 번에 하나씩 확인한다.
|
||||
|
||||
// [E-syntax-effect-union] 파라미터 위치의 합집합은 문법에 존재하지 않는다.
|
||||
// 검사기가 아니라 파서가 거부한다 (eff_param 프로덕션에 "|"가 없다).
|
||||
pub fn peel[a, e: effects](
|
||||
f: fn(a) effects e | {Logger.write},
|
||||
) effects e -> fn(a)
|
||||
// ERROR (parse): 파라미터 위치의 effects 절은 변수 단독 또는 리터럴 집합만 허용
|
||||
|
||||
// [E-syntax-match-guard] match 가드는 v0 문법에 없다
|
||||
pub fn classify(e: PayError) -> String {
|
||||
match e {
|
||||
Network(r) if r.retryable => "retry",
|
||||
// ERROR (parse): match 가드는 지원되지 않음. 분기 본문에서 if를 쓸 것
|
||||
_ => "other",
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -15,10 +15,14 @@
|
||||
| 05_move_errors | **에러가 나야 하는** 코드 — 진단 하나씩 |
|
||||
| 06_affine_closure | callable affinity (fn vs affine fn), own과의 직교성 |
|
||||
| 07_module_interface | interface artifact가 담아야 할 것 전부 |
|
||||
| 08_syntax_errors | **파서가** 거부해야 하는 코드 |
|
||||
|
||||
`05_move_errors.cool`은 통과하면 안 되는 파일이다. 각 함수 주석의 `[E-...]`
|
||||
태그가 기대 진단이며, 체커가 생기면 그대로 테스트 케이스가 된다.
|
||||
`E-syntax-*` 태그는 파서가, 나머지는 체커가 거부해야 한다.
|
||||
05와 08은 통과하면 안 되는 파일이다. 각 함수 주석의 `[E-...]` 태그가 기대
|
||||
진단이다. 둘의 목적이 다르다 — **05는 구문은 맞지만 검사기가 거부해야 하고,
|
||||
08은 파서가 거부해야 한다.** 그래서 파일을 나눴다: 한 파일에 섞으면 파서가
|
||||
첫 오류에서 멈춰 검사기 케이스에 영영 도달하지 못한다.
|
||||
|
||||
오류 복구는 아직 없다. 파서는 첫 오류에서 멈춘다.
|
||||
|
||||
## 확정된 표기
|
||||
|
||||
|
||||
@@ -218,3 +218,147 @@ let () =
|
||||
Token.Newline;
|
||||
Token.Eof;
|
||||
])
|
||||
|
||||
(* ================================================================== *)
|
||||
(* 파서 *)
|
||||
(* ================================================================== *)
|
||||
|
||||
let parse_ok src =
|
||||
match Lexer.lex_result src with
|
||||
| Error e ->
|
||||
failwith (Printf.sprintf "렉서 오류 %d:%d %s" e.pos.line e.pos.col e.msg)
|
||||
| Ok ts -> (
|
||||
match Parser.parse_result ts with
|
||||
| Ok m -> m
|
||||
| Error e ->
|
||||
failwith (Printf.sprintf "파서 오류 %d:%d %s" e.pos.line e.pos.col e.msg))
|
||||
|
||||
let parse_err src =
|
||||
match Lexer.lex_result src with
|
||||
| Error e -> Some e.msg
|
||||
| Ok ts -> (
|
||||
match Parser.parse_result ts with Ok _ -> None | Error e -> Some e.msg)
|
||||
|
||||
let one src =
|
||||
match (parse_ok src).Ast.items with
|
||||
| [ it ] -> Ast.show_item it
|
||||
| items -> Printf.sprintf "<항목 %d개>" (List.length items)
|
||||
|
||||
(* --- 어순: effects 절은 파라미터 뒤, 화살표 앞 --- *)
|
||||
|
||||
let () =
|
||||
check "함수 선언 어순"
|
||||
(one "pub fn f(a: Int) effects {A.b} -> Int {\n a\n}"
|
||||
= "pub (fn f (a:Int) [eset A.b] -> Int (block a))");
|
||||
check "결과 위치의 effect 합집합"
|
||||
(one "fn f() effects e1 | e2" = "(fn f () [evar e1 | evar e2] decl)");
|
||||
check "함수 타입의 어순과 중첩"
|
||||
(one "fn f(g: fn(Int) effects e -> Bool) -> fn(Int) -> Int"
|
||||
= "(fn f (g:(fn (Int) [evar e] -> Bool)) -> (fn (Int) -> Int) decl)")
|
||||
|
||||
(* --- 대괄호 제네릭: 전위는 리터럴, 후위는 인스턴스화 --- *)
|
||||
|
||||
let () =
|
||||
check "타입 위치의 대괄호"
|
||||
(one "fn f(xs: List[Int]) -> Result[a, Error]"
|
||||
= "(fn f (xs:(List Int)) -> (Result a Error) decl)");
|
||||
check "식 위치의 후위 대괄호는 인스턴스화"
|
||||
(one "fn f() {\n map[Path, Int, {A.b}](xs, g)\n}"
|
||||
= "(fn f () (block (call (inst map Path Int [eset A.b]) xs g)))");
|
||||
check "전위 대괄호는 리스트 리터럴"
|
||||
(one "fn f() {\n let xs = [1, 2, 3]\n xs\n}"
|
||||
= "(fn f () (block (let xs (list 1 2 3)) xs))")
|
||||
|
||||
(* --- 파라미터 수식어 순서 --- *)
|
||||
|
||||
let () =
|
||||
check "own은 이름 앞, affine은 타입 안"
|
||||
(one "fn f(own a: affine fn() effects {F.c}, mut b: Int)"
|
||||
= "(fn f (own a:(affine-fn () [eset F.c]) mut b:Int) decl)")
|
||||
|
||||
(* --- 선언 --- *)
|
||||
|
||||
let () =
|
||||
check "copyable struct"
|
||||
(one "pub copyable struct R {\n id: Id,\n n: Int,\n}"
|
||||
= "pub copyable (struct R (id Id) (n Int))");
|
||||
check "enum variant"
|
||||
(one "pub enum E {\n A(Code),\n B,\n}" = "pub (enum E (A Code) (B))");
|
||||
check "capability"
|
||||
(one "pub capability C {\n fn m(id: Id) effects {C.m} -> R\n}"
|
||||
= "pub (capability C (fn m (id:Id) [eset C.m] -> R decl))");
|
||||
check "import와 reexport"
|
||||
(List.length (parse_ok "import \"d/p\" as P\nreexport N\n").Ast.items = 2);
|
||||
check "const" (one "pub const N: Int = 3" = "pub (const N:Int 3)")
|
||||
|
||||
(* --- 식 --- *)
|
||||
|
||||
let () =
|
||||
check "우선순위"
|
||||
(one "fn f() {\n a + b * c == d && e\n}"
|
||||
= "(fn f () (block (&& (== (+ a (* b c)) d) e)))");
|
||||
check "postfix 연쇄"
|
||||
(one "fn f() {\n p.q(r)?.s\n}"
|
||||
= "(fn f () (block (. (? (call (. p q) r)) s)))");
|
||||
check "if는 식"
|
||||
(one "fn f() {\n let x = if c {\n a\n } else {\n b\n }\n x\n}"
|
||||
= "(fn f () (block (let x (if c (block a) (block b))) x))");
|
||||
check "match"
|
||||
(one "fn f() {\n match e {\n A(_) => 1,\n _ => 2,\n }\n}"
|
||||
= "(fn f () (block (match e ((A _) => 1) (_ => 2))))");
|
||||
check "scope"
|
||||
(one "fn f() {\n scope sc {\n sc.spawn(g)\n }\n}"
|
||||
= "(fn f () (block (scope sc (block (call (. sc spawn) g)))))");
|
||||
check "struct 리터럴"
|
||||
(one "fn f() {\n H { a: 1 }\n}" = "(fn f () (block (struct H (a 1))))")
|
||||
|
||||
(* --- 파서가 거부해야 하는 것 --- *)
|
||||
|
||||
let () =
|
||||
check "파라미터 위치의 effect 합집합"
|
||||
(parse_err "fn f(g: fn(a) effects e | {A.b})"
|
||||
= Some "파라미터 위치의 effects 절에는 합집합을 쓸 수 없습니다 (변수 단독 또는 리터럴 집합만 가능)");
|
||||
check "match 가드"
|
||||
(parse_err
|
||||
"fn f() {\n match e {\n A(r) if r.x => 1,\n _ => 2,\n }\n}"
|
||||
= Some "match 가드는 v0에 없습니다 (분기 본문에서 if를 쓰십시오)");
|
||||
check "후행 콤마 누락"
|
||||
(parse_err "fn f(\n a: Int\n)" = Some "다중 줄 목록에는 후행 콤마가 필요합니다");
|
||||
check "if 머리의 struct 리터럴"
|
||||
(parse_err "fn f() {\n if H { a: 1 } {\n b\n }\n}" <> None);
|
||||
check "대입 왼쪽 검사"
|
||||
(parse_err "fn f() {\n g() = 1\n}" = Some "대입 왼쪽에는 변수나 필드만 올 수 있습니다");
|
||||
check "한 줄에 문 두 개" (parse_err "fn f() {\n a b\n}" = Some "문 끝에 줄바꿈이 필요합니다")
|
||||
|
||||
(* --- 괄호 안에서는 struct 리터럴이 다시 허용된다 --- *)
|
||||
|
||||
let () =
|
||||
check "괄호로 감싸면 if 머리에서도 가능"
|
||||
(one "fn f() {\n if (H { a: 1 }).b {\n c\n }\n}"
|
||||
= "(fn f () (block (if (. (struct H (a 1)) b) (block c))))")
|
||||
|
||||
(* --- 샘플: 01~07은 파싱되고 08은 거부되어야 한다 --- *)
|
||||
|
||||
let () =
|
||||
let dir = "../samples" in
|
||||
let files =
|
||||
Sys.readdir dir |> Array.to_list
|
||||
|> List.filter (fun f -> Filename.check_suffix f ".cool")
|
||||
|> List.sort compare
|
||||
in
|
||||
List.iter
|
||||
(fun f ->
|
||||
let path = Filename.concat dir f in
|
||||
let is_syntax_error_file = f = "08_syntax_errors.cool" in
|
||||
match Driver.ast path with
|
||||
| Ok m ->
|
||||
if is_syntax_error_file then check (f ^ " 는 파서가 거부해야 한다") false
|
||||
else check (f ^ " 구문 분석") (List.length m.Ast.items > 0)
|
||||
| Error errors ->
|
||||
if is_syntax_error_file then ()
|
||||
else (
|
||||
List.iter
|
||||
(fun e -> Printf.printf " %s\n" (Driver.string_of_error e))
|
||||
errors;
|
||||
check (f ^ " 구문 분석") false))
|
||||
files
|
||||
|
||||
Reference in New Issue
Block a user