Serialization/deserialization for lists
authorDavid Kerkeslager <kerkeslager@gmail.com>
Sun, 9 Oct 2016 19:59:27 +0000 (15:59 -0400)
committerDavid Kerkeslager <kerkeslager@gmail.com>
Sun, 9 Oct 2016 19:59:27 +0000 (15:59 -0400)
serial/serial/binary.py
serial/serial/tags.py
serial/serial/text.py
serial/test.py

index 960acef..d19a221 100644 (file)
@@ -40,6 +40,26 @@ def _serialize_tuple(to):
 
     return struct.pack('!BI', tags.TUPLE, len(payload)) + payload
 
 
     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),
 
 _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.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):
 }
 
 def serialize(to):
@@ -119,6 +140,26 @@ def _deserialize_tuple(b):
 
     return bytes_read, tags.TaggedObject(tag = tags.TUPLE, instance = tuple(instance))
 
 
     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),
 _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.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):
 }
 
 def _deserialize_partial(b):
index 0409d92..b1edfb7 100644 (file)
@@ -16,6 +16,7 @@ UTF8 = 0x21
 UTF16 = 0x22
 UTF32 = 0x23
 TUPLE = 0x30
 UTF16 = 0x22
 UTF32 = 0x23
 TUPLE = 0x30
+LIST = 0x31
 
 TaggedObject = collections.namedtuple(
     'TaggedObject',
 
 TaggedObject = collections.namedtuple(
     'TaggedObject',
index 0f19a0e..2d957d6 100644 (file)
@@ -46,6 +46,14 @@ def _make_string_serializer(prefix):
 
     return serializer
 
 
     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'),
 _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.UTF8: _make_string_serializer('utf8'),
     tags.UTF16: _make_string_serializer('utf16'),
     tags.UTF32: _make_string_serializer('utf32'),
+    tags.LIST: _serialize_list,
 }
 
 def serialize(to):
 }
 
 def serialize(to):
@@ -171,6 +180,39 @@ def _make_string_deserializer(tag, prefix):
 
     return deserializer
 
 
     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'),
 _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'),
     _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:
     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
index 6e17691..239c694 100644 (file)
@@ -42,6 +42,17 @@ EXAMPLE_BINARY_REPRESENTATIONS = [
         ),
         b'\x30\x00\x00\x00\x03\x01\x03\x07'
     ),
         ),
         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):
 ]
 
 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(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):
 ]
 
 class TextSerializeTests(unittest.TestCase):