From 7eb23f68f27c8a04f07d274e98325866b2646580 Mon Sep 17 00:00:00 2001 From: Jianling Zhong Date: Sat, 19 Oct 2024 10:46:21 -0700 Subject: [PATCH] Add support for ratio Currently, ratio (of two integers) will be converted to a real number during parsing. --- .../include/cpp/jank/read/lex.hpp | 21 ++++- .../include/cpp/jank/read/parse.hpp | 1 + .../cpp/jank/runtime/core/make_box.hpp | 1 - compiler+runtime/src/cpp/jank/read/lex.cpp | 50 ++++++++++++ compiler+runtime/src/cpp/jank/read/parse.cpp | 17 ++++ compiler+runtime/test/cpp/jank/read/lex.cpp | 77 +++++++++++++++++-- compiler+runtime/test/cpp/jank/read/parse.cpp | 26 +++++++ 7 files changed, 184 insertions(+), 9 deletions(-) diff --git a/compiler+runtime/include/cpp/jank/read/lex.hpp b/compiler+runtime/include/cpp/jank/read/lex.hpp index d4678baba..61b243b8b 100644 --- a/compiler+runtime/include/cpp/jank/read/lex.hpp +++ b/compiler+runtime/include/cpp/jank/read/lex.hpp @@ -42,6 +42,8 @@ namespace jank::read::lex integer, /* Has double data. */ real, + /* Has two integer data. */ + ratio, /* Has string data. */ string, /* Has string data. */ @@ -49,6 +51,14 @@ namespace jank::read::lex eof, }; + struct ratio + { + native_integer numerator{}; + native_integer denominator{}; + native_bool operator==(ratio const &rhs) const; + native_bool operator!=(ratio const &rhs) const; + }; + struct token { token() = default; @@ -65,6 +75,7 @@ namespace jank::read::lex token(size_t const p, size_t const s, token_kind const k, native_persistent_string_view const); token(size_t const p, size_t const s, token_kind const k, char const * const); token(size_t const p, size_t const s, token_kind const k, native_bool const); + token(size_t const p, size_t const s, token_kind const k, ratio const); native_bool operator==(token const &rhs) const; native_bool operator!=(token const &rhs) const; @@ -81,12 +92,18 @@ namespace jank::read::lex size_t pos{ ignore_pos }; size_t size{ 1 }; token_kind kind{ token_kind::eof }; - boost::variant + boost::variant data; }; std::ostream &operator<<(std::ostream &os, token const &t); std::ostream &operator<<(std::ostream &os, token::no_data const &t); + std::ostream &operator<<(std::ostream &os, ratio const &t); } namespace jank::read @@ -142,6 +159,8 @@ namespace jank::read::lex size_t pos{}; /* Whether or not the previous token requires a space after it. */ native_bool require_space{}; + /* True when seeing a '/' following a number. */ + native_bool found_slash_after_number{}; native_persistent_string_view file; }; } diff --git a/compiler+runtime/include/cpp/jank/read/parse.hpp b/compiler+runtime/include/cpp/jank/read/parse.hpp index 0c526575a..7e1db7aaa 100644 --- a/compiler+runtime/include/cpp/jank/read/parse.hpp +++ b/compiler+runtime/include/cpp/jank/read/parse.hpp @@ -107,6 +107,7 @@ namespace jank::read::parse object_result parse_boolean(); object_result parse_keyword(); object_result parse_integer(); + object_result parse_ratio(); object_result parse_real(); object_result parse_string(); object_result parse_escaped_string(); diff --git a/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp b/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp index 8a4217dda..dc9deb3bf 100644 --- a/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp @@ -35,7 +35,6 @@ namespace jank::runtime { return make_box(static_cast(i)); } - [[gnu::always_inline, gnu::flatten, gnu::hot]] inline auto make_box(native_integer const i) { diff --git a/compiler+runtime/src/cpp/jank/read/lex.cpp b/compiler+runtime/src/cpp/jank/read/lex.cpp index 50d0b006c..557a820c6 100644 --- a/compiler+runtime/src/cpp/jank/read/lex.cpp +++ b/compiler+runtime/src/cpp/jank/read/lex.cpp @@ -154,6 +154,24 @@ namespace jank::read { } + token::token(size_t const p, size_t const s, token_kind const k, ratio const d) + : pos{ p } + , size{ s } + , kind{ k } + , data{ d } + { + } + + native_bool ratio::operator==(ratio const &rhs) const + { + return numerator == rhs.numerator && denominator == rhs.denominator; + } + + native_bool ratio::operator!=(ratio const &rhs) const + { + return !(*this == rhs); + } + native_bool token::no_data::operator==(no_data const &) const { return true; @@ -186,6 +204,11 @@ namespace jank::read return os << ""; } + std::ostream &operator<<(std::ostream &os, ratio const &r) + { + return os << r.numerator << "/" << r.denominator; + } + processor::processor(native_persistent_string_view const &f) : file{ f } { @@ -406,6 +429,33 @@ namespace jank::read } found_exponent_sign = true; } + else if(c == '/') + { + require_space = false; + ++pos; + if(found_exponent_sign || is_scientific || expecting_exponent || contains_dot + || found_slash_after_number) + { + return err(error{ token_start, pos, "invalid ratio" }); + } + found_slash_after_number = true; + /* skip the '/' char and look for the denominator number. */ + ++pos; + auto const denominator(next()); + if(denominator.is_ok() && denominator.expect_ok().kind == token_kind::integer) + { + auto const &denominator_token(denominator.expect_ok()); + found_slash_after_number = false; + return ok( + token(token_start, + pos - token_start, + token_kind::ratio, + { .numerator = std::strtoll(file.data() + token_start, nullptr, 10), + .denominator = boost::get(denominator_token.data) })); + } + return err( + error{ token_start, pos, "invalid ratio: expecting an integer denominator" }); + } else if(std::isdigit(c) == 0) { if(expecting_exponent) diff --git a/compiler+runtime/src/cpp/jank/read/parse.cpp b/compiler+runtime/src/cpp/jank/read/parse.cpp index 98ebe086a..396bd2348 100644 --- a/compiler+runtime/src/cpp/jank/read/parse.cpp +++ b/compiler+runtime/src/cpp/jank/read/parse.cpp @@ -182,6 +182,8 @@ namespace jank::read::parse return parse_integer(); case lex::token_kind::real: return parse_real(); + case lex::token_kind::ratio: + return parse_ratio(); case lex::token_kind::string: return parse_string(); case lex::token_kind::escaped_string: @@ -1166,6 +1168,21 @@ namespace jank::read::parse token }; } + processor::object_result processor::parse_ratio() + { + auto const token(token_current->expect_ok()); + ++token_current; + auto const &ratio_data(boost::get(token.data)); + if(ratio_data.denominator == 0) + { + return err(error{ token.pos, "Divide by zero" }); + } + return object_source_info{ make_box(static_cast(ratio_data.numerator) + / ratio_data.denominator), + token, + token }; + } + processor::object_result processor::parse_real() { auto const token(token_current->expect_ok()); diff --git a/compiler+runtime/test/cpp/jank/read/lex.cpp b/compiler+runtime/test/cpp/jank/read/lex.cpp index bf4e8fcf5..432229873 100644 --- a/compiler+runtime/test/cpp/jank/read/lex.cpp +++ b/compiler+runtime/test/cpp/jank/read/lex.cpp @@ -374,7 +374,70 @@ namespace jank::read::lex })); } } - + TEST_CASE("Ratio") + { + SUBCASE("Successes") + { + processor p{ "4/5" }; + native_vector> tokens(p.begin(), p.end()); + CHECK(tokens + == make_tokens({ + { 0, 3, token_kind::ratio, { .numerator = 4, .denominator = 5 } } + })); + processor p2{ "-4/5" }; + native_vector> tokens2(p2.begin(), p2.end()); + CHECK(tokens2 + == make_tokens({ + { 0, 4, token_kind::ratio, { .numerator = -4, .denominator = 5 } } + })); + processor p3{ "-4/-5" }; + native_vector> tokens3(p3.begin(), p3.end()); + CHECK(tokens3 + == make_tokens({ + { 0, 5, token_kind::ratio, { .numerator = -4, .denominator = -5 } } + })); + } + SUBCASE("Failures - x//x") + { + processor p{ "4//5" }; + native_vector> tokens(p.begin(), p.end()); + CHECK( + tokens + == make_results({ { error(0, 4, "invalid ratio: expecting an integer denominator") } })); + } + SUBCASE("Failures - x/x/x") + { + processor p{ "4/5/4" }; + native_vector> tokens(p.begin(), p.end()); + CHECK(tokens + == make_results({ { error(0, 3, "invalid ratio: expecting an integer denominator") }, + { error(3, 3, "invalid symbol") } })); + } + SUBCASE("Failures - x/x/x/x") + { + processor p{ "4/5/4/5/6/7/7" }; + native_vector> tokens(p.begin(), p.end()); + CHECK(tokens + == make_results({ { error(0, 3, "invalid ratio: expecting an integer denominator") }, + { error(3, 3, "invalid symbol") } })); + } + SUBCASE("Failures - x.x/x") + { + processor p{ "4.4/5" }; + native_vector> tokens(p.begin(), p.end()); + CHECK( + tokens + == make_results({ { error(0, 3, "invalid ratio") }, { error(3, 3, "invalid symbol") } })); + } + SUBCASE("Failures - x/x.x") + { + processor p{ "4/5.9" }; + native_vector> tokens(p.begin(), p.end()); + CHECK( + tokens + == make_results({ { error(0, 5, "invalid ratio: expecting an integer denominator") } })); + } + } TEST_CASE("Integer") { SUBCASE("Positive single-char") @@ -589,12 +652,12 @@ namespace jank::read::lex native_vector> tokens(p.begin(), p.end()); CHECK(tokens == make_results({ - token{ 0, 3, token_kind::real, 1000.0l }, - token{ 4, 4, token_kind::real, -100.0l }, - token{ 9, 5, token_kind::real, 0.002l }, - token{ 15, 7, token_kind::real, 2.23e-07l }, - token{ 23, 7, token_kind::real, -1.2e+19l }, - token{ 30, 2, token_kind::character, "\\a"sv }, + token{ 0, 3, token_kind::real, 1000.0l }, + token{ 4, 4, token_kind::real, -100.0l }, + token{ 9, 5, token_kind::real, 0.002l }, + token{ 15, 7, token_kind::real, 2.23e-07l }, + token{ 23, 7, token_kind::real, -1.2e+19l }, + token{ 30, 2, token_kind::character, "\\a"sv }, })); } diff --git a/compiler+runtime/test/cpp/jank/read/parse.cpp b/compiler+runtime/test/cpp/jank/read/parse.cpp index a472d36d7..be378bf92 100644 --- a/compiler+runtime/test/cpp/jank/read/parse.cpp +++ b/compiler+runtime/test/cpp/jank/read/parse.cpp @@ -63,6 +63,32 @@ namespace jank::read::parse CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); } + TEST_CASE("Ratio") + { + SUBCASE("Single Ratio") + { + lex::processor lp{ "4/5" }; + processor p{ lp.begin(), lp.end() }; + auto const r(p.next()); + CHECK(equal(r.expect_ok().unwrap().ptr, make_box(0.8))); + CHECK(r.expect_ok().unwrap().start + == lex::token{ + 0, + 3, + lex::token_kind::ratio, + { .numerator = 4, .denominator = 5 } + }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + } + SUBCASE("Division by zero") + { + lex::processor lp{ "1/0" }; + processor p{ lp.begin(), lp.end() }; + auto const r(p.next()); + CHECK(r.is_err()); + } + } + TEST_CASE("Comments") { lex::processor lp{ ";meow \n1234 ; bar\n;\n\n" };