diff --git a/bin/main.ml b/bin/main.ml index 8fc9226..2cc659f 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -79,10 +79,13 @@ let () = | "run" :: file :: args -> ( let st = Coollang.Session.create ~root:(Filename.dirname file) () in match Coollang.Session.run ~args st file with - | Ok out -> + | out, None -> print_string out; 0 - | Error e -> + | out, Some e -> + (* 실패해도 그때까지의 출력을 먼저 보여준다 *) + print_string out; + flush stdout; prerr_endline (Coollang.Session.string_of_error e); 1) | [ "tokens"; file ] -> dump_tokens file diff --git a/lib/interp.ml b/lib/interp.ml index 3692027..860ec68 100644 --- a/lib/interp.ml +++ b/lib/interp.ml @@ -484,14 +484,17 @@ and eval_binary st env op a b pos = (* main이 선언한 capability만 런타임이 넘긴다. 선언하지 않은 권한은 프로그램 안에 존재하지 않는다. *) +(* 실패해도 그때까지 나온 출력을 함께 돌려준다. + print는 실제로 일어난 effect다. 일어난 일을 안 보여주면 "어디까지 갔나"를 + 알 수 없고, 그게 실패했을 때 가장 먼저 보고 싶은 것이다. *) 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; argv := args; let st = { prog } in 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 -> ( let args = List.map @@ -503,12 +506,12 @@ let run ?(args = []) (prog : Ir.program) (entry : string) main_params in 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 try ignore (apply st Token.{ line = 0; col = 0 } (VFn fn) args); - Ok (Buffer.contents out) + (Buffer.contents out, None) with - | Fail (pos, msg) -> Error (pos, msg) - | Return_exc _ -> Ok (Buffer.contents out))) + | Fail (pos, msg) -> (Buffer.contents out, Some (pos, msg)) + | Return_exc _ -> (Buffer.contents out, None))) diff --git a/lib/session.ml b/lib/session.ml index db18e35..4c6910d 100644 --- a/lib/session.ml +++ b/lib/session.ml @@ -258,14 +258,15 @@ let main_params (m : Ast.modul) = | _ -> []) m.items -let run ?(args = []) st path : (string, error) result = +(* 출력과 실패를 함께 돌려준다. 실패해도 그때까지 나온 것은 보여줘야 한다 *) +let run ?(args = []) st path : string * error option = load st path; let errs = errors st in - if errs <> [] then Error (List.hd errs) + if errs <> [] then ("", Some (List.hd errs)) else match find st path with | None -> - Error { file = path; line = 0; col = 0; message = "모듈을 찾을 수 없습니다" } + ("", Some { file = path; line = 0; col = 0; message = "모듈을 찾을 수 없습니다" }) | Some e -> ( (* IR은 그래프 전체를 받는다. 별칭이 실행 의미에 남지 않도록. *) let mods = @@ -276,5 +277,5 @@ let run ?(args = []) st path : (string, error) result = in let prog = Ir.of_program mods in match Interp.run ~args prog path (main_params e.ast) with - | Ok out -> Ok out - | Error (pos, msg) -> Error (err_of path pos msg)) + | out, None -> (out, None) + | out, Some (pos, msg) -> (out, Some (err_of path pos msg))) diff --git a/test/test_coollang.ml b/test/test_coollang.ml index aaf6662..b92a626 100644 --- a/test/test_coollang.ml +++ b/test/test_coollang.ml @@ -1032,9 +1032,10 @@ let console = let outputs ?(use = []) src expected = match run_src (imports use ^ console ^ src) with - | Ok out -> out = expected - | Error e -> - Printf.printf " (실행 오류: %s)\n" (Session.string_of_error e); + | out, None -> out = expected + | out, Some e -> + Printf.printf " (실행 오류: %s / 그때까지 출력: %S)\n" + (Session.string_of_error e) out; false let () = @@ -1113,7 +1114,7 @@ let () = "a\nb\nafter\n"); (* 권한은 런타임에서만 온다. main이 선언하지 않으면 존재하지 않는다. *) check "선언하지 않은 capability는 실행 시점에도 없다" - (match run_src "pub fn main() { }" with Ok "" -> true | _ -> false); + (match run_src "pub fn main() { }" with "", None -> true | _ -> false); check "런타임이 모르는 capability는 거절한다" (match run_src @@ -1122,8 +1123,28 @@ let () = }\n\n\ pub fn main(d: Db) effects {Db.read} -> Int { d.read() }" with - | Error e -> has_sub e.message "제공하지 않습니다" - | Ok _ -> false) + | _, Some e -> has_sub e.message "제공하지 않습니다" + | _, 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" ] st "../samples/app/main.cool" with - | Error e -> + | _, Some e -> Printf.printf " (실행 오류: %s)\n" (Session.string_of_error e); check "app: 설정 리포트가 돈다" false - | Ok out -> + | out, None -> check "app: 항목과 문제를 센다" (has_sub out "항목 5개, 문제 2개"); check "app: 값의 타입을 모양으로 정한다" (has_sub out "threads = 4 (number)" @@ -1266,14 +1287,14 @@ let () = let () = let st = Session.create ~root:"../samples/app" ~std:"../std" () in match Session.run st "../samples/app/main.cool" with - | Ok out -> check "app: 인자가 없으면 말해준다" (has_sub out "경로가 필요합니다") - | Error _ -> check "app: 인자가 없으면 말해준다" false + | out, None -> check "app: 인자가 없으면 말해준다" (has_sub out "경로가 필요합니다") + | _, Some _ -> check "app: 인자가 없으면 말해준다" false let () = let st = Session.create ~root:"../samples/app" ~std:"../std" () in match Session.run ~args:[ "/없는/파일.conf" ] st "../samples/app/main.cool" with - | Ok out -> check "app: 없는 파일을 Err로 돌려준다" (has_sub out "오류: ") - | Error _ -> check "app: 없는 파일을 Err로 돌려준다" false + | out, None -> check "app: 없는 파일을 Err로 돌려준다" (has_sub out "오류: ") + | _, Some _ -> check "app: 없는 파일을 Err로 돌려준다" false (* std 선언과 런타임 구현이 어긋나면 검사는 통과하고 실행이 죽는다. v0에서 둘은 다른 파일에 있으므로 일치는 테스트가 지킨다 (friction F7). *)