X-Git-Url: https://code.kerkeslager.com/?a=blobdiff_plain;f=don%2Fstring.py;h=e22b328986153cb06ea00e8f2e057c9d1adb5fa7;hb=3e66f7dbcc1ed83ae27197d982bb14b32d8e4d02;hp=a7974a418014c63a030832c614c7356abc93301e;hpb=d19bdaf03aeb26d4812600347273a27d2c9b9caa;p=ton diff --git a/don/string.py b/don/string.py index a7974a4..e22b328 100644 --- a/don/string.py +++ b/don/string.py @@ -1,6 +1,8 @@ import binascii +import collections +import re -from don import constants, _shared +from don import tags, _shared def _integer_size_to_string_serializer(integer_size): minimum = -(2 ** (integer_size - 1)) @@ -36,27 +38,235 @@ def _string_serialize_dictionary(d): return '{ ' + ', '.join(map(serialize_kvp, d.items())) + ' }' _STRING_SERIALIZERS = { - constants.VOID: lambda o: 'null', - constants.TRUE: lambda o: 'true', - constants.FALSE: lambda o: 'false', - constants.INT8: _integer_size_to_string_serializer(8), - constants.INT16: _integer_size_to_string_serializer(16), - constants.INT32: _integer_size_to_string_serializer(32), - constants.INT64: _integer_size_to_string_serializer(64), - constants.FLOAT: _serialize_float, - constants.DOUBLE: _serialize_double, - constants.BINARY: _serialize_binary, - constants.UTF8: _utf_encoding_to_serializer('utf8'), - constants.UTF16: _utf_encoding_to_serializer('utf16'), - constants.UTF32: _utf_encoding_to_serializer('utf32'), - constants.LIST: _string_serialize_list, - constants.DICTIONARY: _string_serialize_dictionary, + tags.VOID: lambda o: 'null', + tags.TRUE: lambda o: 'true', + tags.FALSE: lambda o: 'false', + tags.INT8: _integer_size_to_string_serializer(8), + tags.INT16: _integer_size_to_string_serializer(16), + tags.INT32: _integer_size_to_string_serializer(32), + tags.INT64: _integer_size_to_string_serializer(64), + tags.FLOAT: _serialize_float, + tags.DOUBLE: _serialize_double, + tags.BINARY: _serialize_binary, + tags.UTF8: _utf_encoding_to_serializer('utf8'), + tags.UTF16: _utf_encoding_to_serializer('utf16'), + tags.UTF32: _utf_encoding_to_serializer('utf32'), + tags.LIST: _string_serialize_list, + tags.DICTIONARY: _string_serialize_dictionary, } def serialize(o): - o = _shared._tag(o) + o = tags._tag(o) return _STRING_SERIALIZERS[o.tag](o.value) +def _make_constant_parser(constant, value): + def parser(s): + if s.startswith(constant): + result = _shared.ParseResult( + success = True, + value = value, + remaining = s[len(constant):], + ) + return result + + return _shared._FAILED_PARSE_RESULT + + return parser + +def _make_integer_parser(width): + matcher = re.compile(r'(-?\d+)i' + str(width)) + + def parser(s): + match = matcher.match(s) + + if match: + # TODO Validate that the integer is in range + return _shared.ParseResult( + success = True, + value = int(match.group(1)), + remaining = s[match.end():], + ) + + return _shared._FAILED_PARSE_RESULT + + return parser + +_BINARY32_MATCHER = re.compile(r'(-?\d+\.\d+)f') +_BINARY64_MATCHER = re.compile(r'(-?\d+\.\d+)d') + +def _binary32_parser(s): + match = _BINARY32_MATCHER.match(s) + + if match: + # TODO Validate that the float is in range + return _shared.ParseResult( + success = True, + value = float(match.group(1)), + remaining = s[match.end():], + ) + + return _shared._FAILED_PARSE_RESULT + +def _binary64_parser(s): + match = _BINARY64_MATCHER.match(s) + + if match: + # TODO Validate that the double is in range + return _shared.ParseResult( + success = True, + value = float(match.group(1)), + remaining = s[match.end():], + ) + + return _shared._FAILED_PARSE_RESULT + +_BINARY_MATCHER = re.compile(r'"([\da-f]*)"b') + +def _binary_parser(s): + match = _BINARY_MATCHER.match(s) + + if match: + return _shared.ParseResult( + success = True, + value = binascii.unhexlify(match.group(1)), + remaining = s[match.end():], + ) + + return _shared._FAILED_PARSE_RESULT + +def _make_utf_parser(encoding): + matcher = re.compile(r'"(.*?)"' + encoding) + + def parser(s): + match = matcher.match(s) + + if match: + return _shared.ParseResult( + success = True, + value = match.group(1), + remaining = s[match.end():], + ) + + return _shared._FAILED_PARSE_RESULT + + return parser + +def _prefix_with_comma(parser): + def wrapped(s): + if s.startswith(','): + s = s[1:] + + result = parser(s) + if not result.success: + raise Exception('Trailing comma before "{}"'.format(s)) + + return result + + return _shared._FAILED_PARSE_RESULT + + return wrapped + +def _comma_separate_and_wrap(wrapped_parser, start_wrap, end_wrap, typecaster): + parser_prefixed_with_comma = _prefix_with_comma(wrapped_parser) + + def parser(s): + if s.startswith(start_wrap): + s = s[1:] + else: + return _shared._FAILED_PARSE_RESULT + + value = [] + first = True + + parse_result = wrapped_parser(s) + + while parse_result.success: + value.append(parse_result.value) + s = parse_result.remaining + parse_result = parser_prefixed_with_comma(s) + + if s.startswith(end_wrap): + s = s[1:] + else: + return _shared._FAILED_PARSE_RESULT + + return _shared.ParseResult( + success = True, + value = typecaster(value), + remaining = s, + ) + + return parser + +# This uses _PARSERS which has not been defined yet, but is defined here so it can be used in +# the definition of _list_parser +def _object_parser(source): + for parser in _PARSERS: + result = parser(source) + + if result.success: + return result + + return _shared._FAILED_PARSE_RESULT + +_list_parser = _comma_separate_and_wrap(_object_parser, '[', ']', list) + +def _kvp_parser(s): + key_parse_result = _object_parser(s) + if key_parse_result.success: + s = key_parse_result.remaining + else: + return _shared._FAILED_PARSE_RESULT + + if s.startswith(':'): + s = s[1:] + else: + return _shared._FAILED_PARSE_RESULT + + value_parse_result = _object_parser(s) + if value_parse_result.success: + s = value_parse_result.remaining + else: + return _shared._FAILED_PARSE_RESULT + + return _shared.ParseResult( + success = True, + value = (key_parse_result.value, value_parse_result.value), + remaining = s, + ) + +_dictionary_parser = _comma_separate_and_wrap(_kvp_parser, '{', '}', collections.OrderedDict) + + +_PARSERS = [ + _make_constant_parser('null', None), + _make_constant_parser('true', True), + _make_constant_parser('false', False), + _make_integer_parser(8), + _make_integer_parser(16), + _make_integer_parser(32), + _make_integer_parser(64), + _binary32_parser, + _binary64_parser, + _binary_parser, + _make_utf_parser('utf8'), + _make_utf_parser('utf16'), + _make_utf_parser('utf32'), + _list_parser, + _dictionary_parser, +] + +def _parse(parser, source): + result = parser(source) + + if result.success: + if result.remaining.strip() == '': + return result.value + + raise Exception('Unparsed trailing characters: "{}"'.format(result.remaining)) + + raise Exception('Unable to parse: "{}"'.format(source)) + def deserialize(s): - pass + return _parse(_object_parser, s)