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