diff --git a/bin/main.ml b/bin/main.ml index 07f9a53..3a79a52 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -6,6 +6,7 @@ let usage = cool run typed IR 인터프리터로 실행 cool tokens 토큰 덤프 (렉서 디버깅) cool ast 구문 트리 덤프 (파서 디버깅) + cool deps 외부 참조 목록 (모듈의 의존 표면) cool version 버전 출력 |} @@ -31,6 +32,16 @@ let dump_ast file = m.Coollang.Ast.items; 0 +let dump_deps file = + match Coollang.Driver.resolve file with + | Error errors -> report_errors errors + | Ok info -> + List.iter + (fun (name, (p : Coollang.Token.pos)) -> + Printf.printf "%d:%d %s\n" p.line p.col name) + info.Coollang.Resolve.externals; + 0 + let () = let argv = Array.to_list Sys.argv in let code = @@ -39,6 +50,7 @@ let () = | [ "run"; file ] -> report (Coollang.Driver.run file) | [ "tokens"; file ] -> dump_tokens file | [ "ast"; file ] -> dump_ast file + | [ "deps"; file ] -> dump_deps file | [ "version" ] -> print_endline Coollang.Version.string; 0 diff --git a/lib/driver.ml b/lib/driver.ml index 7977c38..b313c10 100644 --- a/lib/driver.ml +++ b/lib/driver.ml @@ -42,6 +42,20 @@ let parse_file file = let ast (file : string) : (Ast.modul, error list) result = match parse_file file with Ok m -> Ok m | Error e -> Error [ e ] +let resolve (file : string) : (Resolve.info, error list) result = + match parse_file file with + | Error e -> Error [ e ] + | Ok m -> ( + let info, errors = Resolve.resolve m in + match errors with + | [] -> Ok info + | _ -> + Error + (List.map + (fun (e : Resolve.error) -> + { file; line = e.pos.line; col = e.pos.col; message = e.msg }) + errors)) + let check (files : string list) : (unit, error list) result = match files with | [] -> @@ -49,12 +63,16 @@ let check (files : string list) : (unit, error list) result = | _ -> let errors = List.filter_map - (fun f -> match parse_file f with Ok _ -> None | Error e -> Some e) + (fun f -> + match resolve f with + | Ok _ -> None + | Error (e :: _) -> Some e + | Error [] -> None) files in if errors <> [] then Error errors else - (* 구문 분석은 통과했다. 통과했다고 말하지 않는다 — 파이프라인의 + (* 이름 해소는 통과했다. 통과했다고 말하지 않는다 — 파이프라인의 나머지가 아직 없으므로 검사되지 않은 것이다. *) Error (List.map diff --git a/lib/resolve.ml b/lib/resolve.ml new file mode 100644 index 0000000..b709ed8 --- /dev/null +++ b/lib/resolve.ml @@ -0,0 +1,360 @@ +(* 이름 해소. + 이 단계가 답하는 질문은 하나다 — 이 모듈 하나만 보고 무엇을 결정할 수 있는가. + + 결정할 수 있는 것(= 오류로 보고): 중복 정의, 중복 파라미터, 선언되지 않은 + effect 변수, 같은 블록의 재바인딩, 불변 바인딩에 대한 대입, 모듈에 없는 + reexport 대상, 지역 바인딩이 아닌 scope 이름, variant 인자 개수. + + 결정할 수 없는 것(= 외부 참조로 기록): 다른 모듈에서 오는 이름. 모듈 로딩이 + 아직 없으므로 오류가 아니다. 이 목록이 곧 모듈의 의존 표면이고, + interface hash가 소비할 입력이다. *) + +open Ast + +type error = { pos : Token.pos; msg : string } +type info = { externals : (string * Token.pos) list } +type item_kind = K_fn | K_type | K_const | K_import + +type state = { + items : (string, item_kind) Hashtbl.t; + ctors : (string, string * int) Hashtbl.t; (* variant -> (enum, 인자 개수) *) + mutable ty_params : string list; + mutable eff_params : string list; + mutable scopes : (string * bool) list list; (* 이름 -> 가변 여부 *) + mutable errors : error list; + mutable ext : (string * Token.pos) list; +} + +let builtin_types = + [ "Int"; "Bool"; "String"; "Unit"; "List"; "Option"; "Result" ] + +let builtin_values = [ "unit"; "Ok"; "Err"; "Some"; "None" ] +let error st pos msg = st.errors <- { pos; msg } :: st.errors + +let external_ref st name pos = + if not (List.mem_assoc name st.ext) then st.ext <- (name, pos) :: st.ext + +let push st = st.scopes <- [] :: st.scopes +let pop st = match st.scopes with _ :: r -> st.scopes <- r | [] -> () + +let lookup_local st name = + let rec go = function + | [] -> None + | scope :: rest -> ( + match List.assoc_opt name scope with + | Some m -> Some m + | None -> go rest) + in + go st.scopes + +let bind st pos name mut_ = + (match st.scopes with + | scope :: _ when List.mem_assoc name scope -> + error st pos (Printf.sprintf "같은 블록에서 %s을(를) 다시 묶을 수 없습니다" name) + | _ -> ()); + match st.scopes with + | scope :: rest -> st.scopes <- ((name, mut_) :: scope) :: rest + | [] -> st.scopes <- [ [ (name, mut_) ] ] + +(* ------------------------------------------------------------------ *) +(* effect와 타입 *) +(* ------------------------------------------------------------------ *) + +let resolve_eff_atom st = function + | Eff_var v, pos -> + if not (List.mem v st.eff_params) then + error st pos + (Printf.sprintf "선언되지 않은 effect 변수 %s (시그니처에 [%s: effects]로 선언해야 합니다)" + v v) + | Eff_set names, pos -> + List.iter + (fun { cap; _ } -> + if + (not (List.mem cap builtin_types)) + && (not (List.mem cap st.ty_params)) + && Hashtbl.find_opt st.items cap <> Some K_type + then external_ref st cap pos) + names + +let rec resolve_ty st = function + | T_named { name; args; pos } -> + if + (not (List.mem name st.ty_params)) + && (not (List.mem name builtin_types)) + && + match Hashtbl.find_opt st.items name with + | Some K_type | Some K_import -> false + | _ -> true + then external_ref st name pos; + List.iter (resolve_targ st pos) args + | T_fn { params; eff; ret; pos; _ } -> + List.iter (resolve_ty st) params; + (match eff with None -> () | Some a -> resolve_eff_atom st (a, pos)); + Option.iter (resolve_ty st) ret + +and resolve_targ st pos = function + | TA_ty t -> resolve_ty st t + | TA_eff a -> resolve_eff_atom st (a, pos) + +let resolve_eff_result st pos atoms = + List.iter (fun a -> resolve_eff_atom st (a, pos)) atoms + +(* ------------------------------------------------------------------ *) +(* 패턴 *) +(* ------------------------------------------------------------------ *) + +let rec resolve_pattern st seen = function + | P_wild _ | P_lit _ -> () + | P_bind (n, pos) -> ( + (* 알려진 variant면 인자 없는 생성자다. 아니면 바인딩이다. + 이 판정이 이름 해소의 일이다 — 구문만으로는 갈리지 않는다. *) + match Hashtbl.find_opt st.ctors n with + | Some (_, 0) -> () + | Some (enum, arity) -> + error st pos (Printf.sprintf "%s.%s는 인자 %d개가 필요합니다" enum n arity) + | None -> + if List.mem n !seen then + error st pos (Printf.sprintf "패턴에 %s이(가) 두 번 나옵니다" n) + else ( + seen := n :: !seen; + bind st pos n false)) + | P_ctor { name; args; pos } -> + (match Hashtbl.find_opt st.ctors name with + | Some (enum, arity) when arity <> List.length args -> + error st pos + (Printf.sprintf "%s.%s는 인자 %d개가 필요합니다 (%d개 주어짐)" enum name arity + (List.length args)) + | Some _ -> () + | None -> external_ref st name pos); + List.iter (resolve_pattern st seen) args + +(* ------------------------------------------------------------------ *) +(* 식 *) +(* ------------------------------------------------------------------ *) + +let rec resolve_expr st = function + | E_lit _ -> () + | E_ident (n, pos) -> + if lookup_local st n = None then + if Hashtbl.mem st.items n then () + else if List.mem n builtin_values then () + else if Hashtbl.mem st.ctors n then () + else external_ref st n pos + | E_list (xs, _) -> List.iter (resolve_expr st) xs + | E_struct { name; fields; pos } -> + resolve_ty st (T_named { name; args = []; pos }); + List.iter (fun (_, e) -> resolve_expr st e) fields + | E_closure c -> + push st; + List.iter + (fun (n, t) -> + Option.iter (resolve_ty st) t; + bind st c.cl_pos n false) + c.cl_params; + (match c.cl_eff with + | None -> () + | Some a -> resolve_eff_atom st (a, c.cl_pos)); + Option.iter (resolve_ty st) c.cl_ret; + resolve_block st c.cl_body; + pop st + | E_if { cond; then_; else_; _ } -> + resolve_expr st cond; + resolve_block_scoped st then_; + Option.iter (resolve_expr st) else_ + | E_match { scrutinee; arms; _ } -> + resolve_expr st scrutinee; + List.iter + (fun a -> + push st; + let seen = ref [] in + resolve_pattern st seen a.arm_pat; + resolve_expr st a.arm_body; + pop st) + arms + | E_scope { name; body; pos } -> + (* TaskScope는 use 값이라 모듈 수준에 있을 수 없다. scope의 머리는 + 반드시 지역 바인딩(파라미터 포함)이어야 한다. *) + if lookup_local st name = None then + error st pos + (Printf.sprintf + "scope 이름 %s이(가) 지역 바인딩이 아닙니다 (TaskScope는 use 값이라 파라미터나 상위 블록에서 \ + 와야 합니다)" + name); + resolve_block_scoped st body + | E_block b -> resolve_block_scoped st b + | E_call { callee; args; _ } -> + resolve_expr st callee; + List.iter (resolve_expr st) args + | E_field { obj; _ } -> resolve_expr st obj + | E_inst { callee; args; pos } -> + resolve_expr st callee; + List.iter (resolve_targ st pos) args + | E_try { inner; _ } -> resolve_expr st inner + | E_unary { operand; _ } -> resolve_expr st operand + | E_binary { lhs; rhs; _ } -> + resolve_expr st lhs; + resolve_expr st rhs + +and resolve_block_scoped st b = + push st; + resolve_block st b; + pop st + +and resolve_block st b = List.iter (resolve_stmt st) b.stmts + +and resolve_stmt st = function + | S_let { mut_; pat; ty; value; pos } -> + resolve_expr st value; + Option.iter (resolve_ty st) ty; + let seen = ref [] in + (* let의 패턴 바인딩은 가변 여부를 let에서 받는다 *) + let rec bind_pat = function + | P_bind (n, p) when not (Hashtbl.mem st.ctors n) -> + if List.mem n !seen then + error st p (Printf.sprintf "패턴에 %s이(가) 두 번 나옵니다" n) + else ( + seen := n :: !seen; + bind st p n mut_) + | P_ctor { args; _ } -> List.iter bind_pat args + | p -> resolve_pattern st seen p + in + bind_pat pat; + ignore pos + | S_return { value; _ } -> Option.iter (resolve_expr st) value + | S_assign { place; value; pos } -> ( + resolve_expr st value; + resolve_expr st place; + let rec root = function + | E_ident (n, _) -> Some n + | E_field { obj; _ } -> root obj + | _ -> None + in + match root place with + | None -> () + | Some n -> ( + match lookup_local st n with + | Some true -> () + | Some false -> + error st pos + (Printf.sprintf "%s은(는) 불변 바인딩이라 대입할 수 없습니다 (mut이 필요합니다)" n) + | None -> ())) + | S_expr e -> resolve_expr st e + +(* ------------------------------------------------------------------ *) +(* 선언 *) +(* ------------------------------------------------------------------ *) + +let resolve_signature st (d : fn_decl) = + st.ty_params <- + List.filter_map + (fun g -> if g.gp_effect then None else Some g.gp_name) + d.fn_gen; + st.eff_params <- + List.filter_map + (fun g -> if g.gp_effect then Some g.gp_name else None) + d.fn_gen; + let seen = Hashtbl.create 8 in + List.iter + (fun g -> + if Hashtbl.mem seen g.gp_name then + error st g.gp_pos (Printf.sprintf "제네릭 파라미터 %s이(가) 중복입니다" g.gp_name) + else Hashtbl.add seen g.gp_name ()) + d.fn_gen; + let pseen = Hashtbl.create 8 in + List.iter + (fun p -> + if Hashtbl.mem pseen p.p_name then + error st p.p_pos (Printf.sprintf "파라미터 %s이(가) 중복입니다" p.p_name) + else Hashtbl.add pseen p.p_name (); + resolve_ty st p.p_ty) + d.fn_params; + (match d.fn_eff with + | None -> () + | Some atoms -> resolve_eff_result st d.fn_pos atoms); + Option.iter (resolve_ty st) d.fn_ret + +let resolve_fn st (d : fn_decl) = + resolve_signature st d; + match d.fn_body with + | None -> () + | Some body -> + push st; + List.iter (fun p -> bind st p.p_pos p.p_name p.p_mut) d.fn_params; + resolve_block st body; + pop st + +let declare st pos name kind = + if Hashtbl.mem st.items name then + error st pos (Printf.sprintf "%s이(가) 이 모듈에 두 번 정의되었습니다" name) + else Hashtbl.add st.items name kind + +let resolve (m : modul) : info * error list = + let st = + { + items = Hashtbl.create 32; + ctors = Hashtbl.create 32; + ty_params = []; + eff_params = []; + scopes = []; + errors = []; + ext = []; + } + in + (* 1차: 모듈 수준 이름을 모은다. 선언 순서에 의존하지 않는다. *) + List.iter + (fun it -> + match it with + | I_import { alias; pos; _ } -> declare st pos alias K_import + | I_fn { decl; _ } -> declare st decl.fn_pos decl.fn_name K_fn + | I_struct { name; pos; _ } -> declare st pos name K_type + | I_enum { name; variants; pos; _ } -> + declare st pos name K_type; + List.iter + (fun v -> + if Hashtbl.mem st.ctors v.v_name then + error st v.v_pos + (Printf.sprintf "variant %s이(가) 중복입니다" v.v_name) + else Hashtbl.add st.ctors v.v_name (name, List.length v.v_args)) + variants + | I_capability { name; pos; _ } -> declare st pos name K_type + | I_const { name; pos; _ } -> declare st pos name K_const + | I_reexport _ -> ()) + m.items; + (* 2차: 본문을 훑는다. *) + List.iter + (fun it -> + st.ty_params <- []; + st.eff_params <- []; + st.scopes <- []; + match it with + | I_import _ -> () + | I_reexport { name; pos } -> + if not (Hashtbl.mem st.items name) then + error st pos + (Printf.sprintf "%s은(는) 이 모듈에 없습니다 (reexport 대상은 모듈에 있어야 합니다)" + name) + | I_fn { decl; _ } -> resolve_fn st decl + | I_struct { gen; fields; _ } -> + st.ty_params <- List.map (fun g -> g.gp_name) gen; + List.iter (fun f -> resolve_ty st f.f_ty) fields + | I_enum { gen; variants; _ } -> + st.ty_params <- List.map (fun g -> g.gp_name) gen; + List.iter (fun v -> List.iter (resolve_ty st) v.v_args) variants + | I_capability { methods; _ } -> + List.iter (fun d -> resolve_signature st d) methods + | I_const { ty; value; _ } -> + resolve_ty st ty; + push st; + resolve_expr st value; + pop st) + m.items; + let by_pos (_, a) (_, b) = + compare (a.Token.line, a.Token.col) (b.Token.line, b.Token.col) + in + ( { externals = List.sort by_pos (List.rev st.ext) }, + List.sort + (fun a b -> + compare + (a.pos.Token.line, a.pos.Token.col) + (b.pos.Token.line, b.pos.Token.col)) + (List.rev st.errors) ) diff --git a/samples/03_scope_concurrency.cool b/samples/03_scope_concurrency.cool index ac7c3ae..6718a97 100644 --- a/samples/03_scope_concurrency.cool +++ b/samples/03_scope_concurrency.cool @@ -2,6 +2,8 @@ // // scope는 이름을 갖는다. 중첩 시 어느 스코프에 붙는 태스크인지가 코드에 보인다. +import "cool.dev/std/list" as List + pub fn main( sc: TaskScope, fs: FileSystem, @@ -22,24 +24,28 @@ pub fn main( log.write("all done") } -// 중첩. 이름이 있으므로 outer.spawn과 inner.spawn이 그냥 갈린다. -// 안쪽 블록에서 바깥 스코프에 붙이는 것도 표현 가능하다 — 수명이 코드에 보인다. -pub fn fan_out( - outer: TaskScope, - fs: FileSystem, - log: Logger, - groups: List[List[Path]], -) effects {TaskScope.spawn, FileSystem.read, Logger.write} { - List.each(groups, fn(g) { - scope inner { - List.each(g, fn(p) { - inner.spawn(fn() { fs.read(p) }) - }) - // 이 태스크는 inner가 아니라 outer의 수명을 따른다. - outer.spawn(fn() { log.write("group done") }) - } - }) -} +// 중첩 — 아직 표현할 수 없다. 열린 결정 하나가 걸려 있다. +// +// scope X { } 의 X가 "이미 가진 TaskScope를 쓴다"인지 "새 자식 스코프를 만들어 +// X로 묶는다"인지가 정해지지 않았다. 전자면 아래 inner가 어디서도 오지 않고, +// 후자면 자식의 부모가 무엇인지 구문에 없다(= ambient authority). +// 이름 해소가 이 구멍을 잡았다. 결정 전까지 주석으로 둔다. +// +// pub fn fan_out( +// outer: TaskScope, +// fs: FileSystem, +// log: Logger, +// groups: List[List[Path]], +// ) effects {TaskScope.spawn, FileSystem.read, Logger.write} { +// List.each(groups, fn(g) { +// scope inner { +// List.each(g, fn(p) { +// inner.spawn(fn() { fs.read(p) }) +// }) +// outer.spawn(fn() { log.write("group done") }) +// } +// }) +// } // spawn 클로저는 by-move 또는 immutable capture만 가능하다. pub fn broadcast( diff --git a/test/test_coollang.ml b/test/test_coollang.ml index 104ade9..d0b5e90 100644 --- a/test/test_coollang.ml +++ b/test/test_coollang.ml @@ -362,3 +362,109 @@ let () = errors; check (f ^ " 구문 분석") false)) files + +(* ================================================================== *) +(* 이름 해소 *) +(* ================================================================== *) + +let resolve_errs src = + let m = parse_ok src in + let _, errors = Resolve.resolve m in + List.map (fun (e : Resolve.error) -> e.msg) errors + +let resolve_ext src = + let m = parse_ok src in + let info, _ = Resolve.resolve m in + List.map fst info.Resolve.externals + +let has_err src frag = + List.exists + (fun m -> + let n = String.length frag in + let rec go i = + i + n <= String.length m && (String.sub m i n = frag || go (i + 1)) + in + go 0) + (resolve_errs src) + +(* --- 모듈 하나로 결정할 수 있는 것 = 오류 --- *) + +let () = + check "중복 정의" (has_err "fn f()\nfn f()" "두 번 정의"); + check "중복 파라미터" (has_err "fn f(a: Int, a: Int)" "파라미터 a"); + check "중복 제네릭" (has_err "fn f[a, a]()" "제네릭 파라미터 a"); + check "선언되지 않은 effect 변수" (has_err "fn f() effects e" "선언되지 않은 effect 변수 e"); + check "선언된 effect 변수는 통과" (resolve_errs "fn f[e: effects]() effects e" = []); + check "파라미터 타입 안의 effect 변수도 본다" + (has_err "fn f(g: fn() effects e)" "선언되지 않은 effect 변수 e"); + check "같은 블록의 재바인딩" + (has_err "fn f() {\n let a = 1\n let a = 2\n a\n}" "다시 묶을 수 없습니다"); + check "중첩 블록의 가림은 허용" + (resolve_errs + "fn f() {\n let a = 1\n if a {\n let a = 2\n a\n }\n}" + = []); + check "불변 바인딩에 대입" (has_err "fn f() {\n let a = 1\n a = 2\n}" "불변 바인딩"); + check "mut이면 통과" (resolve_errs "fn f() {\n let mut a = 1\n a = 2\n}" = []); + check "모듈에 없는 reexport" (has_err "reexport N" "이 모듈에 없습니다"); + check "모듈에 있는 reexport는 통과" (resolve_errs "enum N {\n A,\n}\nreexport N" = []) + +(* --- scope 머리는 지역 바인딩이어야 한다 --- *) + +let () = + check "지역 바인딩이 아닌 scope 이름" + (has_err "fn f() {\n scope sc {\n sc\n }\n}" "지역 바인딩이 아닙니다"); + check "파라미터로 온 scope는 통과" + (resolve_errs "fn f(sc: TaskScope) {\n scope sc {\n sc\n }\n}" = []) + +(* --- variant는 구문이 아니라 이름 해소가 판정한다 --- *) + +let () = + let enum = "enum E {\n A(Code),\n B,\n}\n" in + check "인자 없는 variant는 바인딩이 아니라 생성자" + (resolve_errs + (enum ^ "fn f(e: E) {\n match e {\n B => 1,\n _ => 2,\n }\n}") + = []); + check "variant 인자 개수" + (has_err + (enum ^ "fn f(e: E) {\n match e {\n A => 1,\n _ => 2,\n }\n}") + "인자 1개가 필요합니다"); + check "패턴의 중복 바인딩" + (has_err + (enum ^ "fn f(e: E) {\n match e {\n A(x) => x,\n _ => 0,\n }\n}" + ^ "\nfn g(e: E) {\n match e {\n A(x) => x,\n _ => 0,\n }\n}") + "두 번 나옵니다" + = false) + +(* --- 결정할 수 없는 것 = 외부 참조 --- *) + +let () = + check "모르는 타입은 외부 참조" (resolve_ext "fn f(a: Widget)" = [ "Widget" ]); + check "import한 이름은 외부 참조가 아니다" + (resolve_ext "import \"d/p\" as P\nfn f() {\n P.go()\n}" = []); + check "지역 이름은 외부 참조가 아니다" (resolve_ext "fn f(a: Int) {\n a\n}" = []); + check "builtin은 외부 참조가 아니다" + (resolve_ext "fn f(a: Int) -> Result[Int, Int] {\n Ok(a)\n}" = []); + check "effect 집합의 capability도 표면에 든다" + (resolve_ext "fn f() effects {Gw.pay}" = [ "Gw" ]) + +(* --- 샘플: 01~07은 이름 해소를 통과해야 한다 --- *) + +let () = + let dir = "../samples" in + let files = + Sys.readdir dir |> Array.to_list + |> List.filter (fun f -> Filename.check_suffix f ".cool") + |> List.filter (fun f -> f <> "08_syntax_errors.cool") + |> List.sort compare + in + List.iter + (fun f -> + let path = Filename.concat dir f in + match Driver.resolve path with + | Ok _ -> () + | Error errors -> + List.iter + (fun e -> Printf.printf " %s\n" (Driver.string_of_error e)) + errors; + check (f ^ " 이름 해소") false) + files