Skip to content

Commit

Permalink
Modify utility code loader as per discussion + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
markflorisson committed Sep 30, 2011
1 parent 3f8fefb commit 2599deb
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 74 deletions.
108 changes: 65 additions & 43 deletions Cython/Compiler/Code.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,19 @@
'WindowsError',
]

Cython_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Utility_dir = os.path.join(Cython_dir, "Utility")
def get_utility_dir():
# make this a function and not global variables:
# http://trac.cython.org/cython_trac/ticket/475
Cython_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
return os.path.join(Cython_dir, "Utility")

class UtilityCodeBase(object):

is_cython_utility = False

_utility_cache = {}

@classmethod
# @classmethod
def _add_utility(cls, utility, type, lines, begin_lineno):
if utility:
if cls.is_cython_utility:
Expand All @@ -68,40 +71,52 @@ def _add_utility(cls, utility, type, lines, begin_lineno):
else:
utility[1] = code

@classmethod
_add_utility = classmethod(_add_utility)

# @classmethod
def load_utilities_from_file(cls, path):
utilities = cls._utility_cache.get(path)
if utilities:
return utilities

filename = os.path.join(Utility_dir, path)
f = codecs.open(filename, encoding='UTF-8')
filename = os.path.join(get_utility_dir(), path)

_, ext = os.path.splitext(path)
if ext in ('.pyx', '.py', '.pxd', '.pxi'):
comment = '#'
else:
comment = '//'
comment = '/'

regex = r'%s\s*Utility(Proto|Code)\s*:\s*((\w|\.)+)\s*' % comment
regex = r'%s{5,20}\s*((\w|\.)+)\s*%s{5,20}' % (comment, comment)
utilities = {}
lines = []

utility = type = None
begin_lineno = 0
for lineno, line in enumerate(f):
m = re.search(regex, line)
if m:
cls._add_utility(utility, type, lines, begin_lineno)

begin_lineno = lineno + 1
type, name = m.group(1), m.group(2)
utility = utilities.setdefault(name, [None, None])
utilities[name] = utility

lines = []
else:
lines.append(line)
f = Utils.open_source_file(filename, encoding='UTF-8')
try:
for lineno, line in enumerate(f):
m = re.search(regex, line)
if m:
cls._add_utility(utility, type, lines, begin_lineno)

begin_lineno = lineno + 1
name = m.group(1)
if name.endswith(".proto"):
name = name[:-6]
type = 'Proto'
else:
type = 'Code'

utility = utilities.setdefault(name, [None, None])
utilities[name] = utility

lines = []
else:
lines.append(line)
finally:
f.close()

if not utility:
raise ValueError("Empty utility code file")
Expand All @@ -114,12 +129,15 @@ def load_utilities_from_file(cls, path):
cls._utility_cache[path] = utilities
return utilities

@classmethod
def load_utility_from_file(cls, path, util_code_name,
context=None, **kwargs):
load_utilities_from_file = classmethod(load_utilities_from_file)

# @classmethod
def load(cls, util_code_name, from_file=None, context=None, **kwargs):
"""
Load a utility code from a file specified by path (relative to
Cython/Utility) and name util_code_name.
Load a utility code from a file specified by from_file (relative to
Cython/Utility) and name util_code_name. If from_file is not given,
load it from the file util_code_name.*. There should be only one file
matched by this pattern.
Utilities in the file can be specified as follows:
Expand All @@ -137,44 +155,48 @@ def load_utility_from_file(cls, path, util_code_name,
one should pass in the 'name' keyword argument to be used for name
mangling of such entries.
"""
proto, impl = cls.load_utility_as_string(path, util_code_name, context)
proto, impl = cls.load_as_string(util_code_name, from_file, context)

if proto is not None:
kwargs['proto'] = proto
if impl is not None:
kwargs['impl'] = impl

if 'name' not in kwargs:
kwargs['name'] = os.path.splitext(path)[0]
if from_file:
kwargs['name'] = os.path.splitext(from_file)[0]
else:
kwargs['name'] = util_code_name

return cls(**kwargs)

@classmethod
def load_utility_as_string(cls, path, util_code_name, context=None):
load = classmethod(load)

