diff --git a/include/cpp/jank/analyze/expr/do.hpp b/include/cpp/jank/analyze/expr/do.hpp index be1b2786c..40f45ce70 100644 --- a/include/cpp/jank/analyze/expr/do.hpp +++ b/include/cpp/jank/analyze/expr/do.hpp @@ -13,7 +13,7 @@ namespace jank::analyze::expr runtime::object_ptr to_runtime_data() const { using namespace runtime::obj; - runtime::object_ptr body_maps(make_box()); + runtime::object_ptr body_maps{ make_box() }; for(auto const &e : body) { body_maps = runtime::conj(body_maps, e->to_runtime_data()); diff --git a/include/cpp/jank/analyze/expr/throw.hpp b/include/cpp/jank/analyze/expr/throw.hpp index 626a13e30..30a8360cc 100644 --- a/include/cpp/jank/analyze/expr/throw.hpp +++ b/include/cpp/jank/analyze/expr/throw.hpp @@ -1,8 +1,5 @@ #pragma once -#include - -#include #include #include diff --git a/include/cpp/jank/analyze/expr/try.hpp b/include/cpp/jank/analyze/expr/try.hpp new file mode 100644 index 000000000..c66657d08 --- /dev/null +++ b/include/cpp/jank/analyze/expr/try.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +namespace jank::analyze::expr +{ + template + struct catch_ + { + runtime::obj::symbol_ptr sym{}; + do_ body{}; + + runtime::object_ptr to_runtime_data() const + { + using namespace runtime::obj; + + return persistent_array_map::create_unique(make_box("__type"), + make_box("expr::try::catch"), + make_box("body"), + body.to_runtime_data(), + make_box("sym"), + sym); + } + }; + + template + struct try_ : expression_base + { + do_ body{}; + catch_ catch_body{}; + option> finally_body{}; + + runtime::object_ptr to_runtime_data() const + { + using namespace runtime::obj; + + return runtime::merge( + static_cast(this)->to_runtime_data(), + persistent_array_map::create_unique(make_box("__type"), + make_box("expr::try"), + make_box("body"), + body.to_runtime_data(), + make_box("catch"), + jank::detail::to_runtime_data(catch_body), + make_box("finally"), + jank::detail::to_runtime_data(finally_body))); + } + }; +} diff --git a/include/cpp/jank/analyze/expression.hpp b/include/cpp/jank/analyze/expression.hpp index fae4800ad..3b1f3fb16 100644 --- a/include/cpp/jank/analyze/expression.hpp +++ b/include/cpp/jank/analyze/expression.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace jank::analyze @@ -39,6 +40,7 @@ namespace jank::analyze expr::do_, expr::if_, expr::throw_, + expr::try_, expr::native_raw>; static constexpr bool pointer_free{ false }; diff --git a/include/cpp/jank/analyze/local_frame.hpp b/include/cpp/jank/analyze/local_frame.hpp index 1969cce01..081baee5b 100644 --- a/include/cpp/jank/analyze/local_frame.hpp +++ b/include/cpp/jank/analyze/local_frame.hpp @@ -52,7 +52,8 @@ namespace jank::analyze { root, fn, - let + let, + catch_ }; static constexpr bool pointer_free{ false }; diff --git a/include/cpp/jank/analyze/processor.hpp b/include/cpp/jank/analyze/processor.hpp index 14b802f79..8b2ed4ad7 100644 --- a/include/cpp/jank/analyze/processor.hpp +++ b/include/cpp/jank/analyze/processor.hpp @@ -96,6 +96,11 @@ namespace jank::analyze expression_type, option const &, native_bool needs_box); + expression_result analyze_try(runtime::obj::persistent_list_ptr const &, + local_frame_ptr &, + expression_type, + option const &, + native_bool needs_box); expression_result analyze_native_raw(runtime::obj::persistent_list_ptr const &, local_frame_ptr &, expression_type, diff --git a/include/cpp/jank/codegen/processor.hpp b/include/cpp/jank/codegen/processor.hpp index 73675837f..1780ead24 100644 --- a/include/cpp/jank/codegen/processor.hpp +++ b/include/cpp/jank/codegen/processor.hpp @@ -115,6 +115,9 @@ namespace jank::codegen option gen(analyze::expr::throw_ const &, analyze::expr::function_arity const &, native_bool box_needed); + option gen(analyze::expr::try_ const &, + analyze::expr::function_arity const &, + native_bool box_needed); option gen(analyze::expr::native_raw const &, analyze::expr::function_arity const &, native_bool box_needed); diff --git a/include/cpp/jank/evaluate.hpp b/include/cpp/jank/evaluate.hpp index e11c9ecbb..a54ef9434 100644 --- a/include/cpp/jank/evaluate.hpp +++ b/include/cpp/jank/evaluate.hpp @@ -47,6 +47,9 @@ namespace jank::evaluate runtime::object_ptr eval(runtime::context &, jit::processor const &, analyze::expr::throw_ const &); + runtime::object_ptr eval(runtime::context &, + jit::processor const &, + analyze::expr::try_ const &); runtime::object_ptr eval(runtime::context &, jit::processor const &, analyze::expr::native_raw const &); diff --git a/include/cpp/jank/prelude.hpp b/include/cpp/jank/prelude.hpp index 575aeb898..fe1d85068 100644 --- a/include/cpp/jank/prelude.hpp +++ b/include/cpp/jank/prelude.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/cpp/jank/runtime/context.hpp b/include/cpp/jank/runtime/context.hpp index 737a2ea3d..604696297 100644 --- a/include/cpp/jank/runtime/context.hpp +++ b/include/cpp/jank/runtime/context.hpp @@ -133,6 +133,7 @@ namespace jank::runtime var_ptr compile_files_var{}; var_ptr current_module_var{}; var_ptr assert_var{}; + var_ptr no_recur_var{}; static thread_local native_unordered_map> thread_binding_frames; diff --git a/src/cpp/jank/analyze/processor.cpp b/src/cpp/jank/analyze/processor.cpp index 62ef89173..c913e012c 100644 --- a/src/cpp/jank/analyze/processor.cpp +++ b/src/cpp/jank/analyze/processor.cpp @@ -40,6 +40,7 @@ namespace jank::analyze { jank::make_box("quote"), make_fn(&processor::analyze_quote)}, { jank::make_box("var"), make_fn(&processor::analyze_var)}, { jank::make_box("throw"), make_fn(&processor::analyze_throw)}, + { jank::make_box("try"), make_fn(&processor::analyze_try)}, {jank::make_box("native/raw"), make_fn(&processor::analyze_native_raw)}, }; } @@ -207,7 +208,8 @@ namespace jank::analyze } result, error> - processor::analyze_fn_arity(runtime::obj::persistent_list_ptr const &list, local_frame_ptr ¤t_frame) + processor::analyze_fn_arity(runtime::obj::persistent_list_ptr const &list, + local_frame_ptr ¤t_frame) { auto const params_obj(list->data.first().unwrap()); if(params_obj->type != runtime::object_type::persistent_vector) @@ -276,7 +278,7 @@ namespace jank::analyze } } - frame->locals.emplace(sym, local_binding{ sym, none }); + frame->locals.emplace(sym, local_binding{ sym, none, current_frame }); param_symbols.emplace_back(sym); } @@ -325,11 +327,12 @@ namespace jank::analyze }; } - processor::expression_result processor::analyze_fn(runtime::obj::persistent_list_ptr const &full_list, - local_frame_ptr ¤t_frame, - expression_type const expr_type, - option const &, - native_bool const) + processor::expression_result + processor::analyze_fn(runtime::obj::persistent_list_ptr const &full_list, + local_frame_ptr ¤t_frame, + expression_type const expr_type, + option const &, + native_bool const) { auto const length(full_list->count()); if(length < 2) @@ -360,7 +363,8 @@ namespace jank::analyze if(first_elem->type == runtime::object_type::persistent_vector) { - auto result(analyze_fn_arity(make_box(list->data.rest()), current_frame)); + auto result(analyze_fn_arity(make_box(list->data.rest()), + current_frame)); if(result.is_err()) { return result.expect_err_move(); @@ -472,6 +476,10 @@ namespace jank::analyze { return err(error{ "unable to use recur outside of a function or loop" }); } + else if(runtime::detail::truthy(rt_ctx.no_recur_var->deref())) + { + return err(error{ "recur is not permitted through a try/catch" }); + } else if(expr_type != expression_type::return_statement) { return err(error{ "recur used outside of tail position" }); @@ -523,8 +531,8 @@ namespace jank::analyze size_t i{}; for(auto const &item : list->data.rest()) { - auto const last(++i == form_count); - auto const form_type(last ? expr_type : expression_type::statement); + auto const is_last(++i == form_count); + auto const form_type(is_last ? expr_type : expression_type::statement); auto form(analyze(item, current_frame, form_type, @@ -535,9 +543,9 @@ namespace jank::analyze return form.expect_err_move(); } - if(last) + if(is_last) { - ret.needs_box = form.expect_ok_ptr()->data->get_base()->needs_box; + ret.needs_box = form.expect_ok()->get_base()->needs_box; } ret.body.emplace_back(form.expect_ok()); @@ -610,8 +618,8 @@ namespace jank::analyze size_t i{}; for(auto const &item : o->data.rest().rest()) { - auto const last(++i == form_count); - auto const form_type(last ? expr_type : expression_type::statement); + auto const is_last(++i == form_count); + auto const form_type(is_last ? expr_type : expression_type::statement); auto res(analyze(item, ret.frame, form_type, fn_ctx, needs_box)); if(res.is_err()) { @@ -619,9 +627,9 @@ namespace jank::analyze } /* Ultimately, whether or not this let is boxed is up to the last form. */ - if(last) + if(is_last) { - ret.needs_box = res.expect_ok_ptr()->data->get_base()->needs_box; + ret.needs_box = res.expect_ok()->get_base()->needs_box; } ret.body.body.emplace_back(res.expect_ok_move()); @@ -764,6 +772,177 @@ namespace jank::analyze }); } + processor::expression_result + processor::analyze_try(runtime::obj::persistent_list_ptr const &list, + local_frame_ptr ¤t_frame, + expression_type const expr_type, + option const &fn_ctx, + native_bool const) + { + expr::try_ ret{ + expression_base{{}, expr_type, current_frame} + }; + + /* The exact shape of a try/catch/finally form isn't known until we traverse it, so + * we do so twice here. The first time, we count the catch/finally forms and ensure + * they're in the correct position. From that, we can calculate the last body form + * index, which is necessary for marking it as return position (and requiring boxing). + * The second iteration will analyze each form accordingly. */ + enum class try_expression_type + { + other, + catch_, + finally_ + }; + + static runtime::obj::symbol catch_{ "catch" }, finally_{ "finally" }; + native_bool has_catch{}, has_finally{}; + + for(auto const &item : list->data.rest()) + { + auto const type(runtime::visit_seqable( + [](auto const typed_item) { + auto const first(typed_item->seq()->first()); + if(runtime::detail::equal(first, &catch_)) + { + return try_expression_type::catch_; + } + else if(runtime::detail::equal(first, &finally_)) + { + return try_expression_type::finally_; + } + else + { + return try_expression_type::other; + } + }, + []() { return try_expression_type::other; }, + item)); + + switch(type) + { + case try_expression_type::other: + if(has_catch || has_finally) + { + return err(error{ "extra forms after catch/finally" }); + } + break; + case try_expression_type::catch_: + if(has_finally) + { + return err(error{ "finally must be the last form of a try" }); + } + if(has_catch) + { + return err(error{ "only one catch may be supplied" }); + } + has_catch = true; + break; + case try_expression_type::finally_: + if(has_finally) + { + return err(error{ "only one finally may be supplied" }); + } + has_finally = true; + break; + } + } + + if(!has_catch) + { + return err(error{ "each try must have a catch clause" }); + } + + /* At this point, we know the form has a catch, maybe a finally, and 0 or more body forms. + * The catch is already asserted to be the last body index + 1, with the finally following it. + * Nothing else will follow that. */ + + size_t const form_count{ list->count() - 1 }; + size_t const last_body_index{ form_count - 1 - (has_catch + has_finally) }; + size_t current_index{}; + + /* Clojure JVM doesn't support recur across try/catch/finally, so we don't either. */ + rt_ctx + .push_thread_bindings(runtime::obj::persistent_hash_map::create_unique( + std::make_pair(rt_ctx.no_recur_var, runtime::obj::boolean::true_const()))) + .expect_ok(); + util::scope_exit const finally{ [&]() { rt_ctx.pop_thread_bindings().expect_ok(); } }; + + for(auto const &item : list->data.rest()) + { + if(current_index <= last_body_index) + { + auto const is_last(current_index == last_body_index); + auto const form_type(is_last ? expr_type : expression_type::statement); + auto form(analyze(item, current_frame, form_type, fn_ctx, is_last)); + if(form.is_err()) + { + return form.expect_err_move(); + } + + ret.body.body.emplace_back(form.expect_ok()); + } + else if(current_index == last_body_index + 1) + { + /* Verify we have (catch ...) */ + auto const catch_list(runtime::expect_object(item)); + auto const catch_body_size(catch_list->count()); + if(catch_body_size == 1) + { + return err(error{ "symbol required after catch" }); + } + + auto const sym_obj(catch_list->data.rest().first().unwrap()); + if(sym_obj->type != runtime::object_type::symbol) + { + return err(error{ "symbol required after catch" }); + } + + auto const sym(runtime::expect_object(sym_obj)); + if(!sym->get_namespace().empty()) + { + return err(error{ "symbol for catch must be unqualified" }); + } + + /* We introduce a new frame so that we can register the sym as a local. + * It holds the exception value which was caught. */ + auto frame(make_box(local_frame::frame_type::catch_, + current_frame->rt_ctx, + current_frame)); + frame->locals.emplace(sym, local_binding{ sym, none, current_frame }); + + /* Now we just turn the body into a do block and have the do analyzer handle the rest. */ + auto const do_list( + catch_list->data.rest().rest().cons(make_box("do"))); + auto do_res(analyze(make_box(do_list), frame, expr_type, fn_ctx, true)); + if(do_res.is_err()) + { + return do_res.expect_err_move(); + } + + ret.catch_body = expr::catch_{ sym, + std::move(boost::get>( + do_res.expect_ok()->data)) }; + has_catch = true; + } + else if(current_index == last_body_index + 2) + { + auto const finally_list(runtime::expect_object(item)); + auto const do_list(finally_list->data.rest().cons(make_box("do"))); + auto do_res( + analyze(make_box(do_list), current_frame, expression_type::statement, fn_ctx, false)); + if(do_res.is_err()) + { + return do_res.expect_err_move(); + } + ret.finally_body = std::move(boost::get>(do_res.expect_ok()->data)); + } + ++current_index; + } + + return make_box(std::move(ret)); + } + processor::expression_result processor::analyze_native_raw(runtime::obj::persistent_list_ptr const &o, local_frame_ptr ¤t_frame, diff --git a/src/cpp/jank/codegen/processor.cpp b/src/cpp/jank/codegen/processor.cpp index 51cf6a5c7..a8d7a995f 100644 --- a/src/cpp/jank/codegen/processor.cpp +++ b/src/cpp/jank/codegen/processor.cpp @@ -951,7 +951,9 @@ namespace jank::codegen auto inserter(std::back_inserter(body_buffer)); auto ret_tmp(runtime::context::unique_string("vec")); - fmt::format_to(inserter, "auto const {}(jank::make_box(", ret_tmp); + fmt::format_to(inserter, + "auto const {}(jank::make_box(", + ret_tmp); for(auto it(data_tmps.begin()); it != data_tmps.end();) { fmt::format_to(inserter, "{}", it->str(true)); @@ -1276,10 +1278,59 @@ namespace jank::codegen { auto inserter(std::back_inserter(body_buffer)); auto const &value_tmp(gen(expr.value, fn_arity, true)); - fmt::format_to(inserter, "throw {};", value_tmp.unwrap().str(true)); + fmt::format_to(inserter, + "throw static_cast({});", + value_tmp.unwrap().str(true)); return none; } + option processor::gen(analyze::expr::try_ const &expr, + analyze::expr::function_arity const &fn_arity, + native_bool const needs_box) + { + auto inserter(std::back_inserter(body_buffer)); + auto ret_tmp(runtime::context::unique_string("try")); + fmt::format_to(inserter, "object_ptr {}{{ obj::nil::nil_const() }};", ret_tmp); + + fmt::format_to(inserter, "{{"); + if(expr.finally_body.is_some()) + { + fmt::format_to(inserter, "jank::util::scope_exit const finally{{ [&](){{ "); + gen(expr.finally_body.unwrap(), fn_arity, needs_box); + fmt::format_to(inserter, "}} }};"); + } + + fmt::format_to(inserter, "try {{"); + auto const &body_tmp(gen(expr.body, fn_arity, needs_box)); + if(body_tmp.is_some()) + { + fmt::format_to(inserter, "{} = {};", ret_tmp, body_tmp.unwrap().str(needs_box)); + } + if(expr.expr_type == analyze::expression_type::return_statement) + { + fmt::format_to(inserter, "return {};", ret_tmp); + } + fmt::format_to(inserter, "}}"); + + fmt::format_to(inserter, + "catch(jank::runtime::object_ptr const {}) {{", + runtime::munge(expr.catch_body.sym->name)); + auto const &catch_tmp(gen(expr.catch_body.body, fn_arity, needs_box)); + if(catch_tmp.is_some()) + { + fmt::format_to(inserter, "{} = {};", ret_tmp, catch_tmp.unwrap().str(needs_box)); + } + if(expr.expr_type == analyze::expression_type::return_statement) + { + fmt::format_to(inserter, "return {};", ret_tmp); + } + fmt::format_to(inserter, "}}"); + + fmt::format_to(inserter, "}}"); + + return ret_tmp; + } + option processor::gen(analyze::expr::native_raw const &expr, analyze::expr::function_arity const &fn_arity, native_bool const) diff --git a/src/cpp/jank/evaluate.cpp b/src/cpp/jank/evaluate.cpp index 05ffb7b06..7548436dd 100644 --- a/src/cpp/jank/evaluate.cpp +++ b/src/cpp/jank/evaluate.cpp @@ -387,11 +387,17 @@ namespace jank::evaluate runtime::object_ptr eval(runtime::context &rt_ctx, jit::processor const &jit_prc, analyze::expr::throw_ const &expr) - /* TODO: Clojure wraps this in a fn and does bytecodegen. Why? */ { throw eval(rt_ctx, jit_prc, expr.value); } + runtime::object_ptr eval(runtime::context &rt_ctx, + jit::processor const &jit_prc, + analyze::expr::try_ const &expr) + { + return runtime::dynamic_call(eval(rt_ctx, jit_prc, wrap_expression(expr))); + } + runtime::object_ptr eval(runtime::context &rt_ctx, jit::processor const &jit_prc, analyze::expr::native_raw const &expr) diff --git a/src/cpp/jank/runtime/context.cpp b/src/cpp/jank/runtime/context.cpp index 091e39b2f..a34cf3a6c 100644 --- a/src/cpp/jank/runtime/context.cpp +++ b/src/cpp/jank/runtime/context.cpp @@ -44,41 +44,22 @@ namespace jank::runtime compile_files_var->bind_root(obj::boolean::false_const()); compile_files_var->dynamic.store(true); - /* TODO: Non-standard binding in clojure.core. */ - auto const current_module_sym(make_box("clojure.core/*current-module*")); - current_module_var = core->intern_var(current_module_sym); - current_module_var->bind_root(obj::persistent_string::empty()); - current_module_var->dynamic.store(true); - auto const assert_sym(make_box("clojure.core/*assert*")); assert_var = core->intern_var(assert_sym); assert_var->bind_root(obj::boolean::true_const()); assert_var->dynamic.store(true); - intern_ns(make_box("native")); + /* These are not actually interned. */ + current_module_var + = make_box(core, make_box("*current-module*"))->set_dynamic(true); + no_recur_var + = make_box(core, make_box("*no-recur*"))->set_dynamic(true); - std::function in_ns_fn([this](object_ptr const sym) { - return visit_object( - [this](auto const typed_sym) { - using T = typename decltype(typed_sym)::value_type; + intern_ns(make_box("native")); - if constexpr(std::same_as) - { - auto const new_ns(intern_ns(typed_sym)); - current_ns_var->set(new_ns).expect_ok(); - return obj::nil::nil_const(); - } - else - /* TODO: throw. */ - { - return obj::nil::nil_const(); - } - }, - sym); - }); + /* This won't be set until clojure.core is loaded. */ auto const in_ns_sym(make_box("clojure.core/in-ns")); in_ns_var = intern_var(in_ns_sym).expect_ok(); - in_ns_var->bind_root(make_box(in_ns_fn)); /* TODO: Remove this once it can be defined in jank. */ auto const seq_sym(make_box("clojure.core/seq")); @@ -135,9 +116,12 @@ namespace jank::runtime in_ns_var = intern_var(make_box("clojure.core/in-ns")).expect_ok(); compile_files_var = intern_var(make_box("clojure.core/*compile-files*")).expect_ok(); - current_module_var - = intern_var(make_box("clojure.core/*current-module*")).expect_ok(); assert_var = core->intern_var(make_box("clojure.core/*assert*")); + + current_module_var + = make_box(core, make_box("*current-module*"))->set_dynamic(true); + no_recur_var + = make_box(core, make_box("*no-recur*"))->set_dynamic(true); } context::~context() @@ -221,7 +205,8 @@ namespace jank::runtime if(detail::truthy(compile_files_var->deref())) { - auto const ¤t_module(expect_object(current_module_var->deref())->data); + auto const ¤t_module( + expect_object(current_module_var->deref())->data); auto wrapped_exprs(evaluate::wrap_expressions(exprs, an_prc)); wrapped_exprs.name = "__ns"; auto const &module( @@ -537,8 +522,8 @@ namespace jank::runtime return typed_o; } - auto const args( - make_box(typed_o->data.rest().cons(obj::nil::nil_const()).cons(typed_o))); + auto const args(make_box( + typed_o->data.rest().cons(obj::nil::nil_const()).cons(typed_o))); return apply_to(var.unwrap()->deref(), args); } }, @@ -598,7 +583,9 @@ namespace jank::runtime using T = typename decltype(typed_more)::value_type; if constexpr(std::same_as) - { std::putc('\n', stdout); } + { + std::putc('\n', stdout); + } else if constexpr(behavior::sequenceable) { fmt::memory_buffer buff; diff --git a/src/cpp/jank/runtime/util.cpp b/src/cpp/jank/runtime/util.cpp index e2b9e9e7c..b03c7d615 100644 --- a/src/cpp/jank/runtime/util.cpp +++ b/src/cpp/jank/runtime/util.cpp @@ -201,7 +201,7 @@ namespace jank::runtime } /* TODO: Support symbols and other data; Clojure takes in anything and passes it through str. */ - object_ptr munge(object_ptr o) + object_ptr munge(object_ptr const o) { return visit_object( [](auto const typed_o) -> object_ptr { @@ -209,11 +209,11 @@ namespace jank::runtime if constexpr(std::same_as) { - return jank::make_box(munge(typed_o->data)); + return make_box(munge(typed_o->data)); } else { - throw "munging only supported for strings right now"; + throw std::runtime_error{ "munging only supported for strings right now" }; } }, o); diff --git a/src/jank/clojure/core.jank b/src/jank/clojure/core.jank index 372c547f1..0fa10574d 100644 --- a/src/jank/clojure/core.jank +++ b/src/jank/clojure/core.jank @@ -2,7 +2,6 @@ ; Namespace management. (def *ns*) -(def in-ns) ; Exceptions (def ex-info @@ -1490,6 +1489,12 @@ (reset-meta! (var pow) {:arities {2 {:supports-unboxed-input? true :unboxed-output? true}}}) +; Namespaces. +(defn in-ns [sym] + (if (symbol? sym) + (native/raw "__rt_ctx.current_ns_var->set(__rt_ctx.intern_ns(expect_object(sym))).expect_ok();") + (throw "argument to in-ns must be a symbol"))) + ; Vars. (defn var? [o] (native/raw "__value = make_box(#{ o }#->type == object_type::var);")) diff --git a/test/cpp/jank/jit/processor.cpp b/test/cpp/jank/jit/processor.cpp index da197c4f1..ad9523c16 100644 --- a/test/cpp/jank/jit/processor.cpp +++ b/test/cpp/jank/jit/processor.cpp @@ -35,7 +35,8 @@ namespace jank::jit TEST_CASE("files") { runtime::context rt_ctx; - auto const cardinal_result(rt_ctx.intern_keyword(runtime::obj::symbol{ "", "success" }, true).expect_ok()); + auto const cardinal_result( + rt_ctx.intern_keyword(runtime::obj::symbol{ "", "success" }, true).expect_ok()); rt_ctx.load_module("/clojure.core").expect_ok(); size_t test_count{}; @@ -49,23 +50,18 @@ namespace jank::jit for(auto const &dir_entry : boost::filesystem::recursive_directory_iterator("test/jank")) { if(!boost::filesystem::is_regular_file(dir_entry.path())) - { continue; } + { + continue; + } auto const &filename(dir_entry.path().filename().string()); - auto const expect_success - (boost::algorithm::starts_with(filename, "pass-")); - auto const expect_failure - (boost::algorithm::starts_with(filename, "fail-")); - auto const expect_throw - (boost::algorithm::starts_with(filename, "throw-")); - auto const allow_failure - (boost::algorithm::starts_with(filename, "warn-")); - CHECK_MESSAGE - ( - (expect_success || expect_failure || allow_failure || expect_throw), - "Test file needs to begin with pass- or fail- or throw- or warn-: ", - dir_entry - ); + auto const expect_success(boost::algorithm::starts_with(filename, "pass-")); + auto const expect_failure(boost::algorithm::starts_with(filename, "fail-")); + auto const expect_throw(boost::algorithm::starts_with(filename, "throw-")); + auto const allow_failure(boost::algorithm::starts_with(filename, "warn-")); + CHECK_MESSAGE((expect_success || expect_failure || allow_failure || expect_throw), + "Test file needs to begin with pass- or fail- or throw- or warn-: ", + dir_entry); ++test_count; runtime::context test_rt_ctx{ rt_ctx }; @@ -80,29 +76,19 @@ namespace jank::jit * going to intentionally make that happen. */ std::streambuf * const old_cout{ std::cout.rdbuf(captured_output.rdbuf()) }; std::streambuf * const old_cerr{ std::cerr.rdbuf(captured_output.rdbuf()) }; - util::scope_exit const _ - { - [=]() - { - std::cout.rdbuf(old_cout); - std::cerr.rdbuf(old_cerr); - } - }; + util::scope_exit const _{ [=]() { + std::cout.rdbuf(old_cout); + std::cerr.rdbuf(old_cerr); + } }; auto const result(test_rt_ctx.eval_file(dir_entry.path().string())); if(!expect_success) { - failures.push_back - ( - { - dir_entry.path(), - fmt::format - ( + failures.push_back( + { dir_entry.path(), + fmt::format( "Test failure was expected, but it passed with {}", - (result == nullptr ? "nullptr" : runtime::detail::to_string(result)) - ) - } - ); + (result == nullptr ? "nullptr" : runtime::detail::to_string(result))) }); passed = false; } else @@ -114,13 +100,9 @@ namespace jank::jit } else if(!runtime::detail::equal(result, cardinal_result)) { - failures.push_back - ( - { - dir_entry.path(), - fmt::format("Result is not :success: {}", runtime::detail::to_string(result)) - } - ); + failures.push_back( + { dir_entry.path(), + fmt::format("Result is not :success: {}", runtime::detail::to_string(result)) }); passed = false; } } @@ -137,24 +119,16 @@ namespace jank::jit { if(expect_success || (expect_throw && !runtime::detail::equal(e, cardinal_result))) { - failures.push_back - ( - { - dir_entry.path(), - fmt::format("Exception thrown: {}", runtime::detail::to_string(e)) - } - ); + failures.push_back( + { dir_entry.path(), + fmt::format("Exception thrown: {}", runtime::detail::to_string(e)) }); passed = false; } else if(expect_failure && runtime::detail::equal(e, cardinal_result)) { - failures.push_back - ( - { - dir_entry.path(), - fmt::format("Expected failure, thrown: {}", runtime::detail::to_string(e)) - } - ); + failures.push_back( + { dir_entry.path(), + fmt::format("Expected failure, thrown: {}", runtime::detail::to_string(e)) }); passed = false; } } @@ -162,13 +136,9 @@ namespace jank::jit { if(!expect_throw || !runtime::detail::equal(e, cardinal_result)) { - failures.push_back - ( - { - dir_entry.path(), - fmt::format("Exception thrown: {}", runtime::detail::to_string(e)) - } - ); + failures.push_back( + { dir_entry.path(), + fmt::format("Exception thrown: {}", runtime::detail::to_string(e)) }); passed = false; } } @@ -182,9 +152,13 @@ namespace jank::jit } if(allow_failure) - { fmt::print(fmt::fg(fmt::color::orange), "allowed failure\n"); } + { + fmt::print(fmt::fg(fmt::color::orange), "allowed failure\n"); + } else if(passed) - { fmt::print(fmt::fg(fmt::color::green), "success\n"); } + { + fmt::print(fmt::fg(fmt::color::green), "success\n"); + } else { fmt::print(fmt::fg(fmt::color::red), "failure\n"); @@ -195,13 +169,10 @@ namespace jank::jit CHECK(failures.empty()); for(auto const &f : failures) { - fmt::print - ( - "{}: {} {}\n", - fmt::styled("failure", fmt::fg(fmt::color::red)), - f.path.string(), - f.error - ); + fmt::print("{}: {} {}\n", + fmt::styled("failure", fmt::fg(fmt::color::red)), + f.path.string(), + f.error); } fmt::print("tested {} jank files\n", test_count); } diff --git a/test/cpp/jank/runtime/context.cpp b/test/cpp/jank/runtime/context.cpp index 776d0d2a2..abbe3270c 100644 --- a/test/cpp/jank/runtime/context.cpp +++ b/test/cpp/jank/runtime/context.cpp @@ -6,28 +6,38 @@ namespace jank::runtime { - TEST_CASE("Initialization") + TEST_SUITE("runtime::context") { - context ctx; - auto const locked_namespaces(ctx.namespaces.rlock()); - CHECK(locked_namespaces->find(jank::make_box("clojure.core")) != locked_namespaces->end()); - CHECK(locked_namespaces->find(jank::make_box("missing")) == locked_namespaces->end()); - CHECK(expect_object(ctx.current_ns_var->get_root())->name->equal(obj::symbol("", "clojure.core"))); - } - - TEST_CASE("Namespace changing") - { - context ctx; + TEST_CASE("Initialization") { + context ctx; auto const locked_namespaces(ctx.namespaces.rlock()); - CHECK(expect_object(ctx.current_ns_var->get_root())->name->equal(obj::symbol("", "clojure.core"))); - CHECK(locked_namespaces->find(jank::make_box("test")) == locked_namespaces->end()); + CHECK(locked_namespaces->find(make_box("clojure.core")) + != locked_namespaces->end()); + CHECK(locked_namespaces->find(make_box("missing")) == locked_namespaces->end()); + CHECK(expect_object(ctx.current_ns_var->get_root()) + ->name->equal(obj::symbol("", "clojure.core"))); } - expect_object(ctx.in_ns_var->get_root())->call(jank::make_box("test")); + + TEST_CASE("Namespace changing") { - auto const locked_namespaces(ctx.namespaces.rlock()); - CHECK(locked_namespaces->find(jank::make_box("test")) != locked_namespaces->end()); - CHECK(expect_object(ctx.current_ns_var->get_root())->name->equal(obj::symbol("", "test"))); + context ctx; + ctx.load_module("/clojure.core").expect_ok(); + + { + auto const locked_namespaces(ctx.namespaces.rlock()); + CHECK(expect_object(ctx.current_ns_var->get_root()) + ->name->equal(obj::symbol("", "clojure.core"))); + CHECK(locked_namespaces->find(make_box("test")) == locked_namespaces->end()); + } + + dynamic_call(ctx.in_ns_var->get_root(), make_box("test")); + + { + auto const locked_namespaces(ctx.namespaces.rlock()); + CHECK(locked_namespaces->find(make_box("test")) != locked_namespaces->end()); + CHECK(expect_object(ctx.current_ns_var->deref())->name->equal(obj::symbol("", "test"))); + } } } } diff --git a/test/jank/form/try/fail-catch-after-finally.jank b/test/jank/form/try/fail-catch-after-finally.jank new file mode 100644 index 000000000..14532af20 --- /dev/null +++ b/test/jank/form/try/fail-catch-after-finally.jank @@ -0,0 +1,5 @@ +(try + (finally + ) + (catch _ + )) diff --git a/test/jank/form/try/fail-multiple-catch.jank b/test/jank/form/try/fail-multiple-catch.jank new file mode 100644 index 000000000..8681f12de --- /dev/null +++ b/test/jank/form/try/fail-multiple-catch.jank @@ -0,0 +1,5 @@ +(try + (catch a + ) + (catch b + )) diff --git a/test/jank/form/try/fail-multiple-finally.jank b/test/jank/form/try/fail-multiple-finally.jank new file mode 100644 index 000000000..2c7bcad42 --- /dev/null +++ b/test/jank/form/try/fail-multiple-finally.jank @@ -0,0 +1,5 @@ +(try + (finally + ) + (finally + )) diff --git a/test/jank/form/try/fail-no-catch.jank b/test/jank/form/try/fail-no-catch.jank new file mode 100644 index 000000000..5c9c01329 --- /dev/null +++ b/test/jank/form/try/fail-no-catch.jank @@ -0,0 +1 @@ +(try) diff --git a/test/jank/form/try/fail-other-form-after-finally.jank b/test/jank/form/try/fail-other-form-after-finally.jank new file mode 100644 index 000000000..a2521023f --- /dev/null +++ b/test/jank/form/try/fail-other-form-after-finally.jank @@ -0,0 +1,7 @@ +(try + (+ 1 2) + (catch e + ) + (finally + ) + :success) diff --git a/test/jank/form/try/fail-recur-in-catch.jank b/test/jank/form/try/fail-recur-in-catch.jank new file mode 100644 index 000000000..03c11b560 --- /dev/null +++ b/test/jank/form/try/fail-recur-in-catch.jank @@ -0,0 +1,5 @@ +(fn* [] + (try + :success + (catch _ + (recur)))) diff --git a/test/jank/form/try/fail-recur-in-finally.jank b/test/jank/form/try/fail-recur-in-finally.jank new file mode 100644 index 000000000..7138a4470 --- /dev/null +++ b/test/jank/form/try/fail-recur-in-finally.jank @@ -0,0 +1,7 @@ +(fn* [] + (try + :success + (catch _ + ) + (finally + (recur)))) diff --git a/test/jank/form/try/fail-recur-in-try.jank b/test/jank/form/try/fail-recur-in-try.jank new file mode 100644 index 000000000..4d85496b0 --- /dev/null +++ b/test/jank/form/try/fail-recur-in-try.jank @@ -0,0 +1,5 @@ +(fn* [] + (try + (recur) + (catch _ + ))) diff --git a/test/jank/form/try/pass-catch-returns-exception.jank b/test/jank/form/try/pass-catch-returns-exception.jank new file mode 100644 index 000000000..952d86862 --- /dev/null +++ b/test/jank/form/try/pass-catch-returns-exception.jank @@ -0,0 +1,4 @@ +(try + (throw :success) + (catch e + e)) diff --git a/test/jank/form/try/pass-expression-no-throw.jank b/test/jank/form/try/pass-expression-no-throw.jank new file mode 100644 index 000000000..57fda7e9e --- /dev/null +++ b/test/jank/form/try/pass-expression-no-throw.jank @@ -0,0 +1,6 @@ +(assert (= (try + :success + (catch _)) + :success)) + +:success diff --git a/test/jank/form/try/pass-expression-with-throw.jank b/test/jank/form/try/pass-expression-with-throw.jank new file mode 100644 index 000000000..8748f5816 --- /dev/null +++ b/test/jank/form/try/pass-expression-with-throw.jank @@ -0,0 +1,6 @@ +(assert (= (try + (throw :success) + (catch e + e)))) + +:success diff --git a/test/jank/form/try/pass-nested.jank b/test/jank/form/try/pass-nested.jank new file mode 100644 index 000000000..6b09b61c1 --- /dev/null +++ b/test/jank/form/try/pass-nested.jank @@ -0,0 +1,7 @@ +(try + (try + (throw :success) + (catch e1 + (throw e1))) + (catch e2 + e2)) diff --git a/test/jank/form/try/pass-no-throw-with-catch-and-finally.jank b/test/jank/form/try/pass-no-throw-with-catch-and-finally.jank new file mode 100644 index 000000000..2d7c4114e --- /dev/null +++ b/test/jank/form/try/pass-no-throw-with-catch-and-finally.jank @@ -0,0 +1,6 @@ +(try + :success + (catch _ + ) + (finally + )) diff --git a/test/jank/form/try/pass-no-throw-with-catch.jank b/test/jank/form/try/pass-no-throw-with-catch.jank new file mode 100644 index 000000000..2744628ae --- /dev/null +++ b/test/jank/form/try/pass-no-throw-with-catch.jank @@ -0,0 +1,3 @@ +(try + :success + (catch _)) diff --git a/test/jank/form/try/pass-no-value-from-finally.jank b/test/jank/form/try/pass-no-value-from-finally.jank new file mode 100644 index 000000000..3429cb4f6 --- /dev/null +++ b/test/jank/form/try/pass-no-value-from-finally.jank @@ -0,0 +1,17 @@ +(assert (= (try + 1 + (catch _ + 2) + (finally + 3)) + 1)) + +(assert (= (try + (throw :anything) + (catch _ + 2) + (finally + 3)) + 2)) + +:success diff --git a/test/jank/form/try/pass-unboxed-position.jank b/test/jank/form/try/pass-unboxed-position.jank new file mode 100644 index 000000000..df43ecd16 --- /dev/null +++ b/test/jank/form/try/pass-unboxed-position.jank @@ -0,0 +1,10 @@ +; A box will be required. +(def a (fn* [] + (let* [a 5 + b (try + 7 + (catch _ + ))] + []))) + +:success