From b14242fbc01ad9daf42f33b53eba4a2420454278 Mon Sep 17 00:00:00 2001 From: David Kerkeslager Date: Sun, 2 Oct 2016 14:03:12 -0400 Subject: [PATCH] Add text serialization for null, booleans, and integers --- serial/serial/text.py | 109 +++++++++++++++++++++++++++++ serial/{test_binary.py => test.py} | 42 +++++++++-- 2 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 serial/serial/text.py rename serial/{test_binary.py => test.py} (58%) 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_binary.py b/serial/test.py similarity index 58% rename from serial/test_binary.py rename to serial/test.py index c4cdd3b..35bb800 100644 --- a/serial/test_binary.py +++ b/serial/test.py @@ -1,8 +1,8 @@ import unittest -from serial import binary, tags +from serial import binary, tags, text -EXAMPLE_REPRESENTATIONS = [ +EXAMPLE_BINARY_REPRESENTATIONS = [ (tags.TaggedObject(tags.NULL, None), b'\x00'), (tags.TaggedObject(tags.TRUE, True), b'\x01'), (tags.TaggedObject(tags.FALSE, False), b'\x02'), @@ -44,16 +44,46 @@ EXAMPLE_REPRESENTATIONS = [ ), ] -class SerializeTests(unittest.TestCase): +class BinarySerializeTests(unittest.TestCase): def test_serialize(self): - for tagged_object, expected in EXAMPLE_REPRESENTATIONS: + for tagged_object, expected in EXAMPLE_BINARY_REPRESENTATIONS: actual = binary.serialize(tagged_object) self.assertEqual(expected, actual) -class DeserializeTests(unittest.TestCase): +class BinaryDeserializeTests(unittest.TestCase): def test_deserialize(self): - for expected, representation in EXAMPLE_REPRESENTATIONS: + 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() -- 2.20.1