Skip to content

Commit

Permalink
Add support for ratio
Browse files Browse the repository at this point in the history
Currently, ratio (of two integers) will be converted to a real number during parsing.
  • Loading branch information
jianlingzhong committed Oct 21, 2024
1 parent 15d6b06 commit 7eb23f6
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 9 deletions.
21 changes: 20 additions & 1 deletion compiler+runtime/include/cpp/jank/read/lex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,23 @@ namespace jank::read::lex
integer,
/* Has double data. */
real,
/* Has two integer data. */
ratio,
/* Has string data. */
string,
/* Has string data. */
escaped_string,
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;
Expand All @@ -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;
Expand All @@ -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<no_data, native_integer, native_real, native_persistent_string_view, native_bool>
boost::variant<no_data,
native_integer,
native_real,
native_persistent_string_view,
native_bool,
ratio>
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
Expand Down Expand Up @@ -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;
};
}
1 change: 1 addition & 0 deletions compiler+runtime/include/cpp/jank/read/parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ namespace jank::runtime
{
return make_box<runtime::obj::integer>(static_cast<native_integer>(i));
}

[[gnu::always_inline, gnu::flatten, gnu::hot]]
inline auto make_box(native_integer const i)
{
Expand Down
50 changes: 50 additions & 0 deletions compiler+runtime/src/cpp/jank/read/lex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -186,6 +204,11 @@ namespace jank::read
return os << "<no data>";
}

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 }
{
Expand Down Expand Up @@ -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<native_integer>(denominator_token.data) }));
}
return err(
error{ token_start, pos, "invalid ratio: expecting an integer denominator" });
}
else if(std::isdigit(c) == 0)
{
if(expecting_exponent)
Expand Down
17 changes: 17 additions & 0 deletions compiler+runtime/src/cpp/jank/read/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<lex::ratio>(token.data));
if(ratio_data.denominator == 0)
{
return err(error{ token.pos, "Divide by zero" });
}
return object_source_info{ make_box<obj::real>(static_cast<native_real>(ratio_data.numerator)
/ ratio_data.denominator),
token,
token };
}

processor::object_result processor::parse_real()
{
auto const token(token_current->expect_ok());
Expand Down
77 changes: 70 additions & 7 deletions compiler+runtime/test/cpp/jank/read/lex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,70 @@ namespace jank::read::lex
}));
}
}

TEST_CASE("Ratio")
{
SUBCASE("Successes")
{
processor p{ "4/5" };
native_vector<result<token, error>> tokens(p.begin(), p.end());
CHECK(tokens
== make_tokens({
{ 0, 3, token_kind::ratio, { .numerator = 4, .denominator = 5 } }
}));
processor p2{ "-4/5" };
native_vector<result<token, error>> tokens2(p2.begin(), p2.end());
CHECK(tokens2
== make_tokens({
{ 0, 4, token_kind::ratio, { .numerator = -4, .denominator = 5 } }
}));
processor p3{ "-4/-5" };
native_vector<result<token, error>> 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<result<token, error>> 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<result<token, error>> 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<result<token, error>> 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<result<token, error>> 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<result<token, error>> 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")
Expand Down Expand Up @@ -589,12 +652,12 @@ namespace jank::read::lex
native_vector<result<token, error>> 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 },
}));
}

Expand Down
26 changes: 26 additions & 0 deletions compiler+runtime/test/cpp/jank/read/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" };
Expand Down

0 comments on commit 7eb23f6

Please sign in to comment.