Skip to content

Commit

Permalink
Merge branch 'extensions'
Browse files Browse the repository at this point in the history
  • Loading branch information
gvx committed Dec 29, 2024
2 parents 4591cc1 + 16d13ec commit 74a2359
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 1 deletion.
29 changes: 29 additions & 0 deletions EXTENSION_API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
This document lays out the API provided for extension authors by bitser.

An extension object is a table that contains at least the following keys:

* `"bitser-type"`: a string, like `"userdata"`. The extension will only be
used to serialize a value `v` if `type(v)` equals the value of this key.
A value of this type is called a "potential match" in the rest of this
document. Note that this type does not need to be natively supported by
bitser.
* `"bitser-match"`: a function that takes a single argument and returns a
boolean. The extension will only be used if this function returns `true`.
It does not need to check `type(v)`, as it will only be called for potential
matches.
* `"bitser-dump"`: a function that takes a single potential match as argument
and returns a single value that bitser is able to serialize (either natively
or through an extension).
* `"bitser-load"`: a function that takes a single argument that is a
deserialized copy of a value previously returned by the dump function, that
returns a potential match.

All other keys will be ignored, but string keys that start with `"bitser-"`
are reserved for future versions of this API.

Extension authors SHOULD NOT call `bitser.registerExtension`. This should be
left to extension users, so they may choose an ID that does not conflict with
other extensions they may be using.

The matching function SHOULD be highly performant, as it is called for every
potential match that is to be serialized.
30 changes: 30 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
* [`bitser.includeMetatables`](#includeMetatables)
* [`bitser.register`](#register)
* [`bitser.registerClass`](#registerclass)
* [`bitser.registerExtension`](#registerextension)
* [`bitser.unregister`](#unregister)
* [`bitser.unregisterClass`](#unregisterclass)
* [`bitser.unregisterExtension`](#unregisterextension)
* [`bitser.reserveBuffer`](#reservebuffer)
* [`bitser.clearBuffer`](#clearbuffer)

Expand Down Expand Up @@ -202,6 +204,24 @@ Returns the registered class as a convenience.

See also: [`bitser.unregisterClass`](#unregisterclass).


## registerExtension

```lua
bitser.registerExtension(extension_id, extension)
```

Registers the extension `extension` and give it the identifier `extension_id`. This is a way to allow 3rd party libraries to
extend the functionality of bitser, for example to be able to serialize exotic data, or to allow certain optimisations.

To implement your own bitser extensions, see
[EXTENSION_API.md](EXTENSION_API.md).

The name `extension_id` must not conflict with that of other extensions you're
using. Strings are recommended, but other primitive types will work.

See also: [`bitser.unregisterExtension`](#unregisterextension).

## unregister

```lua
Expand All @@ -223,6 +243,16 @@ which is useful in a context where you don't have a reference to the class you w

See also: [`bitser.registerClass`](#registerclass).

## unregisterExtension

```lua
bitser.unregister(extension_id)
```

Deregisters the previously registered extension with the id `extension_id`.

See also: [`bitser.registerExtension`](#registerextension).

## reserveBuffer

```lua
Expand Down
33 changes: 33 additions & 0 deletions bitser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ local class_registry = {}
local class_name_registry = {}
local classkey_registry = {}
local class_deserialize_registry = {}
local extension_registry = {}
local extensions_by_type = {}
local EXTENSION_TYPE_KEY = 'bitser-type'
local EXTENSION_MATCH_KEY = 'bitser-match'
local EXTENSION_LOAD_KEY = 'bitser-load'
local EXTENSION_DUMP_KEY = 'bitser-dump'

local serialize_value

Expand Down Expand Up @@ -263,6 +269,17 @@ serialize_value = function(value, seen)
end
return
end
if extensions_by_type[t] then
for extension_id, extension in pairs(extensions_by_type[t]) do
if extension[EXTENSION_MATCH_KEY](value) then
-- extension
Buffer_write_byte(254)
serialize_value(extension_id, seen)
serialize_value(extension[EXTENSION_DUMP_KEY](value), seen)
return
end
end
end
(types[t] or
error("cannot serialize type " .. t)
)(value, seen)
Expand Down Expand Up @@ -377,6 +394,10 @@ local function deserialize_value(seen)
elseif t == 250 then
--short int
return Buffer_read_data("int16_t[1]", 2)[0]
elseif t == 254 then
--extension
local extension_id = deserialize_value(seen)
return extension_registry[extension_id][EXTENSION_LOAD_KEY](deserialize_value(seen))
else
error("unsupported serialized type " .. t)
end
Expand Down Expand Up @@ -482,4 +503,16 @@ end, unregisterClass = function(name)
classkey_registry[name] = nil
class_deserialize_registry[name] = nil
class_registry[name] = nil
end, registerExtension = function(extension_id, extension)
assert(not extension_registry[extension_id], 'extension with id ' .. extension_id .. ' already registered')
local ty = extension[EXTENSION_TYPE_KEY]
assert(type(ty) == 'string' and type(extension[EXTENSION_MATCH_KEY]) == 'function' and type(extension[EXTENSION_LOAD_KEY]) == 'function' and type(extension[EXTENSION_DUMP_KEY]) == 'function', 'not a valid extension')
extension_registry[extension_id] = extension
if not extensions_by_type[ty] then
extensions_by_type[ty] = {}
end
extensions_by_type[ty][extension_id] = extension
end, unregisterExtension = function(extension_id)
extensions_by_type[extension_registry[extension_id][EXTENSION_TYPE_KEY]][extension_id] = nil
extension_registry[extension_id] = nil
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION}
17 changes: 16 additions & 1 deletion spec/bitser_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,6 @@ describe("bitser", function()
assert.has_error(function() bitser.registerClass('Horse', {mane = 'majestic'}) end, "no deserializer given for unsupported class library")
end)
it("cannot deserialize values from unassigned type bytes", function()
assert.has_error(function() bitser.loads("\254") end, "unsupported serialized type 254")
assert.has_error(function() bitser.loads("\255") end, "unsupported serialized type 255")
end)
it("can load from raw data", function()
Expand Down Expand Up @@ -327,4 +326,20 @@ describe("bitser", function()

bitser.unregisterClass("class")
end)
it("provides a simple extension mechanism", function()
local MATCH_CALLS = 0
local LOAD_CALLS = 0
local DUMP_CALLS = 0
bitser.registerExtension('test', {
['bitser-type'] = 'number',
['bitser-match'] = function(value) MATCH_CALLS = MATCH_CALLS + 1; return value > 0 end,
['bitser-load'] = function(value) LOAD_CALLS = LOAD_CALLS + 1; return tonumber(value) end,
['bitser-dump'] = function(value) DUMP_CALLS = DUMP_CALLS + 1; return tostring(value) end })
local t = {1.0, -1.0, 0., -1/0, 'strings should not match'}
test_serdeser(t)
assert.are.same(4, MATCH_CALLS)
assert.are.same(1, LOAD_CALLS)
assert.are.same(1, DUMP_CALLS)
bitser.unregisterExtension('test')
end)
end)

0 comments on commit 74a2359

Please sign in to comment.