Skip to content

Commit

Permalink
Implement ratio parsing returning a ruby Rational.
Browse files Browse the repository at this point in the history
  • Loading branch information
edporras committed May 20, 2019
1 parent 5e49955 commit 93081e8
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 221 deletions.
564 changes: 354 additions & 210 deletions ext/edn_turbo/edn_parser.cc

Large diffs are not rendered by default.

50 changes: 45 additions & 5 deletions ext/edn_turbo/edn_parser.rl
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,13 @@
// try to parse a decimal first
const char *np = parse_decimal(fpc, pe, v);
if (np == nullptr) {
// if we can't, try to parse it as an int
np = parse_integer(fpc, pe, v);
// if we can't, try to parse it as a ratio
np = parse_ratio(fpc, pe, v);

// otherwise, an int
if (np == nullptr) {
np = parse_integer(fpc, pe, v);
}
}

if (np) {
Expand Down Expand Up @@ -319,7 +324,6 @@ const char* edn::Parser::parse_keyword(const char *p, const char *pe, VALUE& v)
}



// ============================================================
// decimal parsing machine
//
Expand Down Expand Up @@ -386,6 +390,37 @@ const char* edn::Parser::parse_integer(const char *p, const char *pe, VALUE& v)
}


// ============================================================
// ratio parsing machine
//
%%{
machine EDN_ratio;
include EDN_common;

write data noerror;


main := (
('-'|'+')? (integer '/' integer)
) (^[0-9+\-\/]? @exit);
}%%


const char* edn::Parser::parse_ratio(const char *p, const char *pe, VALUE& v)
{
int cs;

%% write init;
const char* p_save = p;
%% write exec;

if (cs >= EDN_ratio_first_final) {
v = edn::util::ratio_to_ruby(p_save, p - p_save);
return p + 1;
}
else if (cs == EDN_ratio_en_main) {} // silence ragel warning
return nullptr;
}

// ============================================================
// operator parsing - handles tokens w/ a leading operator:
Expand Down Expand Up @@ -419,8 +454,13 @@ const char* edn::Parser::parse_integer(const char *p, const char *pe, VALUE& v)
// try to parse a decimal first
const char *np = parse_decimal(p_save, pe, v);
if (np == nullptr) {
// if we can't, try to parse it as an int
np = parse_integer(p_save, pe, v);
// if we can't, try to parse it as a ratio
np = parse_ratio(p_save, pe, v);

if (np == nullptr) {
// again, if we can't, try to parse it as an int
np = parse_integer(p_save, pe, v);
}
}

