resolve: 이름 해소와 모듈 의존 표면 산출

이 단계가 답하는 질문은 하나다 — 이 모듈 하나만 보고 무엇을 결정할 수 있는가.
그 경계를 산출물로 만들었다.

결정할 수 있는 것은 오류로 보고한다: 중복 정의, 중복 파라미터·제네릭,
선언되지 않은 effect 변수, 같은 블록의 재바인딩, 불변 바인딩에 대한 대입,
모듈에 없는 reexport 대상, 지역 바인딩이 아닌 scope 이름, variant 인자 개수.

결정할 수 없는 것은 외부 참조로 기록하고 오류로 만들지 않는다. 모듈 로딩이
아직 없으므로 해소할 방법이 없고, 이 목록이 곧 모듈의 의존 표면이자
interface hash가 소비할 입력이다. cool deps로 볼 수 있다.

이름 해소가 아니면 못 하는 판정 하나를 구현했다: match의 맨 이름이 바인딩인지
인자 없는 생성자인지는 구문으로 갈리지 않는다. enum 선언에서 만든 생성자
표로 판정하고, 같은 표로 인자 개수도 검사한다.

샘플에서 실제 오류 둘을 잡았다:
- 03의 scope inner가 어디서도 오지 않는다. scope X { }의 X가 "이미 가진
  TaskScope를 쓴다"인지 "새 자식 스코프를 만들어 X로 묶는다"인지가 정해지지
  않은 탓이다. 후자라면 자식의 부모가 구문에 없어 ambient authority가 된다.
  결정 전까지 해당 예제를 주석으로 두고 이유를 적었다.
- 03이 List를 import 없이 쓰고 있었다. 외부 참조 목록에 드러나 채웠다.

TaskScope가 use 값이라는 사실에서 검사 하나가 따라 나온다: scope의 머리는
모듈 수준 이름일 수 없고 반드시 지역 바인딩이어야 한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
2026-08-30 02:30:52 +09:00
co-authored by Claude Opus 5
parent 1bd88fc4d7
commit 94e28d427b
5 changed files with 522 additions and 20 deletions
+12
View File
@@ -6,6 +6,7 @@ let usage =
cool run <file.cool> typed IR 인터프리터로 실행 cool run <file.cool> typed IR 인터프리터로 실행
cool tokens <file.cool> 토큰 덤프 (렉서 디버깅) cool tokens <file.cool> 토큰 덤프 (렉서 디버깅)
cool ast <file.cool> 구문 트리 덤프 (파서 디버깅) cool ast <file.cool> 구문 트리 덤프 (파서 디버깅)
cool deps <file.cool> 외부 참조 목록 (모듈의 의존 표면)
cool version 버전 출력 cool version 버전 출력
|} |}
@@ -31,6 +32,16 @@ let dump_ast file =
m.Coollang.Ast.items; m.Coollang.Ast.items;
0 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 () =
let argv = Array.to_list Sys.argv in let argv = Array.to_list Sys.argv in
let code = let code =
@@ -39,6 +50,7 @@ let () =
| [ "run"; file ] -> report (Coollang.Driver.run file) | [ "run"; file ] -> report (Coollang.Driver.run file)
| [ "tokens"; file ] -> dump_tokens file | [ "tokens"; file ] -> dump_tokens file
| [ "ast"; file ] -> dump_ast file | [ "ast"; file ] -> dump_ast file
| [ "deps"; file ] -> dump_deps file
| [ "version" ] -> | [ "version" ] ->
print_endline Coollang.Version.string; print_endline Coollang.Version.string;
0 0
+20 -2
View File
@@ -42,6 +42,20 @@ let parse_file file =
let ast (file : string) : (Ast.modul, error list) result = let ast (file : string) : (Ast.modul, error list) result =
match parse_file file with Ok m -> Ok m | Error e -> Error [ e ] 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 = let check (files : string list) : (unit, error list) result =
match files with match files with
| [] -> | [] ->
@@ -49,12 +63,16 @@ let check (files : string list) : (unit, error list) result =
| _ -> | _ ->
let errors = let errors =
List.filter_map 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 files
in in
if errors <> [] then Error errors if errors <> [] then Error errors
else else
(* 구문 분석은 통과했다. 통과했다고 말하지 않는다 — 파이프라인의 (* 이름 해소는 통과했다. 통과했다고 말하지 않는다 — 파이프라인의
나머지가 아직 없으므로 검사되지 않은 것이다. *) 나머지가 아직 없으므로 검사되지 않은 것이다. *)
Error Error
(List.map (List.map
+360
View File
@@ -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) )
+24 -18
View File
@@ -2,6 +2,8 @@
// //
// scope는 이름을 갖는다. 중첩 시 어느 스코프에 붙는 태스크인지가 코드에 보인다. // scope는 이름을 갖는다. 중첩 시 어느 스코프에 붙는 태스크인지가 코드에 보인다.
import "cool.dev/std/list" as List
pub fn main( pub fn main(
sc: TaskScope, sc: TaskScope,
fs: FileSystem, fs: FileSystem,
@@ -22,24 +24,28 @@ pub fn main(
log.write("all done") log.write("all done")
} }
// 중첩. 이름이 있으므로 outer.spawn과 inner.spawn이 그냥 갈린다. // 중첩 — 아직 표현할 수 없다. 열린 결정 하나가 걸려 있다.
// 안쪽 블록에서 바깥 스코프에 붙이는 것도 표현 가능하다 — 수명이 코드에 보인다. //
pub fn fan_out( // scope X { } 의 X가 "이미 가진 TaskScope를 쓴다"인지 "새 자식 스코프를 만들어
outer: TaskScope, // X로 묶는다"인지가 정해지지 않았다. 전자면 아래 inner가 어디서도 오지 않고,
fs: FileSystem, // 후자면 자식의 부모가 무엇인지 구문에 없다(= ambient authority).
log: Logger, // 이름 해소가 이 구멍을 잡았다. 결정 전까지 주석으로 둔다.
groups: List[List[Path]], //
) effects {TaskScope.spawn, FileSystem.read, Logger.write} { // pub fn fan_out(
List.each(groups, fn(g) { // outer: TaskScope,
scope inner { // fs: FileSystem,
List.each(g, fn(p) { // log: Logger,
inner.spawn(fn() { fs.read(p) }) // groups: List[List[Path]],
}) // ) effects {TaskScope.spawn, FileSystem.read, Logger.write} {
// 이 태스크는 inner가 아니라 outer의 수명을 따른다. // List.each(groups, fn(g) {
outer.spawn(fn() { log.write("group done") }) // 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만 가능하다. // spawn 클로저는 by-move 또는 immutable capture만 가능하다.
pub fn broadcast( pub fn broadcast(
+106
View File
@@ -362,3 +362,109 @@ let () =
errors; errors;
check (f ^ " 구문 분석") false)) check (f ^ " 구문 분석") false))
files 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