Commits

rkruppe  committed 7810988

Start porting serialization to new codegen; simple cases work

  • Participants
  • Parent commits c1deafe
  • Branches experiments

Comments (0)

Files changed (3)

File tutagx/meta/from_yaml.py

 import itertools
 import yaml
 
-from tutagx.meta import model, validate
-from tutagx.meta.model import ModelMeta
-from tutagx.meta.common import FunctionCodeGen
+from tutagx.meta import model, validate, process
+from tutagx.meta.model import ModelMeta, Value
+from tutagx.meta.common import typeident, wrap_model
+import tutagx.util.codegen as cg
 
 _ReaderNamespace = namedtuple('_ReaderNamespace', 'seen unfinished extra')
 
     # which refer to the placeholder, cycles would be inevitable.)
 
 
-class _Decoder(FunctionCodeGen):
-    _TARGET_FUNC_ATTR = 'decode_func'
-    _HINT_PREFIX = 'decode_'
-    _ARGS = ('obj', 'seen', 'unfinished', 'toplevel')
-    # For subclasses
-    _EXTRA_ARGS = ()
+@process.oneshot
+def _decoder():
+    RESERVED = ('classes', 'result', 'oid', 'json_val', 'k', '__Placeholder')
+    sig = cg.Signature('$obj', 'seen', 'unfinished')
+    symtab = cg.SymbolTable(RESERVED, sig)
+    emit = cg.CodeEmitter()
 
-    def __init__(self, *args):
-        self._classes = {}
-        self._ARGS += self._EXTRA_ARGS
-        super().__init__(*args, reserved=(
-            'classes', 'result', 'oid', 'json_val', 'k', '__Placeholder'
-        ))
+    classes = {}
 
-    def _conversion(self, expr, t):
-        # Takes a string that's a valid expression in the current scope,
-        # evaluating to an object whose model type is described by node.
-        # Return a string with semantics equivalent to
-        #   self.genfunc(node) + '(' + expr + ', seen)'
-
-        # In fact, it will be such a function call for nontrivial types.
-        # But for many simpler types, it inlines the definition,
-        # simplifying the resulting code - both more readable and faster
+    def conversion(expr, t):
         if isinstance(t, (model.Integer, model.Float, model.String)):
-            return expr  # primitive value
+            return expr
         if isinstance(t, model.List):
-            return self._list_conv(t, expr)
+            return list_conv(t, expr)
         if isinstance(t, model.Dict):
-            return self._dict_conv(t, expr)
+            return dict_conv(t, expr)
         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)
-        return self._descend(self.genfunc(t), expr)
+        else:
+            func = yield t
+            return sig.call(func, expr)
 
-    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 _dict_conv(self, node, dict_expr):
-        key_expr = self._conversion('k', node.keys)
-        val_expr = self._conversion('{}[k]'.format(dict_expr), node.values)
+    def dict_conv(node, dict_expr):
+        key_expr = yield from conversion('k', node.keys)
+        val_expr = yield from conversion(str(dict_expr) + '[k]', node.values)
         return '{{{}: {} for k in {}}}'.format(key_expr, val_expr, dict_expr)
 
