app: 개밥 먹기 — 일하는 프로그램 하나와 그 마찰 보고
1단계 최소 IO: File(읽기), Args capability. 권한의 출처는 여전히 런타임 하나이고, IO 오류는 Result[a, String]이다 — 런타임이 사용자 정의 enum을 만들 수 없고, 만들 수 있게 하면 런타임이 프로그램의 타입을 알아야 한다. 2단계 std 확장: fold, filter, push, concat, reverse, is_empty, String.split/trim/starts_with/contains, Int.parse. 3단계 samples/app: 설정 파서 + 리포트 도구, 2모듈 304줄. 검사기를 시험 하려고 쓴 것이 아니라 일을 하려고 쓴 첫 프로그램이다. 산출물은 프로그램이 아니라 docs/friction.md다. 요약: - 되돌리기 비싼 결정은 하나도 후회되지 않았다. capability 전달, effect 명시, 실패를 버릴 수 없음, 소진적 match — 300줄 내내 거추장스럽지 않았고 소진성은 실제로 실수를 잡았다(Value에 경우 하나 추가하니 고칠 자리 넷을 정확히 짚었다). - 불편은 전부 되돌리기 싼 것들이었다. 리스트 n번째 접근이 없어 fold로 우회(40줄), else if가 없어 3~4단 중첩, String.concat이 2항이라 중첩 지옥. 304줄 중 70줄쯤이 이 셋 때문에 존재한다. v0의 질문은 "되돌리기 비싼 결정이 옳은가"였고 답은 그렇다이다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+87
-1
@@ -81,8 +81,46 @@ let bind (env : env) n v : env =
|
||||
(* 런타임이 제공하는 것 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
(* 문자열 도우미. 언어에 인덱싱 연산자가 없으므로 이 일은 런타임 몫이다. *)
|
||||
let find_sub hay needle =
|
||||
let n = String.length needle and h = String.length hay in
|
||||
let rec go i =
|
||||
if i + n > h then None
|
||||
else if String.sub hay i n = needle then Some i
|
||||
else go (i + 1)
|
||||
in
|
||||
if n = 0 then Some 0 else go 0
|
||||
|
||||
let split_on s sep =
|
||||
if sep = "" then [ s ]
|
||||
else
|
||||
let n = String.length sep in
|
||||
let rec go s acc =
|
||||
match find_sub s sep with
|
||||
| None -> List.rev (s :: acc)
|
||||
| Some i ->
|
||||
go
|
||||
(String.sub s (i + n) (String.length s - i - n))
|
||||
(String.sub s 0 i :: acc)
|
||||
in
|
||||
go s []
|
||||
|
||||
let out = Buffer.create 1024
|
||||
|
||||
(* 프로그램 인자. 런타임이 들고 있다가 Args capability를 통해서만 준다 —
|
||||
전역 변수로 아무 데서나 읽을 수 있으면 그것이 ambient authority다. *)
|
||||
let argv : string list ref = ref []
|
||||
|
||||
let read_whole path =
|
||||
let ic = open_in_bin path in
|
||||
let n = in_channel_length ic in
|
||||
let s = really_input_string ic n in
|
||||
close_in ic;
|
||||
s
|
||||
|
||||
(* IO 오류는 문자열로 돌려준다. 런타임이 사용자 정의 enum을 만들 수는 없고,
|
||||
만들 수 있게 하면 런타임이 프로그램의 타입을 알아야 한다. v0의 선은
|
||||
여기다 — Result[a, String]. *)
|
||||
let root_capability name : value option =
|
||||
match name with
|
||||
| "Console" ->
|
||||
@@ -96,6 +134,26 @@ let root_capability name : value option =
|
||||
Buffer.add_char out '\n';
|
||||
VUnit );
|
||||
] ))
|
||||
| "File" ->
|
||||
Some
|
||||
(VCap
|
||||
( "File",
|
||||
[
|
||||
( "read",
|
||||
fun args ->
|
||||
match args with
|
||||
| [ VStr path ] -> (
|
||||
try VEnum ("Result", "Ok", [ VStr (read_whole path) ])
|
||||
with Sys_error m ->
|
||||
VEnum ("Result", "Err", [ VStr m ]))
|
||||
| _ -> VEnum ("Result", "Err", [ VStr "read: 경로가 필요합니다" ])
|
||||
);
|
||||
] ))
|
||||
| "Args" ->
|
||||
Some
|
||||
(VCap
|
||||
( "Args",
|
||||
[ ("all", fun _ -> VList (List.map (fun s -> VStr s) !argv)) ] ))
|
||||
| "TaskScope" ->
|
||||
(* 루트 스코프. 구조적 동시성의 뿌리도 런타임이 준다 — 프로그램이
|
||||
스스로 만들 수 있으면 부모 없는 작업이 생긴다. *)
|
||||
@@ -106,10 +164,24 @@ let builtin pos name (args : value list) : value =
|
||||
match (name, args) with
|
||||
| "string.concat", [ VStr a; VStr b ] -> VStr (a ^ b)
|
||||
| "string.len", [ VStr a ] -> VInt (String.length a)
|
||||
| "string.is_empty", [ VStr a ] -> VBool (a = "")
|
||||
| "string.split", [ VStr s; VStr sep ] -> VList (List.map (fun x -> VStr x) (split_on s sep))
|
||||
| "string.trim", [ VStr s ] -> VStr (String.trim s)
|
||||
| "string.starts_with", [ VStr s; VStr p ] ->
|
||||
VBool (String.length s >= String.length p && String.sub s 0 (String.length p) = p)
|
||||
| "string.contains", [ VStr s; VStr n ] -> VBool (find_sub s n <> None)
|
||||
| "int.show", [ VInt n ] -> VStr (string_of_int n)
|
||||
| "int.abs", [ VInt n ] -> VInt (abs n)
|
||||
| "int.parse", [ VStr s ] -> (
|
||||
match int_of_string_opt (String.trim s) with
|
||||
| Some n -> VEnum ("Result", "Ok", [ VInt n ])
|
||||
| None -> VEnum ("Result", "Err", [ VStr (s ^ "은(는) 정수가 아닙니다") ]))
|
||||
| "bool.show", [ VBool b ] -> VStr (if b then "true" else "false")
|
||||
| "list.len", [ VList xs ] -> VInt (List.length xs)
|
||||
| "list.is_empty", [ VList xs ] -> VBool (xs = [])
|
||||
| "list.push", [ VList xs; x ] -> VList (xs @ [ x ])
|
||||
| "list.concat", [ VList xs; VList ys ] -> VList (xs @ ys)
|
||||
| "list.reverse", [ VList xs ] -> VList (List.rev xs)
|
||||
| _ ->
|
||||
fail pos (Printf.sprintf "%s은(는) 런타임이 제공하지 않습니다 (표준 라이브러리가 아직 없습니다)" name)
|
||||
|
||||
@@ -228,6 +300,19 @@ and apply st pos f args =
|
||||
match args with
|
||||
| [ VList xs; f ] -> VList (List.map (fun x -> apply st pos f [ x ]) xs)
|
||||
| _ -> fail pos "list.map은 리스트와 함수를 받습니다")
|
||||
| VBuiltin "list.filter" -> (
|
||||
match args with
|
||||
| [ VList xs; f ] ->
|
||||
VList
|
||||
(List.filter
|
||||
(fun x -> match apply st pos f [ x ] with VBool b -> b | _ -> false)
|
||||
xs)
|
||||
| _ -> fail pos "list.filter는 리스트와 함수를 받습니다")
|
||||
| VBuiltin "list.fold" -> (
|
||||
match args with
|
||||
| [ VList xs; init; f ] ->
|
||||
List.fold_left (fun acc x -> apply st pos f [ acc; x ]) init xs
|
||||
| _ -> fail pos "list.fold는 리스트, 초기값, 함수를 받습니다")
|
||||
| VBuiltin n -> builtin pos n args
|
||||
| VNative f -> f args
|
||||
| other -> fail pos (Printf.sprintf "%s은(는) 부를 수 없습니다" (show other))
|
||||
@@ -313,10 +398,11 @@ and eval_binary st env op a b pos =
|
||||
|
||||
(* main이 선언한 capability만 런타임이 넘긴다. 선언하지 않은 권한은
|
||||
프로그램 안에 존재하지 않는다. *)
|
||||
let run (prog : Ir.program) (entry : string)
|
||||
let run ?(args = []) (prog : Ir.program) (entry : string)
|
||||
(main_params : (string * string) list) : (string, Token.pos * string) result
|
||||
=
|
||||
Buffer.clear out;
|
||||
argv := args;
|
||||
let st = { prog } in
|
||||
match Hashtbl.find_opt prog.Ir.fns (entry ^ "#main") with
|
||||
| None -> Error (Token.{ line = 0; col = 0 }, "main 함수가 없습니다")
|
||||
|
||||
+2
-2
@@ -258,7 +258,7 @@ let main_params (m : Ast.modul) =
|
||||
| _ -> [])
|
||||
m.items
|
||||
|
||||
let run st path : (string, error) result =
|
||||
let run ?(args = []) st path : (string, error) result =
|
||||
load st path;
|
||||
let errs = errors st in
|
||||
if errs <> [] then Error (List.hd errs)
|
||||
@@ -275,6 +275,6 @@ let run st path : (string, error) result =
|
||||
st.modules []
|
||||
in
|
||||
let prog = Ir.of_program mods in
|
||||
match Interp.run prog path (main_params e.ast) with
|
||||
match Interp.run ~args 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