File: enaml_compiler.py

package info (click to toggle)
python-enaml 0.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,284 kB
  • sloc: python: 31,443; cpp: 4,499; makefile: 140; javascript: 68; lisp: 53; sh: 20
file content (287 lines) | stat: -rw-r--r-- 13,574 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#------------------------------------------------------------------------------
# Copyright (c) 2013-2025, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
#------------------------------------------------------------------------------
import ast

from . import compiler_common as cmn
from .enaml_ast import Module
from .enamldef_compiler import EnamlDefCompiler
from .template_compiler import TemplateCompiler
from ..compat import PY311, PY313

# Increment this number whenever the compiler changes the code which it
# generates. This number is used by the import hooks to know which version
# of a .enamlc file is valid for the Enaml compiler version in use. If
# this number is not incremented on change, it may result in .enamlc
# files which fail on import.
#
# Version History
# ---------------
# 1 : Initial compiler version - 2 February 2012
# 2 : Update line number handling - 26 March 2012
#     When compiling code objects with mode='eval', Python ignores the
#     line number specified by the ast. The workaround is to compile the
#     code object, then make a new copy of it with the proper firstlineno
#     set via the types.CodeType constructor.
# 3 : Update the generated code to remove the toolkit - 21 June 2012
#     This updates the compiler for the coming switch to async UI's
#     which will see the removal of the Toolkit concept. The only
#     magic scope maintained is for that of operators.
# 4 : Update component building - 27 July 2012
#     This updates the compiler to handle the new Enaml creation semantics
#     that don't rely on __enaml_call__. Instead the parent is passed
#     directly to the component cls which is a subclass of Declarative.
#     That class handles calling the builder functions upon instance
#     creation. This allows us to get rid of the EnamlDef class and
#     make enamldef constructs proper subclasses of Declarative.
# 5 : Change the import names - 28 July 2012
#     This changes the imported helper name from _make_decl_subclass_
#     to _make_enamldef_helper_ which is more descriptive, but equally
#     mangled. It also updates the method name used on the Declarative
#     component for adding attribute from _add_decl_attr to the more
#     descriptive _add_user_attribute. Finally, it adds the eval_compile
#     function for compiling Python code in 'eval' mode with proper line
#     number handling.
# 6 : Compile with code tracing - 24 November 2012
#     This updates the compiler to generate code using the idea of code
#     tracing instead of monitors and inverters. The compiler compiles
#     the expressions into functions which are augmented to accept
#     additional arguments. These arguments are tracer objects which will
#     have methods called in response to bytecode ops executing. These
#     methods can then attach listeners as necessary. This is an easier
#     paradigm to develop with than the previous incarnation. This new
#     way also allows the compiler to generate the final code objects
#     upfront, instead of needed to specialize at runtime for a given
#     operator context. This results in a much smaller footprint since
#     then number of code objects created is n instead of n x m.
# 7 : Fix bug with local deletes - 10 December 2012
#     This fixes a bug in the locals optimization where the DELETE_NAME
#     opcode was not being replaced with DELETE_FAST.
# 8 : Generate description dicts instead of builders - 27 January 2013
#     This updates the compiler to generate marshalable description
#     dicts instead of builder functions. The responsibility of building
#     out the object tree has been shifted to the Declarative class. This
#     is a touch slower, but provides a ton more flexibility and enables
#     templated components like `Looper` and `Conditional`.
# 9 : Generate description dicts for attrs and events - 11 March 2013
#     This augments the description dictionary for an enamldef with
#     a list of dicts describing the 'attr' and 'event' keywords for
#     the given enamldef block. These dicts are used by the compiler
#     helper to generate atom members for the new class.
# 10 : Class time post processing and decorators - 17 March 2013
#     This moves a large amount of processing from instantiation time
#     to class definition time. In particular, operators are now bound
#     at the class level. This also adds support for decorators on an
#     enamldef block.
# 11 : Fix a bug in code generation for Python 2.6 - 18 March 2013
#     On Python 2.6 the LIST_APPEND instruction consumes the TOS. This
#     update adds a check for running on < 2.7 and dups the TOS.
# 12 : Post process an enamldef immediately. - 18 March 2013
#     This removes the need for the 2.6 check from version 11 since it
#     does not rely on the LIST_APPEND instruction. It also means
#     that widget names must appear before they are used, just like in
#     normal Python class bodies.
# 13 : Move the post processing of enamldefs to before running the
#     decorators. This means a decorator gets a complete class.
# 14 : Updates to the parser and ast to be more structured - 22 March 2013
#     This updates ast generated by the parser and updates the process
#     for class creation when a module is imported. The serialized data
#     which lives in the code object is unpacked into a construction
#     tree which is then used for various transformations.
# 15 : Complete reimplementation of the expression engine - 22 August 2013
#     This updates the compiler to generate the building logic so that
#     all of the type resolution and type hierarchy building is performed
#     at import time using native code instead of serialized dict and a
#     runtime resolver object (I have no idea what I was thinking with
#     with compiler versions 9 - 14).
# 16 : Support for templates - 9 September 2013
#     This overhauls the compiler with added support for templates to
#     the language grammar. The various compiler bits have been broken
#     out into their own classes and delegate to a CodeGenerator for
#     actually writing the bytecode operations. A large number of new
#     compiler helpers were needed for this, and they are now held in
#     a module level dictionary since the dict must persist for the
#     lifetime of the module in order to insantiate templates. The dict
#     helps remove namespace pollution.
# 17 : Support for aliases - 19 September 2013
#     The introduction of templates with version 16 introduced a strong
#     need for an alias construct. This version implements suppor for
#     such a thing. It was quite the overhaul and represents almost an
#     entirely new compiler.
# 18 : Allow const exprs to raise unsquashed - 20 September 2013
#     There was a bug in the code generated for evaluating template
#     const expressions, where an error raised by a function called
#     by the expression would have its traceback erroneously squashed.
#     This version fixes that bug.
# 19 : Fix a bug in variadic template args - 20 September 2013
#     The code generated for variadic template functions did not set
#     the varargs flag on the code object. This is now fixed.
# 20 : Fix a bug in template instantiation scoping - 13 January 2014
#     The generated code did not properly handle the scope key for
#     binding expressions on template instantiations.
#     https://github.com/nucleic/enaml/issues/78
# 21 : Add support for declarative functions - 2 May 2014
#     This update add support for the 'func' keyword and '->' style
#     declarative method overrides.
# 22 : Update the syntax of arrow functions - 5 May 2014
#     This updates the arrow functions to use "=>" instead of "->".
# 23 : Support for Python 3 and inlining of comprehensions.
# 24 : Call comprehension functions in the proper scope rather than inlining
# 25 : Support for Python 3.6
# 26 : Wrap functions defined inside operators or declarative function to call
#      them with their scope of definition. This allows to handle properly
#      comprehensions and lambdas. Also ensure that we compile the body of the
#      :: operator as a function to properly handle closure.
COMPILER_VERSION = 26


