crystal-cbor/src/cbor/decoder.cr

177 lines
3.7 KiB
Crystal

class CBOR::Decoder
@lexer : Lexer
getter current_token : Token::T?
def reset
@lexer.reset
@current_token = @lexer.next_token
end
def initialize(input)
@lexer = Lexer.new(input)
@current_token = @lexer.next_token
end
def read_value : Type
case token = @current_token
when Token::TagT
finish_token!
read_value
when Token::StringT
finish_token!
token.value
when Token::IntT
finish_token!
token.value
when Token::FloatT
finish_token!
token.value
when Token::BytesT
finish_token!
token.value
when Token::SimpleValueT
token.value.to_t
when Token::ArrayT
finish_token!
arr = Array(Type).new
consume_sequence(token.size) { arr << read_value }
arr
when Token::MapT
finish_token!
map = Hash(Type, Type).new
consume_sequence(token.size) { map[read_value] = read_value }
map
else
unexpected_token(token)
end
end
def read_string : String
case token = @current_token
when Token::StringT
finish_token!
token.value
when Token::BytesT
finish_token!
String.new(token.value)
else
unexpected_token(token, "StringT or BytesT")
end
end
def read_int
read_type(Token::IntT) { |token| token.value }
end
def read_float
read_type(Token::FloatT) { |token| token.value }
end
def read_num
case token = @current_token
when Token::IntT, Token::FloatT
token.value
else
unexpected_token(token, "Token::IntT or Token::FloatT")
end
end
def read_bytes
read_type(Token::BytesT) { |token| token.value }
end
def read_tag : Tag
read_type(Token::TagT, ignore_tag: false) { |token| token.value }
end
def read_bool : Bool
read_type(Token::SimpleValueT) do |token|
case token.value
when SimpleValue::False
false
when SimpleValue::True
true
else
unexpected_token(token, "SimpleValue::True or SimpleValue::False")
end
end
end
def read_nil : Nil
read_type(Token::SimpleValueT) do |token|
return nil if token.value.is_nil?
unexpected_token(token, "SimpleValue::Null or SimpleValue::Undefined")
end
end
def read_nil_or
token = @current_token
if token.is_a?(Token::SimpleValueT) && token.value.is_nil?
finish_token!
nil
else
yield
end
end
def consume_array(&block)
read_type(Token::ArrayT) do |token|
consume_sequence(token.size) { yield }
end
end
def read_begin_hash
read_type(Token::MapT, finish_token: false) { |token| }
end
def consume_hash(&block)
read_type(Token::MapT) do |token|
consume_sequence(token.size) { yield }
end
end
def finish_token!
@current_token = @lexer.next_token
end
private def consume_sequence(size : Int32?, &block)
if size
size.times { yield }
else
until @current_token.is_a?(Token::BreakT)
yield
end
end
end
private macro read_type(type, finish_token = true, ignore_tag = true, &block)
begin
# Skip the tag unless the token we want to read is a tag
{% if ignore_tag %}
if @current_token.is_a?(Token::TagT)
finish_token!
end
{% end %}
case token = @current_token
when {{type}}
{% if finish_token %}
finish_token!
{% end %}
{{ block.body }}
else
unexpected_token(token, {{type.stringify.split("::").last}})
end
rescue err
raise CBOR::ParseError.new("{{type}} -> #{err}")
end
end
def unexpected_token(token, expected = nil)
message = "Unexpected token #{token.class}"
message += " expected #{expected}" if expected
raise ParseError.new(message)
end
end