From c0d3f379053d3d95cbac5ca6539101e8b68d59b3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 3 Jun 2022 21:13:59 +0200 Subject: [PATCH] python: merge(), and support for and-pattern unparsing --- implementations/python/preserves/__init__.py | 2 + implementations/python/preserves/merge.py | 42 ++++++++++++++++++++ implementations/python/preserves/schema.py | 4 +- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 implementations/python/preserves/merge.py diff --git a/implementations/python/preserves/__init__.py b/implementations/python/preserves/__init__.py index 7a163ef..3ec1b12 100644 --- a/implementations/python/preserves/__init__.py +++ b/implementations/python/preserves/__init__.py @@ -6,6 +6,8 @@ from .error import DecodeError, EncodeError, ShortPacket from .binary import Decoder, Encoder, decode, decode_with_annotations, encode, canonicalize from .text import Parser, Formatter, parse, parse_with_annotations, stringify +from .merge import merge + from . import fold loads = parse diff --git a/implementations/python/preserves/merge.py b/implementations/python/preserves/merge.py new file mode 100644 index 0000000..c04fac1 --- /dev/null +++ b/implementations/python/preserves/merge.py @@ -0,0 +1,42 @@ +from .values import ImmutableDict, dict_kvs, Embedded, Record + +def merge_embedded_id(a, b): + return a if a is b else None + +def merge(v0, *vs, merge_embedded=None): + v = v0 + for vN in vs: + v = merge2(v, vN, merge_embedded=merge_embedded) + return v + +def _die(): + raise ValueError('Cannot merge items') + +def merge_seq(aa, bb, merge_embedded=None): + if len(aa) != len(bb): _die() + return [merge2(a, b, merge_embedded=merge_embedded) for (a, b) in zip(aa, bb)] + +def merge2(a, b, merge_embedded=None): + if a == b: + return a + if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): + return merge_seq(a, b) + if isinstance(a, (set, frozenset)) and isinstance(b, (set, frozenset)): + _die() + if isinstance(a, dict) and isinstance(b, dict): + r = {} + for (ak, av) in a.items(): + bv = b.get(ak, None) + r[ak] = av if bv is None else merge2(av, bv, merge_embedded=merge_embedded) + for (bk, bv) in b.items(): + if bk not in r: + r[bk] = bv + return r + if isinstance(a, Record) and isinstance(b, Record): + return Record(merge2(a.key, b.key, merge_embedded=merge_embedded), + merge_seq(a.fields, b.fields, merge_embedded=merge_embedded)) + if isinstance(a, Embedded) and isinstance(b, Embedded): + m = (merge_embedded or merge_embedded_id)(a.embeddedValue, b.embeddedValue) + if m is None: _die() + return Embedded(m) + _die() diff --git a/implementations/python/preserves/schema.py b/implementations/python/preserves/schema.py index becdb23..a94f82c 100644 --- a/implementations/python/preserves/schema.py +++ b/implementations/python/preserves/schema.py @@ -319,7 +319,9 @@ def encode(p, v): return tuple(encode(pp, v) for pp in p[0]) + encode(p[1], v) if p.key == DICT: return dict((k, encode(pp, v)) for (k, pp) in p[0].items()) - raise ValueError('Bad schema') + if p.key == AND: + return merge(*[encode(pp, v) for pp in p[0]]) + raise ValueError(f'Bad schema {p}') def module_path_str(mp): return '.'.join([e.name for e in mp])