if (np) {
Expand Down
4 changes: 3 additions & 1 deletion ext/edn_turbo/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace edn {
VALUE EDN_MAKE_LIST_METHOD = Qnil;
VALUE EDN_MAKE_SET_METHOD = Qnil;
VALUE EDN_MAKE_BIG_DECIMAL_METHOD = Qnil;
VALUE EDN_MAKE_RATIONAL_METHOD = Qnil;
VALUE EDN_TAGGED_ELEM_METHOD = Qnil;
VALUE EDNT_EXTENDED_VALUE_METHOD = Qnil;

Expand Down Expand Up @@ -209,7 +210,7 @@ void Init_edn_turbo(void)
rb_define_method(rb_cParser, "read",
reinterpret_cast<VALUE(*)(ANYARGS)>(&edn::next), 0 );

// bind ruby methods we'll call - these should be defined in edn_turbo.rb
// bind edn-ruby methods we'll call
edn::EDN_MAKE_SYMBOL_METHOD = rb_intern("symbol");
edn::EDN_MAKE_LIST_METHOD = rb_intern("list");
edn::EDN_MAKE_SET_METHOD = rb_intern("set");
Expand All @@ -218,6 +219,7 @@ void Init_edn_turbo(void)

// defined in EDNT - see edn_parser.rb
edn::EDNT_EXTENDED_VALUE_METHOD = rb_intern("extend_for_meta");
edn::EDN_MAKE_RATIONAL_METHOD = rb_intern("rational"); // should be in edn-ruby

// ruby methods
edn::RUBY_STRING_TO_I_METHOD = rb_intern("to_i");
Expand Down
1 change: 1 addition & 0 deletions ext/edn_turbo/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ namespace edn
const char* parse_keyword (const char *p, const char *pe, VALUE& v);
const char* parse_decimal (const char *p, const char *pe, VALUE& v);
const char* parse_integer (const char *p, const char *pe, VALUE& v);
const char* parse_ratio (const char *p, const char *pe, VALUE& v);
const char* parse_operator(const char *p, const char *pe, VALUE& v);
const char* parse_esc_char(const char *p, const char *pe, VALUE& v);
const char* parse_symbol (const char *p, const char *pe, VALUE& v);
Expand Down
13 changes: 9 additions & 4 deletions ext/edn_turbo/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,11 @@ namespace edn
// as above.. TODO: check exponential..
VALUE float_to_ruby(const char* str, std::size_t len)
{
// if big decimal is needed, call into ruby side to get
// the correct value
// if big decimal is needed, call into ruby side to get the
// correct value
if (str[len-1] == 'M' || len >= LD_max_chars)
{
std::string buf(str, len);
VALUE vs = edn_prot_rb_new_str(buf.c_str());
VALUE vs = edn_prot_rb_new_str(str);

if (str[len-1] == 'M') {
return call_module_fn(rb_mEDN, EDN_MAKE_BIG_DECIMAL_METHOD, vs);
Expand All @@ -173,6 +172,12 @@ namespace edn
return rb_float_new(buftotype<double>(str, len));
}

//
// returns a ruby Rational
VALUE ratio_to_ruby(const char* str, std::size_t len)
{
return call_module_fn(rb_mEDN, EDN_MAKE_RATIONAL_METHOD, edn_prot_rb_new_str(str));
}

//
// read from a StringIO - handled from ruby side
Expand Down
2 changes: 2 additions & 0 deletions ext/edn_turbo/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace edn
extern VALUE EDN_MAKE_LIST_METHOD;
extern VALUE EDN_MAKE_SET_METHOD;
extern VALUE EDN_MAKE_BIG_DECIMAL_METHOD;
extern VALUE EDN_MAKE_RATIONAL_METHOD;
extern VALUE EDN_TAGGED_ELEM_METHOD;
extern VALUE EDN_EOF_CONST;

Expand All @@ -45,6 +46,7 @@ namespace edn
// defined in edn_parser_util.cc
VALUE integer_to_ruby(const char* str, std::size_t len);
VALUE float_to_ruby (const char* str, std::size_t len);
VALUE ratio_to_ruby (const char* str, std::size_t len);

VALUE ruby_io_read(VALUE io);

Expand Down
6 changes: 6 additions & 0 deletions lib/edn_turbo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@
# Replace the parser in the EDN module with the C based one.
module EDN
self.parser = EDNT::Parser

# makes a rational type for converting a clojure ratio
# - this should be in edn-ruby
def self.rational(value)
Rational(value)
end
end
14 changes: 13 additions & 1 deletion spec/edn_turbo/edn_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def fixture(*fixture_filename)
end
end

context 'numbers' do
context 'numeric' do
it 'zero' do
expect(subject.parse(' 0 ')).to eq(0)
end
Expand Down Expand Up @@ -187,6 +187,18 @@ def fixture(*fixture_filename)
# expect(val).to eq(4.54e+44)
# expect(val.class).to eq(Float)
# end
it 'ratio' do
val = subject.parse(' 2/3 ')
expect(val).to eq(Rational(2, 3))
end
it 'positive ratio' do
val = subject.parse(' +2/3 ')
expect(val).to eq(Rational(2, 3))
end
it 'negative ratio' do
val = subject.parse(' -2/3 ')
expect(val).to eq(Rational(-2, 3))
end
end

context 'operators' do
Expand Down

0 comments on commit 93081e8

Please sign in to comment.