# Code that will be executed at the top of every enaml module
STARTUP = ['from enaml.core.compiler_helpers import __compiler_helpers']


# Cleanup code that will be included at the end of every enaml module
CLEANUP = []


class EnamlCompiler(cmn.CompilerBase):
    """ A compiler which will compile an Enaml module.

    The entry point is the `compile` classmethod which will compile
    the ast into an appropriate python code object for a module.

    """
    @classmethod
    def compile(cls, node, filename):
        """ The main entry point of the compiler.

        Parameters
        ----------
        node : Module
            The enaml ast Module node that should be compiled.

        filename : str
            The string filename of the module ast being compiled.

        Returns
        -------
        result : CodeType
            The code object for the compiled module.

        """
        assert isinstance(node, Module), 'invalid node'

        # Create the compiler and generate the code.
        compiler = cls(filename=filename)
        return compiler.visit(node)

    def visit_Module(self, node):
        cg = self.code_generator

        # Generate the startup code for the module.
        cg.set_lineno(1)
        for start in STARTUP:
            cg.insert_python_block(ast.parse(start))

        # Create the template map.
        cg.build_map()
        cg.store_global(cmn.TEMPLATE_MAP)

        # Populate the body of the module.
        for item in node.body:
            self.visit(item)

        # Delete the template map.
        cg.delete_global(cmn.TEMPLATE_MAP)

        # Generate the cleanup code for the module.
        for end in CLEANUP:
            cg.insert_python_block(ast.parse(end))

        # Finalize the ops and return the code object.
        cg.load_const(None)
        cg.return_value()
        return cg.to_code()

    def visit_PythonModule(self, node):
        # Inline the bytecode for the Python statement block.
        cg = self.code_generator
        cg.set_lineno(node.lineno)
        cg.insert_python_block(node.ast)

    def visit_EnamlDef(self, node):
        # Invoke the enamldef code and store result in the namespace.
        cg = self.code_generator
        if not PY313 and PY311:
            cg.push_null()
        code = EnamlDefCompiler.compile(node, cg.filename)
        cg.load_const(code)
        cg.make_function()
        if PY313:
            cg.push_null()
        cg.call_function()
        cg.store_global(node.typename)

    def visit_Template(self, node):
        cg = self.code_generator
        cg.set_lineno(node.lineno)

        with cg.try_squash_raise():
            # Python 3.11 and 3.12 requires a NULL before a function that is not a method
            # Python 3.13 one after
            if not PY313 and PY311:
                cg.push_null()

            # Load and validate the parameter specializations
            for index, param in enumerate(node.parameters.positional):
                spec = param.specialization
                if spec is not None:
                    # Python 3.11 and 3.12 requires a NULL before a function that is not a method
                    # Python 3.13 one after
                    if not PY313 and PY311:
                        cg.push_null()
                    cmn.load_helper(cg, 'validate_spec', from_globals=True)
                    if PY313:
                        cg.push_null()
                    cg.load_const(index)
                    cmn.safe_eval_ast(
                        cg, spec.ast, node.name, param.lineno, set()
                    )
                    cg.call_function(2)
                else:
                    cg.load_const(None)

            # Store the specializations as a tuple
            cg.build_tuple(len(node.parameters.positional))

            # Evaluate the default parameters
            for param in node.parameters.keywords:
                cmn.safe_eval_ast(
                    cg, param.default.ast, node.name, param.lineno, set()
                )

            # Under Python 3.6+ default positional arguments are passed as a
            # single tuple and MAKE_FUNCTION is passed the flag 0x01 to
            # indicate that there is default positional arguments.
            cg.build_tuple(len(node.parameters.keywords))            # tuple

            # Generate the template code and function
            code = TemplateCompiler.compile(node, cg.filename)
            cg.load_const(code)                                      # tuple -> code
            cg.make_function(0x01)                                   # tuple -> func

            # Load and call the helper which will build the template
            cmn.load_helper(cg, 'make_template', from_globals=True)  # tuple -> func -> helper
            cg.rot_three()                                           # helper -> func -> tuple
            if PY313:
                cg.push_null()  # helper -> func -> tuple -> null
                cg.rot_three()  # helper -> null -> func -> tuple
            cg.load_const(node.name)
            cg.load_global('globals', push_null=True)
            cg.call_function()
            cg.load_global(cmn.TEMPLATE_MAP)
            cg.call_function(5)
            cg.pop_top()