0a324cb694f2e26d64e4098f56240d775cc75917
[ton] / don / string.py
1 import binascii
2 import collections
3 import functools
4 import re
5
6 from don import tags, _shared
7
8 def _integer_size_to_string_serializer(integer_size):
9     minimum = -(2 ** (integer_size - 1))
10     maximum = 2 ** (integer_size - 1) - 1
11     
12     def serializer(integer):
13         assert minimum <= integer and integer <= maximum
14         return '{}i{}'.format(integer, integer_size)
15
16     return serializer
17
18 def _serialize_float(f):
19     return '{}f'.format(f)
20
21 def _serialize_double(d):
22     return '{}d'.format(d)
23
24 def _serialize_binary(b):
25     return '"{}"b'.format(binascii.hexlify(b).decode('ascii'))
26
27 def _utf_encoding_to_serializer(utf_encoding):
28     def serializer(s):
29         return '"{}"{}'.format(s, utf_encoding)
30
31     return serializer
32
33 def _string_serialize_list(l):
34     return '[{}]'.format(', '.join(map(serialize, l)))
35
36 def _string_serialize_dictionary(d):
37     def serialize_kvp(kvp):
38         return serialize(kvp[0]) + ': ' + serialize(kvp[1])
39     return '{ ' + ', '.join(map(serialize_kvp, d.items())) + ' }'
40
41 _STRING_SERIALIZERS = {
42     tags.VOID: lambda o: 'null',
43     tags.TRUE: lambda o: 'true',
44     tags.FALSE: lambda o: 'false',
45     tags.INT8: _integer_size_to_string_serializer(8),
46     tags.INT16: _integer_size_to_string_serializer(16),
47     tags.INT32: _integer_size_to_string_serializer(32),
48     tags.INT64: _integer_size_to_string_serializer(64),
49     tags.FLOAT: _serialize_float,
50     tags.DOUBLE: _serialize_double,
51     tags.BINARY: _serialize_binary,
52     tags.UTF8: _utf_encoding_to_serializer('utf8'),
53     tags.UTF16: _utf_encoding_to_serializer('utf16'),
54     tags.UTF32: _utf_encoding_to_serializer('utf32'),
55     tags.LIST: _string_serialize_list,
56     tags.DICTIONARY: _string_serialize_dictionary,
57 }
58
59 def serialize(o):
60     o = tags._tag(o)
61     
62     return _STRING_SERIALIZERS[o.tag](o.value)
63
64 def _consume_leading_whitespace(wrapped_parser):
65     @functools.wraps(wrapped_parser)
66     def parser(s):
67         s = s.lstrip()
68         return wrapped_parser(s)
69
70     return parser
71
72 def _make_constant_parser(constant, value):
73     @_consume_leading_whitespace
74     def constant_parser(s):
75         if s.startswith(constant):
76             result = _shared.ParseResult(
77                 success = True,
78                 value = value,
79                 remaining = s[len(constant):],
80             )
81             return result
82
83         return _shared._FAILED_PARSE_RESULT
84
85     return constant_parser
86
87 def _make_integer_parser(width):
88     matcher = re.compile(r'(-?\d+)i' + str(width))
89
90     @_consume_leading_whitespace
91     def integer_parser(s):
92         match = matcher.match(s)
93
94         if match:
95             # TODO Validate that the integer is in range
96             return _shared.ParseResult(
97                 success = True,
98                 value = int(match.group(1)),
99                 remaining = s[match.end():],
100             )
101
102         return _shared._FAILED_PARSE_RESULT
103
104     return integer_parser
105
106 _BINARY32_MATCHER = re.compile(r'(-?\d+\.\d+)f')
107 _BINARY64_MATCHER = re.compile(r'(-?\d+\.\d+)d')
108
109 @_consume_leading_whitespace
110 def _binary32_parser(s):
111     match = _BINARY32_MATCHER.match(s)
112
113     if match:
114         # TODO Validate that the float is in range
115         return _shared.ParseResult(
116             success = True,
117             value = float(match.group(1)),
118             remaining = s[match.end():],
119         )
120
121     return _shared._FAILED_PARSE_RESULT
122
123 @_consume_leading_whitespace
124 def _binary64_parser(s):
125     match = _BINARY64_MATCHER.match(s)
126
127     if match:
128         # TODO Validate that the double is in range
129         return _shared.ParseResult(
130             success = True,
131             value = float(match.group(1)),
132             remaining = s[match.end():],
133         )
134
135     return _shared._FAILED_PARSE_RESULT
136
137 _BINARY_MATCHER = re.compile(r'"([\da-f]*)"b')
138
139 @_consume_leading_whitespace
140 def _binary_parser(s):
141     match = _BINARY_MATCHER.match(s)
142
143     if match:
144         return _shared.ParseResult(
145             success = True,
146             value = binascii.unhexlify(match.group(1)),
147             remaining = s[match.end():],
148         )
149
150     return _shared._FAILED_PARSE_RESULT
151
152 def _make_utf_parser(encoding):
153     matcher = re.compile(r'"(.*?)"' + encoding)
154
155     @_consume_leading_whitespace
156     def utf_parser(s):
157         match = matcher.match(s)
158
159         if match:
160             return _shared.ParseResult(
161                 success = True,
162                 value = match.group(1),
163                 remaining = s[match.end():],
164             )
165
166         return _shared._FAILED_PARSE_RESULT
167
168     return utf_parser
169
170 def _prefix_with_comma(parser):
171     def wrapped(s):
172         if s.startswith(','):
173             s = s[1:]
174
175             result = parser(s)
176             if not result.success:
177                 raise Exception('Trailing comma before "{}"'.format(s))
178
179             return result
180
181         return _shared._FAILED_PARSE_RESULT
182
183     return wrapped
184
185 def _comma_separate_and_wrap(wrapped_parser, start_wrap, end_wrap, typecaster):
186     parser_prefixed_with_comma = _prefix_with_comma(wrapped_parser)
187
188     def parser(s):
189         if s.startswith(start_wrap):
190             s = s[1:]
191         else:
192             return _shared._FAILED_PARSE_RESULT
193
194         value = []
195         first = True
196
197         parse_result = wrapped_parser(s)
198
199         while parse_result.success:
200             value.append(parse_result.value)
201             s = parse_result.remaining
202             parse_result = parser_prefixed_with_comma(s)
203
204         if s.startswith(end_wrap):
205             s = s[1:]
206         else:
207             return _shared._FAILED_PARSE_RESULT
208
209         return _shared.ParseResult(
210             success = True,
211             value = typecaster(value),
212             remaining = s,
213         )
214
215     return parser
216
217 # This uses _PARSERS which has not been defined yet, but is defined here so it can be used in
218 # the definition of _list_parser
219 def _object_parser(source):
220     for parser in _PARSERS:
221         result = parser(source)
222
223         if result.success:
224             return result
225
226     return _shared._FAILED_PARSE_RESULT
227
228 _list_parser = _comma_separate_and_wrap(_object_parser, '[', ']', list)
229
230 def _kvp_parser(s):
231     key_parse_result = _object_parser(s)
232     if key_parse_result.success:
233         s = key_parse_result.remaining
234     else:
235         return _shared._FAILED_PARSE_RESULT
236
237     if s.startswith(':'):
238         s = s[1:]
239     else:
240         return _shared._FAILED_PARSE_RESULT
241
242     value_parse_result = _object_parser(s)
243     if value_parse_result.success:
244         s = value_parse_result.remaining
245     else:
246         return _shared._FAILED_PARSE_RESULT
247
248     return _shared.ParseResult(
249         success = True,
250         value = (key_parse_result.value, value_parse_result.value),
251         remaining = s,
252     )
253
254 _dictionary_parser = _comma_separate_and_wrap(_kvp_parser, '{', '}', collections.OrderedDict)
255
256
257 _PARSERS = [
258     _make_constant_parser('null', None),
259     _make_constant_parser('true', True),
260     _make_constant_parser('false', False),
261     _make_integer_parser(8),
262     _make_integer_parser(16),
263     _make_integer_parser(32),
264     _make_integer_parser(64),
265     _binary32_parser,
266     _binary64_parser,
267     _binary_parser,
268     _make_utf_parser('utf8'),
269     _make_utf_parser('utf16'),
270     _make_utf_parser('utf32'),
271     _list_parser,
272     _dictionary_parser,
273 ]
274
275 def _parse(parser, source):
276     result = parser(source)
277
278     if result.success:
279         if result.remaining.strip() == '':
280             return result.value
281
282         raise Exception('Unparsed trailing characters: "{}"'.format(result.remaining))
283
284     raise Exception('Unable to parse: "{}"'.format(source))
285
286 def deserialize(s):
287     return _parse(_object_parser, s)