-    def _descend(self, func, expr, *extra):
-        args = (expr, 'seen', 'unfinished', 'False') + extra
-        return '{}({})'.format(func, ', '.join(args))
-
-    # These three steps are pulled out to allow overriding object creation
-    # to some extend (specifically, for tutagx.model.{entity,context}
-    # which requires object creation to go though a builder).
-
-    def _construct_object(self, target, cls):
-        self.line(
-            '{} = classes[{!r}, {!r}](already_complete=False)',
+    def _construct_object(target, cls):
+        emit.linef('{} = classes[{!r}, {!r}](already_complete=False)',
             target, cls.__module__, cls.__name__
         )
 
-    def _add_attributes(self, target, params):
+    def _add_attributes(target, params):
         for name, expr in params:
-            self.line('{}.{} = {}', target, name, expr)
+            emit.linef('{}.{} = {}', target, name, expr)
 
-    def _finish_constructed(self, obj):
-        self.line('{}._on_loaded()', obj)
+    def _finish_constructed(obj):
+        emit.line(obj, '._on_loaded()')
 
-    def visit_ref(self, node):
+    def visit_toplevel(node):
+        if isinstance(node, Value):
+            yield from visit_value(node)
+            return
+        cls = ModelMeta.model_for(node)
+        struct = ModelMeta.struct_for(node)
+        classes[cls.__module__, cls.__name__] = cls
+        member_names = tuple(name for name, _ in struct.members)
+        loaders = []
+        for name, t in struct.members:
+            conv = yield from conversion('obj[{!r}]'.format(name), t)
+            loaders.append(conv)
+
+        emit.line('oid = obj["$id"]')
+        emit.line('if oid in unfinished: result = seen[oid]')
+        with emit.block('else'):
+            _construct_object('result', cls)
+            emit.line('seen[oid] = result')
+        emit.line('unfinished.discard(oid)')
+        emit.line('result.object_id = oid')
+        _add_attributes('result', zip(member_names, loaders))
+        _finish_constructed('result')
+        emit.line('return result')
+
+    def visit_ref(node):
         cls = ModelMeta.model_for(node)
         struct_node = ModelMeta.struct_for(node)
-        self._classes[cls.__module__, cls.__name__] = cls
-        return self._visit_struct(struct_node, cls, cls.is_value_type)
+        classes[cls.__module__, cls.__name__] = cls
+        emit.line('oid = obj')
+        emit.line(
+            'assert isinstance(oid, str), repr(oid) + " is not an ID"'
+        )
+        emit.line('if oid in seen: return seen[oid]')
+        _construct_object('obj', cls)
+        emit.line('seen[oid] = obj')
+        emit.line('unfinished.add(oid)')
+        emit.line('return obj')
 
-    visit_value = visit_ref  # happens to be identical for this purpose
+    def visit_value(node):
+        cls = ModelMeta.model_for(node)
+        struct = ModelMeta.struct_for(node)
+        classes[cls.__module__, cls.__name__] = cls
+        member_names = tuple(name for name, _ in struct.members)
+        loaders = []
+        for name, t in struct.members:
+            conv = yield from conversion('obj[{!r}]'.format(name), t)
+            loaders.append(conv)
+
+        _construct_object('result', cls)
+        _add_attributes('result', zip(member_names, loaders))
+        _finish_constructed('result')
+        emit.line('return result')
 
     # NOT a visit, as we can only handle structs behind Ref/Value.
     # We needs a way to get to the class behind the model, to create objects.
     # But of the model types, only Ref and Value store the real class.
-    def _visit_struct(self, node, cls, value):
+    def do_struct(node, cls, value):
         member_names = tuple(name for name, _ in node.members)
-        loaders = tuple(
-            self._conversion('obj[{!r}]'.format(name), t)
-            for name, t in node.members
-        )
+        loaders = []
+        for name, t in node.members:
+            conv = yield from conversion('obj[{!r}]'.format(name), t)
+            loaders.append(conv)
         if value:
-            visit = self._visit_value_struct
+            visit = do_value_struct
         else:
-            visit = self._visit_ref_struct
+            visit = do_ref_struct
         visit(cls, member_names, loaders)
 
-    def _visit_ref_struct(self, cls, member_names, load_exprs):
-        # The object graph may be split in multiple parts, and loaded in any
-        # order. Thus this complex logic.
-        # ``unfinished`` it a set of object IDs that have been encountered
-        # before, but have not been loaded yet.
-        with self.block('if not toplevel:'):
-            self.line('oid = obj')
-            self.line(
-                'assert isinstance(oid, str), repr(oid) + " is not an ID"'
-            )
-            self.line('if oid in seen: return seen[oid]')
-            # Because we can't load the object yet, we use a placeholder
-            self._construct_object('obj', cls)
-            self.line('seen[oid] = obj')
-            self.line('unfinished.add(oid)')
-            self.line('return obj')
+    visit_integer = visit_string = visit_float = NotImplemented
+    visit_list = visit_dict = NotImplemented
+    visit_struct = visit_maybe = NotImplemented
 
-        self.line('oid = obj["$id"]')
-        # The object may have been seen before and thus may already exist
-        # (unfinished) - we must reuse that object, to preserve sharing
-        self.line('if oid in unfinished: result = seen[oid]')
-        with self.block('else:'):
-            self._construct_object('result', cls)
-            self.line('seen[oid] = result')
-        self.line('unfinished.discard(oid)')
-        self.line('result.object_id = oid')
-        self._add_attributes('result', zip(member_names, load_exprs))
-        self._finish_constructed('result')
-        self.line('return result')
+    def visit_union(node):
+        with emit.block('if isinstance(obj, str)'):
+            emit.line('return __Placeholder()')
+        for tag, t in node.alternatives:
+            with emit.block('if obj["$type"] == ', repr(tag)):
+                conv = yield from conversion('obj', t)
+                emit.line('return ', conv)
 
-    def _visit_value_struct(self, cls, member_names, load_exprs):
-        self._construct_object('result', cls)
-        self._add_attributes('result', zip(member_names, load_exprs))
-        self._finish_constructed('result')
-        self.line('return result')
+    code_ns = {'classes': classes, '__Placeholder': _Placeholder}
 
-    def visit_struct(self, node):
-        raise TypeError("Cannot handle structs directly contained "
-                        "inside other types, i.e. without Ref/Value")
-
-    def visit_union(self, node):
-        with self.block('if isinstance(obj, str):'):
-            self.line('return __Placeholder()')
-        for tag, t in node.alternatives:
-            with self.block('if obj["$type"] == {!r}:', tag):
-                self.line('return {}', self._conversion('obj', t))
-
-    def make_namespace(self):
-        return {'classes': self._classes, '__Placeholder': _Placeholder}
+    return cg.generate_code(
+        locals(), typeident, sig, emit, symtab,
+        entry_point=visit_toplevel, code_ns=code_ns
+    )
 
 
 class YAMLReader:
 
     def __init__(self, model):
         self._mcls = type(model)
-        decoder_class = getattr(self._mcls, '_decoder', _Decoder)
-        decoder = decoder_class(model)
         self._cls = model
-        self._decoder = decoder.decode_func
+        self._decoder = _decoder(wrap_model(model))
 
     def decode(self, val, namespace=None, validator_ns=None, source=None):
         results, errors = self.decode_many(
             namespace = type(self).new_namespace()
         if validator_ns is None:
             validator_ns = validate.Validator.new_namespace()
-        args = (namespace.seen, namespace.unfinished, True) + namespace.extra
+        args = (namespace.seen, namespace.unfinished)
         validator = validate.Validator(self._cls)
         results = []
         for raw_value in raw_values:

File tutagx/model/entity.py

 from tutagx.model.context import GameContext
 
 
-class _EntityDecoder(from_yaml._Decoder):
+class _EntityDecoder:#(from_yaml._Decoder):
     _EXTRA_ARGS = ('gcx',)
 
     def _construct_object(self, target, cls):

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')