이 단계가 답하는 질문은 하나다 — 이 모듈 하나만 보고 무엇을 결정할 수 있는가.
그 경계를 산출물로 만들었다.
결정할 수 있는 것은 오류로 보고한다: 중복 정의, 중복 파라미터·제네릭,
선언되지 않은 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
90 lines
2.9 KiB
OCaml
90 lines
2.9 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 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 resolve f with
|
|
| Ok _ -> None
|
|
| Error (e :: _) -> Some e
|
|
| Error [] -> None)
|
|
files
|
|
in
|
|
if errors <> [] then Error errors
|
|
else
|
|
(* 이름 해소는 통과했다. 통과했다고 말하지 않는다 — 파이프라인의
|
|
나머지가 아직 없으므로 검사되지 않은 것이다. *)
|
|
Error
|
|
(List.map
|
|
(fun f ->
|
|
{
|
|
file = f;
|
|
line = 0;
|
|
col = 0;
|
|
message = "구문 분석까지 통과. 이름 해소가 아직 구현되지 않았습니다";
|
|
})
|
|
files)
|
|
|
|
let run (file : string) : (unit, error list) result =
|
|
Error [ { file; line = 0; col = 0; message = "interpreter가 아직 구현되지 않았습니다" } ]
|