# @classmethod
def load_as_string(cls, util_code_name, from_file=None, context=None):
"""
Load a utility code as a string. Returns (proto, implementation)
"""
utilities = cls.load_utilities_from_file(path)
if from_file is None:
files = glob.glob(os.path.join(get_utility_dir(),
util_code_name + '.*'))
if len(files) != 1:
raise ValueError("Need exactly one utility file")

from_file, = files

utilities = cls.load_utilities_from_file(from_file)

proto, impl = utilities[util_code_name]
if proto:
if context is not None:
if context is not None:
if proto:
proto = tempita.sub(proto, **context)

if impl:
if context is not None:
if impl:
impl = tempita.sub(impl, **context)

return proto, impl

@classmethod
def load_utility(cls, name, context=None, **kwargs):
"Load utility name with context from a utility file name.suffix"
files = glob.glob(os.path.join(Utility_dir, name + '.*'))
if len(files) != 1:
raise ValueError("Need exactly one utility file")

return cls.load_utilities_from_file(files[0], name, context, **kwargs)
load_as_string = classmethod(load_as_string)

def __str__(self):
return "<%s(%s)" % (type(self).__name__, self.name)
Expand Down
5 changes: 2 additions & 3 deletions Cython/Compiler/CythonScope.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ def create_cython_scope(context, create_testscope):

# Load test utilities for the cython scope

def load_testscope_utility(cython_util_name, *args, **kwargs):
return CythonUtilityCode.load_utility_from_file(
"TestCythonScope.pyx", cython_util_name, *args, **kwargs)
def load_testscope_utility(cy_util_name, **kwargs):
return CythonUtilityCode.load(cy_util_name, "TestCythonScope.pyx", **kwargs)


