Skip to content

Commit

Permalink
case switch support (#158)
Browse files Browse the repository at this point in the history
* case switch support

* format code

* clean up unused code

* remove unused macro expansions in case

* Enable IR optimizations again

* reformat code

* reformat code

* split failure test files for case

* remove useless comment

* remove unused code

* fix format

* address comments

* format code

* update case tests

* add error::analysis_invalid_case

* update case tests

* rename case-map-1 and case-map-2

* add comments and newline for formatting

* address comments; add unicode test file;

* format code

---------

Co-authored-by: jeaye <contact@jeaye.com>
  • Loading branch information
jianlingzhong and jeaye authored Feb 19, 2025
1 parent 1a75d85 commit 4324bb3
Show file tree
Hide file tree
Showing 56 changed files with 730 additions and 112 deletions.
45 changes: 45 additions & 0 deletions compiler+runtime/include/cpp/jank/analyze/expr/case.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include <jank/detail/to_runtime_data.hpp>
#include <jank/analyze/expression_base.hpp>

namespace jank::analyze::expr
{
using namespace jank::runtime;

template <typename E>
struct case_ : expression_base
{
native_box<E> value_expr{};
native_integer shift{};
native_integer mask{};
native_box<E> default_expr{};
native_vector<native_integer> keys{};
native_vector<native_box<E>> exprs{};

void propagate_position(expression_position const pos)
{
default_expr->propagate_position(pos);
for(auto &expr : exprs)
{
expr->propagate_position(pos);
}
position = pos;
}

object_ptr to_runtime_data() const
{
return merge(static_cast<expression_base const *>(this)->to_runtime_data(),
obj::persistent_array_map::create_unique(make_box("__type"),
make_box("expr::case"),
make_box("value_expr"),
value_expr->to_runtime_data(),
make_box("shift"),
make_box(shift),
make_box("mask"),
make_box(mask),
make_box("default_expr"),
default_expr->to_runtime_data()));
}
};
}
4 changes: 3 additions & 1 deletion compiler+runtime/include/cpp/jank/analyze/expression.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <jank/analyze/expr/if.hpp>
#include <jank/analyze/expr/throw.hpp>
#include <jank/analyze/expr/try.hpp>
#include <jank/analyze/expr/case.hpp>

namespace jank::analyze
{
Expand All @@ -46,7 +47,8 @@ namespace jank::analyze
expr::do_<E>,
expr::if_<E>,
expr::throw_<E>,
expr::try_<E>>;
expr::try_<E>,
expr::case_<E>>;

static constexpr native_bool pointer_free{ false };

Expand Down
8 changes: 7 additions & 1 deletion compiler+runtime/include/cpp/jank/analyze/processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,13 @@ namespace jank::analyze
option<expr::function_context_ptr> const &,
native_bool needs_box);

/* Returns whether or not the form is a special symbol. */
expression_result analyze_case(runtime::obj::persistent_list_ptr const &,
local_frame_ptr &,
expression_position,
option<expr::function_context_ptr> const &,
native_bool needs_box);

/* Returns whether the form is a special symbol. */
native_bool is_special(runtime::object_ptr form);

using special_function_type
Expand Down
4 changes: 4 additions & 0 deletions compiler+runtime/include/cpp/jank/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ extern "C"
jank_native_bool jank_truthy(jank_object_ptr o);
jank_native_bool jank_equal(jank_object_ptr l, jank_object_ptr r);
jank_native_hash jank_to_hash(jank_object_ptr o);
jank_native_integer jank_to_integer(jank_object_ptr o);
jank_native_integer jank_shift_mask_case_integer(jank_object_ptr o,
jank_native_integer shift,
jank_native_integer mask);

void jank_set_meta(jank_object_ptr o, jank_object_ptr meta);

Expand Down
2 changes: 2 additions & 0 deletions compiler+runtime/include/cpp/jank/codegen/llvm_processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ namespace jank::codegen
analyze::expr::function_arity<analyze::expression> const &);
llvm::Value *gen(analyze::expr::try_<analyze::expression> const &,
analyze::expr::function_arity<analyze::expression> const &);
llvm::Value *gen(analyze::expr::case_<analyze::expression> const &,
analyze::expr::function_arity<analyze::expression> const &);

