diff options
author | Robert Bradshaw <robertwb@gmail.com> | 2015-09-03 20:42:14 -0700 |
---|---|---|
committer | Robert Bradshaw <robertwb@gmail.com> | 2015-09-03 21:42:19 -0700 |
commit | 96eea730c633ebba37e779471fadaedaf267e0f6 (patch) | |
tree | 402615e4aa33a013b8724ab7bf313189f86b8af3 | |
parent | 7da496024a2ecb93a85de79756e4e89f2d289187 (diff) | |
download | cython-96eea730c633ebba37e779471fadaedaf267e0f6.tar.gz |
Make cpdef enums into first-class types.
For example
cpdef enum Eggs
SOFT
HARD
SCRAMBLED
produces three constants with int values that print as strings,
and a type Eggs with attributes Eggs.SOFT, etc. and list(Eggs)
giving the set of all enum values. Instantiating Eggs with a
numeric or string value will return the appropriate constant.
-rw-r--r-- | CHANGES.rst | 2 | ||||
-rw-r--r-- | Cython/Compiler/Nodes.py | 10 | ||||
-rw-r--r-- | Cython/Compiler/Pipeline.py | 25 | ||||
-rw-r--r-- | Cython/Compiler/UtilityCode.py | 11 | ||||
-rw-r--r-- | Cython/Utility/CpdefEnums.pyx | 41 | ||||
-rw-r--r-- | tests/run/cpdef_enums.pyx | 7 |
6 files changed, 94 insertions, 2 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index c96ce9bad..1d0a630b4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,8 @@ Features added * The embedded C code comments that show the original source code can be discarded with the new directive ``emit_code_comments=False``. +* Cpdef enums are now first-class iterable, callable types in Python. + Bugs fixed ---------- diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index afa4cf7bf..1f408507e 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -1488,7 +1488,7 @@ class CEnumDefNode(StatNode): self.entry = env.declare_enum(self.name, self.pos, cname = self.cname, typedef_flag = self.typedef_flag, visibility = self.visibility, api = self.api, - create_wrapper = self.create_wrapper) + create_wrapper = self.create_wrapper and self.name is None) def analyse_declarations(self, env): if self.items is not None: @@ -1496,6 +1496,12 @@ class CEnumDefNode(StatNode): self.entry.defined_in_pxd = 1 for item in self.items: item.analyse_declarations(env, self.entry) + if self.create_wrapper and self.name is not None: + from .UtilityCode import CythonUtilityCode + env.use_utility_code(CythonUtilityCode.load( + "EnumType", "CpdefEnums.pyx", + context={"name": self.name, "items": tuple(item.name for item in self.items)}, + outer_module_scope=env.global_scope())) def analyse_expressions(self, env): return self @@ -1535,7 +1541,7 @@ class CEnumDefItemNode(StatNode): entry = env.declare_const(self.name, enum_entry.type, self.value, self.pos, cname = self.cname, visibility = enum_entry.visibility, api = enum_entry.api, - create_wrapper = enum_entry.create_wrapper) + create_wrapper = enum_entry.create_wrapper and enum_entry.name is None) enum_entry.enum_values.append(entry) if enum_entry.name: enum_entry.type.values.append(entry.cname) diff --git a/Cython/Compiler/Pipeline.py b/Cython/Compiler/Pipeline.py index 66d496701..d6f9622f0 100644 --- a/Cython/Compiler/Pipeline.py +++ b/Cython/Compiler/Pipeline.py @@ -80,11 +80,36 @@ def use_utility_code_definitions(scope, target, seen=None): elif entry.as_module: use_utility_code_definitions(entry.as_module, target, seen) +def sort_utility_codes(utilcodes): + ranks = {} + def get_rank(utilcode): + if utilcode not in ranks: + ranks[utilcode] = 0 + ranks[utilcode] = 1 + min([get_rank(dep) for dep in utilcode.requires or ()] or [-1]) + return ranks[utilcode] + for utilcode in utilcodes: + get_rank(utilcode) + return [utilcode for utilcode, _ in sorted(ranks.items(), key=lambda kv: kv[1])] + +def normalize_deps(utilcodes): + deps = {} + for utilcode in utilcodes: + deps[utilcode] = utilcode + def unify_dep(dep): + if dep in deps: + return deps[dep] + else: + deps[dep] = dep + return dep + for utilcode in utilcodes: + utilcode.requires = [unify_dep(dep) for dep in utilcode.requires or ()] def inject_utility_code_stage_factory(context): def inject_utility_code_stage(module_node): module_node.prepare_utility_code() use_utility_code_definitions(context.cython_scope, module_node.scope) + module_node.scope.utility_code_list = sort_utility_codes(module_node.scope.utility_code_list) + normalize_deps(module_node.scope.utility_code_list) added = [] # Note: the list might be extended inside the loop (if some utility code # pulls in other utility code, explicitly or implicitly) diff --git a/Cython/Compiler/UtilityCode.py b/Cython/Compiler/UtilityCode.py index b29755dd5..592a5aadb 100644 --- a/Cython/Compiler/UtilityCode.py +++ b/Cython/Compiler/UtilityCode.py @@ -146,6 +146,16 @@ class CythonUtilityCode(Code.UtilityCodeBase): pipeline = Pipeline.insert_into_pipeline(pipeline, scope_transform, before=transform) + for dep in self.requires: + if isinstance(dep, CythonUtilityCode): + def scope_transform(module_node): + module_node.scope.merge_in(dep.tree.scope) + return module_node + + transform = ParseTreeTransforms.AnalyseDeclarationsTransform + pipeline = Pipeline.insert_into_pipeline(pipeline, scope_transform, + before=transform) + if self.outer_module_scope: # inject outer module between utility code module and builtin module def scope_transform(module_node): @@ -158,6 +168,7 @@ class CythonUtilityCode(Code.UtilityCodeBase): (err, tree) = Pipeline.run_pipeline(pipeline, tree, printtree=False) assert not err, err + self.tree = tree return tree def put_code(self, output): diff --git a/Cython/Utility/CpdefEnums.pyx b/Cython/Utility/CpdefEnums.pyx new file mode 100644 index 000000000..82ea19c88 --- /dev/null +++ b/Cython/Utility/CpdefEnums.pyx @@ -0,0 +1,41 @@ +#################### EnumBase #################### + +cimport cython + +@cython.internal +cdef class __Pyx_EnumMeta(type): + def __init__(cls, name, parents, dct): + type.__init__(cls, name, parents, dct) + cls.__values__ = [] + def __iter__(cls): + return iter(getattr(cls, '__values__', ())) + +# @cython.internal +cdef type __Pyx_EnumBase +class __Pyx_EnumBase(int): + __metaclass__ = __Pyx_EnumMeta + def __new__(cls, value, name=None): + for v in cls.__values__: + if v == value or v.name == value: + return v + if name is None: + raise ValueError("Unknown enum value: '%s'" % value) + res = int.__new__(cls, value) + res.name = name + setattr(cls, name, res) + cls.__values__.append(res) + return res + def __repr__(self): + return self.name + def __str__(self): + return self.name + +#################### EnumType #################### +#@requires: EnumBase + +class {{name}}(__Pyx_EnumBase): + pass +cdef dict __Pyx_globals = globals() +{{for item in items}} +__Pyx_globals['{{item}}'] = {{name}}({{item}}, '{{item}}') +{{endfor}} diff --git a/tests/run/cpdef_enums.pyx b/tests/run/cpdef_enums.pyx index 9dab5ee47..9849b9730 100644 --- a/tests/run/cpdef_enums.pyx +++ b/tests/run/cpdef_enums.pyx @@ -32,6 +32,13 @@ True >>> RANK_3 # doctest: +ELLIPSIS Traceback (most recent call last): NameError: ...name 'RANK_3' is not defined + +>>> list(PyxEnum) +[TWO, THREE, FIVE] +>>> PyxEnum.TWO + PyxEnum.THREE == PyxEnum.FIVE +True +>>> PyxEnum(2) is PyxEnum("TWO") is PyxEnum.TWO +True """ |