Commits

rkruppe  committed 0c058bd

Also port encoder

  • Participants
  • Parent commits 7810988
  • Branches experiments

Comments (0)

Files changed (2)

File tutagx/meta/to_yaml.py

 from collections import namedtuple
 import yaml
 
-from tutagx.meta import model
+from tutagx.util import codegen as cg
+from tutagx.meta import model, process
 from tutagx.meta.model import ModelMeta
-from tutagx.meta.common import FunctionCodeGen
+from tutagx.meta.common import wrap_model, typeident
 
 _WriterNamespace = namedtuple('_WriterNamespace', 'seen')
 
 
-class _Encoder(FunctionCodeGen):
-    _TARGET_FUNC_ATTR = 'encode_func'
-    _ARGS = ('obj', 'seen')
-    _HINT_PREFIX = 'encode_'
+@process.oneshot
+def _encoder():
+    RESERVED = ('item', 'synth_id', 'oid', 'result', 'CLASSES', 'kind')
+    sig = cg.Signature('$obj', 'seen')
+    symtab = cg.SymbolTable(RESERVED, sig)
+    emit = cg.CodeEmitter()
+    classes = {}
 
-    def __init__(self, *args):
-        self._classes = {}
-        super().__init__(*args, reserved=(
-            'item', 'synth_id', 'oid', 'result', 'CLASSES',
-            'kind'
-        ))
-
-    def _conversion(self, expr, t):
-        # see from_yaml._Decoder's identically-named method
+    def conversion(expr, t):
         if isinstance(t, (model.Integer, model.Float, model.String)):
             return expr
         if isinstance(t, model.List):
-            return self._list_conv(t, expr)
+            list_conv = yield from list_conv(t, expr)
+            return conv
         if isinstance(t, model.Maybe):
-            t_conv = self._conversion(expr, t.t)
+            t_conv = yield from conversion(expr, t.t)
             if t_conv == expr:
                 return expr
-            return '(None if {0} is None else ({1}))'.format(expr, t_conv)
-        # Intercept ANY recursion on reference types, even for the top-level
-        # type which was passed to __init__ (we only generate the function for
-        # it at all because the superclass starts by calling visit).
+            return '(None if {0} is None else {1})'.format(expr, t_conv)
         if isinstance(t, model.Ref):
-            return '({}).object_id'.format(expr)
-        return self.genfunc(t) + '(' + expr + ', seen)'
+            return expr + '.object_id'
+        func = yield t
+        return sig.call(func, expr)
 
-    def generic_visit(self, node):
-        self.line('return obj')
-
-    def visit_ref(self, node):
-        if node is self.toplevel_node:
-            self._struct(ModelMeta.struct_for(node), value=False)
-        else:
-            self.line('return obj.object_id')
-
-    def visit_value(self, node):
-        return self._struct(ModelMeta.struct_for(node), value=True)
-
-    def _list_conv(self, node, list_expr):
-        item_expr = self._conversion('item', node.items)
+    def _list_conv(node, list_expr):
+        item_expr = yield from conversion('item', node.items)
         return '[{} for item in {}]'.format(item_expr, list_expr)
 
-    def visit_list(self, node):
-        with self.block('if id(obj) in seen:'):
-            self.line('raise RuntimeError("Lists must not be shared")')
-        self.line('seen[id(obj)] = None')
-        self.line('return {}', self._list_conv(node, 'obj'))
+    def _names_and_conversions(node):
+        struct = ModelMeta.struct_for(node)
+        member_names = tuple(name for name, _ in struct.members)
+        convs = []
+        for name, t in struct.members:
+            conv = yield from conversion('obj.' + name, t)
+            convs.append(conv)
+        return member_names, tuple(convs)
 
-    # Not a visit - we need information from the Ref to differentiate
-    # between reference types and value types.
-    def _struct(self, node, value, extra=None):
-        member_names = tuple(name for name, _ in node.members)
-        convs = tuple(
-            self._conversion('obj.{}'.format(name), t)
-            for name, t in node.members
-        )
-        if extra is None:
-            extra = {}
-        if value:
-            return self._visit_value_struct(member_names, convs, extra)
-        else:
-            return self._visit_ref_struct(member_names, convs, extra)
+    def visit_toplevel(cls):
+        node = wrap_model(cls)
+        if isinstance(node, model.Value):
+            yield from visit_value(node)
+            return
+        member_names, convs = yield from _names_and_conversions(node)
+        emit.line('synth_id = id(obj)')
+        emit.line('if synth_id in seen: return seen[synth_id]')
+        emit.line('oid = obj.object_id')
+        emit.line('seen[synth_id] = oid')
+        emit.line('result = {')
+        emit.indent()
+        for member_name, conv in zip(member_names, convs):
+            emit.linef('{!r}: {},', member_name, conv)
+        emit.dedent()
+        emit.line('}')
+        emit.line('result["$id"] = oid')
+        emit.line('return result')
 
