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:
+12
@@ -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
@@ -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
@@ -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) )
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user