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:
+61
-15
@@ -26,13 +26,40 @@ type entry = {
|
||||
|
||||
type t = {
|
||||
root : string;
|
||||
std : string option; (* 표준 라이브러리 디렉터리 *)
|
||||
modules : (string, entry) Hashtbl.t;
|
||||
(* 통계: 무엇이 몇 번 재검사됐는지. 측정이 목적이므로 처음부터 센다. *)
|
||||
mutable checked : string list;
|
||||
}
|
||||
|
||||
let create ?(root = ".") () =
|
||||
{ root; modules = Hashtbl.create 16; checked = [] }
|
||||
(* 표준 라이브러리를 찾는다. COOL_STD가 있으면 그것, 없으면 위로 올라가며
|
||||
std/list.cool을 찾는다. 못 찾으면 없는 것이고, 그때 std 이름들은 외부
|
||||
참조로 남는다 — 없다고 말하지 않는다. *)
|
||||
let find_std root =
|
||||
match Sys.getenv_opt "COOL_STD" with
|
||||
| Some d when Sys.file_exists d -> Some d
|
||||
| _ ->
|
||||
let rec up dir n =
|
||||
if n = 0 then None
|
||||
else
|
||||
let cand = Filename.concat dir "std" in
|
||||
if Sys.file_exists (Filename.concat cand "list.cool") then Some cand
|
||||
else
|
||||
let parent = Filename.dirname dir in
|
||||
if parent = dir then None else up parent (n - 1)
|
||||
in
|
||||
up
|
||||
(if Filename.is_relative root then Filename.concat (Sys.getcwd ()) root
|
||||
else root)
|
||||
8
|
||||
|
||||
let create ?(root = ".") ?std () =
|
||||
{
|
||||
root;
|
||||
std = (match std with Some _ -> std | None -> find_std root);
|
||||
modules = Hashtbl.create 16;
|
||||
checked = [];
|
||||
}
|
||||
|
||||
(* 패키지 경로와 지역 모듈 경로를 구분한다. 첫 세그먼트에 점이 있으면
|
||||
패키지 참조다 (cool.dev/std/list). v0에는 패키지 해소가 없으므로 그런
|
||||
@@ -42,7 +69,24 @@ let is_package path =
|
||||
| Some i -> String.contains (String.sub path 0 i) '.'
|
||||
| None -> String.contains path '.'
|
||||
|
||||
let resolve_import st path = Filename.concat st.root (path ^ ".cool")
|
||||
let std_prefix = "cool.dev/std/"
|
||||
|
||||
let starts_with p s =
|
||||
String.length s >= String.length p && String.sub s 0 (String.length p) = p
|
||||
|
||||
(* 경로를 파일로 바꾼다. 바꿀 수 없으면 None — v0에는 패키지 해소가 없으므로
|
||||
표준 라이브러리 밖의 패키지는 불투명하게 남는다. *)
|
||||
let resolve_import st path : string option =
|
||||
if starts_with std_prefix path then
|
||||
let name =
|
||||
String.sub path (String.length std_prefix)
|
||||
(String.length path - String.length std_prefix)
|
||||
in
|
||||
match st.std with
|
||||
| Some d -> Some (Filename.concat d (name ^ ".cool"))
|
||||
| None -> None
|
||||
else if is_package path then None
|
||||
else Some (Filename.concat st.root (path ^ ".cool"))
|
||||
|
||||
let read_file file =
|
||||
let ic = open_in_bin file in
|
||||
@@ -84,7 +128,9 @@ let check_module st path : entry =
|
||||
let imports =
|
||||
List.filter_map
|
||||
(fun (a, p) ->
|
||||
if is_package p then None else Some (a, resolve_import st p))
|
||||
match resolve_import st p with
|
||||
| Some f -> Some (a, f)
|
||||
| None -> None)
|
||||
(imports_of ast)
|
||||
in
|
||||
(* 의존 모듈의 exported surface를 소비 측 별칭으로 한정해 합친다.
|
||||
@@ -137,8 +183,9 @@ let rec load st ?(visiting = []) path : unit =
|
||||
| Ok ast ->
|
||||
List.iter
|
||||
(fun (_, p) ->
|
||||
if not (is_package p) then
|
||||
load st ~visiting:(path :: visiting) (resolve_import st p))
|
||||
match resolve_import st p with
|
||||
| Some f -> load st ~visiting:(path :: visiting) f
|
||||
| None -> ())
|
||||
(imports_of ast));
|
||||
Hashtbl.replace st.modules path (check_module st path)
|
||||
end
|
||||
@@ -211,15 +258,14 @@ let run st path : (string, error) result =
|
||||
| None ->
|
||||
Error { file = path; line = 0; col = 0; message = "모듈을 찾을 수 없습니다" }
|
||||
| Some e -> (
|
||||
let dep_surface =
|
||||
List.concat_map
|
||||
(fun (alias, p) ->
|
||||
match Hashtbl.find_opt st.modules p with
|
||||
| Some d -> Iface.qualify alias d.iface.Iface.items
|
||||
| None -> [])
|
||||
e.imports
|
||||
(* IR은 그래프 전체를 받는다. 별칭이 실행 의미에 남지 않도록. *)
|
||||
let mods =
|
||||
Hashtbl.fold
|
||||
(fun p (d : entry) acc ->
|
||||
{ Ir.m_path = p; m_ast = d.ast; m_deps = d.imports } :: acc)
|
||||
st.modules []
|
||||
in
|
||||
let prog = Ir.of_module ~imports:dep_surface e.ast in
|
||||
match Interp.run prog (main_params e.ast) with
|
||||
let prog = Ir.of_program mods in
|
||||
match Interp.run prog path (main_params e.ast) with
|
||||
| Ok out -> Ok out
|
||||
| Error (pos, msg) -> Error (err_of path pos msg))
|
||||
|
||||
Reference in New Issue
Block a user