Skip to content

Commit

Permalink
Incomplete deserialization support
Browse files Browse the repository at this point in the history
related to #12

Adds the deserialize proc overload that accepts mutable object to fill with deserialized
content. If the deserialization was not complete (due to lack of data in the stream)
the part of the object passed for which data has not obtained will be left untouched
  • Loading branch information
xomachine committed Oct 27, 2019
1 parent ccb7a9f commit c95d82d
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 38 deletions.
91 changes: 61 additions & 30 deletions nesm/documentation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,40 @@
## code can be seen when **-d:debug** is passed to Nim compiler):
##
## .. code-block:: nim
## type
## Ball = object
## weight: float32
## diameter: int32
## isHollow: bool
##
## proc size(obj: Ball): int =
## result += 4
## result += 4
## result += 1
## result += 0
##
## proc serialize(obj: Ball; thestream: Stream) =
## type
## Ball = object
## weight: float32
## diameter: int32
## isHollow: bool
##
## proc size(obj169004: Ball): Natural =
## (0 + 4 + 4 + 1)
##
## proc serialize(obj169005: Ball; thestream: Stream) =
## discard
## thestream.writeData(obj.weight.unsafeAddr, 4)
## thestream.writeData(obj.diameter.unsafeAddr, 4)
## thestream.writeData(obj.isHollow.unsafeAddr, 1)
## thestream.writeData(obj169005.weight.unsafeAddr, 4)
## thestream.writeData(obj169005.diameter.unsafeAddr, 4)
## thestream.writeData(obj169005.isHollow.unsafeAddr, 1)
##
## proc serialize(obj: Ball): string =
## let ss142002 = newStringStream()
## serialize(obj, ss142002)
## ss142002.data
## proc serialize(obj169010: Ball): string =
## let ss169012 = newStringStream()
## serialize(obj169010, ss169012)
## ss169012.data
##
## proc deserialize(thetype142004: typedesc[Ball]; thestream: Stream): Ball =
## proc deserialize(obj169006: var Ball; thestream: Stream) =
## discard
## assert(4 ==
## thestream.readData(result.weight.unsafeAddr, 4),
## "Stream has not provided enough data")
## assert(4 ==
## thestream.readData(result.diameter.unsafeAddr, 4),
## "Stream has not provided enough data")
## assert(1 ==
## thestream.readData(result.isHollow.unsafeAddr, 1),
## "Stream has not provided enough data")
## doAssert(4 ==
## thestream.readData(obj169006.weight.unsafeAddr, 4),
## "Stream has not provided enough data")
## doAssert(4 ==
## thestream.readData(obj169006.diameter.unsafeAddr, 4),
## "Stream has not provided enough data")
## doAssert(1 ==
## thestream.readData(obj169006.isHollow.unsafeAddr, 1),
## "Stream has not provided enough data")
##
## proc deserialize(a169008: typedesc[Ball]; thestream: Stream): Ball =
## deserialize(result, thestream)
##
## As you may see from the code above, the macro generates three kinds
## of procedures: serializer, deserializer and size estimator.
Expand Down Expand Up @@ -342,6 +342,25 @@
## will be raised. To disable such a behaviour user may use the
## **-d:disableEnumChecks** compiler switch.
##
## Default values and incomplete deserialization
## ---------------------------------------------
## **NESM** is able to perform incomplete deserialization. When the stream ends
## before the whole object is deserialized, **NESM** raises an exception.
## When you are using `(obj: var TheType, thestream: Stream)` variant of
## the `deserialize` proc you can get an incomplete serialization result
## in the object passed as the first parameter after handling the exception.
##
## Note that if the object already contain values before passing it to
## the deserialize proc those values will be overwriten only for data
## available in the stream. All other values will be left untouched.
##
## There are two edge cases in the incomplete deserialization behaviour:
## * incomplete seq or string with size available in the stream will be overwritten
## by seq of that size with default elements then the available elements
## will be filled and all others will be left as default values
## (not the values passed in the original object)
## * incomplete cstring will not be overwritten until zero byte is encountered in the stream
##
## Future ideas
## ------------
## The following will not necessarily be done but may be
Expand Down Expand Up @@ -392,6 +411,18 @@ proc deserialize*(thetype: typedesc[TheType],
## When the stream will not provide enough bytes
## an `AssertionError` will be raised.
discard

proc deserialize*(obj: TheType,
stream: Stream): TheType
{.raises: AssertionError.} =
## Interprets the data received from the `stream`
## as `TheType` and deserializes it then to the `obj`.
## The `obj` may be filled partially if the `stream`
## has not provided enough data.
## When the stream will not provide enough bytes
## an `AssertionError` will be raised.
discard

proc deserialize*(thetype: typedesc[TheType],
data: string | seq[byte | char | int8 | uint8]): TheType
{.raises: AssertionError.} =
Expand Down
23 changes: 17 additions & 6 deletions nesm/procgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ proc makeDeclaration(typenode: NimNode, kind: ProcType, is_exported: bool,
let procname =
if is_exported: newIdentNode($kind).postfix("*")
else: newIdentNode($kind)
let bgsource = case kind
of deserialize: newIdentNode("result")
else: nskParam.genSym("obj")
let bgsource = nskParam.genSym("obj")
let body = bodygen(bgsource)
let STREAM_NAME = getStreamName()
case kind
Expand All @@ -36,8 +34,8 @@ proc makeDeclaration(typenode: NimNode, kind: ProcType, is_exported: bool,
proc `procname`(`bgsource`: `typenode`, `STREAM_NAME`: Stream) = `body`
of deserialize:
quote do:
proc `procname`(a: typedesc[`typenode`],
`STREAM_NAME`: Stream): `typenode` = `body`
proc `procname`(`bgsource`: var `typenode`,
`STREAM_NAME`: Stream) = `body`
of size:
if is_static:
quote do:
Expand All @@ -46,6 +44,18 @@ proc makeDeclaration(typenode: NimNode, kind: ProcType, is_exported: bool,
quote do:
proc `procname`(`bgsource`: `typenode`): Natural = `body`

proc makeDefaultDeserializeConversion(typenode: NimNode, is_exported: bool): NimNode =
## Makes proc to convert old proc with typedesc argument to new one with default
## value
let rawprocname = newIdentNode("deserialize")
let procname = if is_exported: rawprocname.postfix("*") else: rawprocname
let resultname = newIdentNode("result")
let STREAM_NAME = getStreamName()
quote do:
proc `procname`(a: typedesc[`typenode`],
`STREAM_NAME`: Stream): `typenode` =
`rawprocname`(`resultname`, `STREAM_NAME`)

proc makeSerializeStreamConversion(typenode: NimNode,
is_exported: bool): NimNode =
## Makes overloaded serialize proc which returns ``string`` as input instead
Expand Down Expand Up @@ -98,11 +108,12 @@ proc generateProcs(context: var Context, obj: NimNode): NimNode =
let stream_deserializer =
makeDeclaration(typenode, deserialize, is_shared, context.is_static,
typeinfo.deserialize)
let classic_deserializer = makeDefaultDeserializeConversion(typenode, is_shared)
let string_serializer = makeSerializeStreamConversion(typenode, is_shared)
let string_deserializer =
if context.is_static: makeDeserializeStreamConversion(typenode ,is_shared)
else: newEmptyNode()
newStmtList(size_proc, stream_serializer, string_serializer,
stream_deserializer, string_deserializer)
stream_deserializer, classic_deserializer, string_deserializer)


55 changes: 55 additions & 0 deletions tests/defaults.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import unittest
from nesm import serializable
from streams import setPosition
import helpers/rnw


suite "Default values tests":
test "Basic":
serializable:
type
IncompleteObj = object
a: int32
SimpleObj = object
a: int32
b: int32
let rnw = get_reader_n_writer()
let aval = rand(1000).int32
let bval = rand(1001..2000).int32
let x = IncompleteObj(a: aval)
x.serialize(rnw)
rnw.setPosition(0)
var y = SimpleObj(a: rand(2001..3000).int32, b: bval)
try:
y.deserialize(rnw)
except:
discard
check(aval == y.a)
check(bval == y.b)

test "Seq":
serializable:
type
IncompleteSeqObj = object
a: int32
b: int32
c: int32
SeqObj = object
a: int32
b: seq[int32]
let rnw = get_reader_n_writer()
let aval = rand(1001..2000).int32
let bval = random_seq_with(int32(rand(1000)))
let cval = rand(4001..5000).int32
let x = IncompleteSeqObj(a: aval, b: bval.len.int32,
c: cval)
x.serialize(rnw)
rnw.setPosition(0)
var y = SeqObj(a: rand(2001..3000).int32, b: bval)
try:
y.deserialize(rnw)
except:
discard
check(aval == y.a)
require(bval.len == y.b.len)
check(cval == y.b[0])
5 changes: 3 additions & 2 deletions tests/helpers/rnw.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ when NimMajor > 0 or NimMinor > 17 or (NimMinor == 17 and NimPatch > 2):
else:
from random import random
proc rand[T](a: T): int = random(a)
from streams import Stream, StringStream, newStringStream
from streams import Stream, StringStream, newStringStream,
setPosition

export rand
export rand, setPosition

type
RandomStream = ref RandomStreamObj
Expand Down

0 comments on commit c95d82d

Please sign in to comment.