crystal-cbor/src/cbor/lexer.cr

208 lines
5.0 KiB
Crystal

class CBOR::Lexer
def self.new(slice : Bytes)
new IO::Memory.new(slice)
end
@eof : Bool = false
def initialize(@io : IO)
end
def reset
@io.seek 0
@eof = false
end
def next_token : Token::T?
return nil if @eof
byte = next_byte
return nil unless byte
decode(byte)
end
# Read the next pair of tokens, useful for maps.
# Raises an exception if there are no two pairs left.
def next_pair : Tuple(Token::T, Token::T)
pairs = Array(Token::T).new(2) do
token = next_token
raise ParseError.new("Unexpected EOF while reading next pair") unless token
token
end
Tuple.new(pairs[0], pairs[1])
end
private def decode(byte : UInt8) : Token::T
case byte
when 0x00..0x1b
consume_int(read_size(byte))
when 0x20..0x3b
consume_int(to_negative_int(read_size(byte - 0x20)))
when 0x40..0x5b
consume_binary(read_size(byte - 0x40))
when 0x5f
read_bytes_array
when 0x60..0x7b
consume_string(read_size(byte - 0x60))
when 0x7f
read_string_array
when 0x80..0x9b
array_start(read_size(byte - 0x80))
when 0x9f
Token::ArrayT.new
when 0xa0..0xbb
map_start(read_size(byte - 0xa0))
when 0xbf
Token::MapT.new
when 0xc0..0xdb
consume_tag(read_size(byte - 0xc0))
when 0xe0..0xf8
consume_simple_value(read_size(byte - 0xe0))
when 0xf9
Token::FloatT.new(value: Float32.new(read(UInt16)))
when 0xfa
Token::FloatT.new(value: read(Float32))
when 0xfb
Token::FloatT.new(value: read(Float64))
when 0xff
Token::BreakT.new
else
raise ParseError.new("Unexpected byte 0x#{byte.to_s(16)}")
end
end
private def read_bytes_array : Token::BytesT
bytes = BytesArray.new
chunks = Array(Int32).new
until_break do |token|
unless token.is_a?(Token::BytesT)
raise ParseError.new("Invalid token #{token.class} while parsing a bytes array")
end
chunks << token.value.size
bytes << token.value
end
Token::BytesT.new(value: bytes.to_bytes, chunks: chunks)
end
private def read_string_array : Token::StringT
value = ""
chunks = Array(Int32).new
until_break do |token|
unless token.is_a?(Token::StringT)
raise ParseError.new("Invalid token #{token.class} while parsing a string array")
end
chunks << token.value.size
value += token.value
end
Token::StringT.new(value: value, chunks: chunks)
end
private def until_break(&block : Token::T ->)
loop do
token = next_token
raise ParseError.new("Unexpected EOF while searching for 0xff (break)") unless token
break if token.is_a?(Token::BreakT)
yield token
end
end
# Reads the size for the next token type
private def read_size(current_byte : UInt8) : Int
case current_byte
when 0x00..0x17
current_byte
when 0x18
read(UInt8)
when 0x19
read(UInt16)
when 0x1a
read(UInt32)
when 0x1b
read(UInt64)
else
raise ParseError.new("Unexpected byte 0x#{current_byte.to_s(16)} while reading size")
end
end
private def next_byte : UInt8?
byte = @io.read_byte
return byte if byte
@eof = true
nil
end
private def consume_int(value)
Token::IntT.new(value: value)
end
private def consume_binary(size : Int)
bytes = read_bytes(size)
Token::BytesT.new(value: bytes)
end
private def consume_string(size)
Token::StringT.new(value: @io.read_string(size))
end
private def array_start(size)
raise ParseError.new("Maximum size for array exeeded") if size > Int32::MAX
Token::ArrayT.new(size: size.to_i32)
end
private def map_start(size)
raise ParseError.new("Maximum size for array exeeded") if size > Int32::MAX
Token::MapT.new(size: size.to_i32)
end
private def consume_tag(id) : Token::TagT
raise ParseError.new("Maximum size for tag ID exceeded") if id > UInt32::MAX
Token::TagT.new(value: Tag.new(id.to_u32))
end
private def consume_simple_value(id) : Token::SimpleValueT
raise ParseError.new("Invalid simple value #{id}") if id > 255
Token::SimpleValueT.new(value: SimpleValue.new(id.to_u8))
end
# Creates a method overloaded for each UInt sizes to convert the UInt into
# the respective Int capable of containing the value
{% begin %}
{% uints = %w(UInt8 UInt16 UInt32 UInt64) %}
{% conv = %w(to_i8 to_i16 to_i32 to_i64 to_i128) %}
{% for uint, index in uints %}
# Reads the `{{uint.id}}` as a negative integer, returning the smallest
# integer capable of containing the value.
def to_negative_int(value : {{uint.id}})
int = begin
-value.{{conv[index].id}}
rescue OverflowError
-value.{{conv[index + 1].id}}
end
int - 1
end
{% end %}
{% end %}
private def read_bytes(size)
bytes = Bytes.new(size)
@io.read_fully(bytes)
bytes
end
private def read(type : T.class) forall T
@io.read_bytes(T, IO::ByteFormat::NetworkEndian)
end
end