From 7fc53ab11410c91a0e17f5c7087fd6cc02565fce Mon Sep 17 00:00:00 2001 From: Alberto Restifo Date: Fri, 8 May 2020 11:40:18 +0200 Subject: [PATCH] Add support for BigDecimal and BigInt --- .vscode/settings.json | 3 +++ README.md | 2 +- spec/cbor/from_cbor_spec.cr | 3 +++ src/cbor/from_cbor.cr | 49 ++++++++++++++++++++++++++++++++----- 4 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f5df97a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "crystal-lang.mainFile": "${workspaceRoot}/src/cbor.cr" +} \ No newline at end of file diff --git a/README.md b/README.md index abfb633..77efb91 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ and the values are encoded in the respective Crystal types: - `Time` - `BigInt` -- `BigFloat` +- `BigDecimal` ## Development diff --git a/spec/cbor/from_cbor_spec.cr b/spec/cbor/from_cbor_spec.cr index ba31c6b..b9b4629 100644 --- a/spec/cbor/from_cbor_spec.cr +++ b/spec/cbor/from_cbor_spec.cr @@ -40,6 +40,9 @@ describe "CBOR helpers on basic types" do {Tuple(Int8, Int8), Bytes[0x82, 0x01, 0x02], {1_i8, 2_i8}}, {NamedTuple(a: UInt8, b: Array(UInt8)), Bytes[0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03], {a: 1_u8, b: [2_u8, 3_u8]}}, {BigInt, Bytes[0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], BigInt.new("18446744073709551616")}, + {BigInt, Bytes[0x18, 0x18], BigInt.new(24)}, # BigInt from int + {BigDecimal, Bytes[0xc4, 0x82, 0x21, 0x19, 0x6a, 0xb3], BigDecimal.new(273.15)}, + {BigDecimal, Bytes[0xc5, 0x82, 0x20, 0x03], BigDecimal.new(1.5)}, ] tests.each do |tt| diff --git a/src/cbor/from_cbor.cr b/src/cbor/from_cbor.cr index 8b0a53a..439d440 100644 --- a/src/cbor/from_cbor.cr +++ b/src/cbor/from_cbor.cr @@ -149,12 +149,20 @@ def Time.new(decoder : CBOR::Decoder) end end -# Reads the CBOR value as a BigInt. The value must be surrounded by a tag with -# value 2 (positive) or 3 (negative). +# Reads the CBOR value as a BigInt. +# If the next token is an integer, then the integer will be transformed as a +# BigInt, otherwhise the value must be surrounded by a tag with value 2 +# (positive) or 3 (negative). def BigInt.new(decoder : CBOR::Decoder) - case tag = decoder.read_tag - when CBOR::Tag::PositiveBigNum, - CBOR::Tag::NegativeBigNum + case token = decoder.current_token + when CBOR::Token::TagT + decoder.finish_token! + + tag = token.value + unless tag == CBOR::Tag::PositiveBigNum || tag == CBOR::Tag::NegativeBigNum + raise CBOR::ParseError.new("Expected tag to have value 2 or 3, got #{tag.value}") + end + big = new(decoder.read_bytes.hexstring, 16) if tag == CBOR::Tag::NegativeBigNum @@ -163,8 +171,37 @@ def BigInt.new(decoder : CBOR::Decoder) end big + when CBOR::Token::IntT + decoder.finish_token! + new(token.value) else - raise CBOR::ParseError.new("Expected tag to have value 2 or 3, got #{tag.value}") + decoder.unexpected_token(token, "IntT or TagT") + end +end + +# Reads the CBOR value as a BigDecimal. +# If the next token is a flaot, then it'll be transformed to a BigDecimal, +# otherwhise the value must be correctly tagged with value 4 (decimal fraction) +# or 5 (big float). +def BigDecimal.new(decoder : CBOR::Decoder) + case token = decoder.current_token + when CBOR::Token::TagT + decoder.finish_token! + + tag = token.value + unless tag == CBOR::Tag::Decimal || tag == CBOR::Tag::BigFloat + raise CBOR::ParseError.new("Expected tag to have value 4 or 5, got #{tag.value}") + end + + exponent, matissa = Tuple(Int128, BigInt).new(decoder) + + base = tag == CBOR::Tag::Decimal ? 10.0 : 2.0 + e = base**exponent + new(matissa) * new(e) + when CBOR::Token::FloatT + new(token.value) + else + decoder.unexpected_token(token, "FloatT or TagT") end end