Skip to content

Commit

Permalink
Support 128-bit integers in JSON::PullParser#read? (crystal-lang#13837
Browse files Browse the repository at this point in the history
)
  • Loading branch information
HertzDevil authored and Blacksmoke16 committed Dec 11, 2023
1 parent 7667ee1 commit bbd0141
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 79 deletions.
77 changes: 23 additions & 54 deletions spec/std/json/pull_parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -343,15 +343,17 @@ describe JSON::PullParser do
assert_raw %({"foo":"bar"})
end

describe "read?" do
describe "#read?" do
{% for pair in [[Int8, 1_i8],
[Int16, 1_i16],
[Int32, 1_i32],
[Int64, 1_i64],
[Int128, "Int128.new(1)".id],
[UInt8, 1_u8],
[UInt16, 1_u16],
[UInt32, 1_u32],
[UInt64, 1_u64],
[UInt128, "UInt128.new(1)".id],
[Float32, 1.0_f32],
[Float64, 1.0],
[String, "foo"],
Expand All @@ -370,33 +372,27 @@ describe JSON::PullParser do
end
{% end %}

it_reads Float32::MIN
it_reads -10_f32
it_reads 0_f32
it_reads 10_f32
it_reads Float32::MAX

it_reads Float64::MIN
it_reads -10_f64
it_reads 0_f64
it_reads 10_f64
it_reads Float64::MAX

it_reads Int64::MIN
it_reads -10_i64
it_reads 0_i64
it_reads 10_i64
it_reads Int64::MAX

it "reads > Int64::MAX" do
pull = JSON::PullParser.new(Int64::MAX.to_s + "0")
pull.read?(Int64).should be_nil
end
{% for num in Int::Primitive.union_types %}
it_reads {{ num }}::MIN
{% unless num < Int::Unsigned %}
it_reads {{ num }}.new(-10)
it_reads {{ num }}.zero
{% end %}
it_reads {{ num }}.new(10)
it_reads {{ num }}::MAX
{% end %}

it "reads < Int64::MIN" do
pull = JSON::PullParser.new(Int64::MIN.to_s + "0")
pull.read?(Int64).should be_nil
end
{% for i in [8, 16, 32, 64, 128] %}
it "returns nil in place of Int{{i}} when an overflow occurs" do
JSON::PullParser.new(Int{{i}}::MAX.to_s + "0").read?(Int{{i}}).should be_nil
JSON::PullParser.new(Int{{i}}::MIN.to_s + "0").read?(Int{{i}}).should be_nil
end

it "returns nil in place of UInt{{i}} when an overflow occurs" do
JSON::PullParser.new(UInt{{i}}::MAX.to_s + "0").read?(UInt{{i}}).should be_nil
JSON::PullParser.new("-1").read?(UInt{{i}}).should be_nil
end
{% end %}

it "reads > Float32::MAX" do
pull = JSON::PullParser.new(Float64::MAX.to_s)
Expand All @@ -408,16 +404,6 @@ describe JSON::PullParser do
pull.read?(Float32).should be_nil
end

it "reads > UInt64::MAX" do
pull = JSON::PullParser.new(UInt64::MAX.to_s + "0")
pull.read?(UInt64).should be_nil
end

it "reads == UInt64::MAX" do
pull = JSON::PullParser.new(UInt64::MAX.to_s)
pull.read?(UInt64).should eq(UInt64::MAX)
end

it "reads > Float64::MAX" do
pull = JSON::PullParser.new("1" + Float64::MAX.to_s)
pull.read?(Float64).should be_nil
Expand All @@ -428,23 +414,6 @@ describe JSON::PullParser do
pull.read?(Float64).should be_nil
end

{% for pair in [[Int8, Int64::MAX],
[Int16, Int64::MAX],
[Int32, Int64::MAX],
[UInt8, -1],
[UInt16, -1],
[UInt32, -1],
[UInt64, -1],
[Float32, Float64::MAX]] %}
{% type = pair[0] %}
{% value = pair[1] %}

it "returns nil in place of {{type}} when an overflow occurs" do
pull = JSON::PullParser.new({{value}}.to_json)
pull.read?({{type}}).should be_nil
end
{% end %}

it "doesn't accept nan or infinity" do
pull = JSON::PullParser.new(%("nan"))
pull.read?(Float64).should be_nil
Expand Down
10 changes: 5 additions & 5 deletions spec/std/json/serialization_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,11 @@ describe "JSON serialization" do
Union(Bool, Array(Int32)).from_json(%(true)).should be_true
end

{% for type in %w(Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64).map(&.id) %}
it "deserializes union with {{type}} (fast path)" do
Union({{type}}, Array(Int32)).from_json(%(#{ {{type}}::MAX })).should eq({{type}}::MAX)
end
{% end %}
{% for type in Int::Primitive.union_types %}
it "deserializes union with {{type}} (fast path)" do
Union({{type}}, Array(Int32)).from_json({{type}}::MAX.to_s).should eq({{type}}::MAX)
end
{% end %}

it "deserializes union with Float32 (fast path)" do
Union(Float32, Array(Int32)).from_json(%(1)).should eq(1)
Expand Down
3 changes: 2 additions & 1 deletion src/json/from_json.cr
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ end
"UInt128" => "u128",
} %}
def {{type.id}}.new(pull : JSON::PullParser)
# TODO: use `PullParser#read?` instead
location = pull.location
value =
{% if type == "UInt64" || type == "UInt128" || type == "Int128" %}
Expand Down Expand Up @@ -401,7 +402,7 @@ def Union.new(pull : JSON::PullParser)
return pull.read_string
{% end %}
when .int?
{% type_order = [Int64, UInt64, Int32, UInt32, Int16, UInt16, Int8, UInt8, Float64, Float32] %}
{% type_order = [Int128, UInt128, Int64, UInt64, Int32, UInt32, Int16, UInt16, Int8, UInt8, Float64, Float32] %}
{% for type in type_order.select { |t| T.includes? t } %}
value = pull.read?({{type}})
return value unless value.nil?
Expand Down
38 changes: 19 additions & 19 deletions src/json/pull_parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -418,27 +418,27 @@ class JSON::PullParser
read_bool if kind.bool?
end

{% for type in [Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32] %}
# Reads an {{type}} value and returns it.
#
# If the value is not an integer or does not fit in a {{type}} variable, it returns `nil`.
def read?(klass : {{type}}.class)
{{type}}.new(int_value).tap { read_next } if kind.int?
rescue JSON::ParseException | OverflowError
nil
end
{% begin %}
# types that don't fit into `Int64` (integer type for `JSON::Any`)'s range
{% large_ints = [UInt64, Int128, UInt128] %}

{% for int in Int::Primitive.union_types %}
{% is_large_int = large_ints.includes?(int) %}

# Reads an `{{int}}` value and returns it.
#
# If the value is not an integer or does not fit in an `{{int}}`, it
# returns `nil`.
def read?(klass : {{int}}.class) : {{int}}?
if kind.int?
{{int}}.new({{ is_large_int ? "raw_value".id : "int_value".id }}).tap { read_next }
end
rescue JSON::ParseException | {{ is_large_int ? ArgumentError : OverflowError }}
nil
end
{% end %}
{% end %}

# Reads an `Int64` value and returns it.
#
# If the value is not an integer or does not fin in an `Int64` variable, it returns `nil`.
def read?(klass : UInt64.class) : UInt64?
# UInt64 is a special case due to exceeding bounds of @int_value
UInt64.new(raw_value).tap { read_next } if kind.int?
rescue JSON::ParseException | ArgumentError
nil
end

# Reads an `Float32` value and returns it.
#
# If the value is not an integer or does not fit in an `Float32`, it returns `nil`.
Expand Down

0 comments on commit bbd0141

Please sign in to comment.