llvm::Value *gen_var(obj::symbol_ptr qualified_name) const;
llvm::Value *gen_c_string(native_persistent_string const &s) const;
Expand Down
3 changes: 3 additions & 0 deletions compiler+runtime/include/cpp/jank/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ namespace jank::error
parse_invalid_keyword,
internal_parse_failure,

analysis_invalid_case,
analysis_invalid_def,
analysis_invalid_fn,
analysis_invalid_fn_parameters,
Expand Down Expand Up @@ -158,6 +159,8 @@ namespace jank::error
return "parse/invalid-keyword";
case kind::internal_parse_failure:
return "internal/parse-failure";
case kind::analysis_invalid_case:
return "analysis/invalid-case";
case kind::analysis_invalid_def:
return "analysis/invalid-def";
case kind::analysis_invalid_fn:
Expand Down
3 changes: 2 additions & 1 deletion compiler+runtime/include/cpp/jank/error/analyze.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#pragma once

#include <jank/error.hpp>
#include <jank/read/lex.hpp>

namespace jank::error
{
error_ptr
analysis_invalid_case(native_persistent_string const &message, read::source const &source);
error_ptr
analysis_invalid_def(native_persistent_string const &message, read::source const &source);
error_ptr
Expand Down
1 change: 1 addition & 0 deletions compiler+runtime/include/cpp/jank/evaluate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ namespace jank::evaluate
runtime::object_ptr eval(analyze::expr::if_<analyze::expression> const &);
runtime::object_ptr eval(analyze::expr::throw_<analyze::expression> const &);
runtime::object_ptr eval(analyze::expr::try_<analyze::expression> const &);
runtime::object_ptr eval(analyze::expr::case_<analyze::expression> const &);
}
1 change: 0 additions & 1 deletion compiler+runtime/include/cpp/jank/runtime/obj/nil.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ namespace jank::runtime::obj
/* behavior::sequenceable */
nil_ptr first() const;
nil_ptr next() const;
obj::cons_ptr conj(object_ptr head) const;

/* behavior::sequenceable_in_place */
nil_ptr next_in_place();
Expand Down
2 changes: 2 additions & 0 deletions compiler+runtime/src/cpp/clojure/core_native.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,8 @@ jank_object_ptr jank_load_clojure_core_native()
intern_fn("prefers", &core_native::prefers);
intern_val("int-min", std::numeric_limits<native_integer>::min());
intern_val("int-max", std::numeric_limits<native_integer>::max());
intern_val("int32-min", std::numeric_limits<int32_t>::min());
intern_val("int32-max", std::numeric_limits<int32_t>::max());
intern_fn("sleep", &core_native::sleep);
intern_fn("current-time", &core_native::current_time);
intern_fn("create-ns", &core_native::intern_ns);
Expand Down
120 changes: 120 additions & 0 deletions compiler+runtime/src/cpp/jank/analyze/processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ namespace jank::analyze
{ make_box<symbol>("var"), make_fn(&processor::analyze_var_call) },
{ make_box<symbol>("throw"), make_fn(&processor::analyze_throw) },
{ make_box<symbol>("try"), make_fn(&processor::analyze_try) },
{ make_box<symbol>("case*"), make_fn(&processor::analyze_case) },
};
}

Expand Down Expand Up @@ -166,6 +167,125 @@ namespace jank::analyze
});
}

