Current section

Files

Jump to
bson lib bson_tokenizer.ex
Raw

lib/bson_tokenizer.ex

defmodule BsonTk do
@moduledoc """
`BsonTk` tokenize a bson document see tokenize/1 in module `Bson`
Before being decoded bson documents are tokenized, this means cut into chunks that are easy to decode.
"""
defmodule Int32 do
defstruct part: nil
@moduledoc """
Token for 32-bit Integer
"""
end
defmodule Int64 do
defstruct part: nil
@moduledoc """
Token for 64-bit integer
"""
end
defmodule Float do
defstruct part: nil
@moduledoc """
Token for Floating point
"""
end
defmodule Doc do
defstruct part: nil
@moduledoc """
Token for Embedded BSON Document
"""
end
defmodule Array do
defstruct part: nil
@moduledoc """
Token for Array
"""
end
defmodule String do
defstruct part: nil
@moduledoc """
Token for UTF-8 string
"""
end
defmodule Atom do
defstruct part: nil
@moduledoc """
Token for Symbol
"""
end
defmodule Bool do
defstruct part: nil
@moduledoc """
Token for Boolean
"""
end
defmodule ObjectId do
defstruct part: nil
@moduledoc """
Token for MongoDb ObjectId
"""
end
defmodule Bin do
defstruct part: nil, subtype: nil
@moduledoc """
Token for Binary data
"""
end
defmodule Regex do
defstruct pattern: nil, opts: nil
@moduledoc """
Token for Regular expression
"""
end
defmodule JS do
defstruct code: nil, scope: nil
@moduledoc """
Token for JavaScript code with or without scope
"""
end
defmodule Now do
defstruct part: nil
@moduledoc """
Token for UTC datetime
"""
end
defmodule Timestamp do
defstruct inc: nil, ts: nil
@moduledoc """
Token for Timestamp
"""
end
@doc """
tokenize one element, this is a key-value pair, of a bson document starting at position `from`.
It returns {{`tk_name`, `tk_element`}, `tk_end`} where:
* `tk_name` - is the token of the element name (binary part {from, to})
* `tk_element` - is the token of the element value (see `tokenize_element/3`)
* `tk_end` - is the end position of the element
"""
def tokenize(bson, from, to) do
name_end = Bson.peek_cstring_end(bson, from+1, to)
tk_name = {from+1, name_end-(from+1)}
tk_head = binary_part(bson, from, 1)
{tk_element, tk_end} = tokenize_element(tk_head, bson, name_end+1)
{{tk_name, tk_element}, tk_end}
end
@doc """
tokenize one element value of bson starting at position `from`.
Based on the tag in front of an element of a Bson document (see e_list in specs),
it identifies the element type and created the appropriate BsonTk struct.
It returns {`tk_element`, `tk_end`} where:
* `tk_element` - is the token of the element value. It can be an atom `nil`, `MIN_KEY`, `MAX_KEY`
or a struct
(`BsonTk.Int32`, `BsonTk.Int64`, `BsonTk.Float`, `BsonTk.Doc`, `BsonTk.Array`, `BsonTk.String`, `BsonTk.Atom`,
`BsonTk.Bool`, `BsonTk.ObjectId`, `BsonTk.Bin`, `BsonTk.Regex`, `BsonTk.JS`, `BsonTk.Now`, `BsonTk.Timestamp`)
* `tk_end` - is the end position of the element
"""
def tokenize_element("\x10", _, from), do: {%Int32{part: {from, 4}}, from+4}
def tokenize_element("\x12", _, from), do: {%Int64{part: {from, 8}}, from+8}
def tokenize_element("\x01", _, from), do: {%Float{part: {from, 8}}, from+8}
def tokenize_element("\x07", _, from), do: {%ObjectId{part: {from, 12}}, from+12}
def tokenize_element("\x08", _, from), do: {%Bool{part: {from, 1}}, from+1}
def tokenize_element("\x09", _, from), do: {%Now{part: {from, 8}}, from+8}
def tokenize_element("\x06", _, from), do: {nil, from+0}
def tokenize_element("\x0a", _, from), do: {nil, from+0}
def tokenize_element("\x11", _, from), do: {%Timestamp{inc: {from, 4}, ts: {from+4, 4}}, from+8}
def tokenize_element(<<0xff>>, _, from), do: {MIN_KEY, from+0}
def tokenize_element("\x7f", _, from), do: {MAX_KEY, from+0}
# string
def tokenize_element("\x02", bson, from) do
len = Bson.int32(bson, from)-1
{%String{part: {from+4, len}}, from+len+4+1}
end
# atom
def tokenize_element("\x0e", bson, from) do
len = Bson.int32(bson, from)-1
{%Atom{part: {from+4, len}}, from+len+4+1}
end
# document
def tokenize_element("\x03", bson, from) do
len = Bson.int32(bson, from)-5
{%Doc{part: {from+4, len}}, from+len+4+1}
end
# array
def tokenize_element("\x04", bson, from) do
len = Bson.int32(bson, from)-5
{%Array{part: {from+4, len}}, from+len+4+1}
end
# regex
def tokenize_element("\x0b", bson, from) do
to = byte_size(bson) -1
patternend = Bson.peek_cstring_end(bson, from, to)
optsend = Bson.peek_cstring_end(bson, patternend+1, to)
{%BsonTk.Regex{pattern: {from, patternend-from}, opts: {patternend+1, optsend-(patternend+1)}}, optsend+1}
end
# javascript
def tokenize_element("\x0d", bson, from) do
len = Bson.int32(bson, from)-1
{%BsonTk.JS{code: {from+4, len}}, from+len+4+1}
end
# javascript with scope
def tokenize_element("\x0f", bson, code_ws_from) do
# code_ws_len = Bson.int32(bson, code_ws_from)-1 #code_w_s length
codefrom = code_ws_from+4
codelen = Bson.int32(bson, codefrom)-1 #string length
scopefrom = codefrom+4+codelen+1
scopelen = Bson.int32(bson, scopefrom)-5 #doc length
{%BsonTk.JS{code: {codefrom+4, codelen}, scope: {scopefrom+4, scopelen}}, scopefrom+scopelen+4+1}
end
# binary
def tokenize_element("\x05", bson, from) do
len = Bson.int32(bson, from)
{%BsonTk.Bin{part: {from+5, len}, subtype: binary_part(bson, from+4, 1)}, from+len+5}
end
# binary
def tokenize_element(tag, _, _), do: raise "unsupported tag (" <> tag <> ")"
@doc """
Tokenize elements of a bson document (e_list in the spec)
"""
def tokenize_e_list(bson, from, to), do: tokenize_e_list(bson, from, to, [])
defp tokenize_e_list(_bson, from, from, acc), do: acc |> Enum.reverse
defp tokenize_e_list(bson, from, to, acc) do
{tk, tk_end} = tokenize(bson, from, to)
tokenize_e_list(bson, tk_end, to, [tk | acc])
end
end