From 4689baa84252ba845bfba1dd6faea9ab3f103051 Mon Sep 17 00:00:00 2001 From: David Kerkeslager Date: Sun, 9 Oct 2016 15:59:27 -0400 Subject: [PATCH] Serialization/deserialization for lists --- serial/serial/binary.py | 42 +++++++++++++++++++++++++++++ serial/serial/tags.py | 1 + serial/serial/text.py | 58 ++++++++++++++++++++++++++++++++++++++--- serial/test.py | 22 ++++++++++++++++ 4 files changed, 119 insertions(+), 4 deletions(-) diff --git a/serial/serial/binary.py b/serial/serial/binary.py index 960acef..d19a221 100644 --- a/serial/serial/binary.py +++ b/serial/serial/binary.py @@ -40,6 +40,26 @@ def _serialize_tuple(to): return struct.pack('!BI', tags.TUPLE, len(payload)) + payload +def _serialize_list(to): + assert isinstance(to.instance, list) + + # TODO Actually handle this case somehow + assert len(to.instance) > 0 + + # TODO Do this a better way + serialized_items = [serialize(i) for i in to.instance] + list_tag = serialized_items[0][0] + + def check_and_strip_prefix(b): + item_tag = b[0] + assert list_tag == item_tag + return b[1:] + + payload = b''.join(check_and_strip_prefix(si) for si in serialized_items) + + fmt = '!BBI' + + return struct.pack(fmt, tags.LIST, list_tag, len(payload)) + payload _TAGS_TO_SERIALIZERS = { tags.NULL: _make_tag_only_serializer(tags.NULL, None), @@ -58,6 +78,7 @@ _TAGS_TO_SERIALIZERS = { tags.UTF16: _make_string_serializer(lambda s: s.encode('utf-16')), tags.UTF32: _make_string_serializer(lambda s: s.encode('utf-32')), tags.TUPLE: _serialize_tuple, + tags.LIST: _serialize_list, } def serialize(to): @@ -119,6 +140,26 @@ def _deserialize_tuple(b): return bytes_read, tags.TaggedObject(tag = tags.TUPLE, instance = tuple(instance)) +def _deserialize_list(b): + list_tag_bytes = b.read(1) + assert len(list_tag_bytes) == 1 + list_tag = list_tag_bytes[0] + + bytes_read, payload = _read_length_then_payload(b) + + payload_stream = io.BytesIO(payload) + + total_bytes_read = 0 + instance = [] + + while total_bytes_read < len(payload): + partial_bytes_read, item = _TAGS_TO_PARSERS[list_tag](payload_stream) + total_bytes_read += partial_bytes_read + instance.append(item) + + # TODO Return tags = (tags.LIST, list_tag) to function like a generic type + return bytes_read, tags.TaggedObject(tag = tags.LIST, instance = instance) + _TAGS_TO_PARSERS = { tags.NULL: _make_tag_only_parser(tags.NULL, None), tags.TRUE: _make_tag_only_parser(tags.TRUE, True), @@ -136,6 +177,7 @@ _TAGS_TO_PARSERS = { tags.UTF16: _make_string_deserializer(tags.UTF16, lambda b: b.decode('utf-16')), tags.UTF32: _make_string_deserializer(tags.UTF32, lambda b: b.decode('utf-32')), tags.TUPLE: _deserialize_tuple, + tags.LIST: _deserialize_list, } def _deserialize_partial(b): diff --git a/serial/serial/tags.py b/serial/serial/tags.py index 0409d92..b1edfb7 100644 --- a/serial/serial/tags.py +++ b/serial/serial/tags.py @@ -16,6 +16,7 @@ UTF8 = 0x21 UTF16 = 0x22 UTF32 = 0x23 TUPLE = 0x30 +LIST = 0x31 TaggedObject = collections.namedtuple( 'TaggedObject', diff --git a/serial/serial/text.py b/serial/serial/text.py index 0f19a0e..2d957d6 100644 --- a/serial/serial/text.py +++ b/serial/serial/text.py @@ -46,6 +46,14 @@ def _make_string_serializer(prefix): return serializer +def _indent(s, depth = 2): + return '\n'.join(' ' * depth + line for line in s.split('\n')) + +def _serialize_list(to): + assert isinstance(to.instance, list) + + return '[\n' + _indent(',\n'.join(serialize(i) for i in to.instance)) + '\n]' + _SERIALIZERS = { tags.NULL: _make_literal_serializer(None, 'null'), tags.TRUE: _make_literal_serializer(True, 'true'), @@ -62,6 +70,7 @@ _SERIALIZERS = { tags.UTF8: _make_string_serializer('utf8'), tags.UTF16: _make_string_serializer('utf16'), tags.UTF32: _make_string_serializer('utf32'), + tags.LIST: _serialize_list, } def serialize(to): @@ -171,6 +180,39 @@ def _make_string_deserializer(tag, prefix): return deserializer +def _deserialize_list(s): + s = s.lstrip() + + if not s.startswith('['): + return False, None, None + + instance = [] + + s = s[1:].lstrip() + + succeeded, result, s = _deserialize_one(s) + + # TODO Handle empty lists + + if succeeded: + instance.append(result) + + s = s.lstrip() + + while not s.startswith(']'): + assert s.startswith(',') + s = s[1:].lstrip() + + succeeded, result, s = _deserialize_one(s) + + # TODO Handle trailing commas + assert succeeded + instance.append(result) + s = s.lstrip() + + assert s.startswith(']') + return True, tags.TaggedObject(tag = tags.LIST, instance = instance), s[1:] + _DESERIALIZERS = [ _make_literal_deserializer(tags.NULL, None, 'null'), _make_literal_deserializer(tags.TRUE, True, 'true'), @@ -187,14 +229,22 @@ _DESERIALIZERS = [ _make_string_deserializer(tags.UTF8, 'utf8'), _make_string_deserializer(tags.UTF16, 'utf16'), _make_string_deserializer(tags.UTF32, 'utf32'), + _deserialize_list, ] -def deserialize(s): +def _deserialize_one(s): for deserializer in _DESERIALIZERS: succeeded, result, remaining = deserializer(s) if succeeded: - assert remaining == '' - return result + return succeeded, result, remaining + + return False, None, None + +def deserialize(s): + succeeded, result, remaining = _deserialize_one(s) + + assert succeeded + assert remaining == '' - raise Exception() + return result diff --git a/serial/test.py b/serial/test.py index 6e17691..239c694 100644 --- a/serial/test.py +++ b/serial/test.py @@ -42,6 +42,17 @@ EXAMPLE_BINARY_REPRESENTATIONS = [ ), b'\x30\x00\x00\x00\x03\x01\x03\x07' ), + ( + tags.TaggedObject( + tag = tags.LIST, + instance = [ + tags.TaggedObject(tag = tags.UINT8, instance = 9), + tags.TaggedObject(tag = tags.UINT8, instance = 22), + tags.TaggedObject(tag = tags.UINT8, instance = 36), + ], + ), + b'\x31\x03\x00\x00\x00\x03\x09\x16\x24', + ), ] class BinarySerializeTests(unittest.TestCase): @@ -76,6 +87,17 @@ EXAMPLE_TEXT_REPRESENTATIONS = [ (tags.TaggedObject(tags.UTF8, 'Lol!'), 'utf8"Lol!"'), (tags.TaggedObject(tags.UTF16, 'かわ'), 'utf16"かわ"'), (tags.TaggedObject(tags.UTF32, '漢'), 'utf32"漢"'), + ( + tags.TaggedObject( + tag = tags.LIST, + instance = [ + tags.TaggedObject(tag = tags.UINT8, instance = 9), + tags.TaggedObject(tag = tags.UINT8, instance = 22), + tags.TaggedObject(tag = tags.UINT8, instance = 36), + ], + ), + '[\n 9u8,\n 22u8,\n 36u8\n]' + ), ] class TextSerializeTests(unittest.TestCase): -- 2.20.1