From: Dag Sverre Seljebotn Date: Mon, 4 Aug 2008 13:25:52 +0000 (+0200) Subject: Added global compilation option/pragma support to parser and command line X-Git-Tag: 0.9.8.1~49^2~24 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=2191ad0d35115b2910e736ce58dab6272a0bba3c;p=cython.git Added global compilation option/pragma support to parser and command line --- diff --git a/Cython/Compiler/CmdLine.py b/Cython/Compiler/CmdLine.py index fb63b8af..c218fce4 100644 --- a/Cython/Compiler/CmdLine.py +++ b/Cython/Compiler/CmdLine.py @@ -37,6 +37,7 @@ Options: -a, --annotate Produce an colorized version of the source. --convert-range Convert for loops using range() function to for...from loops. --cplus Output a c++ rather than c file. + -O, --option =[,>=", "**=", "//=") spaces = Rep1(Any(" \t\f")) - comment = Str("#") + Rep(AnyBut("\n")) escaped_newline = Str("\\\n") lineterm = Eol + Opt(Str("\n")) + + comment_start = Str("#") + comment = comment_start + Rep(AnyBut("\n")) + option_comment = comment_start + Str("cython:") + Rep(AnyBut("\n")) return Lexicon([ (name, 'IDENT'), @@ -93,11 +97,13 @@ def make_lexicon(): #(stringlit, 'STRING'), (beginstring, Method('begin_string_action')), + (option_comment, 'option_comment'), (comment, IGNORE), (spaces, IGNORE), (escaped_newline, IGNORE), State('INDENT', [ + (option_comment + lineterm, 'option_comment'), (Opt(spaces) + Opt(comment) + lineterm, IGNORE), (indentation, Method('indentation_action')), (Eof, Method('eof_action')) diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py index 7f05fc25..237d0a3c 100644 --- a/Cython/Compiler/Main.py +++ b/Cython/Compiler/Main.py @@ -58,12 +58,13 @@ class Context: # include_directories [string] # future_directives [object] - def __init__(self, include_directories): + def __init__(self, include_directories, pragma_overrides): #self.modules = {"__builtin__" : BuiltinScope()} import Builtin self.modules = {"__builtin__" : Builtin.builtin_scope} self.include_directories = include_directories self.future_directives = set() + self.pragma_overrides = pragma_overrides self.pxds = {} # full name -> node tree @@ -76,6 +77,7 @@ class Context: from ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform + from ParseTreeTransforms import ResolveOptions from Optimize import FlattenInListTransform, SwitchTransform, OptimizeRefcounting from Buffer import IntroduceBufferAuxiliaryVars from ModuleNode import check_c_classes @@ -91,6 +93,7 @@ class Context: NormalizeTree(self), PostParse(self), _specific_post_parse, + ResolveOptions(self, self.pragma_overrides), FlattenInListTransform(), WithTransform(self), DecoratorTransform(self), @@ -481,7 +484,7 @@ def create_default_resultobj(compilation_source, options): def run_pipeline(source, options, full_module_name = None): # Set up context - context = Context(options.include_path) + context = Context(options.include_path, options.pragma_overrides) # Set up source object cwd = os.getcwd() @@ -531,6 +534,7 @@ class CompilationOptions: defaults to true when recursive is true. verbose boolean Always print source names being compiled quiet boolean Don't print source names in recursive mode + pragma_overrides dict Overrides for pragma options (see Options.py) Following options are experimental and only used on MacOSX: @@ -723,7 +727,9 @@ default_options = dict( recursive = 0, timestamps = None, verbose = 0, - quiet = 0) + quiet = 0, + pragma_overrides = {} +) if sys.platform == "mac": from Cython.Mac.MacSystem import c_compile, c_link, CCompilerError default_options['use_listing_file'] = 1 diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index 600864c1..69ab2cae 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -1,5 +1,5 @@ # -# Pyrex - Compilation-wide options +# Cython - Compilation-wide options and pragma declarations # cache_builtins = 1 # Perform lookups on builtin names only once @@ -52,3 +52,58 @@ optimize_simple_methods = 1 # Append the c file and line number to the traceback for exceptions. c_line_in_traceback = 1 + + +# Declare pragmas +option_types = { + 'boundscheck' : bool +} + +option_defaults = { + 'boundscheck' : True +} + +def parse_option_list(s): + """ + Parses a comma-seperated list of pragma options. Whitespace + is not considered. + + >>> parse_option_list(' ') + {} + >>> (parse_option_list('boundscheck=True') == + ... {'boundscheck': True}) + True + >>> parse_option_list(' asdf') + Traceback (most recent call last): + ... + ValueError: Expected "=" in option "asdf" + >>> parse_option_list('boundscheck=hey') + Traceback (most recent call last): + ... + ValueError: Must pass a boolean value for option "boundscheck" + >>> parse_option_list('unknown=True') + Traceback (most recent call last): + ... + ValueError: Unknown option: "unknown" + """ + result = {} + for item in s.split(','): + item = item.strip() + if not item: continue + if not '=' in item: raise ValueError('Expected "=" in option "%s"' % item) + name, value = item.strip().split('=') + try: + type = option_types[name] + except KeyError: + raise ValueError('Unknown option: "%s"' % name) + if type is bool: + value = value.lower() + if value in ('true', 'yes'): + value = True + elif value in ('false', 'no'): + value = False + else: raise ValueError('Must pass a boolean value for option "%s"' % name) + result[name] = value + else: + assert False + return result diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 8ed26bad..d808a971 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -6,6 +6,7 @@ from Cython.Compiler.TreeFragment import TreeFragment from Cython.Utils import EncodedString from Cython.Compiler.Errors import CompileError from sets import Set as set +import copy class NormalizeTree(CythonTransform): """ @@ -253,6 +254,37 @@ class PxdPostParse(CythonTransform): else: return node +class ResolveOptions(CythonTransform): + """ + After parsing, options can be stored in a number of places: + - #cython-comments at the top of the file (stored in ModuleNode) + - Command-line arguments overriding these + - @cython.optionname decorators + - with cython.optionname: statements + + This transform is responsible for annotating each node with an + "options" attribute linking it to a dict containing the exact + options that are in effect for that node. Any corresponding decorators + or with statements are removed in the process. + """ + + def __init__(self, context, compilation_option_overrides): + super(ResolveOptions, self).__init__(context) + self.compilation_option_overrides = compilation_option_overrides + + def visit_ModuleNode(self, node): + options = copy.copy(Options.option_defaults) + options.update(node.option_comments) + options.update(self.compilation_option_overrides) + self.options = options + node.options = options + self.visitchildren(node) + return node + + def visit_Node(self, node): + node.options = self.options + self.visitchildren(node) + return node class WithTransform(CythonTransform): diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 4e07a820..429bf6a3 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -11,6 +11,7 @@ from ModuleNode import ModuleNode from Errors import error, warning, InternalError from Cython import Utils import Future +import Options class Ctx(object): # Parsing context @@ -506,6 +507,8 @@ def p_atom(s): elif sy == 'NULL': s.next() return ExprNodes.NullNode(pos) + elif sy == 'option_comment': + s.error("#cython option comments only allowed at beginning of file") else: s.error("Expected an identifier or literal") @@ -2309,6 +2312,17 @@ def p_code(s, level=None): repr(s.sy), repr(s.systring))) return body +def p_option_comments(s): + result = {} + while s.sy == 'option_comment': + opts = s.systring[len("#cython:"):] + try: + result.update(Options.parse_option_list(opts)) + except ValueError, e: + s.error(e.message, fatal=False) + s.next() + return result + def p_module(s, pxd, full_module_name): s.add_type_name("object") s.add_type_name("Py_buffer") @@ -2318,11 +2332,15 @@ def p_module(s, pxd, full_module_name): level = 'module_pxd' else: level = 'module' + + option_comments = p_option_comments(s) body = p_statement_list(s, Ctx(level = level), first_statement = 1) if s.sy != 'EOF': s.error("Syntax error in statement [%s,%s]" % ( repr(s.sy), repr(s.systring))) - return ModuleNode(pos, doc = doc, body = body, full_module_name = full_module_name) + return ModuleNode(pos, doc = doc, body = body, + full_module_name = full_module_name, + option_comments = option_comments) #---------------------------------------------- # diff --git a/Cython/Compiler/Scanning.py b/Cython/Compiler/Scanning.py index 32c833a3..ee01e08f 100644 --- a/Cython/Compiler/Scanning.py +++ b/Cython/Compiler/Scanning.py @@ -429,12 +429,13 @@ class PyrexScanner(Scanner): def looking_at_type_name(self): return self.sy == 'IDENT' and self.systring in self.type_names - def error(self, message, pos = None): + def error(self, message, pos = None, fatal = True): if pos is None: pos = self.position() if self.sy == 'INDENT': - error(pos, "Possible inconsistent indentation") - raise error(pos, message) + err = error(pos, "Possible inconsistent indentation") + err = error(pos, message) + if fatal: raise err def expect(self, what, message = None): if self.sy == what: diff --git a/Cython/Compiler/TreeFragment.py b/Cython/Compiler/TreeFragment.py index 92419837..81865f81 100644 --- a/Cython/Compiler/TreeFragment.py +++ b/Cython/Compiler/TreeFragment.py @@ -20,7 +20,7 @@ Support for parsing strings into code trees. class StringParseContext(Main.Context): def __init__(self, include_directories, name): - Main.Context.__init__(self, include_directories) + Main.Context.__init__(self, include_directories, {}) self.module_name = name def find_module(self, module_name, relative_to = None, pos = None, need_pxd = 1): diff --git a/tests/errors/e_options.pyx b/tests/errors/e_options.pyx new file mode 100644 index 00000000..a100e4cc --- /dev/null +++ b/tests/errors/e_options.pyx @@ -0,0 +1,22 @@ + +#cython: nonexistant +#cython: some=9 + +# The one below should NOT raise an error + +#cython: boundscheck=True + +# However this one should +#cython: boundscheck=sadf + +print 3 + +#cython: boundscheck=True + +_ERRORS = u""" +2:0: Expected "=" in option "nonexistant" +3:0: Unknown option: "some" +10:0: Must pass a boolean value for option "boundscheck" +14:0: #cython option comments only allowed at beginning of file +""" + diff --git a/tests/run/options.pyx b/tests/run/options.pyx new file mode 100644 index 00000000..c56bb5d0 --- /dev/null +++ b/tests/run/options.pyx @@ -0,0 +1,4 @@ +#cython: boundscheck=False + +def f(object[int, 2] buf): + print buf[3, 2]