Skip to content

Commit

Permalink
Add reader conditional support
Browse files Browse the repository at this point in the history
No splicing yet. That comes next.
  • Loading branch information
jeaye committed Feb 7, 2024
1 parent 5564923 commit c41ff94
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 4 deletions.
1 change: 1 addition & 0 deletions include/cpp/jank/read/lex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace jank::read::lex
meta_hint,
reader_macro,
reader_macro_comment,
reader_macro_conditional,
/* Has string data. */
comment,
nil,
Expand Down
1 change: 1 addition & 0 deletions include/cpp/jank/read/parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ namespace jank::read::parse
object_result parse_reader_macro();
object_result parse_reader_macro_set();
object_result parse_reader_macro_comment();
object_result parse_reader_macro_conditional();
object_result parse_symbol();
object_result parse_nil();
object_result parse_boolean();
Expand Down
17 changes: 14 additions & 3 deletions src/cpp/jank/read/lex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,10 +563,21 @@ namespace jank::read
auto const oc(peek());
++pos;

if(oc.is_some() && oc.unwrap() == '_')
if(oc.is_some())
{
++pos;
return ok(token{ token_start, pos - token_start, token_kind::reader_macro_comment });
switch(oc.unwrap())
{
case '_':
++pos;
return ok(
token{ token_start, pos - token_start, token_kind::reader_macro_comment });
case '?':
++pos;
return ok(
token{ token_start, pos - token_start, token_kind::reader_macro_conditional });
default:
break;
}
}

return ok(token{ token_start, pos - token_start, token_kind::reader_macro });
Expand Down
60 changes: 60 additions & 0 deletions src/cpp/jank/read/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ namespace jank::read::parse
}
return res;
}
case lex::token_kind::reader_macro_conditional:
{
auto res(parse_reader_macro_conditional());
if(res.is_ok() && res.expect_ok() == nullptr)
{
continue;
}
return res;
}
case lex::token_kind::nil:
return parse_nil();
case lex::token_kind::boolean:
Expand Down Expand Up @@ -458,6 +467,57 @@ namespace jank::read::parse
return ok(nullptr);
}

processor::object_result processor::parse_reader_macro_conditional()
{
auto const start_token(token_current.latest.unwrap().expect_ok());
++token_current;

auto list_result(next());
if(list_result.is_err())
{
return list_result;
}
else if(list_result.expect_ok() == nullptr)
{
return err(
error{ start_token.pos, native_persistent_string{ "value after #? must be present" } });
}
else if(list_result.expect_ok()->type != runtime::object_type::persistent_list)
{
return err(
error{ start_token.pos, native_persistent_string{ "value after #? must be a list" } });
}

auto const list(runtime::expect_object<runtime::obj::persistent_list>(list_result.expect_ok()));

if(list.data->count() % 2 == 1)
{
return err(
error{ start_token.pos, native_persistent_string{ "#? expects an even number of forms" } });
}

auto const jank_keyword(rt_ctx.intern_keyword("", "jank").expect_ok());
auto const default_keyword(rt_ctx.intern_keyword("", "default").expect_ok());

for(auto it(list->fresh_seq()); it != nullptr;)
{
auto const kw(it->first());
/* We take the first match, checking for :jank first. If there are duplicates, it doesn't
* matter. If :default comes first, we'll always take it. In short, order is important. This
* matches Clojure's behavior. */
if(runtime::detail::equal(kw, jank_keyword) || runtime::detail::equal(kw, default_keyword))
{
it = it->next_in_place();
auto const form(it->first());
return ok(form);
}

it = it->next_in_place()->next_in_place();
}

return ok(nullptr);
}

processor::object_result processor::parse_nil()
{
++token_current;
Expand Down
50 changes: 50 additions & 0 deletions test/cpp/jank/read/lex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,56 @@ namespace jank::read::lex
}));
}

SUBCASE("Comment")
{
SUBCASE("Empty")
{
processor p{ "#_" };
native_vector<result<token, error>> tokens(p.begin(), p.end());
CHECK(tokens
== make_tokens({
{0, 2, token_kind::reader_macro_comment}
}));
}

SUBCASE("No whitespace after")
{
processor p{ "#_[]" };
native_vector<result<token, error>> tokens(p.begin(), p.end());
CHECK(tokens
== make_tokens({
{0, 2, token_kind::reader_macro_comment},
{2, 1, token_kind::open_square_bracket},
{3, 1, token_kind::close_square_bracket}
}));
}
}

SUBCASE("Conditional")
{
SUBCASE("Empty")
{
processor p{ "#?" };
native_vector<result<token, error>> tokens(p.begin(), p.end());
CHECK(tokens
== make_tokens({
{0, 2, token_kind::reader_macro_conditional}
}));
}

SUBCASE("With following list")
{
processor p{ "#?()" };
native_vector<result<token, error>> tokens(p.begin(), p.end());
CHECK(tokens
== make_tokens({
{0, 2, token_kind::reader_macro_conditional},
{2, 1, token_kind::open_paren},
{3, 1, token_kind::close_paren}
}));
}
}

SUBCASE("Set")
{
SUBCASE("Empty")
Expand Down
100 changes: 99 additions & 1 deletion test/cpp/jank/read/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,6 @@ namespace jank::read::parse
rt_ctx.intern_keyword(runtime::obj::symbol{ "foo" }).expect_ok(),
runtime::obj::boolean::true_const())));
}
/* TODO: With codegen? */
}

TEST_CASE("Reader macro")
Expand Down Expand Up @@ -663,6 +662,105 @@ namespace jank::read::parse
CHECK(r.is_err());
}
}

SUBCASE("Conditional")
{
SUBCASE("EOF")
{
lex::processor lp{ "#?" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_err());
}

SUBCASE("Non-list after")
{
lex::processor lp{ "#?[]" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_err());
}

SUBCASE("No match")
{
lex::processor lp{ "[#?(:clj 0 :cljs 1) 9]" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_ok());
CHECK(r.expect_ok() != nullptr);
CHECK(runtime::detail::equal(
r.expect_ok(),
make_box<runtime::obj::persistent_vector>(std::in_place, make_box(9))));
}

SUBCASE("Default match")
{
lex::processor lp{ "[#?(:clj 0 :cljs 1 :default 8) 9]" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_ok());
CHECK(r.expect_ok() != nullptr);
CHECK(runtime::detail::equal(
r.expect_ok(),
make_box<runtime::obj::persistent_vector>(std::in_place, make_box(8), make_box(9))));
}

SUBCASE("jank match")
{
lex::processor lp{ "[#?(:clj 0 :cljs 1 :jank 7 :default 8) 9]" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_ok());
CHECK(r.expect_ok() != nullptr);
CHECK(runtime::detail::equal(
r.expect_ok(),
make_box<runtime::obj::persistent_vector>(std::in_place, make_box(7), make_box(9))));
}

SUBCASE("First match picked")
{
lex::processor lp{ "[#?(:default -1 :clj 0 :cljs 1 :jank 7 :default 8) 9]" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_ok());
CHECK(r.expect_ok() != nullptr);
CHECK(runtime::detail::equal(
r.expect_ok(),
make_box<runtime::obj::persistent_vector>(std::in_place, make_box(-1), make_box(9))));
}

SUBCASE("Nested")
{
lex::processor lp{ "[#?(:clj 0 :cljs 1 :jank #?(:default 5) :default 8) 9]" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_ok());
CHECK(r.expect_ok() != nullptr);
CHECK(runtime::detail::equal(
r.expect_ok(),
make_box<runtime::obj::persistent_vector>(std::in_place, make_box(5), make_box(9))));
}

SUBCASE("Other reader macro")
{
lex::processor lp{ "#?(:default #{1})" };
runtime::context rt_ctx;
processor p{ rt_ctx, lp.begin(), lp.end() };
auto const r(p.next());
CHECK(r.is_ok());
CHECK(r.expect_ok() != nullptr);
CHECK(runtime::detail::equal(
r.expect_ok(),
make_box<runtime::obj::persistent_set>(std::in_place, make_box(1))));
}
}
}
}
}

0 comments on commit c41ff94

Please sign in to comment.