summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Bradshaw <robertwb@gmail.com>2015-09-03 20:42:14 -0700
committerRobert Bradshaw <robertwb@gmail.com>2015-09-03 21:42:19 -0700
commit96eea730c633ebba37e779471fadaedaf267e0f6 (patch)
tree402615e4aa33a013b8724ab7bf313189f86b8af3
parent7da496024a2ecb93a85de79756e4e89f2d289187 (diff)
downloadcython-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.rst2
-rw-r--r--Cython/Compiler/Nodes.py10
-rw-r--r--Cython/Compiler/Pipeline.py25
-rw-r--r--Cython/Compiler/UtilityCode.py11
-rw-r--r--Cython/Utility/CpdefEnums.pyx41
-rw-r--r--tests/run/cpdef_enums.pyx7
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
"""