std: 표준 라이브러리 — effect 다형성이 처음으로 검사된다
std/list.cool, string.cool, int.cool, bool.cool. 본문 없는 선언이고 런타임이 구현한다. 이 파일들은 구현이 아니라 시험대다. 부채 상환이 아니라 검증이다. std가 없을 때 List.each는 모르는 이름이라 조용히 통과했다. "모르는 것을 틀렸다고 말하지 않는다"는 맞는 원칙이지만, 그 그늘에 검사되지 않는 영역이 숨어 있었다. 넣자마자 샘플 01이 깨졌다 — List.each에 Result를 반환하는 클로저를 넘기고 그 안에서 ?를 쓰고 있었다. each는 값을 남기지 않는 클로저만 받고, ?는 클로저 밖으로 나가지 못하며, 결과를 버릴 방법은 언어에 없다. map으로 고쳤다. 이것이 std를 먼저 한 이유 그 자체다. - IR은 이제 모듈 그래프 전체를 받고 전역 이름은 "<경로>#<이름>"으로 정규화된다. 별칭은 가져오는 쪽의 선택이므로 실행 의미에 남아서는 안 된다. - 본문 없는 선언은 런타임 구현으로 낮아지고, 그 이름은 모듈 파일에서 온다 (std/list.cool의 each = "list.each"). 별칭과 무관하다. - prelude 없음. std도 명시적으로 가져온다. - samples/12: effect 변수가 호출 지점에서 실제로 해소된다는 증거. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
@@ -17,11 +17,14 @@ type pos = Token.pos
|
||||
|
||||
(* 이름은 낮추기 시점에 분류된다. 실행 중에 "이게 지역인가 전역인가"를
|
||||
다시 묻지 않는다. *)
|
||||
(* 전역 이름은 정규화된다: "<모듈 경로>#<이름>". 별칭은 가져오는 쪽의 선택이라
|
||||
실행 의미에 들어와서는 안 된다 — 같은 함수가 부르는 자리마다 다른 이름이 되면
|
||||
IR은 더 이상 v1의 번역 대상이 아니다. *)
|
||||
type ref_kind =
|
||||
| R_local of string
|
||||
| R_global of string (* 이 프로그램의 함수 *)
|
||||
| R_ctor of string * string (* enum 이름, variant 이름 *)
|
||||
| R_builtin of string (* String.concat 등 런타임 제공 *)
|
||||
| R_global of string (* "<경로>#<이름>" *)
|
||||
| R_ctor of string * string (* enum 이름, 정규화된 variant 이름 *)
|
||||
| R_builtin of string (* 본문 없는 선언 = 런타임이 구현한다 *)
|
||||
|
||||
type pat =
|
||||
| IP_wild
|
||||
@@ -71,7 +74,23 @@ let qual modl name = match modl with Some a -> a ^ "." ^ name | None -> name
|
||||
|
||||
(* 지역 이름 스택. 검사 단계가 아니라 분류만 한다 — 여기서 못 찾은 이름은
|
||||
전역이거나 생성자이거나 런타임 제공이다. *)
|
||||
type lctx = { prog : program; mutable locals : string list list }
|
||||
type modinfo = {
|
||||
m_path : string; (* 정규 경로 *)
|
||||
m_ast : Ast.modul;
|
||||
m_deps : (string * string) list; (* 별칭 -> 정규 경로 *)
|
||||
}
|
||||
|
||||
(* 본문 없는 선언은 런타임이 구현한다. 그 이름은 모듈 파일 이름에서 온다:
|
||||
std/list.cool의 each는 "list.each"다. 가져오는 쪽의 별칭과 무관하다. *)
|
||||
let builtin_name path name =
|
||||
Filename.remove_extension (Filename.basename path) ^ "." ^ name
|
||||
|
||||
type lctx = {
|
||||
prog : program;
|
||||
mods : (string, modinfo) Hashtbl.t;
|
||||
cur : modinfo;
|
||||
mutable locals : string list list;
|
||||
}
|
||||
|
||||
let lpush c = c.locals <- [] :: c.locals
|
||||
let lpop c = match c.locals with _ :: r -> c.locals <- r | [] -> ()
|
||||
@@ -82,41 +101,85 @@ let lbind c n =
|
||||
| [] -> c.locals <- [ [ n ] ]
|
||||
|
||||
let is_local c n = List.exists (fun s -> List.mem n s) c.locals
|
||||
let builtin_ctors = [ "Ok"; "Err"; "Some"; "None" ]
|
||||
|
||||
let builtins =
|
||||
[
|
||||
"String.concat";
|
||||
"String.len";
|
||||
"Int.show";
|
||||
"Bool.show";
|
||||
"List.len";
|
||||
"List.each";
|
||||
"List.map";
|
||||
"print";
|
||||
]
|
||||
(* 한 모듈 안에서 이름 하나를 분류한다. 지역이 아니면 그 모듈의 정의를 본다. *)
|
||||
let in_module c (mi : modinfo) name =
|
||||
let key = mi.m_path ^ "#" ^ name in
|
||||
if Hashtbl.mem c.prog.ctors key then
|
||||
match Hashtbl.find_opt c.prog.ctors key with
|
||||
| Some (enum, _) -> Some (R_ctor (enum, key))
|
||||
| None -> None
|
||||
else if Hashtbl.mem c.prog.consts key then Some (R_global key)
|
||||
else
|
||||
let decl =
|
||||
List.find_map
|
||||
(function
|
||||
| Ast.I_fn { decl; _ } when decl.fn_name = name -> Some decl
|
||||
| _ -> None)
|
||||
mi.m_ast.items
|
||||
in
|
||||
match decl with
|
||||
| Some d when d.fn_body <> None -> Some (R_global key)
|
||||
| Some _ -> Some (R_builtin (builtin_name mi.m_path name))
|
||||
| None -> None
|
||||
|
||||
let classify c name =
|
||||
if is_local c name then R_local name
|
||||
else if List.mem name builtin_ctors then
|
||||
R_ctor ((if name = "Ok" || name = "Err" then "Result" else "Option"), name)
|
||||
else
|
||||
match Hashtbl.find_opt c.prog.ctors name with
|
||||
| Some (enum, _) -> R_ctor (enum, name)
|
||||
| None ->
|
||||
if Hashtbl.mem c.prog.fns name || Hashtbl.mem c.prog.consts name then
|
||||
R_global name
|
||||
else R_builtin name
|
||||
match in_module c c.cur name with
|
||||
| Some k -> k
|
||||
| None -> (
|
||||
(* 한정 이름 "A.f": A가 이 모듈의 import면 그 모듈에서 찾는다. *)
|
||||
match String.index_opt name '.' with
|
||||
| None -> R_builtin name
|
||||
| Some i -> (
|
||||
let a = String.sub name 0 i in
|
||||
let f = String.sub name (i + 1) (String.length name - i - 1) in
|
||||
match List.assoc_opt a c.cur.m_deps with
|
||||
| None -> R_builtin name
|
||||
| Some path -> (
|
||||
match Hashtbl.find_opt c.mods path with
|
||||
| None -> R_builtin name
|
||||
| Some mi -> (
|
||||
match in_module c mi f with
|
||||
| Some k -> k
|
||||
| None -> R_builtin (builtin_name path f)))))
|
||||
|
||||
(* 패턴의 생성자도 정규 이름으로 접는다. 값 쪽과 같은 키를 써야 match가
|
||||
성립한다 — 두 곳이 다른 규칙을 쓰면 조용히 안 맞는다. *)
|
||||
let ctor_key c modl name : string option =
|
||||
if List.mem name builtin_ctors then Some name
|
||||
else
|
||||
let path =
|
||||
match modl with
|
||||
| None -> Some c.cur.m_path
|
||||
| Some a -> List.assoc_opt a c.cur.m_deps
|
||||
in
|
||||
match path with
|
||||
| None -> None
|
||||
| Some p ->
|
||||
let k = p ^ "#" ^ name in
|
||||
if Hashtbl.mem c.prog.ctors k then Some k else None
|
||||
|
||||
let rec lower_pat c (p : Ast.pattern) : pat =
|
||||
match p with
|
||||
| Ast.P_wild _ -> IP_wild
|
||||
| Ast.P_lit (l, _) -> IP_lit l
|
||||
| Ast.P_bind (n, _) ->
|
||||
| Ast.P_bind (n, _) -> (
|
||||
(* 인자 없는 생성자는 이름만 쓴다. 바인딩과 구별은 여기서 끝난다. *)
|
||||
if Hashtbl.mem c.prog.ctors n then IP_ctor (n, [])
|
||||
else (
|
||||
lbind c n;
|
||||
IP_bind n)
|
||||
match ctor_key c None n with
|
||||
| Some k -> IP_ctor (k, [])
|
||||
| None ->
|
||||
lbind c n;
|
||||
IP_bind n)
|
||||
| Ast.P_ctor { modl; name; args; _ } ->
|
||||
IP_ctor (qual modl name, List.map (lower_pat c) args)
|
||||
let k =
|
||||
match ctor_key c modl name with Some k -> k | None -> qual modl name
|
||||
in
|
||||
IP_ctor (k, List.map (lower_pat c) args)
|
||||
|
||||
let rec lower c (e : Ast.expr) : t =
|
||||
match e with
|
||||
@@ -238,35 +301,43 @@ and lower_stmt c (s : Ast.stmt) : stmt =
|
||||
S_assign { place = lower c place; value = lower c value; pos }
|
||||
| Ast.S_expr e -> S_do (lower c e)
|
||||
|
||||
let of_module ?(imports : Ast.item list = []) (m : Ast.modul) : program =
|
||||
(* 모듈 그래프 전체를 하나의 IR 프로그램으로 낮춘다.
|
||||
|
||||
모듈 하나씩 낮추고 싶은 유혹이 있지만, 그러면 별칭이 실행 의미에 남는다.
|
||||
같은 함수가 A에서는 Shapes.double, B에서는 Geo.double이 되어 IR이 더 이상
|
||||
프로그램의 정규형이 아니게 된다. 정규 경로로 한 번 접는다. *)
|
||||
let of_program (mods : modinfo list) : program =
|
||||
let prog =
|
||||
{
|
||||
fns = Hashtbl.create 32;
|
||||
ctors = Hashtbl.create 32;
|
||||
caps = Hashtbl.create 8;
|
||||
consts = Hashtbl.create 8;
|
||||
fns = Hashtbl.create 64;
|
||||
ctors = Hashtbl.create 64;
|
||||
caps = Hashtbl.create 16;
|
||||
consts = Hashtbl.create 16;
|
||||
}
|
||||
in
|
||||
let items = imports @ m.items in
|
||||
(* 1차: 이름부터. 낮추기가 이름을 분류하려면 전체가 먼저 보여야 한다. *)
|
||||
let index = Hashtbl.create 16 in
|
||||
List.iter (fun mi -> Hashtbl.replace index mi.m_path mi) mods;
|
||||
(* 1차: 이름부터. 낮추기가 이름을 분류하려면 그래프 전체가 먼저 보여야 한다. *)
|
||||
List.iter
|
||||
(fun (it : Ast.item) ->
|
||||
match it with
|
||||
| Ast.I_enum { name; variants; _ } ->
|
||||
List.iter
|
||||
(fun (v : Ast.variant) ->
|
||||
Hashtbl.replace prog.ctors v.v_name (name, List.length v.v_args))
|
||||
variants
|
||||
| Ast.I_capability { name; methods; _ } ->
|
||||
Hashtbl.replace prog.caps name
|
||||
(List.map (fun (d : Ast.fn_decl) -> d.fn_name) methods)
|
||||
| Ast.I_fn { decl; _ } when decl.fn_body <> None ->
|
||||
Hashtbl.replace prog.fns decl.fn_name
|
||||
{ fn_name = decl.fn_name; fn_params = []; fn_body = I_unit }
|
||||
| Ast.I_const { name; _ } -> Hashtbl.replace prog.consts name I_unit
|
||||
| _ -> ())
|
||||
items;
|
||||
(* builtin 생성자도 IR이 알아야 한다 *)
|
||||
(fun mi ->
|
||||
let key n = mi.m_path ^ "#" ^ n in
|
||||
List.iter
|
||||
(fun (it : Ast.item) ->
|
||||
match it with
|
||||
| Ast.I_enum { name; variants; _ } ->
|
||||
List.iter
|
||||
(fun (v : Ast.variant) ->
|
||||
Hashtbl.replace prog.ctors (key v.v_name)
|
||||
(name, List.length v.v_args))
|
||||
variants
|
||||
| Ast.I_capability { name; methods; _ } ->
|
||||
Hashtbl.replace prog.caps name
|
||||
(List.map (fun (d : Ast.fn_decl) -> d.fn_name) methods)
|
||||
| Ast.I_const { name; _ } ->
|
||||
Hashtbl.replace prog.consts (key name) I_unit
|
||||
| _ -> ())
|
||||
mi.m_ast.items)
|
||||
mods;
|
||||
List.iter
|
||||
(fun (v, e, n) -> Hashtbl.replace prog.ctors v (e, n))
|
||||
[
|
||||
@@ -276,29 +347,37 @@ let of_module ?(imports : Ast.item list = []) (m : Ast.modul) : program =
|
||||
("None", "Option", 0);
|
||||
];
|
||||
(* 2차: 본문 *)
|
||||
let c = { prog; locals = [] } in
|
||||
List.iter
|
||||
(fun (it : Ast.item) ->
|
||||
match it with
|
||||
| Ast.I_fn { decl; _ } -> (
|
||||
match decl.fn_body with
|
||||
| None -> ()
|
||||
| Some body ->
|
||||
(fun mi ->
|
||||
let c = { prog; mods = index; cur = mi; locals = [] } in
|
||||
let key n = mi.m_path ^ "#" ^ n in
|
||||
List.iter
|
||||
(fun (it : Ast.item) ->
|
||||
match it with
|
||||
| Ast.I_fn { decl; _ } -> (
|
||||
match decl.fn_body with
|
||||
| None -> ()
|
||||
| Some body ->
|
||||
c.locals <- [];
|
||||
lpush c;
|
||||
List.iter
|
||||
(fun (p : Ast.param) -> lbind c p.p_name)
|
||||
decl.fn_params;
|
||||
let b = lower_block c body in
|
||||
lpop c;
|
||||
Hashtbl.replace prog.fns (key decl.fn_name)
|
||||
{
|
||||
fn_name = key decl.fn_name;
|
||||
fn_params =
|
||||
List.map
|
||||
(fun (p : Ast.param) -> p.p_name)
|
||||
decl.fn_params;
|
||||
fn_body = b;
|
||||
})
|
||||
| Ast.I_const { name; value; _ } ->
|
||||
c.locals <- [];
|
||||
lpush c;
|
||||
List.iter (fun (p : Ast.param) -> lbind c p.p_name) decl.fn_params;
|
||||
let b = lower_block c body in
|
||||
lpop c;
|
||||
Hashtbl.replace prog.fns decl.fn_name
|
||||
{
|
||||
fn_name = decl.fn_name;
|
||||
fn_params =
|
||||
List.map (fun (p : Ast.param) -> p.p_name) decl.fn_params;
|
||||
fn_body = b;
|
||||
})
|
||||
| Ast.I_const { name; value; _ } ->
|
||||
c.locals <- [];
|
||||
Hashtbl.replace prog.consts name (lower c value)
|
||||
| _ -> ())
|
||||
items;
|
||||
Hashtbl.replace prog.consts (key name) (lower c value)
|
||||
| _ -> ())
|
||||
mi.m_ast.items)
|
||||
mods;
|
||||
prog
|
||||
|
||||
Reference in New Issue
Block a user