Commit 143113fc authored by Robert Bradshaw's avatar Robert Bradshaw

#479, sub-directives via keywords

parent 5553fc00
...@@ -64,6 +64,9 @@ directive_defaults = { ...@@ -64,6 +64,9 @@ directive_defaults = {
'profile': False, 'profile': False,
'infer_types': False, 'infer_types': False,
'autotestdict': True, 'autotestdict': True,
'warn': None,
'warn.undeclared': False,
# test support # test support
'test_assert_path_exists' : [], 'test_assert_path_exists' : [],
......
...@@ -427,55 +427,74 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -427,55 +427,74 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
node.cython_attribute = self.directive_names.get(node.name) node.cython_attribute = self.directive_names.get(node.name)
return node return node
def try_to_parse_directive(self, node): def try_to_parse_directives(self, node):
# If node is the contents of an directive (in a with statement or # If node is the contents of an directive (in a with statement or
# decorator), returns (directivename, value). # decorator), returns a list of (directivename, value) pairs.
# Otherwise, returns None # Otherwise, returns None
optname = None
if isinstance(node, CallNode): if isinstance(node, CallNode):
self.visit(node.function) self.visit(node.function)
optname = node.function.as_cython_attribute() optname = node.function.as_cython_attribute()
if optname:
if optname: directivetype = Options.directive_types.get(optname)
directivetype = Options.directive_types.get(optname) if directivetype:
if directivetype: args, kwds = node.explicit_args_kwds()
args, kwds = node.explicit_args_kwds() directives = []
if optname == 'infer_types': key_value_pairs = []
if kwds is not None or len(args) != 1: if kwds is not None and directivetype is not dict:
raise PostParseError(node.function.pos, for keyvalue in kwds.key_value_pairs:
'The %s directive takes one compile-time boolean argument' % optname) key, value = keyvalue
elif isinstance(args[0], BoolNode): sub_optname = "%s.%s" % (optname, key.value)
return (optname, args[0].value) if Options.directive_types.get(sub_optname):
elif isinstance(args[0], NoneNode): directives.append(self.try_to_parse_directive(sub_optname, [value], None, keyvalue.pos))
return (optname, None) else:
else: key_value_pairs.append(keyvalue)
raise PostParseError(node.function.pos, if not key_value_pairs:
'The %s directive takes one compile-time boolean argument' % optname) kwds = None
elif directivetype is bool: else:
if kwds is not None or len(args) != 1 or not isinstance(args[0], BoolNode): kwds.key_value_pairs = key_value_pairs
raise PostParseError(node.function.pos, if directives and not kwds and not args:
'The %s directive takes one compile-time boolean argument' % optname) return directives
return (optname, args[0].value) directives.append(self.try_to_parse_directive(optname, args, kwds, node.function.pos))
elif directivetype is str: return directives
if kwds is not None or len(args) != 1 or not isinstance(args[0], (StringNode, UnicodeNode)):
raise PostParseError(node.function.pos,
'The %s directive takes one compile-time string argument' % optname)
return (optname, str(args[0].value))
elif directivetype is dict:
if len(args) != 0:
raise PostParseError(node.function.pos,
'The %s directive takes no prepositional arguments' % optname)
return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs])
elif directivetype is list:
if kwds and len(kwds) != 0:
raise PostParseError(node.function.pos,
'The %s directive takes no keyword arguments' % optname)
return optname, [ str(arg.value) for arg in args ]
else:
assert False
return None return None
def try_to_parse_directive(self, optname, args, kwds, pos):
directivetype = Options.directive_types.get(optname)
if optname == 'infer_types':
if kwds is not None or len(args) != 1:
raise PostParseError(pos,
'The %s directive takes one compile-time boolean argument' % optname)
elif isinstance(args[0], BoolNode):
return (optname, args[0].value)
elif isinstance(args[0], NoneNode):
return (optname, None)
else:
raise PostParseError(pos,
'The %s directive takes one compile-time boolean argument' % optname)
elif directivetype is bool:
if kwds is not None or len(args) != 1 or not isinstance(args[0], BoolNode):
raise PostParseError(pos,
'The %s directive takes one compile-time boolean argument' % optname)
return (optname, args[0].value)
elif directivetype is str:
if kwds is not None or len(args) != 1 or not isinstance(args[0], (StringNode, UnicodeNode)):
raise PostParseError(pos,
'The %s directive takes one compile-time string argument' % optname)
return (optname, str(args[0].value))
elif directivetype is dict:
if len(args) != 0:
raise PostParseError(pos,
'The %s directive takes no prepositional arguments' % optname)
return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs])
elif directivetype is list:
if kwds and len(kwds) != 0:
raise PostParseError(pos,
'The %s directive takes no keyword arguments' % optname)
return optname, [ str(arg.value) for arg in args ]
else:
assert False
def visit_with_directives(self, body, directives): def visit_with_directives(self, body, directives):
olddirectives = self.directives olddirectives = self.directives
newdirectives = copy.copy(olddirectives) newdirectives = copy.copy(olddirectives)
...@@ -495,9 +514,9 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -495,9 +514,9 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
# Split the decorators into two lists -- real decorators and directives # Split the decorators into two lists -- real decorators and directives
realdecs = [] realdecs = []
for dec in node.decorators: for dec in node.decorators:
directive = self.try_to_parse_directive(dec.decorator) new_directives = self.try_to_parse_directives(dec.decorator)
if directive is not None: if new_directives is not None:
directives.append(directive) directives.extend(new_directives)
else: else:
realdecs.append(dec) realdecs.append(dec)
if realdecs and isinstance(node, CFuncDefNode): if realdecs and isinstance(node, CFuncDefNode):
...@@ -533,26 +552,28 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -533,26 +552,28 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
def visit_CVarDefNode(self, node): def visit_CVarDefNode(self, node):
if node.decorators: if node.decorators:
for dec in node.decorators: for dec in node.decorators:
directive = self.try_to_parse_directive(dec.decorator) for directive in self.try_to_parse_directives(dec.decorator) or []:
if directive is not None and directive[0] == u'locals': if directive is not None and directive[0] == u'locals':
node.directive_locals = directive[1] node.directive_locals = directive[1]
else: else:
self.context.nonfatal_error(PostParseError(dec.pos, self.context.nonfatal_error(PostParseError(dec.pos,
"Cdef functions can only take cython.locals() decorator.")) "Cdef functions can only take cython.locals() decorator."))
continue
return node return node
# Handle with statements # Handle with statements
def visit_WithStatNode(self, node): def visit_WithStatNode(self, node):
directive = self.try_to_parse_directive(node.manager) directive_dict = {}
if directive is not None: for directive in self.try_to_parse_directives(node.manager) or []:
if node.target is not None: if directive is not None:
self.context.nonfatal_error( if node.target is not None:
PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'")) self.context.nonfatal_error(
else: PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'"))
name, value = directive else:
if self.check_directive_scope(node.pos, name, 'with statement'): name, value = directive
return self.visit_with_directives(node.body, {name:value}) if self.check_directive_scope(node.pos, name, 'with statement'):
directive_dict[name] = value
if directive_dict:
return self.visit_with_directives(node.body, directive_dict)
return self.visit_Node(node) return self.visit_Node(node)
class WithTransform(CythonTransform, SkipDeclarations): class WithTransform(CythonTransform, SkipDeclarations):
......
...@@ -2579,7 +2579,7 @@ def p_code(s, level=None): ...@@ -2579,7 +2579,7 @@ def p_code(s, level=None):
repr(s.sy), repr(s.systring))) repr(s.sy), repr(s.systring)))
return body return body
COMPILER_DIRECTIVE_COMMENT_RE = re.compile(r"^#\s*cython:\s*(\w+\s*=.*)$") COMPILER_DIRECTIVE_COMMENT_RE = re.compile(r"^#\s*cython:\s*((\w|[.])+\s*=.*)$")
def p_compiler_directive_comments(s): def p_compiler_directive_comments(s):
result = {} result = {}
......
# cython: boundscheck = False # cython: boundscheck = False
# cython: ignoreme = OK # cython: ignoreme = OK
# cython: warn.undeclared = False
# This testcase is most useful if you inspect the generated C file # This testcase is most useful if you inspect the generated C file
...@@ -32,3 +33,8 @@ def i(object[int] buf): ...@@ -32,3 +33,8 @@ def i(object[int] buf):
with bc(True): with bc(True):
print buf[3] # bs print buf[3] # bs
from cython cimport warn as my_warn
@my_warn(undeclared=True)
def j():
pass
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