문서가 "사활"이라고 지목한 단계다. effects 절이 여기서부터 장식이 아니라 검사 대상이 된다. 핵심 규칙 — 미선언 effect = compile error(철학 1). 수행한 자리를 들고 다니므로 진단이 함수 머리가 아니라 실제로 수행한 줄에 붙는다. effect가 흐르는 경로 넷을 모두 막았다: - capability 메서드 호출이 그 메서드의 선언된 effect를 요구한다 - 함수 호출이 그 함수의 effect를 물려준다 - 클로저의 effect는 정의한 자리가 아니라 부르는 자리에서 일어난다. 클로저를 만들기만 하는 것은 effect가 아니고, 인자로 넘겨 호출되는 순간 호출자의 것이 된다 - 파라미터가 허용한 범위를 넘는 함수를 넘기면 거부한다 effect 변수는 결정 위치에서 인자의 effect로 묶인다. 순서가 중요해서 한 번 틀렸다 — 포함 검사를 unify보다 먼저 하면 아직 해소되지 않은 미지수를 제약으로 오해해 정당한 코드를 거부한다. unify가 먼저고, 남는 차이만이 위반이다. 결정되지 않은 미지수는 판정을 미룬다 — 모르는 것을 위반이라고 말하지 않는다. 보안 정리 (i)을 직접 구현했다: capability 메서드는 값을 통해서만 부를 수 있다. 타입 이름으로 부를 수 있으면 capability 없이 effect를 수행하게 되어 정리가 무너진다. effect 검사는 타입 검사와 같은 순회에서 돈다. effect 변수의 해소가 타입 변수와 같은 지점에서 일어나므로 떼어내면 순회와 인스턴스화를 두 번 한다. 소유는 나뉘되 순회는 하나다 — 문서에 근거를 적었다. 10_effect_errors.cool 추가. capability를 직접 정의해야 메서드의 effect가 알려지므로 외부 타입을 하나도 쓰지 않는다. 통과해야 하는 6개와 거부해야 하는 7개가 모두 의도대로 갈린다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
115 lines
3.7 KiB
OCaml
115 lines
3.7 KiB
OCaml
(* v0 파이프라인.
|
|
parse -> name resolution -> type check -> effect/capability check
|
|
-> interface artifact + hash -> (cool run 시) 얇은 typed IR -> interpreter
|
|
|
|
현재 구현된 단계: 어휘 분석, 구문 분석. *)
|
|
|
|
type error = { file : string; line : int; col : int; message : string }
|
|
|
|
let string_of_error { file; line; col; message } =
|
|
Printf.sprintf "%s:%d:%d: %s" file line col message
|
|
|
|
let read_file file =
|
|
try
|
|
let ic = open_in_bin file in
|
|
let n = in_channel_length ic in
|
|
let s = really_input_string ic n in
|
|
close_in ic;
|
|
Ok s
|
|
with Sys_error msg -> Error { file; line = 0; col = 0; message = msg }
|
|
|
|
let lex_file file =
|
|
match read_file file with
|
|
| Error e -> Error e
|
|
| Ok src -> (
|
|
match Lexer.lex_result src with
|
|
| Ok tokens -> Ok tokens
|
|
| Error { pos; msg } ->
|
|
Error { file; line = pos.line; col = pos.col; message = msg })
|
|
|
|
let tokens (file : string) : (Token.t list, error list) result =
|
|
match lex_file file with Ok ts -> Ok ts | Error e -> Error [ e ]
|
|
|
|
let parse_file file =
|
|
match lex_file file with
|
|
| Error e -> Error e
|
|
| Ok tokens -> (
|
|
match Parser.parse_result tokens with
|
|
| Ok m -> Ok m
|
|
| Error { pos; msg } ->
|
|
Error { file; line = pos.line; col = pos.col; message = msg })
|
|
|
|
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 typecheck (file : string) : (unit, error list) result =
|
|
match parse_file file with
|
|
| Error e -> Error [ e ]
|
|
| Ok m -> (
|
|
let _, rerrors = Resolve.resolve m in
|
|
match rerrors with
|
|
| _ :: _ ->
|
|
Error
|
|
(List.map
|
|
(fun (e : Resolve.error) ->
|
|
{ file; line = e.pos.line; col = e.pos.col; message = e.msg })
|
|
rerrors)
|
|
| [] -> (
|
|
match Typecheck.check m with
|
|
| [] -> Ok ()
|
|
| terrors ->
|
|
Error
|
|
(List.map
|
|
(fun (e : Typecheck.error) ->
|
|
{
|
|
file;
|
|
line = e.pos.line;
|
|
col = e.pos.col;
|
|
message = e.msg;
|
|
})
|
|
terrors)))
|
|
|
|
let check (files : string list) : (unit, error list) result =
|
|
match files with
|
|
| [] ->
|
|
Error [ { file = "<none>"; line = 0; col = 0; message = "검사할 파일이 없습니다" } ]
|
|
| _ ->
|
|
let errors =
|
|
List.filter_map
|
|
(fun f -> match typecheck f with Ok _ -> None | Error es -> Some es)
|
|
files
|
|
|> List.concat
|
|
in
|
|
if errors <> [] then Error errors
|
|
else
|
|
(* effect 검사까지는 통과했다. 통과했다고 말하지 않는다 — 파이프라인의
|
|
나머지가 아직 없으므로 검사되지 않은 것이다. *)
|
|
Error
|
|
(List.map
|
|
(fun f ->
|
|
{
|
|
file = f;
|
|
line = 0;
|
|
col = 0;
|
|
message =
|
|
"effect/capability 검사까지 통과. move/affinity 검사가 아직 구현되지 않았습니다";
|
|
})
|
|
files)
|
|
|
|
let run (file : string) : (unit, error list) result =
|
|
Error [ { file; line = 0; col = 0; message = "interpreter가 아직 구현되지 않았습니다" } ]
|