undecorated_methods_protos = UtilityCode(proto=u"""
Expand Down
23 changes: 9 additions & 14 deletions Cython/Compiler/MemoryView.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,9 @@ def put_code(self, output):
copy_contents_name=copy_contents_name
)

_, copy_code = UtilityCode.load_utility_as_string(
"MemoryView_C.c", "MemviewSliceCopyTemplate", context)
_, copy_code = UtilityCode.load_as_string("MemviewSliceCopyTemplate",
from_file="MemoryView_C.c",
context=context)
code.put(copy_code)


Expand Down Expand Up @@ -737,30 +738,24 @@ def visit_SingleAssignmentNode(self, node):
return node


def load_memview_cy_utility(name, *args, **kwargs):
return CythonUtilityCode.load_utility_from_file(
"MemoryView.pyx", name, *args, **kwargs)

def load_memview_c_utility(name, *args, **kwargs):
return UtilityCode.load_utility_from_file(
"MemoryView_C.c", name, *args, **kwargs)

def load_memview_c_string(name):
return UtilityCode.load_utility_as_string("MemoryView_C.c", name)
def load_memview_cy_utility(util_code_name, **kwargs):
return CythonUtilityCode.load(util_code_name, "MemoryView.pyx", **kwargs)

def load_memview_c_utility(util_code_name, **kwargs):
return UtilityCode.load(util_code_name, "MemoryView_C.c", **kwargs)

context = {
'memview_struct_name': memview_objstruct_cname,
'max_dims': Options.buffer_max_dims,
'memviewslice_name': memviewslice_cname,
}
memviewslice_declare_code = load_memview_c_utility(
name="MemviewSliceStruct",
"MemviewSliceStruct",
proto_block='utility_code_proto_before_types',
context=context)

memviewslice_init_code = load_memview_c_utility(
name="MemviewSliceInit",
"MemviewSliceInit",
context={'BUF_MAX_NDIMS': Options.buffer_max_dims},
requires=[memviewslice_declare_code],
)
68 changes: 68 additions & 0 deletions Cython/Compiler/Tests/TestUtilityLoad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import unittest

from Cython.Compiler import Code, UtilityCode


def strip_2tup(tup):
return tup[0] and tup[0].strip(), tup[1] and tup[1].strip()

class TestUtilityLoader(unittest.TestCase):
"""
Test loading UtilityCodes
"""

expected = "test {{loader}} prototype", "test {{loader}} impl"
expected_tempita = (expected[0].replace('{{loader}}', 'Loader'),
expected[1].replace('{{loader}}', 'Loader'))

required = "I am a dependency proto", "I am a dependency impl"

context = dict(loader='Loader')

name = "TestUtilityLoader"
filename = "TestUtilityLoader.c"
cls = Code.UtilityCode

def test_load_as_string(self):
got = strip_2tup(self.cls.load_as_string(self.name))
self.assertEquals(got, self.expected)

got = strip_2tup(self.cls.load_as_string(self.name, self.filename))
self.assertEquals(got, self.expected)

got = strip_2tup(self.cls.load_as_string(self.name, context=self.context))
self.assertEquals(got, self.expected_tempita)

def test_load(self):
utility = self.cls.load(self.name)
got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected)

# Not implemented yet
#required, = utility.requires
#self.assertEquals((required.proto, required.impl), self.required)

utility = self.cls.load(self.name, from_file=self.filename)
got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected)


class TestCythonUtilityLoader(TestUtilityLoader):
"""
Test loading CythonUtilityCodes
"""

# Just change the attributes and run the same tests
expected = None, "test {{cy_loader}} impl"
expected_tempita = None, "test CyLoader impl"

required = None, "I am a Cython dependency impl"

context = dict(cy_loader='CyLoader')

name = "TestCyUtilityLoader"
filename = "TestCyUtilityLoader.pyx"
cls = UtilityCode.CythonUtilityCode

# Small hack to pass our tests above
cls.proto = None
4 changes: 2 additions & 2 deletions Cython/Compiler/UtilityCode.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def __init__(self, impl, name="__pyxutil", prefix="", requires=None):
# while the generated node trees can be altered in the compilation of a
# single file.
# Hence, delay any processing until later.
self.pyx = impl
self.impl = impl
self.name = name
self.prefix = prefix
self.requires = requires or []
Expand All @@ -86,7 +86,7 @@ def get_tree(self, entries_only=False):
context = CythonUtilityCodeContext(self.name)
context.prefix = self.prefix
#context = StringParseContext(self.name)
tree = parse_from_strings(self.name, self.pyx, context=context,
tree = parse_from_strings(self.name, self.impl, context=context,
allow_struct_enum_decorator=True)
pipeline = Pipeline.create_pipeline(context, 'pyx', exclude_classes=excludes)

Expand Down
4 changes: 2 additions & 2 deletions Cython/Utility/MemoryView.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# UtilityCode: CythonArray
########## CythonArray ##########

cdef extern from "stdlib.h":
void *malloc(size_t)
Expand Down Expand Up @@ -134,7 +134,7 @@ cdef class array:
cdef array array_cwrapper(tuple shape, Py_ssize_t itemsize, char *format, char *mode):
return array(shape, itemsize, format, mode.decode('ASCII'))

# UtilityCode: MemoryView
########## MemoryView ##########

# from cpython cimport ...
cdef extern from "pythread.h":
Expand Down
8 changes: 4 additions & 4 deletions Cython/Utility/MemoryView_C.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// UtilityProto: MemviewSliceStruct
////////// MemviewSliceStruct.proto //////////

/* memoryview slice struct */

Expand All @@ -11,7 +11,7 @@ typedef struct {
Py_ssize_t suboffsets[{{max_dims}}];
} {{memviewslice_name}};

// UtilityProto: MemviewSliceInit
////////// MemviewSliceInit.proto //////////

#define __Pyx_BUF_MAX_NDIMS %(BUF_MAX_NDIMS)d

Expand All @@ -36,7 +36,7 @@ static int __Pyx_init_memviewslice(
int ndim,
__Pyx_memviewslice *memviewslice);

// UtilityCode: MemviewSliceInit
////////// MemviewSliceInit //////////

static int __Pyx_ValidateAndInit_memviewslice(
struct __pyx_memoryview_obj *memview,
Expand Down Expand Up @@ -218,7 +218,7 @@ static int __Pyx_init_memviewslice(
return retval;
}

// UtilityCode: MemviewSliceCopyTemplate
////////// MemviewSliceCopyTemplate //////////

static __Pyx_memviewslice {{copy_name}}(const __Pyx_memviewslice from_mvs) {

Expand Down
2 changes: 2 additions & 0 deletions Cython/Utility/TestCyUtilityLoader.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
########## TestCyUtilityLoader ##########
test {{cy_loader}} impl
Loading

0 comments on commit 2599deb

Please sign in to comment.