-    # For structs, we currently use dicts to make the results more
-    # human-readable, but that's not strictly necessary.
-    # We could just as well use a list, as the order is defined
-    # and preserved, so each list item would be unambiguous.
-    # One could create a "viewer" tool from the model types
-    # to prettify this and other features.
-    # OTOH this allows preserving compability when reordering members
-    # (But if we want such compability universally, we might as well
-    # forget about member order completely.)
+    def visit_ref(node):
+        emit.line('return obj.object_id')
 
-    def _visit_ref_struct(self, member_names, convs, extra):
-        # Only called for top-level object, _conversion intercepts recursion
-        # into to make sure only one (reference type) object is actually
-        # encoded per call, even for self-referential types.
-        self.line('synth_id = id(obj)')
-        self.line('if synth_id in seen: return seen[synth_id]')
-        self.line('oid = obj.object_id')
-        self.line('seen[synth_id] = oid')
-        with self.block('result = {{'):
-            for member_name, conv in zip(member_names, convs):
-                self.line('{!r}: {},', member_name, conv)
-            for k, v in extra.items():
-                self.line('{!r}: {},', k, v)
-        self.line('}}')
-        self.line('result["$id"] = oid')
-        self.line('return result')
-
-    def _visit_value_struct(self, member_names, convs, extra):
-        self.line('synth_id = id(obj)')
-        with self.block('if synth_id in seen:'):
-            self.line(
-                'raise RuntimeError("{{}} is a value type '
+    def visit_value(node):
+        member_names, convs = yield from _names_and_conversions(node)
+        emit.line('synth_id = id(obj)')
+        with emit.block('if synth_id in seen'):
+            emit.line(
+                'raise RuntimeError("{{}} is a value type ',
                 'and must not be shared".format(obj))'
             )
-        self.line('seen[synth_id] = None')
-        with self.block('return {{'):
-            for member_name, conv in zip(member_names, convs):
-                self.line('{!r}: {},', member_name, conv)
-        self.line('}}')
+        emit.line('seen[synth_id] = None')
+        emit.line('return {')
+        emit.indent()
+        for member_name, conv in zip(member_names, convs):
+            emit.linef('{!r}: {},', member_name, conv)
+        emit.dedent()
+        emit.line('}')
 
-    def visit_union(self, node):
+    def visit_list(node):
+        with emit.block('if id(obj) in seen'):
+            emit.line('raise RuntimeError("Lists must not be shared")')
+        emit.line('seen[id(obj)] = None')
+        conv = yield from _list_conv(node, 'obj')
+        emit.line('return ', conv)
+
+    def visit_union(node):
         for tag, t in node.alternatives:
             cls = ModelMeta.model_for(t)
-            qualname = cls.__module__ + '.' + cls.__name__
-            self._classes[qualname] = cls
-            with self.block('if isinstance(obj, CLASSES[{!r}]):', qualname):
-                self.line('return {}', self._conversion('obj', t))
+            classes[cls.__qualname__] = cls
+            key = repr(cls.__qualname__)
+            with emit.block('if isinstance(obj, CLASSES[', key, '])'):
+                conv = yield from conversion('obj', t)
+                emit.line('return ', conv)
 
-    def make_namespace(self):
-        return {'CLASSES': self._classes}
+    visit_float = visit_integer = visit_string = NotImplemented
+    visit_dict = visit_maybe = visit_struct = NotImplemented
 
+    code_ns = {'CLASSES': classes}
+    return cg.generate_code(
+        locals(), typeident, sig, emit, symtab,
+        entry_point=visit_toplevel, code_ns=code_ns
+    )
 
 # By default, the "scalar style" is all-or-nothing.
 # Either *every single scalar* is forced into a particular style (which
 
 class YAMLWriter:
     def __init__(self, model):
-        enc = _Encoder(model)
-        self._encoder = enc.encode_func
+        self._encoder = _encoder(model)
 
     def encode(self, obj, namespace=None):
         return self.encode_many([obj], namespace)[0]

File tutagx/util/codegen.py

         The filename can be overriden via :arg:`src_name`.
         """
         src = self.text()
-        for i, line in enumerate(src.split('\n')):
-            print("!", i+1, line)
         return compile(src, src_name, 'exec')