diff --git a/bin/main.ml b/bin/main.ml index 7f47fd5..07f9a53 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -5,6 +5,7 @@ let usage = cool check ... 타입/effect/capability 검사 (fast path) cool run typed IR 인터프리터로 실행 cool tokens 토큰 덤프 (렉서 디버깅) + cool ast 구문 트리 덤프 (파서 디버깅) 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 diff --git a/docs/grammar.ebnf b/docs/grammar.ebnf index 3bb3fa8..2f90802 100644 --- a/docs/grammar.ebnf +++ b/docs/grammar.ebnf @@ -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. diff --git a/lib/ast.ml b/lib/ast.ml new file mode 100644 index 0000000..6b41afb --- /dev/null +++ b/lib/ast.ml @@ -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 diff --git a/lib/driver.ml b/lib/driver.ml index 5de862e..7977c38 100644 --- a/lib/driver.ml +++ b/lib/driver.ml @@ -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) diff --git a/lib/parser.ml b/lib/parser.ml new file mode 100644 index 0000000..ba60301 --- /dev/null +++ b/lib/parser.ml @@ -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 diff --git a/samples/02_higher_order_effects.cool b/samples/02_higher_order_effects.cool index 0c1665d..e54b5bc 100644 --- a/samples/02_higher_order_effects.cool +++ b/samples/02_higher_order_effects.cool @@ -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( diff --git a/samples/05_move_errors.cool b/samples/05_move_errors.cool index d4a64a7..51ed26a 100644 --- a/samples/05_move_errors.cool +++ b/samples/05_move_errors.cool @@ -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", - } -} diff --git a/samples/08_syntax_errors.cool b/samples/08_syntax_errors.cool new file mode 100644 index 0000000..793a463 --- /dev/null +++ b/samples/08_syntax_errors.cool @@ -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", + } +} diff --git a/samples/README.md b/samples/README.md index 20f9583..a121781 100644 --- a/samples/README.md +++ b/samples/README.md @@ -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은 파서가 거부해야 한다.** 그래서 파일을 나눴다: 한 파일에 섞으면 파서가 +첫 오류에서 멈춰 검사기 케이스에 영영 도달하지 못한다. + +오류 복구는 아직 없다. 파서는 첫 오류에서 멈춘다. ## 확정된 표기 diff --git a/test/test_coollang.ml b/test/test_coollang.ml index 840f45a..104ade9 100644 --- a/test/test_coollang.ml +++ b/test/test_coollang.ml @@ -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