support for keyword-only arguments and required keywords (PEP 3102)
authorStefan Behnel <scoder@users.berlios.de>
Sun, 23 Sep 2007 19:26:36 +0000 (21:26 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Sun, 23 Sep 2007 19:26:36 +0000 (21:26 +0200)
Cython/Compiler/Nodes.py
Cython/Compiler/Parsing.py

index 07d2e8956683b79164cce0a8b1ee8b6980759fb8..cd7ab4850ce66f3fc2f73731c3b99331114de271 100644 (file)
@@ -332,6 +332,7 @@ class CArgDeclNode(Node):
     # default        ExprNode or None
     # default_entry  Symtab.Entry       Entry for the variable holding the default value
     # is_self_arg    boolean            Is the "self" arg of an extension type method
+    # kw_only        boolean            Is a keyword-only argument
     
     is_self_arg = 0
     
@@ -1051,9 +1052,12 @@ class DefNode(FuncDefNode):
             arg_addrs = []
             arg_formats = []
             default_seen = 0
+            kw_only_args = []
             for arg in self.args:
                 arg_entry = arg.entry
                 if arg.is_generic:
+                    if arg.kw_only:
+                        kw_only_args.append(arg_entry)
                     if arg.default:
                         code.putln(
                             "%s = %s;" % (
@@ -1062,7 +1066,7 @@ class DefNode(FuncDefNode):
                         if not default_seen:
                             arg_formats.append("|")
                         default_seen = 1
-                    elif default_seen:
+                    elif default_seen and not arg.kw_only:
                         error(arg.pos, "Non-default argument following default argument")
                     if arg.needs_conversion:
                         arg_addrs.append("&" + arg.hdr_cname)
@@ -1076,6 +1080,18 @@ class DefNode(FuncDefNode):
                         error(arg.pos, 
                             "Cannot convert Python object argument to type '%s' (when parsing input arguments)" 
                                 % arg.type)
+            error_return_code = "return %s;" % self.error_value()
+            if kw_only_args:
+                max_normal_args = len(self.args) - len(kw_only_args)
+                code.putln("if (%s && PyTuple_GET_SIZE(%s) > %d) {" % (
+                        Naming.args_cname,
+                        Naming.args_cname,
+                        max_normal_args))
+                code.putln('PyErr_Format(PyExc_TypeError, "function takes at most %d non-keyword arguments (%%d given)", PyTuple_GET_SIZE(%s));' % (
+                        max_normal_args,
+                        Naming.args_cname))
+                code.putln(error_return_code)
+                code.putln("}")
             argformat = '"%s"' % string.join(arg_formats, "")
             has_starargs = self.star_arg is not None or self.starstar_arg is not None
             if has_starargs:
@@ -1086,7 +1102,6 @@ class DefNode(FuncDefNode):
             code.put(
                 'if (unlikely(!PyArg_ParseTupleAndKeywords(%s))) ' %
                     pt_argstring)
-            error_return_code = "return %s;" % self.error_value()
             if has_starargs:
                 code.putln("{")
                 code.put_xdecref(Naming.args_cname, py_object_type)
@@ -1097,7 +1112,33 @@ class DefNode(FuncDefNode):
                 code.putln("}")
             else:
                 code.putln(error_return_code)
-            
+            # check that all required keywords were passed
+            required_keyword_entries = []
+            kw_checks = []
+            for arg in self.args:
+                if arg.is_generic and arg.kw_only and not arg.default:
+                    arg_entry = arg.entry
+                    required_keyword_entries.append(arg_entry)
+                    kw_checks.append("!" + arg_entry.cname)
+            if required_keyword_entries:
+                kw_check = ' || '.join(kw_checks)
+                code.putln("if (unlikely(%s)) {" % kw_check)
+                for entry in required_keyword_entries:
+                    kw_checks.pop()
+                    if kw_checks:
+                        code.putln('if (!%s) {' % entry.cname)
+                    code.putln('PyErr_SetString(PyExc_TypeError, "keyword argument \'%s\' is required");' % (
+                            entry.name))
+                    if kw_checks:
+                        code.put("} else ")
+                if has_starargs:
+                    code.put_xdecref(Naming.args_cname, py_object_type)
+                    code.put_xdecref(Naming.kwds_cname, py_object_type)
+                    self.generate_arg_xdecref(self.star_arg, code)
+                    self.generate_arg_xdecref(self.starstar_arg, code)
+                code.putln(error_return_code)
+                code.putln("}")
+
     def put_stararg_decrefs(self, code):
         if self.star_arg or self.starstar_arg:
             code.put_xdecref(Naming.args_cname, py_object_type)
@@ -1128,7 +1169,7 @@ class DefNode(FuncDefNode):
                     star_arg_addr,
                     starstar_arg_addr,
                     self.error_value()))
-    
+
     def generate_argument_conversion_code(self, code):
         # Generate code to convert arguments from
         # signature type to declared type, if needed.
index cd660a91871245e3a7815aaf18593a52c9fec2d1..91e697aa56e1896b2b8ab3ae8fef0828a57ba29b 100644 (file)
@@ -1529,15 +1529,15 @@ def p_c_func_options(s):
 c_arg_list_terminators = ('*', '**', '.', ')')
 c_arg_list_trailers = ('.', '*', '**')
 
-def p_c_arg_list(s, in_pyfunc, cmethod_flag = 0):
+def p_c_arg_list(s, in_pyfunc, cmethod_flag = 0, kw_only = 0):
     args = []
     if s.sy not in c_arg_list_terminators:
-        args.append(p_c_arg_decl(s, in_pyfunc, cmethod_flag))
+        args.append(p_c_arg_decl(s, in_pyfunc, cmethod_flag, kw_only))
         while s.sy == ',':
             s.next()
             if s.sy in c_arg_list_terminators:
                 break
-            args.append(p_c_arg_decl(s, in_pyfunc))
+            args.append(p_c_arg_decl(s, in_pyfunc, kw_only = kw_only))
     return args
 
 def p_optional_ellipsis(s):
@@ -1547,7 +1547,7 @@ def p_optional_ellipsis(s):
     else:
         return 0
 
-def p_c_arg_decl(s, in_pyfunc, cmethod_flag = 0):
+def p_c_arg_decl(s, in_pyfunc, cmethod_flag = 0, kw_only = 0):
     pos = s.position()
     not_none = 0
     default = None
@@ -1569,7 +1569,8 @@ def p_c_arg_decl(s, in_pyfunc, cmethod_flag = 0):
         base_type = base_type,
         declarator = declarator,
         not_none = not_none,
-        default = default)
+        default = default,
+        kw_only = kw_only)
 
 def p_cdef_statement(s, level, visibility = 'private'):
     pos = s.position()
@@ -1771,12 +1772,20 @@ def p_def_statement(s):
     starstar_arg = None
     if s.sy == '*':
         s.next()
-        star_arg = p_py_arg_decl(s)
         if s.sy == ',':
             s.next()
-            if s.sy == '**':
+            if s.sy == 'IDENT':
+                args.extend(p_c_arg_list(s, in_pyfunc = 1, kw_only = 1))
+        else:
+            star_arg = p_py_arg_decl(s)
+            if s.sy == ',':
                 s.next()
-                starstar_arg = p_py_arg_decl(s)
+                if s.sy == '**':
+                    s.next()
+                    starstar_arg = p_py_arg_decl(s)
+                elif s.sy == 'IDENT':
+                    args.extend(p_c_arg_list(s, in_pyfunc = 1,
+                                             kw_only = 1))
     elif s.sy == '**':
         s.next()
         starstar_arg = p_py_arg_decl(s)