diff --git a/spec/cbor/from_cbor_spec.cr b/spec/cbor/from_cbor_spec.cr index ffd4d49..e618363 100644 --- a/spec/cbor/from_cbor_spec.cr +++ b/spec/cbor/from_cbor_spec.cr @@ -21,6 +21,14 @@ describe "CBOR helpers on basic types" do {Nil, Bytes[0xf7], nil}, {Float32, Bytes[0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a], 1.1_f32}, {Float64, Bytes[0xfa, 0x47, 0xc3, 0x50, 0x00], 100000.0_f64}, + {Array(Int8), Bytes[0x83, 0x01, 0x02, 0x03], [1_i8, 2_i8, 3_i8]}, + {Array(Array(Int8) | Int8), + Bytes[0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05], + [1_i8, [2_i8, 3_i8], [4_i8, 5_i8]]}, + {Array(UInt8), Bytes[0x9f, 0xff], [] of UInt8}, + # {Array(Array(Int8) | Int8), + # Bytes[0x9f, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff, 0xff], + # [1_i8, [2_i8, 3_i8], [4_i8, 5_i8]]}, ] tests.each do |tt| diff --git a/src/cbor/decoder.cr b/src/cbor/decoder.cr index 0f295ce..7aff98a 100644 --- a/src/cbor/decoder.cr +++ b/src/cbor/decoder.cr @@ -1,6 +1,6 @@ class CBOR::Decoder @lexer : Lexer - @current_token : Token::T? + getter current_token : Token::T? def initialize(input) @lexer = Lexer.new(input) @@ -70,10 +70,27 @@ class CBOR::Decoder end end + def consume_array(&block) + read_type(Token::ArrayT, finish_token: false) do |token| + read(token.size) { yield } + end + end + private def finish_token! @current_token = @lexer.next_token end + def read(size : Int32?, &block) + if size + size.times { yield } + else + @lexer.until_break do |token| + @current_token = token + yield + end + end + end + private macro read_type(type, finish_token = true, ignore_tag = true, &block) # Skip the tag unless the token we want to read is a tag {% if ignore_tag %} diff --git a/src/cbor/from_cbor.cr b/src/cbor/from_cbor.cr index 6855e41..a8e6cf7 100644 --- a/src/cbor/from_cbor.cr +++ b/src/cbor/from_cbor.cr @@ -39,6 +39,12 @@ def Slice.new(decoder : CBOR::Decoder) decoder.read_bytes.to_slice end +def Array.new(decoder : CBOR::Decoder) + arr = new + decoder.consume_array { arr << T.new(decoder) } + arr +end + # Reads the CBOR values as a time. The value must be surrounded by a time tag as # specified by [Section 2.4.1 of RFC 7049][1]. # @@ -58,3 +64,52 @@ def Time.new(decoder : CBOR::Decoder) raise CBOR::ParseError.new("Expected tag to have value 0 or 1, got #{tag.value.to_s}") end end + +def Union.new(decoder : CBOR::Decoder) + token = decoder.current_token + + # Optimization: use fast path for primitive types + {% begin %} + # Here we store types that are not primitive types + {% non_primitives = [] of Nil %} + + {% for type, index in T %} + {% if type == Nil %} + return decoder.read_nil if token.is_a?(CBOR::Token::SimpleValueT) + {% elsif type == Bool %} + return decoder.read_bool if token.is_a?(CBOR::Token::SimpleValueT) + {% elsif type == String %} + return decoder.read_string if token.is_a?(CBOR::Token::StringT) + {% elsif type == Int8 || type == Int16 || type == Int32 || type == Int64 || + type == UInt8 || type == UInt16 || type == UInt32 || type == UInt64 %} + return {{type}}.new(decoder) if token.is_a?(CBOR::Token::IntT) + {% elsif type == Float32 || type == Float64 %} + return {{type}}.new(decoder) if token.is_a?(CBOR::Token::FloatT) + {% unless T.any? { |t| t < Int } %} + return {{type}}.new(decoder) if token.is_a?(CBOR::Token::IntT) + {% end %} + {% else %} + {% non_primitives << type %} + {% end %} + {% end %} + + # If after traversing all the types we are left with just one + # non-primitive type, we can parse it directly (no need to use `read_raw`) + {% if non_primitives.size == 1 %} + return {{non_primitives[0]}}.new(decoder) + {% else %} + raise "What is this?" + # node = decoder.read_node + # {% for type in non_primitives %} + # unpacker = CBOR::NodeUnpacker.new(node) + # begin + # return {{type}}.new(unpacker) + # rescue e : CBOR::TypeCastError + # # ignore + # end + # {% end %} + # {% end %} + {% end %} + + raise CBOR::ParseError.new("Couldn't parse data as " + {{T.stringify}}) +end