Add serialization/deserialization for binary
[sandbox] / serial / serial / text.py
1 import binascii
2 import re
3
4 from . import tags
5
6 def _make_literal_serializer(expected_value, literal):
7     def serializer(to):
8         assert to.instance is expected_value
9         return literal
10
11     return serializer
12
13 def _make_integer_serializer(lower_bound, upper_bound, suffix):
14     def _serializer(to):
15         assert lower_bound <= to.instance and to.instance < upper_bound
16         return '{}{}'.format(to.instance, suffix)
17
18     return _serializer
19
20 def _make_unsigned_integer_serializer(bit_length):
21     return _make_integer_serializer(0, 2 << (bit_length - 1), 'u{}'.format(bit_length))
22
23 def _make_signed_integer_serializer(bit_length):
24     upper_bound = 2 << (bit_length - 2)
25     lower_bound = -upper_bound
26     return _make_integer_serializer(lower_bound, upper_bound, 'i{}'.format(bit_length))
27
28 def _serialize_binary(to):
29     return 'bin"{}"'.format(binascii.hexlify(to.instance).decode('ascii'))
30
31 _SERIALIZERS = {
32     tags.NULL: _make_literal_serializer(None, 'null'),
33     tags.TRUE: _make_literal_serializer(True, 'true'),
34     tags.FALSE: _make_literal_serializer(False, 'false'),
35     tags.UINT8: _make_unsigned_integer_serializer(8),
36     tags.UINT16: _make_unsigned_integer_serializer(16),
37     tags.UINT32: _make_unsigned_integer_serializer(32),
38     tags.UINT64: _make_unsigned_integer_serializer(64),
39     tags.INT8: _make_signed_integer_serializer(8),
40     tags.INT16: _make_signed_integer_serializer(16),
41     tags.INT32: _make_signed_integer_serializer(32),
42     tags.INT64: _make_signed_integer_serializer(64),
43     tags.BINARY: _serialize_binary,
44 }
45
46 def serialize(to):
47     return _SERIALIZERS[to.tag](to)
48
49 def _make_literal_deserializer(tag, instance, literal):
50     def _deserializer(s):
51         if s.startswith(literal):
52             return True, tags.TaggedObject(tag = tag, instance = instance), s[len(literal):]
53
54         return False, None, None
55
56     return _deserializer
57
58 def _make_regex_deserializer(tag, decoder, regex):
59     matcher = re.compile(regex).match
60
61     def _deserializer(s):
62         match = matcher(s)
63
64         if match is None:
65             return False, None, None
66
67         return True, tags.TaggedObject(tag = tag, instance = decoder(match)), s[match.end():]
68
69     return _deserializer
70
71 def _make_unsigned_int_deserializer(tag, bit_length):
72     bound = 2 << (bit_length - 1)
73
74     def _decoder(match):
75         result = int(match.group(1))
76         assert result < bound
77         return result
78
79     return _make_regex_deserializer(tag, _decoder, r'(\d+)' + 'u{}'.format(bit_length))
80
81 def _make_signed_int_deserializer(tag, bit_length):
82     upper_bound = 2 << (bit_length - 2)
83     lower_bound = -upper_bound
84
85     def _decoder(match):
86         result = int(match.group(1))
87         assert lower_bound <= result and result < upper_bound
88         return result
89
90     return _make_regex_deserializer(tag, _decoder, r'(-?\d+)' + 'i{}'.format(bit_length))
91
92 _BINARY_MATCHER = re.compile(r'bin"([\da-f]*)"').match
93
94 def _deserialize_binary(s):
95     match = _BINARY_MATCHER(s)
96
97     if match is None:
98         return False, None, None
99
100     result = tags.TaggedObject(
101         tag = tags.BINARY,
102         instance = binascii.unhexlify(match.group(1)),
103     )
104
105     return True, result, s[match.end():]
106
107 _DESERIALIZERS = [
108     _make_literal_deserializer(tags.NULL, None, 'null'),
109     _make_literal_deserializer(tags.TRUE, True, 'true'),
110     _make_literal_deserializer(tags.FALSE, False, 'false'),
111     _make_unsigned_int_deserializer(tags.UINT8, 8),
112     _make_unsigned_int_deserializer(tags.UINT16, 16),
113     _make_unsigned_int_deserializer(tags.UINT32, 32),
114     _make_unsigned_int_deserializer(tags.UINT64, 64),
115     _make_signed_int_deserializer(tags.INT8, 8),
116     _make_signed_int_deserializer(tags.INT16, 16),
117     _make_signed_int_deserializer(tags.INT32, 32),
118     _make_signed_int_deserializer(tags.INT64, 64),
119     _deserialize_binary,
120 ]
121
122 def deserialize(s):
123     for deserializer in _DESERIALIZERS:
124         succeeded, result, remaining = deserializer(s)
125
126         if succeeded:
127             assert remaining == ''
128             return result
129
130     raise Exception()