From: David Kerkeslager Date: Sun, 2 Oct 2016 18:03:12 +0000 (-0400) Subject: Add text serialization for null, booleans, and integers X-Git-Url: https://code.kerkeslager.com/?p=sandbox;a=commitdiff_plain;h=b14242fbc01ad9daf42f33b53eba4a2420454278 Add text serialization for null, booleans, and integers --- diff --git a/serial/serial/text.py b/serial/serial/text.py new file mode 100644 index 0000000..cf2bedc --- /dev/null +++ b/serial/serial/text.py @@ -0,0 +1,109 @@ +import re + +from . import tags + +def _make_literal_serializer(expected_value, literal): + def serializer(to): + assert to.instance is expected_value + return literal + + return serializer + +def _make_integer_serializer(lower_bound, upper_bound, suffix): + def _serializer(to): + assert lower_bound <= to.instance and to.instance < upper_bound + return '{}{}'.format(to.instance, suffix) + + return _serializer + +def _make_unsigned_integer_serializer(bit_length): + return _make_integer_serializer(0, 2 << (bit_length - 1), 'u{}'.format(bit_length)) + +def _make_signed_integer_serializer(bit_length): + upper_bound = 2 << (bit_length - 2) + lower_bound = -upper_bound + return _make_integer_serializer(lower_bound, upper_bound, 'i{}'.format(bit_length)) + +_SERIALIZERS = { + tags.NULL: _make_literal_serializer(None, 'null'), + tags.TRUE: _make_literal_serializer(True, 'true'), + tags.FALSE: _make_literal_serializer(False, 'false'), + tags.UINT8: _make_unsigned_integer_serializer(8), + tags.UINT16: _make_unsigned_integer_serializer(16), + tags.UINT32: _make_unsigned_integer_serializer(32), + tags.UINT64: _make_unsigned_integer_serializer(64), + tags.INT8: _make_signed_integer_serializer(8), + tags.INT16: _make_signed_integer_serializer(16), + tags.INT32: _make_signed_integer_serializer(32), + tags.INT64: _make_signed_integer_serializer(64), +} + +def serialize(to): + return _SERIALIZERS[to.tag](to) + +def _make_literal_deserializer(tag, instance, literal): + def _deserializer(s): + if s.startswith(literal): + return True, tags.TaggedObject(tag = tag, instance = instance), s[len(literal):] + + return False, None, None + + return _deserializer + +def _make_regex_deserializer(tag, decoder, regex): + matcher = re.compile(regex).match + + def _deserializer(s): + match = matcher(s) + + if match is None: + return False, None, None + + return True, tags.TaggedObject(tag = tag, instance = decoder(match)), s[match.end():] + + return _deserializer + +def _make_unsigned_int_deserializer(tag, bit_length): + bound = 2 << (bit_length - 1) + + def _decoder(match): + result = int(match.group(1)) + assert result < bound + return result + + return _make_regex_deserializer(tag, _decoder, r'(\d+)' + 'u{}'.format(bit_length)) + +def _make_signed_int_deserializer(tag, bit_length): + upper_bound = 2 << (bit_length - 2) + lower_bound = -upper_bound + + def _decoder(match): + result = int(match.group(1)) + assert lower_bound <= result and result < upper_bound + return result + + return _make_regex_deserializer(tag, _decoder, r'(-?\d+)' + 'i{}'.format(bit_length)) + +_DESERIALIZERS = [ + _make_literal_deserializer(tags.NULL, None, 'null'), + _make_literal_deserializer(tags.TRUE, True, 'true'), + _make_literal_deserializer(tags.FALSE, False, 'false'), + _make_unsigned_int_deserializer(tags.UINT8, 8), + _make_unsigned_int_deserializer(tags.UINT16, 16), + _make_unsigned_int_deserializer(tags.UINT32, 32), + _make_unsigned_int_deserializer(tags.UINT64, 64), + _make_signed_int_deserializer(tags.INT8, 8), + _make_signed_int_deserializer(tags.INT16, 16), + _make_signed_int_deserializer(tags.INT32, 32), + _make_signed_int_deserializer(tags.INT64, 64), +] + +def deserialize(s): + for deserializer in _DESERIALIZERS: + succeeded, result, remaining = deserializer(s) + + if succeeded: + assert remaining == '' + return result + + raise Exception() diff --git a/serial/test.py b/serial/test.py new file mode 100644 index 0000000..35bb800 --- /dev/null +++ b/serial/test.py @@ -0,0 +1,89 @@ +import unittest + +from serial import binary, tags, text + +EXAMPLE_BINARY_REPRESENTATIONS = [ + (tags.TaggedObject(tags.NULL, None), b'\x00'), + (tags.TaggedObject(tags.TRUE, True), b'\x01'), + (tags.TaggedObject(tags.FALSE, False), b'\x02'), + (tags.TaggedObject(tags.UINT8, 7), b'\x03\x07'), + (tags.TaggedObject(tags.UINT16, 7), b'\x04\x00\x07'), + (tags.TaggedObject(tags.UINT32, 7), b'\x05\x00\x00\x00\x07'), + (tags.TaggedObject(tags.UINT64, 7), b'\x06\x00\x00\x00\x00\x00\x00\x00\x07'), + (tags.TaggedObject(tags.INT8, 7), b'\x10\x07'), + (tags.TaggedObject(tags.INT16, 7), b'\x11\x00\x07'), + (tags.TaggedObject(tags.INT32, 7), b'\x12\x00\x00\x00\x07'), + (tags.TaggedObject(tags.INT64, 7), b'\x13\x00\x00\x00\x00\x00\x00\x00\x07'), + (tags.TaggedObject(tags.UINT8, 254), b'\x03\xfe'), + (tags.TaggedObject(tags.UINT16, 65534), b'\x04\xff\xfe'), + (tags.TaggedObject(tags.UINT32, 4294967294), b'\x05\xff\xff\xff\xfe'), + (tags.TaggedObject(tags.UINT64, 18446744073709551614), b'\x06\xff\xff\xff\xff\xff\xff\xff\xfe'), + (tags.TaggedObject(tags.INT8, -2), b'\x10\xfe'), + (tags.TaggedObject(tags.INT16, -2), b'\x11\xff\xfe'), + (tags.TaggedObject(tags.INT32, -2), b'\x12\xff\xff\xff\xfe'), + (tags.TaggedObject(tags.INT64, -2), b'\x13\xff\xff\xff\xff\xff\xff\xff\xfe'), + (tags.TaggedObject(tags.BINARY, b'\xde\xad\xbe\xef'), b'\x20\x00\x00\x00\x04\xde\xad\xbe\xef'), + (tags.TaggedObject(tags.UTF8, 'Lol!'), b'\x21\x00\x00\x00\x04Lol!'), + (tags.TaggedObject(tags.UTF16, 'かわ'), b'\x22\x00\x00\x00\x06\xff\xfeK0\x8f0'), + (tags.TaggedObject(tags.UTF32, '漢'), b'\x23\x00\x00\x00\x08\xff\xfe\x00\x00"o\x00\x00'), + ( + tags.TaggedObject( + tags.TUPLE, + ( + tags.TaggedObject( + tags.TRUE, + True, + ), + tags.TaggedObject( + tags.UINT8, + 7, + ), + ), + ), + b'\x30\x00\x00\x00\x03\x01\x03\x07' + ), +] + +class BinarySerializeTests(unittest.TestCase): + def test_serialize(self): + for tagged_object, expected in EXAMPLE_BINARY_REPRESENTATIONS: + actual = binary.serialize(tagged_object) + self.assertEqual(expected, actual) + +class BinaryDeserializeTests(unittest.TestCase): + def test_deserialize(self): + for expected, representation in EXAMPLE_BINARY_REPRESENTATIONS: + actual = binary.deserialize(representation) + self.assertEqual(expected, actual) + +EXAMPLE_TEXT_REPRESENTATIONS = [ + (tags.TaggedObject(tags.NULL, None), 'null'), + (tags.TaggedObject(tags.TRUE, True), 'true'), + (tags.TaggedObject(tags.FALSE, False), 'false'), + (tags.TaggedObject(tags.UINT8, 42), '42u8'), + (tags.TaggedObject(tags.UINT16, 42), '42u16'), + (tags.TaggedObject(tags.UINT32, 42), '42u32'), + (tags.TaggedObject(tags.UINT64, 42), '42u64'), + (tags.TaggedObject(tags.INT8, 42), '42i8'), + (tags.TaggedObject(tags.INT16, 42), '42i16'), + (tags.TaggedObject(tags.INT32, 42), '42i32'), + (tags.TaggedObject(tags.INT64, 42), '42i64'), + (tags.TaggedObject(tags.INT8, -2), '-2i8'), + (tags.TaggedObject(tags.INT16, -2), '-2i16'), + (tags.TaggedObject(tags.INT32, -2), '-2i32'), + (tags.TaggedObject(tags.INT64, -2), '-2i64'), +] + +class TextSerializeTests(unittest.TestCase): + def test_serialize(self): + for tagged_object, expected in EXAMPLE_TEXT_REPRESENTATIONS: + actual = text.serialize(tagged_object) + self.assertEqual(expected, actual) + +class TextDeserializeTests(unittest.TestCase): + def test_deserialize(self): + for expected, representation in EXAMPLE_TEXT_REPRESENTATIONS: + actual = text.deserialize(representation) + self.assertEqual(expected, actual) + +unittest.main() diff --git a/serial/test_binary.py b/serial/test_binary.py deleted file mode 100644 index c4cdd3b..0000000 --- a/serial/test_binary.py +++ /dev/null @@ -1,59 +0,0 @@ -import unittest - -from serial import binary, tags - -EXAMPLE_REPRESENTATIONS = [ - (tags.TaggedObject(tags.NULL, None), b'\x00'), - (tags.TaggedObject(tags.TRUE, True), b'\x01'), - (tags.TaggedObject(tags.FALSE, False), b'\x02'), - (tags.TaggedObject(tags.UINT8, 7), b'\x03\x07'), - (tags.TaggedObject(tags.UINT16, 7), b'\x04\x00\x07'), - (tags.TaggedObject(tags.UINT32, 7), b'\x05\x00\x00\x00\x07'), - (tags.TaggedObject(tags.UINT64, 7), b'\x06\x00\x00\x00\x00\x00\x00\x00\x07'), - (tags.TaggedObject(tags.INT8, 7), b'\x10\x07'), - (tags.TaggedObject(tags.INT16, 7), b'\x11\x00\x07'), - (tags.TaggedObject(tags.INT32, 7), b'\x12\x00\x00\x00\x07'), - (tags.TaggedObject(tags.INT64, 7), b'\x13\x00\x00\x00\x00\x00\x00\x00\x07'), - (tags.TaggedObject(tags.UINT8, 254), b'\x03\xfe'), - (tags.TaggedObject(tags.UINT16, 65534), b'\x04\xff\xfe'), - (tags.TaggedObject(tags.UINT32, 4294967294), b'\x05\xff\xff\xff\xfe'), - (tags.TaggedObject(tags.UINT64, 18446744073709551614), b'\x06\xff\xff\xff\xff\xff\xff\xff\xfe'), - (tags.TaggedObject(tags.INT8, -2), b'\x10\xfe'), - (tags.TaggedObject(tags.INT16, -2), b'\x11\xff\xfe'), - (tags.TaggedObject(tags.INT32, -2), b'\x12\xff\xff\xff\xfe'), - (tags.TaggedObject(tags.INT64, -2), b'\x13\xff\xff\xff\xff\xff\xff\xff\xfe'), - (tags.TaggedObject(tags.BINARY, b'\xde\xad\xbe\xef'), b'\x20\x00\x00\x00\x04\xde\xad\xbe\xef'), - (tags.TaggedObject(tags.UTF8, 'Lol!'), b'\x21\x00\x00\x00\x04Lol!'), - (tags.TaggedObject(tags.UTF16, 'かわ'), b'\x22\x00\x00\x00\x06\xff\xfeK0\x8f0'), - (tags.TaggedObject(tags.UTF32, '漢'), b'\x23\x00\x00\x00\x08\xff\xfe\x00\x00"o\x00\x00'), - ( - tags.TaggedObject( - tags.TUPLE, - ( - tags.TaggedObject( - tags.TRUE, - True, - ), - tags.TaggedObject( - tags.UINT8, - 7, - ), - ), - ), - b'\x30\x00\x00\x00\x03\x01\x03\x07' - ), -] - -class SerializeTests(unittest.TestCase): - def test_serialize(self): - for tagged_object, expected in EXAMPLE_REPRESENTATIONS: - actual = binary.serialize(tagged_object) - self.assertEqual(expected, actual) - -class DeserializeTests(unittest.TestCase): - def test_deserialize(self): - for expected, representation in EXAMPLE_REPRESENTATIONS: - actual = binary.deserialize(representation) - self.assertEqual(expected, actual) - -unittest.main()