Start lexer and tests

dev
Alberto Restifo 2020-04-19 00:18:54 +02:00
parent 3b7d42c242
commit 9d1c255c2c
3 changed files with 129 additions and 57 deletions

35
spec/cbor/lexer_spec.cr Normal file
View File

@ -0,0 +1,35 @@
require "../spec_helper"
describe CBOR::Lexer do
describe "examples from the RFC7049 Appendix A" do
tests : Array(Tuple(String, Bytes)) = [
{"0", [0x00]},
{"1", [0x01]},
# {"10", "0a"},
# {"23", "17"},
# {"24", "18 18"},
# {"25", "18 19"},
# {"100", "18 64"},
# {"1000", "19 03 e8"},
# {"1000000", "1a 00 0f 42 40"},
# {"1000000000000", "1b 00 00 00 e8 d4 a5 10 00"},
# {"18446744073709551615", "1b ff ff ff ff ff ff ff ff"},
# {"18446744073709551616", "c2 49 01 00 00 00 00 00 00 0000"},
# {"-18446744073709551616", "3b ff ff ff ff ff ff ff ff"},
# {"-18446744073709551617", "c3 49 01 00 00 00 00 00 00 00 00"},
# {"-1", "20"},
# {"-10", "29"},
# {"-100", "38 63"},
# {"-1000", "39 03 e7"},
]
tests.each do |tt|
it "Reads #{tt[1].inspect} as #{tt[0]}" do
lexer = CBOR::Lexer.new(tt[1])
token = lexer.read_token
CBOR::Token.to_s(token).should eq(tt[0])
end
end
end
end

View File

@ -1,67 +1,97 @@
require "./token"
class CBOR::Lexer
@current_byte : UInt8
def self.new(string : String)
new IO::Memory.new(string)
end
def self.new(slice : Bytes)
new IO::Memory.new(slice)
end
@token : Token::T
def initialize(@io : IO)
@current_byte = 0x0
@byte_number = 0
@current_byte_number = 0
@token = Token::NullT.new(0)
@token_finished = true
end
def next_token : Token?
byte = @io.read_byte
return nil if byte.nil?
@[AlwaysInline]
def current_token : Token::T
if @token_finished
@token_finished = false
@token = next_token
else
@token
end
end
@current_byte = byte
@[AlwaysInline]
def finish_token!
@token_finished = true
end
# See: RFC7049 Appedix B
case @current_byte
when .<= 0x17
Token.new(kind: Token::Kind::UInt, value: @curren_byte)
@[AlwaysInline]
def read_token : Token::T
if @token_finished
@token = next_token
else
finish_token!
end
@token
end
private def next_token
@current_byte_number = @byte_number
current_byte = next_byte
case current_byte
when 0x00..0x17
consume_int(current_byte)
when 0x18
read_uint8
consume_int(read(Uint8))
when 0x19
read_uint16
consume_int(read(Uint16))
when 0x1a
read_uint32
consume_int(read(Uint32))
when 0x1b
read_uint64
when .<= 0x37
Token.new(kind: Token::Kind::NInt, value: Int8(@curren_byte))
consume_int(read(Uint64))
when 0x20..0x37
consume_int(flip(current_byte.to_i8))
when 0x38
consume_int(flip(read(Uint8).to_i8))
when 0x39
consume_int(flip(read(Uint16).to_i16))
when 0x3a
consume_int(flip(read(Uint32).to_i32))
when 0x3b
consume_int(flip(read(Uint64).to_i64))
end
end
private def read_uint8
private def next_byte : Uint8
byte = @io.read_byte
return unexpect_eof if byte.nil?
Token.new(kind: Token::Kind::UInt, value: byte)
@byte_number += 1
fail unless byte
byte
end
private def read_uint16
value = UInt16.from_io(read_next(2), IO::ByteFormat::BigEndian)
Token.new(kind: Token::Kind::UInt, value: value)
private def consume_int(value)
Token::IntT.new(@current_byte_number, value)
end
private def read_uint32
value = UInt32.from_io(read_next(4), IO::ByteFormat::BigEndian)
Token.new(kind: Token::Kind::UInt, value: value)
private def flip(value)
-1 - value
end
private def read_uint64
value = UInt64.from_io(read_next(8), IO::ByteFormat::BigEndian)
Token.new(kind: Token::Kind::UInt, value: value)
private def read(type : T.class) forall T
@byte_number += sizeof(T)
@io.read_bytes(T, IO::ByteFormat::NetworkEndian)
end
private def read_next(n : Int)
slice = Bytes.new(n)
read = @io.read(slice)
return unexpect_eof if read == 0
slice
end
private def unexpected_eof
raise "Unexpected EOF"
private def fail
raise "Pase error"
end
end

View File

@ -1,14 +1,21 @@
class CBOR::Token(T)
enum Kind
UInt8
NInt
Byte
Text
Array
Map
Float
end
class CBOR::Token
record NullT, byte_number : Int32
record BoolT, byte_number : Int32, value : Bool
record ArrayT, byte_number : Int32, size : UInt32
record MapT, byte_number : Int32, size : UInt32
record IntT, byte_number : Int32, value : Int8 | UInt8 | Int16 | UInt16 | Int32 | UInt32 | Int64 | UInt64
record FloatT, byte_number : Int32, value : Float64
record StringT, byte_number : Int32, value : String
record BytesT, byte_number : Int32, value : Bytes
def initialize(@value : T, @kind : Kind)
alias T = NullT | BoolT | ArrayT | MapT | IntT | FloatT | StringT | BytesT
def self.to_s(token : T)
case token
when IntT
token.value.to_s
else
"NOT IMPLEMENTED YET!"
end
end
end