processor::expression_result processor::analyze_case(obj::persistent_list_ptr const &o,
local_frame_ptr &f,
expression_position const position,
option<expr::function_context_ptr> const &fc,
native_bool const needs_box)
{
if(auto const length(o->count()); length != 6)
{
return error::analysis_invalid_case("Invalid case*: exactly 6 parameters are needed.",
meta_source(o->meta));
}

auto it{ o->data.rest() };
if(it.first().is_none())
{
return error::analysis_invalid_case("Value expression is missing.", meta_source(o->meta));
}
auto const value_expr_obj{ it.first().unwrap() };
auto const value_expr{ analyze(value_expr_obj, f, expression_position::value, fc, needs_box) };
if(value_expr.is_err())
{
return error::analysis_invalid_case(value_expr.expect_err()->message, meta_source(o->meta));
}

it = it.rest();
if(it.first().is_none())
{
return error::analysis_invalid_case("Shift value is missing.", meta_source(o->meta));
}
auto const shift_obj{ it.first().unwrap() };
if(shift_obj.data->type != object_type::integer)
{
return error::analysis_invalid_case("Shift value should be an integer.",
meta_source(o->meta));
}
auto const shift{ runtime::expect_object<runtime::obj::integer>(shift_obj) };

it = it.rest();
if(it.first().is_none())
{
return error::analysis_invalid_case("Mask value is missing.", meta_source(o->meta));
}
auto const mask_obj{ it.first().unwrap() };
if(mask_obj.data->type != object_type::integer)
{
return error::analysis_invalid_case("Mask value should be an integer.", meta_source(o->meta));
}
auto const mask{ runtime::expect_object<runtime::obj::integer>(mask_obj) };

it = it.rest();
if(it.first().is_none())
{
return error::analysis_invalid_case("Default expression is missing.", meta_source(o->meta));
}
auto const default_expr_obj{ it.first().unwrap() };
auto const default_expr{ analyze(default_expr_obj, f, position, fc, needs_box) };

it = it.rest();
if(it.first().is_none())
{
return error::analysis_invalid_case("Keys and expressions are missing.",
meta_source(o->meta));
}
auto const imap_obj{ it.first().unwrap() };

struct keys_and_exprs
{
native_vector<native_integer> keys{};
native_vector<expression_ptr> exprs{};
};

auto const keys_exprs{ visit_map_like(
[&](auto const typed_imap_obj) -> string_result<keys_and_exprs> {
keys_and_exprs ret{};
for(auto seq{ typed_imap_obj->seq() }; seq != nullptr; seq = seq->next())
{
auto const e{ seq->first() };
auto const k_obj{ runtime::nth(e, make_box(0)) };
auto const v_obj{ runtime::nth(e, make_box(1)) };
if(k_obj.data->type != object_type::integer)
{
return err("Map key for case* is expected to be an integer.");
}
auto const key{ runtime::expect_object<obj::integer>(k_obj) };
auto const expr{ analyze(v_obj, f, position, fc, needs_box) };
if(expr.is_err())
{
return err(expr.expect_err()->message);
}
ret.keys.push_back(key->data);
ret.exprs.push_back(expr.expect_ok());
}
return ret;
},
[&]() -> string_result<keys_and_exprs> {
return err("Case keys and expressions should be a map-like.");
},
imap_obj) };

if(keys_exprs.is_err())
{
return error::analysis_invalid_case(keys_exprs.expect_err(), meta_source(o->meta));
}

auto case_expr{
make_box<expression>(expr::case_<expression>{
expression_base{ {}, position, f, needs_box },
value_expr.expect_ok(),
shift->data,
mask->data,
default_expr.expect_ok(),
keys_exprs.expect_ok().keys,
keys_exprs.expect_ok().exprs,
}
)
};
return case_expr;
}

processor::expression_result processor::analyze_symbol(runtime::obj::symbol_ptr const &sym,
local_frame_ptr &current_frame,
expression_position const position,
Expand Down
38 changes: 38 additions & 0 deletions compiler+runtime/src/cpp/jank/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,44 @@ extern "C"
return to_hash(o_obj);
}

static native_integer to_integer_or_hash(object const *o)
{
if(o->type == object_type::integer)
{
return expect_object<obj::integer>(o)->data;
}

return to_hash(o);
}

jank_native_integer jank_to_integer(jank_object_ptr const o)
{
auto const o_obj(reinterpret_cast<object *>(o));
return to_integer_or_hash(o_obj);
}

jank_native_integer jank_shift_mask_case_integer(jank_object_ptr const o,
jank_native_integer const shift,
jank_native_integer const mask)
{
auto const o_obj(reinterpret_cast<object *>(o));
auto integer{ to_integer_or_hash(o_obj) };
if(mask != 0)
{
if(o_obj->type == object_type::integer)
{
/* We don't hash the integer if it's an int32 value. This is to be consistent with how keys are hashed in jank's
* case macro. */
integer = (integer >= std::numeric_limits<int32_t>::min()
&& integer <= std::numeric_limits<int32_t>::max())
? integer
: hash::integer(integer);
}
integer = (integer >> shift) & mask;
}
return integer;
}

void jank_set_meta(jank_object_ptr const o, jank_object_ptr const meta)
{
auto const o_obj(reinterpret_cast<object *>(o));
Expand Down
Loading

0 comments on commit 4324bb3

Please sign in to comment.