Commit 1e5476fb authored by Stefan Behnel's avatar Stefan Behnel

major cleanup refactoring of the utility code loading support,

simple support for loading requirements (at least from the same utility code type and file),
caching support
parent 0301ebf1
...@@ -748,8 +748,11 @@ def get_type_information_cname(code, dtype, maxdepth=None): ...@@ -748,8 +748,11 @@ def get_type_information_cname(code, dtype, maxdepth=None):
), safe=True) ), safe=True)
return name return name
def load_buffer_utility(util_code_name, **kwargs): def load_buffer_utility(util_code_name, context=None, **kwargs):
return UtilityCode.load(util_code_name, "Buffer.c", **kwargs) if context is None:
return UtilityCode.load(util_code_name, "Buffer.c", **kwargs)
else:
return TempitaUtilityCode.load(util_code_name, "Buffer.c", context=context, **kwargs)
context = dict(max_dims=str(Options.buffer_max_dims)) context = dict(max_dims=str(Options.buffer_max_dims))
buffer_struct_declare_code = load_buffer_utility("BufferStructDeclare", buffer_struct_declare_code = load_buffer_utility("BufferStructDeclare",
......
...@@ -7,7 +7,7 @@ import cython ...@@ -7,7 +7,7 @@ import cython
cython.declare(os=object, re=object, operator=object, cython.declare(os=object, re=object, operator=object,
Naming=object, Options=object, StringEncoding=object, Naming=object, Options=object, StringEncoding=object,
Utils=object, SourceDescriptor=object, StringIOTree=object, Utils=object, SourceDescriptor=object, StringIOTree=object,
DebugFlags=object, none_or_sub=object, basestring=object) DebugFlags=object, basestring=object)
import os import os
import re import re
...@@ -48,13 +48,31 @@ def get_utility_dir(): ...@@ -48,13 +48,31 @@ def get_utility_dir():
return os.path.join(Cython_dir, "Utility") return os.path.join(Cython_dir, "Utility")
class UtilityCodeBase(object): class UtilityCodeBase(object):
"""
Support for loading utility code from a file.
Code sections in the file can be specified as follows:
##### MyUtility.proto #####
##### MyUtility #####
#@requires: MyOtherUtility
for prototypes and implementation respectively. For non-python or
-cython files backslashes should be used instead. 5 to 30 comment
characters may be used on either side.
If the @cname decorator is not used and this is a CythonUtilityCode,
one should pass in the 'name' keyword argument to be used for name
mangling of such entries.
"""
is_cython_utility = False is_cython_utility = False
_utility_cache = {} _utility_cache = {}
@classmethod @classmethod
def _add_utility(cls, utility, type, lines, begin_lineno): def _add_utility(cls, utility, type, lines, begin_lineno, tags=None):
if utility is None: if utility is None:
return return
...@@ -65,6 +83,10 @@ class UtilityCodeBase(object): ...@@ -65,6 +83,10 @@ class UtilityCodeBase(object):
utility[0] = code utility[0] = code
else: else:
utility[1] = code utility[1] = code
if tags:
all_tags = utility[2]
for name, values in tags.iteritems():
all_tags.setdefault(name, set()).update(values)
@classmethod @classmethod
def load_utilities_from_file(cls, path): def load_utilities_from_file(cls, path):
...@@ -80,7 +102,10 @@ class UtilityCodeBase(object): ...@@ -80,7 +102,10 @@ class UtilityCodeBase(object):
else: else:
comment = '/' comment = '/'
replace_comments = re.compile(r'^\s*//.*|^\s*/\*[^*]*\*/').sub replace_comments = re.compile(r'^\s*//.*|^\s*/\*[^*]*\*/').sub
match_header = re.compile(r'%s{5,30}\s*((\w|\.)+)\s*%s{5,30}' % (comment, comment)).match match_special = re.compile(
(r'^%(C)s{5,30}\s*((?:\w|\.)+)\s*%(C)s{5,30}|'
r'^%(C)s+@(requires)\s*:\s*((?:\w|\.)+)' # add more tag names here at need
) % {'C':comment}).match
f = Utils.open_source_file(filename, encoding='UTF-8') f = Utils.open_source_file(filename, encoding='UTF-8')
try: try:
...@@ -90,24 +115,30 @@ class UtilityCodeBase(object): ...@@ -90,24 +115,30 @@ class UtilityCodeBase(object):
utilities = {} utilities = {}
lines = [] lines = []
tags = {}
utility = type = None utility = type = None
begin_lineno = 0 begin_lineno = 0
for lineno, line in enumerate(all_lines): for lineno, line in enumerate(all_lines):
m = match_header(line) m = match_special(line)
if m: if m:
cls._add_utility(utility, type, lines, begin_lineno) if m.group(1):
name = m.group(1)
begin_lineno = lineno + 1 cls._add_utility(utility, type, lines, begin_lineno, tags)
lines = []
begin_lineno = lineno + 1
name = m.group(1) del lines[:]
if name.endswith(".proto"): tags.clear()
name = name[:-6]
type = 'Proto' if name.endswith(".proto"):
name = name[:-6]
type = 'Proto'
else:
type = 'Code'
utility = utilities.setdefault(name, [None, None, {}])
else: else:
type = 'Code' tags.setdefault(m.group(2), set()).add(m.group(3))
utility = utilities.setdefault(name, [None, None]) lines.append('') # keep line number correct
else: else:
lines.append(replace_comments('', line).rstrip()) lines.append(replace_comments('', line).rstrip())
...@@ -115,55 +146,18 @@ class UtilityCodeBase(object): ...@@ -115,55 +146,18 @@ class UtilityCodeBase(object):
raise ValueError("Empty utility code file") raise ValueError("Empty utility code file")
# Don't forget to add the last utility code # Don't forget to add the last utility code
cls._add_utility(utility, type, lines, begin_lineno) cls._add_utility(utility, type, lines, begin_lineno, tags)
cls._utility_cache[path] = utilities cls._utility_cache[path] = utilities
return utilities return utilities
@classmethod @classmethod
def load(cls, util_code_name, from_file=None, context=None, **kwargs): def load(cls, util_code_name, from_file=None, **kwargs):
"""
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:
##### MyUtility.proto #####
##### MyUtility #####
for prototypes and implementation respectively. For non-python or
-cython files /-es should be used instead. 5 to 30 pound signs may be
used on either side.
If context is given, the utility is considered a tempita template.
The context dict (which may be empty) will be unpacked to form
all the variables in the template.
If the @cname decorator is not used and this is a CythonUtilityCode,
one should pass in the 'name' keyword argument to be used for name
mangling of such entries.
""" """
proto, impl = cls.load_as_string(util_code_name, from_file, context) Load utility code from a file specified by from_file (relative to
Cython/Utility) and name util_code_name. If from_file is not given,
if proto is not None: load it from the file util_code_name.*. There should be only one
kwargs['proto'] = proto file matched by this pattern.
if impl is not None:
kwargs['impl'] = impl
if 'name' not in kwargs:
kwargs['name'] = util_code_name
if 'file' not in kwargs and from_file:
kwargs['file'] = from_file
return cls(**kwargs)
@classmethod
def load_as_string(cls, util_code_name, from_file=None, context=None):
"""
Load a utility code as a string. Returns (proto, implementation)
""" """
if from_file is None: if from_file is None:
utility_dir = get_utility_dir() utility_dir = get_utility_dir()
...@@ -178,29 +172,56 @@ class UtilityCodeBase(object): ...@@ -178,29 +172,56 @@ class UtilityCodeBase(object):
from_file = files[0] from_file = files[0]
utilities = cls.load_utilities_from_file(from_file) utilities = cls.load_utilities_from_file(from_file)
proto, impl, tags = utilities[util_code_name]
if tags:
orig_kwargs = kwargs.copy()
for name, values in tags.iteritems():
if name in kwargs:
continue
# only pass lists when we have to: most argument expect one value or None
if name == 'requires':
values = [ cls.load(dep, from_file, **orig_kwargs) for dep in values ]
elif not values:
values = None
elif len(values) == 1:
values = values[0]
kwargs[name] = values
proto, impl = utilities[util_code_name] if proto is not None:
if context is not None: kwargs['proto'] = proto
proto = sub_tempita(proto, context, from_file, util_code_name) if impl is not None:
impl = sub_tempita(impl, context, from_file, util_code_name) kwargs['impl'] = impl
if cls.is_cython_utility: if 'name' not in kwargs:
# Remember line numbers kwargs['name'] = util_code_name
return proto, impl
return proto and proto.lstrip(), impl and impl.lstrip() if 'file' not in kwargs and from_file:
kwargs['file'] = from_file
def none_or_sub(self, s, context, tempita): return cls(**kwargs)
@classmethod
def load_cached(cls, utility_code_name, from_file=None, _cache={}):
""" """
Format a string in this utility code with context. If None, do nothing. Calls .load(), but using a per-type cache based on utility name and file name.
""" """
if s is None: key = (cls, from_file, utility_code_name)
return None try:
return _cache[key]
if tempita: except KeyError:
return sub_tempita(s, context, self.file, self.name) pass
code = _cache[key] = cls.load(utility_code_name, from_file)
return code
return s % context @classmethod
def load_as_string(cls, util_code_name, from_file=None, **kwargs):
"""
Load a utility code as a string. Returns (proto, implementation)
"""
util = cls.load(util_code_name, from_file, **kwargs)
proto, impl = util.proto, util.impl
return proto and proto.lstrip(), impl and impl.lstrip()
def format_code(self, code_string, replace_empty_lines=re.compile(r'\n\n+').sub): def format_code(self, code_string, replace_empty_lines=re.compile(r'\n\n+').sub):
""" """
...@@ -214,20 +235,6 @@ class UtilityCodeBase(object): ...@@ -214,20 +235,6 @@ class UtilityCodeBase(object):
return "<%s(%s)" % (type(self).__name__, self.name) return "<%s(%s)" % (type(self).__name__, self.name)
def sub_tempita(s, context, file, name):
"Run tempita on string s with given context."
if not s:
return None
if file:
context['__name'] = "%s:%s" % (file, name)
elif name:
context['__name'] = name
from Cython.Tempita import sub
return sub(s, **context)
class UtilityCode(UtilityCodeBase): class UtilityCode(UtilityCodeBase):
""" """
Stores utility code to add during code generation. Stores utility code to add during code generation.
...@@ -261,15 +268,36 @@ class UtilityCode(UtilityCodeBase): ...@@ -261,15 +268,36 @@ class UtilityCode(UtilityCodeBase):
self.name = name self.name = name
self.file = file self.file = file
def __hash__(self):
return hash((self.proto, self.impl))
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, type(self)):
return False
self_proto = getattr(self, 'proto', None)
other_proto = getattr(other, 'proto', None)
return (self_proto, self.impl) == (other_proto, other.impl)
def get_tree(self): def get_tree(self):
pass pass
def specialize(self, pyrex_type=None, tempita=False, **data): def none_or_sub(self, s, context):
"""
Format a string in this utility code with context. If None, do nothing.
"""
if s is None:
return None
return s % context
def specialize(self, pyrex_type=None, **data):
# Dicts aren't hashable... # Dicts aren't hashable...
if pyrex_type is not None: if pyrex_type is not None:
data['type'] = pyrex_type.declaration_code('') data['type'] = pyrex_type.declaration_code('')
data['type_name'] = pyrex_type.specialization_name() data['type_name'] = pyrex_type.specialization_name()
key = data.items(); key.sort(); key = tuple(key) key = tuple(sorted(data.iteritems()))
try: try:
return self._cache[key] return self._cache[key]
except KeyError: except KeyError:
...@@ -279,10 +307,10 @@ class UtilityCode(UtilityCodeBase): ...@@ -279,10 +307,10 @@ class UtilityCode(UtilityCodeBase):
requires = [r.specialize(data) for r in self.requires] requires = [r.specialize(data) for r in self.requires]
s = self._cache[key] = UtilityCode( s = self._cache[key] = UtilityCode(
self.none_or_sub(self.proto, data, tempita), self.none_or_sub(self.proto, data),
self.none_or_sub(self.impl, data, tempita), self.none_or_sub(self.impl, data),
self.none_or_sub(self.init, data, tempita), self.none_or_sub(self.init, data),
self.none_or_sub(self.cleanup, data, tempita), self.none_or_sub(self.cleanup, data),
requires, requires,
self.proto_block) self.proto_block)
...@@ -311,21 +339,33 @@ class UtilityCode(UtilityCodeBase): ...@@ -311,21 +339,33 @@ class UtilityCode(UtilityCodeBase):
self.cleanup(writer, output.module_pos) self.cleanup(writer, output.module_pos)
class ContentHashingUtilityCode(UtilityCode): def sub_tempita(s, context, file, name):
"UtilityCode that hashes and compares based on self.proto and self.impl" "Run tempita on string s with given context."
if not s:
return None
def __hash__(self): if file:
return hash((self.proto, self.impl)) context['__name'] = "%s:%s" % (file, name)
elif name:
context['__name'] = name
def __eq__(self, other): from Cython.Tempita import sub
if self is other: return sub(s, **context)
return True
if not isinstance(other, type(self)):
return False
self_proto = getattr(self, 'proto', None) class TempitaUtilityCode(UtilityCode):
other_proto = getattr(other, 'proto', None) def __init__(self, name=None, proto=None, impl=None, file=None, context=None, **kwargs):
return (self_proto, self.impl) == (other_proto, other.impl) proto = sub_tempita(proto, context, file, name)
impl = sub_tempita(impl, context, file, name)
super(TempitaUtilityCode, self).__init__(
proto, impl, name=name, file=file, **kwargs)
def none_or_sub(self, s, context):
"""
Format a string in this utility code with context. If None, do nothing.
"""
if s is None:
return None
return sub_tempita(s, context, self.file, self.name)
class LazyUtilityCode(UtilityCodeBase): class LazyUtilityCode(UtilityCodeBase):
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
import cython import cython
cython.declare(error=object, warning=object, warn_once=object, InternalError=object, cython.declare(error=object, warning=object, warn_once=object, InternalError=object,
CompileError=object, UtilityCode=object, StringEncoding=object, operator=object, CompileError=object, UtilityCode=object, TempitaUtilityCode=object,
StringEncoding=object, operator=object,
Naming=object, Nodes=object, PyrexTypes=object, py_object_type=object, Naming=object, Nodes=object, PyrexTypes=object, py_object_type=object,
list_type=object, tuple_type=object, set_type=object, dict_type=object, \ list_type=object, tuple_type=object, set_type=object, dict_type=object, \
unicode_type=object, str_type=object, bytes_type=object, type_type=object, unicode_type=object, str_type=object, bytes_type=object, type_type=object,
...@@ -15,7 +16,7 @@ import operator ...@@ -15,7 +16,7 @@ import operator
from Errors import error, warning, warn_once, InternalError, CompileError from Errors import error, warning, warn_once, InternalError, CompileError
from Errors import hold_errors, release_errors, held_errors, report_error from Errors import hold_errors, release_errors, held_errors, report_error
from Code import UtilityCode from Code import UtilityCode, TempitaUtilityCode
import StringEncoding import StringEncoding
import Naming import Naming
import Nodes import Nodes
...@@ -9967,9 +9968,9 @@ proto=""" ...@@ -9967,9 +9968,9 @@ proto="""
(((x) < 0) & ((unsigned long)(x) == 0-(unsigned long)(x))) (((x) < 0) & ((unsigned long)(x) == 0-(unsigned long)(x)))
""") """)
binding_cfunc_utility_code = UtilityCode.load("CythonFunction", binding_cfunc_utility_code = TempitaUtilityCode.load(
context=vars(Naming)) "CythonFunction", context=vars(Naming))
fused_function_utility_code = UtilityCode.load( fused_function_utility_code = TempitaUtilityCode.load(
"FusedFunction", "FusedFunction",
"CythonFunction.c", "CythonFunction.c",
context=vars(Naming), context=vars(Naming),
......
...@@ -2,7 +2,7 @@ from Errors import CompileError, error ...@@ -2,7 +2,7 @@ from Errors import CompileError, error
import ExprNodes import ExprNodes
from ExprNodes import IntNode, NameNode, AttributeNode from ExprNodes import IntNode, NameNode, AttributeNode
import Options import Options
from Code import UtilityCode from Code import UtilityCode, TempitaUtilityCode
from UtilityCode import CythonUtilityCode from UtilityCode import CythonUtilityCode
import Buffer import Buffer
import PyrexTypes import PyrexTypes
...@@ -931,8 +931,11 @@ def load_memview_cy_utility(util_code_name, context=None, **kwargs): ...@@ -931,8 +931,11 @@ def load_memview_cy_utility(util_code_name, context=None, **kwargs):
context=context, **kwargs) context=context, **kwargs)
def load_memview_c_utility(util_code_name, context=None, **kwargs): def load_memview_c_utility(util_code_name, context=None, **kwargs):
return UtilityCode.load(util_code_name, "MemoryView_C.c", if context is None:
context=context, **kwargs) return UtilityCode.load(util_code_name, "MemoryView_C.c", **kwargs)
else:
return TempitaUtilityCode.load(util_code_name, "MemoryView_C.c",
context=context, **kwargs)
def use_cython_array_utility_code(env): def use_cython_array_utility_code(env):
env.global_scope().context.cython_scope.lookup('array_cwrapper').used = True env.global_scope().context.cython_scope.lookup('array_cwrapper').used = True
......
...@@ -15,7 +15,7 @@ import Symtab ...@@ -15,7 +15,7 @@ import Symtab
import Options import Options
import Naming import Naming
from Code import UtilityCode, ContentHashingUtilityCode from Code import UtilityCode
from StringEncoding import EncodedString, BytesLiteral from StringEncoding import EncodedString, BytesLiteral
from Errors import error from Errors import error
from ParseTreeTransforms import SkipDeclarations from ParseTreeTransforms import SkipDeclarations
...@@ -32,11 +32,8 @@ try: ...@@ -32,11 +32,8 @@ try:
except ImportError: except ImportError:
basestring = str # Python 3 basestring = str # Python 3
_utility_cache = {}
def load_c_utility(name): def load_c_utility(name):
if name not in _utility_cache: return UtilityCode.load_cached(name, "Optimize.c")
_utility_cache[name] = ContentHashingUtilityCode.load(name, "Optimize.c")
return _utility_cache[name]
class FakePythonEnv(object): class FakePythonEnv(object):
"A fake environment for creating type test nodes etc." "A fake environment for creating type test nodes etc."
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# Cython/Python language types # Cython/Python language types
# #
from Code import UtilityCode, LazyUtilityCode, ContentHashingUtilityCode from Code import UtilityCode, LazyUtilityCode, TempitaUtilityCode
import StringEncoding import StringEncoding
import Naming import Naming
import copy import copy
...@@ -610,14 +610,14 @@ class MemoryViewSliceType(PyrexType): ...@@ -610,14 +610,14 @@ class MemoryViewSliceType(PyrexType):
return cname + '.memview' return cname + '.memview'
def create_from_py_utility_code(self, env): def create_from_py_utility_code(self, env):
import MemoryView, Buffer, Code import MemoryView, Buffer
# We don't have 'code', so use a LazyUtilityCode with a callback. # We don't have 'code', so use a LazyUtilityCode with a callback.
def lazy_utility_callback(code): def lazy_utility_callback(code):
context['dtype_typeinfo'] = Buffer.get_type_information_cname( context['dtype_typeinfo'] = Buffer.get_type_information_cname(
code, self.dtype) code, self.dtype)
return ContentHashingUtilityCode.load( return TempitaUtilityCode.load(
"ObjectToMemviewSlice", "MemoryView_C.c", context) "ObjectToMemviewSlice", "MemoryView_C.c", context=context)
env.use_utility_code(Buffer.acquire_utility_code) env.use_utility_code(Buffer.acquire_utility_code)
env.use_utility_code(MemoryView.memviewslice_init_code) env.use_utility_code(MemoryView.memviewslice_init_code)
...@@ -693,7 +693,7 @@ class MemoryViewSliceType(PyrexType): ...@@ -693,7 +693,7 @@ class MemoryViewSliceType(PyrexType):
error_condition = error_condition, error_condition = error_condition,
) )
utility = ContentHashingUtilityCode.load( utility = TempitaUtilityCode.load(
utility_name, "MemoryView_C.c", context=context) utility_name, "MemoryView_C.c", context=context)
env.use_utility_code(utility) env.use_utility_code(utility)
return get_function, set_function return get_function, set_function
...@@ -2796,8 +2796,8 @@ class CStructOrUnionType(CType): ...@@ -2796,8 +2796,8 @@ class CStructOrUnionType(CType):
funcname = self.from_py_function, funcname = self.from_py_function,
init = '%s 0 %s' % ('{' * nesting_depth, '}' * nesting_depth) init = '%s 0 %s' % ('{' * nesting_depth, '}' * nesting_depth)
) )
self._convert_from_py_code = ContentHashingUtilityCode.load( self._convert_from_py_code = TempitaUtilityCode.load(
"FromPyStructUtility", "TypeConversion.c", context) "FromPyStructUtility", "TypeConversion.c", context=context)
env.use_utility_code(self._convert_from_py_code) env.use_utility_code(self._convert_from_py_code)
return True return True
......
...@@ -12,10 +12,8 @@ class TestUtilityLoader(unittest.TestCase): ...@@ -12,10 +12,8 @@ class TestUtilityLoader(unittest.TestCase):
""" """
expected = "test {{loader}} prototype", "test {{loader}} impl" 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" required = "req {{loader}} proto", "req {{loader}} impl"
context = dict(loader='Loader') context = dict(loader='Loader')
...@@ -30,24 +28,55 @@ class TestUtilityLoader(unittest.TestCase): ...@@ -30,24 +28,55 @@ class TestUtilityLoader(unittest.TestCase):
got = strip_2tup(self.cls.load_as_string(self.name, self.filename)) got = strip_2tup(self.cls.load_as_string(self.name, self.filename))
self.assertEquals(got, self.expected) 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): def test_load(self):
utility = self.cls.load(self.name) utility = self.cls.load(self.name)
got = strip_2tup((utility.proto, utility.impl)) got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected) self.assertEquals(got, self.expected)
# Not implemented yet required, = utility.requires
#required, = utility.requires got = strip_2tup((required.proto, required.impl))
#self.assertEquals((required.proto, required.impl), self.required) self.assertEquals(got, self.required)
utility = self.cls.load(self.name, from_file=self.filename) utility = self.cls.load(self.name, from_file=self.filename)
got = strip_2tup((utility.proto, utility.impl)) got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected) self.assertEquals(got, self.expected)
utility = self.cls.load_cached(self.name, from_file=self.filename)
got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected)
class TestCythonUtilityLoader(TestUtilityLoader): class TestTempitaUtilityLoader(TestUtilityLoader):
"""
Test loading UtilityCodes with Tempita substitution
"""
expected_tempita = (TestUtilityLoader.expected[0].replace('{{loader}}', 'Loader'),
TestUtilityLoader.expected[1].replace('{{loader}}', 'Loader'))
required_tempita = (TestUtilityLoader.required[0].replace('{{loader}}', 'Loader'),
TestUtilityLoader.required[1].replace('{{loader}}', 'Loader'))
cls = Code.TempitaUtilityCode
def test_load_as_string(self):
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, context=self.context)
got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected_tempita)
required, = utility.requires
got = strip_2tup((required.proto, required.impl))
self.assertEquals(got, self.required_tempita)
utility = self.cls.load(self.name, from_file=self.filename, context=self.context)
got = strip_2tup((utility.proto, utility.impl))
self.assertEquals(got, self.expected_tempita)
class TestCythonUtilityLoader(TestTempitaUtilityLoader):
""" """
Test loading CythonUtilityCodes Test loading CythonUtilityCodes
""" """
...@@ -56,7 +85,8 @@ class TestCythonUtilityLoader(TestUtilityLoader): ...@@ -56,7 +85,8 @@ class TestCythonUtilityLoader(TestUtilityLoader):
expected = None, "test {{cy_loader}} impl" expected = None, "test {{cy_loader}} impl"
expected_tempita = None, "test CyLoader impl" expected_tempita = None, "test CyLoader impl"
required = None, "I am a Cython dependency impl" required = None, "req {{cy_loader}} impl"
required_tempita = None, "req CyLoader impl"
context = dict(cy_loader='CyLoader') context = dict(cy_loader='CyLoader')
...@@ -65,4 +95,7 @@ class TestCythonUtilityLoader(TestUtilityLoader): ...@@ -65,4 +95,7 @@ class TestCythonUtilityLoader(TestUtilityLoader):
cls = UtilityCode.CythonUtilityCode cls = UtilityCode.CythonUtilityCode
# Small hack to pass our tests above # Small hack to pass our tests above
cls.proto = None cls.proto = None
\ No newline at end of file
test_load = TestUtilityLoader.test_load
test_load_tempita = TestTempitaUtilityLoader.test_load
...@@ -66,13 +66,15 @@ class CythonUtilityCode(Code.UtilityCodeBase): ...@@ -66,13 +66,15 @@ class CythonUtilityCode(Code.UtilityCodeBase):
is_cython_utility = True is_cython_utility = True
def __init__(self, impl, name="__pyxutil", prefix="", requires=None, def __init__(self, impl, name="__pyxutil", prefix="", requires=None,
file=None, from_scope=None): file=None, from_scope=None, context=None):
# 1) We need to delay the parsing/processing, so that all modules can be # 1) We need to delay the parsing/processing, so that all modules can be
# imported without import loops # imported without import loops
# 2) The same utility code object can be used for multiple source files; # 2) The same utility code object can be used for multiple source files;
# while the generated node trees can be altered in the compilation of a # while the generated node trees can be altered in the compilation of a
# single file. # single file.
# Hence, delay any processing until later. # Hence, delay any processing until later.
if context is not None:
impl = Code.sub_tempita(impl, context, file, name)
self.impl = impl self.impl = impl
self.name = name self.name = name
self.file = file self.file = file
...@@ -128,6 +130,14 @@ class CythonUtilityCode(Code.UtilityCodeBase): ...@@ -128,6 +130,14 @@ class CythonUtilityCode(Code.UtilityCodeBase):
def put_code(self, output): def put_code(self, output):
pass pass
@classmethod
def load_as_string(cls, util_code_name, from_file=None, **kwargs):
"""
Load a utility code as a string. Returns (proto, implementation)
"""
util = cls.load(util_code_name, from_file, **kwargs)
return util.proto, util.impl # keep line numbers => no lstrip()
def declare_in_scope(self, dest_scope, used=False, cython_scope=None): def declare_in_scope(self, dest_scope, used=False, cython_scope=None):
""" """
Declare all entries from the utility code in dest_scope. Code will only Declare all entries from the utility code in dest_scope. Code will only
......
########## TestCyUtilityLoader ########## ########## TestCyUtilityLoader ##########
#@requires: OtherUtility
test {{cy_loader}} impl test {{cy_loader}} impl
########## OtherUtility ##########
req {{cy_loader}} impl
...@@ -2,4 +2,11 @@ ...@@ -2,4 +2,11 @@
test {{loader}} prototype test {{loader}} prototype
////////// TestUtilityLoader ////////// ////////// TestUtilityLoader //////////
//@requires: OtherUtility
test {{loader}} impl test {{loader}} impl
////////// OtherUtility.proto //////////
req {{loader}} proto
////////// OtherUtility //////////
req {{loader}} impl
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment