interp: 실패해도 그때까지의 출력을 보여준다
0으로 나누는 프로그램을 돌려보니 실패 전에 출력한 것이 통째로 사라졌다. Interp.run이 출력을 버퍼에 모았다가 성공했을 때만 돌려주고 실패하면 버렸다. print는 실제로 일어난 effect다. 일어난 일을 안 보여주면 "어디까지 갔나"를 알 수 없고, 그게 실패했을 때 가장 먼저 보고 싶은 것이다. Interp.run과 Session.run이 이제 (출력, 실패 여부)를 함께 돌려준다. result가 아니라 짝인 이유는 둘이 배타가 아니기 때문이다 — 실패했어도 출력은 있다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+5
-2
@@ -79,10 +79,13 @@ let () =
|
|||||||
| "run" :: file :: args -> (
|
| "run" :: file :: args -> (
|
||||||
let st = Coollang.Session.create ~root:(Filename.dirname file) () in
|
let st = Coollang.Session.create ~root:(Filename.dirname file) () in
|
||||||
match Coollang.Session.run ~args st file with
|
match Coollang.Session.run ~args st file with
|
||||||
| Ok out ->
|
| out, None ->
|
||||||
print_string out;
|
print_string out;
|
||||||
0
|
0
|
||||||
| Error e ->
|
| out, Some e ->
|
||||||
|
(* 실패해도 그때까지의 출력을 먼저 보여준다 *)
|
||||||
|
print_string out;
|
||||||
|
flush stdout;
|
||||||
prerr_endline (Coollang.Session.string_of_error e);
|
prerr_endline (Coollang.Session.string_of_error e);
|
||||||
1)
|
1)
|
||||||
| [ "tokens"; file ] -> dump_tokens file
|
| [ "tokens"; file ] -> dump_tokens file
|
||||||
|
|||||||
+9
-6
@@ -484,14 +484,17 @@ and eval_binary st env op a b pos =
|
|||||||
|
|
||||||
(* main이 선언한 capability만 런타임이 넘긴다. 선언하지 않은 권한은
|
(* main이 선언한 capability만 런타임이 넘긴다. 선언하지 않은 권한은
|
||||||
프로그램 안에 존재하지 않는다. *)
|
프로그램 안에 존재하지 않는다. *)
|
||||||
|
(* 실패해도 그때까지 나온 출력을 함께 돌려준다.
|
||||||
|
print는 실제로 일어난 effect다. 일어난 일을 안 보여주면 "어디까지 갔나"를
|
||||||
|
알 수 없고, 그게 실패했을 때 가장 먼저 보고 싶은 것이다. *)
|
||||||
let run ?(args = []) (prog : Ir.program) (entry : string)
|
let run ?(args = []) (prog : Ir.program) (entry : string)
|
||||||
(main_params : (string * string) list) : (string, Token.pos * string) result
|
(main_params : (string * string) list) : string * (Token.pos * string) option
|
||||||
=
|
=
|
||||||
Buffer.clear out;
|
Buffer.clear out;
|
||||||
argv := args;
|
argv := args;
|
||||||
let st = { prog } in
|
let st = { prog } in
|
||||||
match Hashtbl.find_opt prog.Ir.fns (entry ^ "#main") with
|
match Hashtbl.find_opt prog.Ir.fns (entry ^ "#main") with
|
||||||
| None -> Error (Token.{ line = 0; col = 0 }, "main 함수가 없습니다")
|
| None -> ("", Some (Token.{ line = 0; col = 0 }, "main 함수가 없습니다"))
|
||||||
| Some fn -> (
|
| Some fn -> (
|
||||||
let args =
|
let args =
|
||||||
List.map
|
List.map
|
||||||
@@ -503,12 +506,12 @@ let run ?(args = []) (prog : Ir.program) (entry : string)
|
|||||||
main_params
|
main_params
|
||||||
in
|
in
|
||||||
match List.find_opt Result.is_error args with
|
match List.find_opt Result.is_error args with
|
||||||
| Some (Error m) -> Error (Token.{ line = 0; col = 0 }, m)
|
| Some (Error m) -> ("", Some (Token.{ line = 0; col = 0 }, m))
|
||||||
| _ -> (
|
| _ -> (
|
||||||
let args = List.map Result.get_ok args in
|
let args = List.map Result.get_ok args in
|
||||||
try
|
try
|
||||||
ignore (apply st Token.{ line = 0; col = 0 } (VFn fn) args);
|
ignore (apply st Token.{ line = 0; col = 0 } (VFn fn) args);
|
||||||
Ok (Buffer.contents out)
|
(Buffer.contents out, None)
|
||||||
with
|
with
|
||||||
| Fail (pos, msg) -> Error (pos, msg)
|
| Fail (pos, msg) -> (Buffer.contents out, Some (pos, msg))
|
||||||
| Return_exc _ -> Ok (Buffer.contents out)))
|
| Return_exc _ -> (Buffer.contents out, None)))
|
||||||
|
|||||||
+6
-5
@@ -258,14 +258,15 @@ let main_params (m : Ast.modul) =
|
|||||||
| _ -> [])
|
| _ -> [])
|
||||||
m.items
|
m.items
|
||||||
|
|
||||||
let run ?(args = []) st path : (string, error) result =
|
(* 출력과 실패를 함께 돌려준다. 실패해도 그때까지 나온 것은 보여줘야 한다 *)
|
||||||
|
let run ?(args = []) st path : string * error option =
|
||||||
load st path;
|
load st path;
|
||||||
let errs = errors st in
|
let errs = errors st in
|
||||||
if errs <> [] then Error (List.hd errs)
|
if errs <> [] then ("", Some (List.hd errs))
|
||||||
else
|
else
|
||||||
match find st path with
|
match find st path with
|
||||||
| None ->
|
| None ->
|
||||||
Error { file = path; line = 0; col = 0; message = "모듈을 찾을 수 없습니다" }
|
("", Some { file = path; line = 0; col = 0; message = "모듈을 찾을 수 없습니다" })
|
||||||
| Some e -> (
|
| Some e -> (
|
||||||
(* IR은 그래프 전체를 받는다. 별칭이 실행 의미에 남지 않도록. *)
|
(* IR은 그래프 전체를 받는다. 별칭이 실행 의미에 남지 않도록. *)
|
||||||
let mods =
|
let mods =
|
||||||
@@ -276,5 +277,5 @@ let run ?(args = []) st path : (string, error) result =
|
|||||||
in
|
in
|
||||||
let prog = Ir.of_program mods in
|
let prog = Ir.of_program mods in
|
||||||
match Interp.run ~args prog path (main_params e.ast) with
|
match Interp.run ~args prog path (main_params e.ast) with
|
||||||
| Ok out -> Ok out
|
| out, None -> (out, None)
|
||||||
| Error (pos, msg) -> Error (err_of path pos msg))
|
| out, Some (pos, msg) -> (out, Some (err_of path pos msg)))
|
||||||
|
|||||||
+33
-12
@@ -1032,9 +1032,10 @@ let console =
|
|||||||
|
|
||||||
let outputs ?(use = []) src expected =
|
let outputs ?(use = []) src expected =
|
||||||
match run_src (imports use ^ console ^ src) with
|
match run_src (imports use ^ console ^ src) with
|
||||||
| Ok out -> out = expected
|
| out, None -> out = expected
|
||||||
| Error e ->
|
| out, Some e ->
|
||||||
Printf.printf " (실행 오류: %s)\n" (Session.string_of_error e);
|
Printf.printf " (실행 오류: %s / 그때까지 출력: %S)\n"
|
||||||
|
(Session.string_of_error e) out;
|
||||||
false
|
false
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
@@ -1113,7 +1114,7 @@ let () =
|
|||||||
"a\nb\nafter\n");
|
"a\nb\nafter\n");
|
||||||
(* 권한은 런타임에서만 온다. main이 선언하지 않으면 존재하지 않는다. *)
|
(* 권한은 런타임에서만 온다. main이 선언하지 않으면 존재하지 않는다. *)
|
||||||
check "선언하지 않은 capability는 실행 시점에도 없다"
|
check "선언하지 않은 capability는 실행 시점에도 없다"
|
||||||
(match run_src "pub fn main() { }" with Ok "" -> true | _ -> false);
|
(match run_src "pub fn main() { }" with "", None -> true | _ -> false);
|
||||||
check "런타임이 모르는 capability는 거절한다"
|
check "런타임이 모르는 capability는 거절한다"
|
||||||
(match
|
(match
|
||||||
run_src
|
run_src
|
||||||
@@ -1122,8 +1123,28 @@ let () =
|
|||||||
}\n\n\
|
}\n\n\
|
||||||
pub fn main(d: Db) effects {Db.read} -> Int { d.read() }"
|
pub fn main(d: Db) effects {Db.read} -> Int { d.read() }"
|
||||||
with
|
with
|
||||||
| Error e -> has_sub e.message "제공하지 않습니다"
|
| _, Some e -> has_sub e.message "제공하지 않습니다"
|
||||||
| Ok _ -> false)
|
| _, None -> false)
|
||||||
|
|
||||||
|
(* 실패해도 그때까지의 출력은 남는다. print는 실제로 일어난 effect이고,
|
||||||
|
일어난 일을 안 보여주면 "어디까지 갔나"를 알 수 없다. *)
|
||||||
|
let () =
|
||||||
|
match
|
||||||
|
run_src
|
||||||
|
(imports [ "Int" ] ^ console
|
||||||
|
^ "pub fn risky(a: Int, b: Int) -> Int { a / b }\n\n\
|
||||||
|
pub fn main(c: Console) effects {Console.print} {\n\
|
||||||
|
\ c.print(\"전\")\n\
|
||||||
|
\ c.print(Int.show(risky(10, 0)))\n\
|
||||||
|
\ c.print(\"후\")\n\
|
||||||
|
}")
|
||||||
|
with
|
||||||
|
| out, Some e ->
|
||||||
|
check "0으로 나누면 실행 시점 오류다" (has_sub e.message "0으로 나눌 수 없습니다");
|
||||||
|
check "실패해도 그 전 출력은 남는다" (out = "전\n")
|
||||||
|
| _, None ->
|
||||||
|
check "0으로 나누면 실행 시점 오류다" false;
|
||||||
|
check "실패해도 그 전 출력은 남는다" false
|
||||||
|
|
||||||
(* 표준 라이브러리 시그니처가 실제 검사에 쓰이는지.
|
(* 표준 라이브러리 시그니처가 실제 검사에 쓰이는지.
|
||||||
|
|
||||||
@@ -1250,10 +1271,10 @@ let () =
|
|||||||
~args:[ "../samples/app/example.conf" ]
|
~args:[ "../samples/app/example.conf" ]
|
||||||
st "../samples/app/main.cool"
|
st "../samples/app/main.cool"
|
||||||
with
|
with
|
||||||
| Error e ->
|
| _, Some e ->
|
||||||
Printf.printf " (실행 오류: %s)\n" (Session.string_of_error e);
|
Printf.printf " (실행 오류: %s)\n" (Session.string_of_error e);
|
||||||
check "app: 설정 리포트가 돈다" false
|
check "app: 설정 리포트가 돈다" false
|
||||||
| Ok out ->
|
| out, None ->
|
||||||
check "app: 항목과 문제를 센다" (has_sub out "항목 5개, 문제 2개");
|
check "app: 항목과 문제를 센다" (has_sub out "항목 5개, 문제 2개");
|
||||||
check "app: 값의 타입을 모양으로 정한다"
|
check "app: 값의 타입을 모양으로 정한다"
|
||||||
(has_sub out "threads = 4 (number)"
|
(has_sub out "threads = 4 (number)"
|
||||||
@@ -1266,14 +1287,14 @@ let () =
|
|||||||
let () =
|
let () =
|
||||||
let st = Session.create ~root:"../samples/app" ~std:"../std" () in
|
let st = Session.create ~root:"../samples/app" ~std:"../std" () in
|
||||||
match Session.run st "../samples/app/main.cool" with
|
match Session.run st "../samples/app/main.cool" with
|
||||||
| Ok out -> check "app: 인자가 없으면 말해준다" (has_sub out "경로가 필요합니다")
|
| out, None -> check "app: 인자가 없으면 말해준다" (has_sub out "경로가 필요합니다")
|
||||||
| Error _ -> check "app: 인자가 없으면 말해준다" false
|
| _, Some _ -> check "app: 인자가 없으면 말해준다" false
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
let st = Session.create ~root:"../samples/app" ~std:"../std" () in
|
let st = Session.create ~root:"../samples/app" ~std:"../std" () in
|
||||||
match Session.run ~args:[ "/없는/파일.conf" ] st "../samples/app/main.cool" with
|
match Session.run ~args:[ "/없는/파일.conf" ] st "../samples/app/main.cool" with
|
||||||
| Ok out -> check "app: 없는 파일을 Err로 돌려준다" (has_sub out "오류: ")
|
| out, None -> check "app: 없는 파일을 Err로 돌려준다" (has_sub out "오류: ")
|
||||||
| Error _ -> check "app: 없는 파일을 Err로 돌려준다" false
|
| _, Some _ -> check "app: 없는 파일을 Err로 돌려준다" false
|
||||||
|
|
||||||
(* std 선언과 런타임 구현이 어긋나면 검사는 통과하고 실행이 죽는다.
|
(* std 선언과 런타임 구현이 어긋나면 검사는 통과하고 실행이 죽는다.
|
||||||
v0에서 둘은 다른 파일에 있으므로 일치는 테스트가 지킨다 (friction F7). *)
|
v0에서 둘은 다른 파일에 있으므로 일치는 테스트가 지킨다 (friction F7). *)
|
||||||
|
|||||||
Reference in New Issue
Block a user