mirror of
https://github.com/python/cpython.git
synced 2024-11-23 18:04:37 +08:00
gh-113317: Add ParseArgsCodeGen class (#117707)
This commit is contained in:
parent
a2ae84726b
commit
671cb22094
@ -9,7 +9,7 @@ import libclinic
|
||||
from libclinic import fail, warn
|
||||
from libclinic.function import Class
|
||||
from libclinic.block_parser import Block, BlockParser
|
||||
from libclinic.codegen import BlockPrinter, Destination, Codegen
|
||||
from libclinic.codegen import BlockPrinter, Destination, CodeGen
|
||||
from libclinic.parser import Parser, PythonParser
|
||||
from libclinic.dsl_parser import DSLParser
|
||||
if TYPE_CHECKING:
|
||||
@ -101,7 +101,7 @@ impl_definition block
|
||||
self.modules: ModuleDict = {}
|
||||
self.classes: ClassDict = {}
|
||||
self.functions: list[Function] = []
|
||||
self.codegen = Codegen(self.limited_capi)
|
||||
self.codegen = CodeGen(self.limited_capi)
|
||||
|
||||
self.line_prefix = self.line_suffix = ''
|
||||
|
||||
|
@ -8,93 +8,19 @@ from collections.abc import Iterable
|
||||
|
||||
import libclinic
|
||||
from libclinic import (
|
||||
unspecified, fail, warn, Sentinels, VersionTuple)
|
||||
from libclinic.function import (
|
||||
GETTER, SETTER, METHOD_INIT, METHOD_NEW)
|
||||
from libclinic.codegen import CRenderData, TemplateDict, Codegen
|
||||
unspecified, fail, Sentinels, VersionTuple)
|
||||
from libclinic.codegen import CRenderData, TemplateDict, CodeGen
|
||||
from libclinic.language import Language
|
||||
from libclinic.function import (
|
||||
Module, Class, Function, Parameter,
|
||||
permute_optional_groups)
|
||||
from libclinic.converters import (
|
||||
defining_class_converter, object_converter, self_converter)
|
||||
permute_optional_groups,
|
||||
GETTER, SETTER, METHOD_INIT)
|
||||
from libclinic.converters import self_converter
|
||||
from libclinic.parse_args import ParseArgsCodeGen
|
||||
if TYPE_CHECKING:
|
||||
from libclinic.app import Clinic
|
||||
|
||||
|
||||
def declare_parser(
|
||||
f: Function,
|
||||
*,
|
||||
hasformat: bool = False,
|
||||
codegen: Codegen,
|
||||
) -> str:
|
||||
"""
|
||||
Generates the code template for a static local PyArg_Parser variable,
|
||||
with an initializer. For core code (incl. builtin modules) the
|
||||
kwtuple field is also statically initialized. Otherwise
|
||||
it is initialized at runtime.
|
||||
"""
|
||||
limited_capi = codegen.limited_capi
|
||||
if hasformat:
|
||||
fname = ''
|
||||
format_ = '.format = "{format_units}:{name}",'
|
||||
else:
|
||||
fname = '.fname = "{name}",'
|
||||
format_ = ''
|
||||
|
||||
num_keywords = len([
|
||||
p for p in f.parameters.values()
|
||||
if not p.is_positional_only() and not p.is_vararg()
|
||||
])
|
||||
if limited_capi:
|
||||
declarations = """
|
||||
#define KWTUPLE NULL
|
||||
"""
|
||||
elif num_keywords == 0:
|
||||
declarations = """
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
# define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
|
||||
#else
|
||||
# define KWTUPLE NULL
|
||||
#endif
|
||||
"""
|
||||
else:
|
||||
declarations = """
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
|
||||
#define NUM_KEYWORDS %d
|
||||
static struct {{
|
||||
PyGC_Head _this_is_not_used;
|
||||
PyObject_VAR_HEAD
|
||||
PyObject *ob_item[NUM_KEYWORDS];
|
||||
}} _kwtuple = {{
|
||||
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||
.ob_item = {{ {keywords_py} }},
|
||||
}};
|
||||
#undef NUM_KEYWORDS
|
||||
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||
|
||||
#else // !Py_BUILD_CORE
|
||||
# define KWTUPLE NULL
|
||||
#endif // !Py_BUILD_CORE
|
||||
""" % num_keywords
|
||||
|
||||
condition = '#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)'
|
||||
codegen.add_include('pycore_gc.h', 'PyGC_Head', condition=condition)
|
||||
codegen.add_include('pycore_runtime.h', '_Py_ID()', condition=condition)
|
||||
|
||||
declarations += """
|
||||
static const char * const _keywords[] = {{{keywords_c} NULL}};
|
||||
static _PyArg_Parser _parser = {{
|
||||
.keywords = _keywords,
|
||||
%s
|
||||
.kwtuple = KWTUPLE,
|
||||
}};
|
||||
#undef KWTUPLE
|
||||
""" % (format_ or fname)
|
||||
return libclinic.normalize_snippet(declarations)
|
||||
|
||||
|
||||
class CLanguage(Language):
|
||||
|
||||
body_prefix = "#"
|
||||
@ -104,99 +30,6 @@ class CLanguage(Language):
|
||||
stop_line = "[{dsl_name} start generated code]*/"
|
||||
checksum_line = "/*[{dsl_name} end generated code: {arguments}]*/"
|
||||
|
||||
NO_VARARG: Final[str] = "PY_SSIZE_T_MAX"
|
||||
|
||||
PARSER_PROTOTYPE_KEYWORD: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
|
||||
""")
|
||||
PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = libclinic.normalize_snippet("""
|
||||
static int
|
||||
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
|
||||
""")
|
||||
PARSER_PROTOTYPE_VARARGS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *args)
|
||||
""")
|
||||
PARSER_PROTOTYPE_FASTCALL: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs)
|
||||
""")
|
||||
PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
""")
|
||||
PARSER_PROTOTYPE_DEF_CLASS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
""")
|
||||
PARSER_PROTOTYPE_NOARGS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored))
|
||||
""")
|
||||
PARSER_PROTOTYPE_GETTER: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, void *Py_UNUSED(context))
|
||||
""")
|
||||
PARSER_PROTOTYPE_SETTER: Final[str] = libclinic.normalize_snippet("""
|
||||
static int
|
||||
{c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context))
|
||||
""")
|
||||
METH_O_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({impl_parameters})
|
||||
""")
|
||||
DOCSTRING_PROTOTYPE_VAR: Final[str] = libclinic.normalize_snippet("""
|
||||
PyDoc_VAR({c_basename}__doc__);
|
||||
""")
|
||||
DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
|
||||
PyDoc_STRVAR({c_basename}__doc__,
|
||||
{docstring});
|
||||
""")
|
||||
GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
|
||||
PyDoc_STRVAR({getset_basename}__doc__,
|
||||
{docstring});
|
||||
#define {getset_basename}_HAS_DOCSTR
|
||||
""")
|
||||
IMPL_DEFINITION_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
|
||||
static {impl_return_type}
|
||||
{c_basename}_impl({impl_parameters})
|
||||
""")
|
||||
METHODDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
|
||||
#define {methoddef_name} \
|
||||
{{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}},
|
||||
""")
|
||||
GETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
|
||||
#if defined({getset_basename}_HAS_DOCSTR)
|
||||
# define {getset_basename}_DOCSTR {getset_basename}__doc__
|
||||
#else
|
||||
# define {getset_basename}_DOCSTR NULL
|
||||
#endif
|
||||
#if defined({getset_name}_GETSETDEF)
|
||||
# undef {getset_name}_GETSETDEF
|
||||
# define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}},
|
||||
#else
|
||||
# define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}},
|
||||
#endif
|
||||
""")
|
||||
SETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
|
||||
#if defined({getset_name}_HAS_DOCSTR)
|
||||
# define {getset_basename}_DOCSTR {getset_basename}__doc__
|
||||
#else
|
||||
# define {getset_basename}_DOCSTR NULL
|
||||
#endif
|
||||
#if defined({getset_name}_GETSETDEF)
|
||||
# undef {getset_name}_GETSETDEF
|
||||
# define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}},
|
||||
#else
|
||||
# define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}},
|
||||
#endif
|
||||
""")
|
||||
METHODDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet("""
|
||||
#ifndef {methoddef_name}
|
||||
#define {methoddef_name}
|
||||
#endif /* !defined({methoddef_name}) */
|
||||
""")
|
||||
COMPILER_DEPRECATION_WARNING_PROTOTYPE: Final[str] = r"""
|
||||
// Emit compiler warnings when we get to Python {major}.{minor}.
|
||||
#if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0
|
||||
@ -320,7 +153,7 @@ class CLanguage(Language):
|
||||
argname_fmt: str | None = None,
|
||||
*,
|
||||
fastcall: bool,
|
||||
codegen: Codegen,
|
||||
codegen: CodeGen,
|
||||
) -> str:
|
||||
assert len(params) > 0
|
||||
last_param = next(reversed(params.values()))
|
||||
@ -399,676 +232,10 @@ class CLanguage(Language):
|
||||
def output_templates(
|
||||
self,
|
||||
f: Function,
|
||||
codegen: Codegen,
|
||||
codegen: CodeGen,
|
||||
) -> dict[str, str]:
|
||||
parameters = list(f.parameters.values())
|
||||
assert parameters
|
||||
first_param = parameters.pop(0)
|
||||
assert isinstance(first_param.converter, self_converter)
|
||||
requires_defining_class = False
|
||||
if parameters and isinstance(parameters[0].converter, defining_class_converter):
|
||||
requires_defining_class = True
|
||||
del parameters[0]
|
||||
converters = [p.converter for p in parameters]
|
||||
|
||||
if f.critical_section:
|
||||
codegen.add_include('pycore_critical_section.h', 'Py_BEGIN_CRITICAL_SECTION()')
|
||||
has_option_groups = parameters and (parameters[0].group or parameters[-1].group)
|
||||
simple_return = (f.return_converter.type == 'PyObject *'
|
||||
and not f.critical_section)
|
||||
new_or_init = f.kind.new_or_init
|
||||
|
||||
vararg: int | str = self.NO_VARARG
|
||||
pos_only = min_pos = max_pos = min_kw_only = pseudo_args = 0
|
||||
for i, p in enumerate(parameters, 1):
|
||||
if p.is_keyword_only():
|
||||
assert not p.is_positional_only()
|
||||
if not p.is_optional():
|
||||
min_kw_only = i - max_pos
|
||||
elif p.is_vararg():
|
||||
pseudo_args += 1
|
||||
vararg = i - 1
|
||||
else:
|
||||
if vararg == self.NO_VARARG:
|
||||
max_pos = i
|
||||
if p.is_positional_only():
|
||||
pos_only = i
|
||||
if not p.is_optional():
|
||||
min_pos = i
|
||||
|
||||
meth_o = (len(parameters) == 1 and
|
||||
parameters[0].is_positional_only() and
|
||||
not converters[0].is_optional() and
|
||||
not requires_defining_class and
|
||||
not new_or_init)
|
||||
|
||||
# we have to set these things before we're done:
|
||||
#
|
||||
# docstring_prototype
|
||||
# docstring_definition
|
||||
# impl_prototype
|
||||
# methoddef_define
|
||||
# parser_prototype
|
||||
# parser_definition
|
||||
# impl_definition
|
||||
# cpp_if
|
||||
# cpp_endif
|
||||
# methoddef_ifndef
|
||||
|
||||
return_value_declaration = "PyObject *return_value = NULL;"
|
||||
methoddef_define = self.METHODDEF_PROTOTYPE_DEFINE
|
||||
if new_or_init and not f.docstring:
|
||||
docstring_prototype = docstring_definition = ''
|
||||
elif f.kind is GETTER:
|
||||
methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE
|
||||
if f.docstring:
|
||||
docstring_prototype = ''
|
||||
docstring_definition = self.GETSET_DOCSTRING_PROTOTYPE_STRVAR
|
||||
else:
|
||||
docstring_prototype = docstring_definition = ''
|
||||
elif f.kind is SETTER:
|
||||
if f.docstring:
|
||||
fail("docstrings are only supported for @getter, not @setter")
|
||||
return_value_declaration = "int {return_value};"
|
||||
methoddef_define = self.SETTERDEF_PROTOTYPE_DEFINE
|
||||
docstring_prototype = docstring_definition = ''
|
||||
else:
|
||||
docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR
|
||||
docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR
|
||||
impl_definition = self.IMPL_DEFINITION_PROTOTYPE
|
||||
impl_prototype = parser_prototype = parser_definition = None
|
||||
|
||||
# parser_body_fields remembers the fields passed in to the
|
||||
# previous call to parser_body. this is used for an awful hack.
|
||||
parser_body_fields: tuple[str, ...] = ()
|
||||
def parser_body(
|
||||
prototype: str,
|
||||
*fields: str,
|
||||
declarations: str = ''
|
||||
) -> str:
|
||||
nonlocal parser_body_fields
|
||||
lines = []
|
||||
lines.append(prototype)
|
||||
parser_body_fields = fields
|
||||
|
||||
preamble = libclinic.normalize_snippet("""
|
||||
{{
|
||||
{return_value_declaration}
|
||||
{parser_declarations}
|
||||
{declarations}
|
||||
{initializers}
|
||||
""") + "\n"
|
||||
finale = libclinic.normalize_snippet("""
|
||||
{modifications}
|
||||
{lock}
|
||||
{return_value} = {c_basename}_impl({impl_arguments});
|
||||
{unlock}
|
||||
{return_conversion}
|
||||
{post_parsing}
|
||||
|
||||
{exit_label}
|
||||
{cleanup}
|
||||
return return_value;
|
||||
}}
|
||||
""")
|
||||
for field in preamble, *fields, finale:
|
||||
lines.append(field)
|
||||
return libclinic.linear_format("\n".join(lines),
|
||||
parser_declarations=declarations)
|
||||
|
||||
fastcall = not new_or_init
|
||||
limited_capi = codegen.limited_capi
|
||||
if limited_capi and (pseudo_args or
|
||||
(any(p.is_optional() for p in parameters) and
|
||||
any(p.is_keyword_only() and not p.is_optional() for p in parameters)) or
|
||||
any(c.broken_limited_capi for c in converters)):
|
||||
warn(f"Function {f.full_name} cannot use limited C API")
|
||||
limited_capi = False
|
||||
|
||||
parsearg: str | None
|
||||
if not parameters:
|
||||
parser_code: list[str] | None
|
||||
if f.kind is GETTER:
|
||||
flags = "" # This should end up unused
|
||||
parser_prototype = self.PARSER_PROTOTYPE_GETTER
|
||||
parser_code = []
|
||||
elif f.kind is SETTER:
|
||||
flags = ""
|
||||
parser_prototype = self.PARSER_PROTOTYPE_SETTER
|
||||
parser_code = []
|
||||
elif not requires_defining_class:
|
||||
# no parameters, METH_NOARGS
|
||||
flags = "METH_NOARGS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_NOARGS
|
||||
parser_code = []
|
||||
else:
|
||||
assert fastcall
|
||||
|
||||
flags = "METH_METHOD|METH_FASTCALL|METH_KEYWORDS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
|
||||
return_error = ('return NULL;' if simple_return
|
||||
else 'goto exit;')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {{
|
||||
PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments");
|
||||
%s
|
||||
}}
|
||||
""" % return_error, indent=4)]
|
||||
|
||||
if simple_return:
|
||||
parser_definition = '\n'.join([
|
||||
parser_prototype,
|
||||
'{{',
|
||||
*parser_code,
|
||||
' return {c_basename}_impl({impl_arguments});',
|
||||
'}}'])
|
||||
else:
|
||||
parser_definition = parser_body(parser_prototype, *parser_code)
|
||||
|
||||
elif meth_o:
|
||||
flags = "METH_O"
|
||||
|
||||
if (isinstance(converters[0], object_converter) and
|
||||
converters[0].format_unit == 'O'):
|
||||
meth_o_prototype = self.METH_O_PROTOTYPE
|
||||
|
||||
if simple_return:
|
||||
# maps perfectly to METH_O, doesn't need a return converter.
|
||||
# so we skip making a parse function
|
||||
# and call directly into the impl function.
|
||||
impl_prototype = parser_prototype = parser_definition = ''
|
||||
impl_definition = meth_o_prototype
|
||||
else:
|
||||
# SLIGHT HACK
|
||||
# use impl_parameters for the parser here!
|
||||
parser_prototype = meth_o_prototype
|
||||
parser_definition = parser_body(parser_prototype)
|
||||
|
||||
else:
|
||||
argname = 'arg'
|
||||
if parameters[0].name == argname:
|
||||
argname += '_'
|
||||
parser_prototype = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *%s)
|
||||
""" % argname)
|
||||
|
||||
displayname = parameters[0].get_displayname(0)
|
||||
parsearg = converters[0].parse_arg(argname, displayname, limited_capi=limited_capi)
|
||||
if parsearg is None:
|
||||
converters[0].use_converter()
|
||||
parsearg = """
|
||||
if (!PyArg_Parse(%s, "{format_units}:{name}", {parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""" % argname
|
||||
parser_definition = parser_body(parser_prototype,
|
||||
libclinic.normalize_snippet(parsearg, indent=4))
|
||||
|
||||
elif has_option_groups:
|
||||
# positional parameters with option groups
|
||||
# (we have to generate lots of PyArg_ParseTuple calls
|
||||
# in a big switch statement)
|
||||
|
||||
flags = "METH_VARARGS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_VARARGS
|
||||
parser_definition = parser_body(parser_prototype, ' {option_group_parsing}')
|
||||
|
||||
elif not requires_defining_class and pos_only == len(parameters) - pseudo_args:
|
||||
if fastcall:
|
||||
# positional-only, but no option groups
|
||||
# we only need one call to _PyArg_ParseStack
|
||||
|
||||
flags = "METH_FASTCALL"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_FASTCALL
|
||||
nargs = 'nargs'
|
||||
argname_fmt = 'args[%d]'
|
||||
else:
|
||||
# positional-only, but no option groups
|
||||
# we only need one call to PyArg_ParseTuple
|
||||
|
||||
flags = "METH_VARARGS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_VARARGS
|
||||
if limited_capi:
|
||||
nargs = 'PyTuple_Size(args)'
|
||||
argname_fmt = 'PyTuple_GetItem(args, %d)'
|
||||
else:
|
||||
nargs = 'PyTuple_GET_SIZE(args)'
|
||||
argname_fmt = 'PyTuple_GET_ITEM(args, %d)'
|
||||
|
||||
left_args = f"{nargs} - {max_pos}"
|
||||
max_args = self.NO_VARARG if (vararg != self.NO_VARARG) else max_pos
|
||||
if limited_capi:
|
||||
parser_code = []
|
||||
if nargs != 'nargs':
|
||||
nargs_def = f'Py_ssize_t nargs = {nargs};'
|
||||
parser_code.append(libclinic.normalize_snippet(nargs_def, indent=4))
|
||||
nargs = 'nargs'
|
||||
if min_pos == max_args:
|
||||
pl = '' if min_pos == 1 else 's'
|
||||
parser_code.append(libclinic.normalize_snippet(f"""
|
||||
if ({nargs} != {min_pos}) {{{{
|
||||
PyErr_Format(PyExc_TypeError, "{{name}} expected {min_pos} argument{pl}, got %zd", {nargs});
|
||||
goto exit;
|
||||
}}}}
|
||||
""",
|
||||
indent=4))
|
||||
else:
|
||||
if min_pos:
|
||||
pl = '' if min_pos == 1 else 's'
|
||||
parser_code.append(libclinic.normalize_snippet(f"""
|
||||
if ({nargs} < {min_pos}) {{{{
|
||||
PyErr_Format(PyExc_TypeError, "{{name}} expected at least {min_pos} argument{pl}, got %zd", {nargs});
|
||||
goto exit;
|
||||
}}}}
|
||||
""",
|
||||
indent=4))
|
||||
if max_args != self.NO_VARARG:
|
||||
pl = '' if max_args == 1 else 's'
|
||||
parser_code.append(libclinic.normalize_snippet(f"""
|
||||
if ({nargs} > {max_args}) {{{{
|
||||
PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs});
|
||||
goto exit;
|
||||
}}}}
|
||||
""",
|
||||
indent=4))
|
||||
else:
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_CheckPositional()')
|
||||
parser_code = [libclinic.normalize_snippet(f"""
|
||||
if (!_PyArg_CheckPositional("{{name}}", {nargs}, {min_pos}, {max_args})) {{{{
|
||||
goto exit;
|
||||
}}}}
|
||||
""", indent=4)]
|
||||
|
||||
has_optional = False
|
||||
for i, p in enumerate(parameters):
|
||||
if p.is_vararg():
|
||||
if fastcall:
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
%s = PyTuple_New(%s);
|
||||
if (!%s) {{
|
||||
goto exit;
|
||||
}}
|
||||
for (Py_ssize_t i = 0; i < %s; ++i) {{
|
||||
PyTuple_SET_ITEM(%s, i, Py_NewRef(args[%d + i]));
|
||||
}}
|
||||
""" % (
|
||||
p.converter.parser_name,
|
||||
left_args,
|
||||
p.converter.parser_name,
|
||||
left_args,
|
||||
p.converter.parser_name,
|
||||
max_pos
|
||||
), indent=4))
|
||||
else:
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
%s = PyTuple_GetSlice(%d, -1);
|
||||
""" % (
|
||||
p.converter.parser_name,
|
||||
max_pos
|
||||
), indent=4))
|
||||
continue
|
||||
|
||||
displayname = p.get_displayname(i+1)
|
||||
argname = argname_fmt % i
|
||||
parsearg = p.converter.parse_arg(argname, displayname, limited_capi=limited_capi)
|
||||
if parsearg is None:
|
||||
parser_code = None
|
||||
break
|
||||
if has_optional or p.is_optional():
|
||||
has_optional = True
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (%s < %d) {{
|
||||
goto skip_optional;
|
||||
}}
|
||||
""", indent=4) % (nargs, i + 1))
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
|
||||
if parser_code is not None:
|
||||
if has_optional:
|
||||
parser_code.append("skip_optional:")
|
||||
else:
|
||||
for parameter in parameters:
|
||||
parameter.converter.use_converter()
|
||||
|
||||
if limited_capi:
|
||||
fastcall = False
|
||||
if fastcall:
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_ParseStack()')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}",
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
else:
|
||||
flags = "METH_VARARGS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_VARARGS
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!PyArg_ParseTuple(args, "{format_units}:{name}",
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
parser_definition = parser_body(parser_prototype, *parser_code)
|
||||
|
||||
else:
|
||||
deprecated_positionals: dict[int, Parameter] = {}
|
||||
deprecated_keywords: dict[int, Parameter] = {}
|
||||
for i, p in enumerate(parameters):
|
||||
if p.deprecated_positional:
|
||||
deprecated_positionals[i] = p
|
||||
if p.deprecated_keyword:
|
||||
deprecated_keywords[i] = p
|
||||
|
||||
has_optional_kw = (
|
||||
max(pos_only, min_pos) + min_kw_only
|
||||
< len(converters) - int(vararg != self.NO_VARARG)
|
||||
)
|
||||
|
||||
if limited_capi:
|
||||
parser_code = None
|
||||
fastcall = False
|
||||
else:
|
||||
if vararg == self.NO_VARARG:
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_UnpackKeywords()')
|
||||
args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % (
|
||||
min_pos,
|
||||
max_pos,
|
||||
min_kw_only
|
||||
)
|
||||
nargs = "nargs"
|
||||
else:
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_UnpackKeywordsWithVararg()')
|
||||
args_declaration = "_PyArg_UnpackKeywordsWithVararg", "%s, %s, %s, %s" % (
|
||||
min_pos,
|
||||
max_pos,
|
||||
min_kw_only,
|
||||
vararg
|
||||
)
|
||||
nargs = f"Py_MIN(nargs, {max_pos})" if max_pos else "0"
|
||||
|
||||
if fastcall:
|
||||
flags = "METH_FASTCALL|METH_KEYWORDS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_FASTCALL_KEYWORDS
|
||||
argname_fmt = 'args[%d]'
|
||||
declarations = declare_parser(f, codegen=codegen)
|
||||
declarations += "\nPyObject *argsbuf[%s];" % len(converters)
|
||||
if has_optional_kw:
|
||||
declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, min_pos + min_kw_only)
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf);
|
||||
if (!args) {{
|
||||
goto exit;
|
||||
}}
|
||||
""" % args_declaration, indent=4)]
|
||||
else:
|
||||
# positional-or-keyword arguments
|
||||
flags = "METH_VARARGS|METH_KEYWORDS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
|
||||
argname_fmt = 'fastargs[%d]'
|
||||
declarations = declare_parser(f, codegen=codegen)
|
||||
declarations += "\nPyObject *argsbuf[%s];" % len(converters)
|
||||
declarations += "\nPyObject * const *fastargs;"
|
||||
declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
|
||||
if has_optional_kw:
|
||||
declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, min_pos + min_kw_only)
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf);
|
||||
if (!fastargs) {{
|
||||
goto exit;
|
||||
}}
|
||||
""" % args_declaration, indent=4)]
|
||||
|
||||
if requires_defining_class:
|
||||
flags = 'METH_METHOD|' + flags
|
||||
parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
|
||||
|
||||
if parser_code is not None:
|
||||
if deprecated_keywords:
|
||||
code = self.deprecate_keyword_use(f, deprecated_keywords,
|
||||
argname_fmt,
|
||||
codegen=codegen,
|
||||
fastcall=fastcall)
|
||||
parser_code.append(code)
|
||||
|
||||
add_label: str | None = None
|
||||
for i, p in enumerate(parameters):
|
||||
if isinstance(p.converter, defining_class_converter):
|
||||
raise ValueError("defining_class should be the first "
|
||||
"parameter (after self)")
|
||||
displayname = p.get_displayname(i+1)
|
||||
parsearg = p.converter.parse_arg(argname_fmt % i, displayname, limited_capi=limited_capi)
|
||||
if parsearg is None:
|
||||
parser_code = None
|
||||
break
|
||||
if add_label and (i == pos_only or i == max_pos):
|
||||
parser_code.append("%s:" % add_label)
|
||||
add_label = None
|
||||
if not p.is_optional():
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
elif i < pos_only:
|
||||
add_label = 'skip_optional_posonly'
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (nargs < %d) {{
|
||||
goto %s;
|
||||
}}
|
||||
""" % (i + 1, add_label), indent=4))
|
||||
if has_optional_kw:
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
noptargs--;
|
||||
""", indent=4))
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
else:
|
||||
if i < max_pos:
|
||||
label = 'skip_optional_pos'
|
||||
first_opt = max(min_pos, pos_only)
|
||||
else:
|
||||
label = 'skip_optional_kwonly'
|
||||
first_opt = max_pos + min_kw_only
|
||||
if vararg != self.NO_VARARG:
|
||||
first_opt += 1
|
||||
if i == first_opt:
|
||||
add_label = label
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (!noptargs) {{
|
||||
goto %s;
|
||||
}}
|
||||
""" % add_label, indent=4))
|
||||
if i + 1 == len(parameters):
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
else:
|
||||
add_label = label
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (%s) {{
|
||||
""" % (argname_fmt % i), indent=4))
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=8))
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (!--noptargs) {{
|
||||
goto %s;
|
||||
}}
|
||||
}}
|
||||
""" % add_label, indent=4))
|
||||
|
||||
if parser_code is not None:
|
||||
if add_label:
|
||||
parser_code.append("%s:" % add_label)
|
||||
else:
|
||||
for parameter in parameters:
|
||||
parameter.converter.use_converter()
|
||||
|
||||
declarations = declare_parser(f, codegen=codegen,
|
||||
hasformat=True)
|
||||
if limited_capi:
|
||||
# positional-or-keyword arguments
|
||||
assert not fastcall
|
||||
flags = "METH_VARARGS|METH_KEYWORDS"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
|
||||
{parse_arguments}))
|
||||
goto exit;
|
||||
""", indent=4)]
|
||||
declarations = "static char *_keywords[] = {{{keywords_c} NULL}};"
|
||||
if deprecated_positionals or deprecated_keywords:
|
||||
declarations += "\nPy_ssize_t nargs = PyTuple_Size(args);"
|
||||
|
||||
elif fastcall:
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_ParseStackAndKeywords()')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
else:
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_ParseTupleAndKeywordsFast()')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
if deprecated_positionals or deprecated_keywords:
|
||||
declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
|
||||
if deprecated_keywords:
|
||||
code = self.deprecate_keyword_use(f, deprecated_keywords,
|
||||
codegen=codegen,
|
||||
fastcall=fastcall)
|
||||
parser_code.append(code)
|
||||
|
||||
if deprecated_positionals:
|
||||
code = self.deprecate_positional_use(f, deprecated_positionals)
|
||||
# Insert the deprecation code before parameter parsing.
|
||||
parser_code.insert(0, code)
|
||||
|
||||
assert parser_prototype is not None
|
||||
parser_definition = parser_body(parser_prototype, *parser_code,
|
||||
declarations=declarations)
|
||||
|
||||
|
||||
# Copy includes from parameters to Clinic after parse_arg() has been
|
||||
# called above.
|
||||
for converter in converters:
|
||||
for include in converter.get_includes():
|
||||
codegen.add_include(include.filename, include.reason,
|
||||
condition=include.condition)
|
||||
|
||||
if new_or_init:
|
||||
methoddef_define = ''
|
||||
|
||||
if f.kind is METHOD_NEW:
|
||||
parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
|
||||
else:
|
||||
return_value_declaration = "int return_value = -1;"
|
||||
parser_prototype = self.PARSER_PROTOTYPE_KEYWORD___INIT__
|
||||
|
||||
fields = list(parser_body_fields)
|
||||
parses_positional = 'METH_NOARGS' not in flags
|
||||
parses_keywords = 'METH_KEYWORDS' in flags
|
||||
if parses_keywords:
|
||||
assert parses_positional
|
||||
|
||||
if requires_defining_class:
|
||||
raise ValueError("Slot methods cannot access their defining class.")
|
||||
|
||||
if not parses_keywords:
|
||||
declarations = '{base_type_ptr}'
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_NoKeywords()')
|
||||
fields.insert(0, libclinic.normalize_snippet("""
|
||||
if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4))
|
||||
if not parses_positional:
|
||||
codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_NoPositional()')
|
||||
fields.insert(0, libclinic.normalize_snippet("""
|
||||
if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4))
|
||||
|
||||
parser_definition = parser_body(parser_prototype, *fields,
|
||||
declarations=declarations)
|
||||
|
||||
|
||||
methoddef_cast_end = ""
|
||||
if flags in ('METH_NOARGS', 'METH_O', 'METH_VARARGS'):
|
||||
methoddef_cast = "(PyCFunction)"
|
||||
elif f.kind is GETTER:
|
||||
methoddef_cast = "" # This should end up unused
|
||||
elif limited_capi:
|
||||
methoddef_cast = "(PyCFunction)(void(*)(void))"
|
||||
else:
|
||||
methoddef_cast = "_PyCFunction_CAST("
|
||||
methoddef_cast_end = ")"
|
||||
|
||||
if f.methoddef_flags:
|
||||
flags += '|' + f.methoddef_flags
|
||||
|
||||
methoddef_define = methoddef_define.replace('{methoddef_flags}', flags)
|
||||
methoddef_define = methoddef_define.replace('{methoddef_cast}', methoddef_cast)
|
||||
methoddef_define = methoddef_define.replace('{methoddef_cast_end}', methoddef_cast_end)
|
||||
|
||||
methoddef_ifndef = ''
|
||||
conditional = self.cpp.condition()
|
||||
if not conditional:
|
||||
cpp_if = cpp_endif = ''
|
||||
else:
|
||||
cpp_if = "#if " + conditional
|
||||
cpp_endif = "#endif /* " + conditional + " */"
|
||||
|
||||
if methoddef_define and codegen.add_ifndef_symbol(f.full_name):
|
||||
methoddef_ifndef = self.METHODDEF_PROTOTYPE_IFNDEF
|
||||
|
||||
# add ';' to the end of parser_prototype and impl_prototype
|
||||
# (they mustn't be None, but they could be an empty string.)
|
||||
assert parser_prototype is not None
|
||||
if parser_prototype:
|
||||
assert not parser_prototype.endswith(';')
|
||||
parser_prototype += ';'
|
||||
|
||||
if impl_prototype is None:
|
||||
impl_prototype = impl_definition
|
||||
if impl_prototype:
|
||||
impl_prototype += ";"
|
||||
|
||||
parser_definition = parser_definition.replace("{return_value_declaration}", return_value_declaration)
|
||||
|
||||
compiler_warning = self.compiler_deprecated_warning(f, parameters)
|
||||
if compiler_warning:
|
||||
parser_definition = compiler_warning + "\n\n" + parser_definition
|
||||
|
||||
d = {
|
||||
"docstring_prototype" : docstring_prototype,
|
||||
"docstring_definition" : docstring_definition,
|
||||
"impl_prototype" : impl_prototype,
|
||||
"methoddef_define" : methoddef_define,
|
||||
"parser_prototype" : parser_prototype,
|
||||
"parser_definition" : parser_definition,
|
||||
"impl_definition" : impl_definition,
|
||||
"cpp_if" : cpp_if,
|
||||
"cpp_endif" : cpp_endif,
|
||||
"methoddef_ifndef" : methoddef_ifndef,
|
||||
}
|
||||
|
||||
# make sure we didn't forget to assign something,
|
||||
# and wrap each non-empty value in \n's
|
||||
d2 = {}
|
||||
for name, value in d.items():
|
||||
assert value is not None, "got a None value for template " + repr(name)
|
||||
if value:
|
||||
value = '\n' + value + '\n'
|
||||
d2[name] = value
|
||||
return d2
|
||||
args = ParseArgsCodeGen(f, codegen)
|
||||
return args.parse_args(self)
|
||||
|
||||
@staticmethod
|
||||
def group_to_variable_name(group: int) -> str:
|
||||
|
@ -266,7 +266,7 @@ class Destination:
|
||||
DestinationDict = dict[str, Destination]
|
||||
|
||||
|
||||
class Codegen:
|
||||
class CodeGen:
|
||||
def __init__(self, limited_capi: bool) -> None:
|
||||
self.limited_capi = limited_capi
|
||||
self._ifndef_symbols: set[str] = set()
|
||||
|
940
Tools/clinic/libclinic/parse_args.py
Normal file
940
Tools/clinic/libclinic/parse_args.py
Normal file
@ -0,0 +1,940 @@
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
import libclinic
|
||||
from libclinic import fail, warn
|
||||
from libclinic.function import (
|
||||
Function, Parameter,
|
||||
GETTER, SETTER, METHOD_NEW)
|
||||
from libclinic.converter import CConverter
|
||||
from libclinic.converters import (
|
||||
defining_class_converter, object_converter, self_converter)
|
||||
if TYPE_CHECKING:
|
||||
from libclinic.clanguage import CLanguage
|
||||
from libclinic.codegen import CodeGen
|
||||
|
||||
|
||||
def declare_parser(
|
||||
f: Function,
|
||||
*,
|
||||
hasformat: bool = False,
|
||||
codegen: CodeGen,
|
||||
) -> str:
|
||||
"""
|
||||
Generates the code template for a static local PyArg_Parser variable,
|
||||
with an initializer. For core code (incl. builtin modules) the
|
||||
kwtuple field is also statically initialized. Otherwise
|
||||
it is initialized at runtime.
|
||||
"""
|
||||
limited_capi = codegen.limited_capi
|
||||
if hasformat:
|
||||
fname = ''
|
||||
format_ = '.format = "{format_units}:{name}",'
|
||||
else:
|
||||
fname = '.fname = "{name}",'
|
||||
format_ = ''
|
||||
|
||||
num_keywords = len([
|
||||
p for p in f.parameters.values()
|
||||
if not p.is_positional_only() and not p.is_vararg()
|
||||
])
|
||||
if limited_capi:
|
||||
declarations = """
|
||||
#define KWTUPLE NULL
|
||||
"""
|
||||
elif num_keywords == 0:
|
||||
declarations = """
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
# define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
|
||||
#else
|
||||
# define KWTUPLE NULL
|
||||
#endif
|
||||
"""
|
||||
else:
|
||||
declarations = """
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
|
||||
#define NUM_KEYWORDS %d
|
||||
static struct {{
|
||||
PyGC_Head _this_is_not_used;
|
||||
PyObject_VAR_HEAD
|
||||
PyObject *ob_item[NUM_KEYWORDS];
|
||||
}} _kwtuple = {{
|
||||
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||
.ob_item = {{ {keywords_py} }},
|
||||
}};
|
||||
#undef NUM_KEYWORDS
|
||||
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||
|
||||
#else // !Py_BUILD_CORE
|
||||
# define KWTUPLE NULL
|
||||
#endif // !Py_BUILD_CORE
|
||||
""" % num_keywords
|
||||
|
||||
condition = '#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)'
|
||||
codegen.add_include('pycore_gc.h', 'PyGC_Head', condition=condition)
|
||||
codegen.add_include('pycore_runtime.h', '_Py_ID()', condition=condition)
|
||||
|
||||
declarations += """
|
||||
static const char * const _keywords[] = {{{keywords_c} NULL}};
|
||||
static _PyArg_Parser _parser = {{
|
||||
.keywords = _keywords,
|
||||
%s
|
||||
.kwtuple = KWTUPLE,
|
||||
}};
|
||||
#undef KWTUPLE
|
||||
""" % (format_ or fname)
|
||||
return libclinic.normalize_snippet(declarations)
|
||||
|
||||
|
||||
NO_VARARG: Final[str] = "PY_SSIZE_T_MAX"
|
||||
PARSER_PROTOTYPE_KEYWORD: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
|
||||
""")
|
||||
PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = libclinic.normalize_snippet("""
|
||||
static int
|
||||
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
|
||||
""")
|
||||
PARSER_PROTOTYPE_VARARGS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *args)
|
||||
""")
|
||||
PARSER_PROTOTYPE_FASTCALL: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs)
|
||||
""")
|
||||
PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
""")
|
||||
PARSER_PROTOTYPE_DEF_CLASS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
""")
|
||||
PARSER_PROTOTYPE_NOARGS: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored))
|
||||
""")
|
||||
PARSER_PROTOTYPE_GETTER: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, void *Py_UNUSED(context))
|
||||
""")
|
||||
PARSER_PROTOTYPE_SETTER: Final[str] = libclinic.normalize_snippet("""
|
||||
static int
|
||||
{c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context))
|
||||
""")
|
||||
METH_O_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({impl_parameters})
|
||||
""")
|
||||
DOCSTRING_PROTOTYPE_VAR: Final[str] = libclinic.normalize_snippet("""
|
||||
PyDoc_VAR({c_basename}__doc__);
|
||||
""")
|
||||
DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
|
||||
PyDoc_STRVAR({c_basename}__doc__,
|
||||
{docstring});
|
||||
""")
|
||||
GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
|
||||
PyDoc_STRVAR({getset_basename}__doc__,
|
||||
{docstring});
|
||||
#define {getset_basename}_HAS_DOCSTR
|
||||
""")
|
||||
IMPL_DEFINITION_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
|
||||
static {impl_return_type}
|
||||
{c_basename}_impl({impl_parameters})
|
||||
""")
|
||||
METHODDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
|
||||
#define {methoddef_name} \
|
||||
{{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}},
|
||||
""")
|
||||
GETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
|
||||
#if defined({getset_basename}_HAS_DOCSTR)
|
||||
# define {getset_basename}_DOCSTR {getset_basename}__doc__
|
||||
#else
|
||||
# define {getset_basename}_DOCSTR NULL
|
||||
#endif
|
||||
#if defined({getset_name}_GETSETDEF)
|
||||
# undef {getset_name}_GETSETDEF
|
||||
# define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}},
|
||||
#else
|
||||
# define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}},
|
||||
#endif
|
||||
""")
|
||||
SETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
|
||||
#if defined({getset_name}_HAS_DOCSTR)
|
||||
# define {getset_basename}_DOCSTR {getset_basename}__doc__
|
||||
#else
|
||||
# define {getset_basename}_DOCSTR NULL
|
||||
#endif
|
||||
#if defined({getset_name}_GETSETDEF)
|
||||
# undef {getset_name}_GETSETDEF
|
||||
# define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}},
|
||||
#else
|
||||
# define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}},
|
||||
#endif
|
||||
""")
|
||||
METHODDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet("""
|
||||
#ifndef {methoddef_name}
|
||||
#define {methoddef_name}
|
||||
#endif /* !defined({methoddef_name}) */
|
||||
""")
|
||||
|
||||
|
||||
class ParseArgsCodeGen:
|
||||
func: Function
|
||||
codegen: CodeGen
|
||||
limited_capi: bool = False
|
||||
|
||||
# Function parameters
|
||||
parameters: list[Parameter]
|
||||
converters: list[CConverter]
|
||||
|
||||
# Is 'defining_class' used for the first parameter?
|
||||
requires_defining_class: bool
|
||||
|
||||
# Use METH_FASTCALL calling convention?
|
||||
fastcall: bool
|
||||
|
||||
# Declaration of the return variable (ex: "int return_value;")
|
||||
return_value_declaration: str
|
||||
|
||||
# Calling convention (ex: "METH_NOARGS")
|
||||
flags: str
|
||||
|
||||
# Variables declarations
|
||||
declarations: str
|
||||
|
||||
pos_only: int = 0
|
||||
min_pos: int = 0
|
||||
max_pos: int = 0
|
||||
min_kw_only: int = 0
|
||||
pseudo_args: int = 0
|
||||
vararg: int | str = NO_VARARG
|
||||
|
||||
docstring_prototype: str
|
||||
docstring_definition: str
|
||||
impl_prototype: str | None
|
||||
impl_definition: str
|
||||
methoddef_define: str
|
||||
parser_prototype: str
|
||||
parser_definition: str
|
||||
cpp_if: str
|
||||
cpp_endif: str
|
||||
methoddef_ifndef: str
|
||||
|
||||
parser_body_fields: tuple[str, ...]
|
||||
|
||||
def __init__(self, func: Function, codegen: CodeGen) -> None:
|
||||
self.func = func
|
||||
self.codegen = codegen
|
||||
|
||||
self.parameters = list(self.func.parameters.values())
|
||||
first_param = self.parameters.pop(0)
|
||||
if not isinstance(first_param.converter, self_converter):
|
||||
raise ValueError("the first parameter must use self_converter")
|
||||
|
||||
self.requires_defining_class = False
|
||||
if self.parameters and isinstance(self.parameters[0].converter, defining_class_converter):
|
||||
self.requires_defining_class = True
|
||||
del self.parameters[0]
|
||||
self.converters = [p.converter for p in self.parameters]
|
||||
|
||||
if self.func.critical_section:
|
||||
self.codegen.add_include('pycore_critical_section.h',
|
||||
'Py_BEGIN_CRITICAL_SECTION()')
|
||||
self.fastcall = not self.is_new_or_init()
|
||||
|
||||
self.pos_only = 0
|
||||
self.min_pos = 0
|
||||
self.max_pos = 0
|
||||
self.min_kw_only = 0
|
||||
self.pseudo_args = 0
|
||||
for i, p in enumerate(self.parameters, 1):
|
||||
if p.is_keyword_only():
|
||||
assert not p.is_positional_only()
|
||||
if not p.is_optional():
|
||||
self.min_kw_only = i - self.max_pos
|
||||
elif p.is_vararg():
|
||||
self.pseudo_args += 1
|
||||
self.vararg = i - 1
|
||||
else:
|
||||
if self.vararg == NO_VARARG:
|
||||
self.max_pos = i
|
||||
if p.is_positional_only():
|
||||
self.pos_only = i
|
||||
if not p.is_optional():
|
||||
self.min_pos = i
|
||||
|
||||
def is_new_or_init(self) -> bool:
|
||||
return self.func.kind.new_or_init
|
||||
|
||||
def has_option_groups(self) -> bool:
|
||||
return (bool(self.parameters
|
||||
and (self.parameters[0].group or self.parameters[-1].group)))
|
||||
|
||||
def use_meth_o(self) -> bool:
|
||||
return (len(self.parameters) == 1
|
||||
and self.parameters[0].is_positional_only()
|
||||
and not self.converters[0].is_optional()
|
||||
and not self.requires_defining_class
|
||||
and not self.is_new_or_init())
|
||||
|
||||
def use_simple_return(self) -> bool:
|
||||
return (self.func.return_converter.type == 'PyObject *'
|
||||
and not self.func.critical_section)
|
||||
|
||||
def select_prototypes(self) -> None:
|
||||
self.docstring_prototype = ''
|
||||
self.docstring_definition = ''
|
||||
self.methoddef_define = METHODDEF_PROTOTYPE_DEFINE
|
||||
self.return_value_declaration = "PyObject *return_value = NULL;"
|
||||
|
||||
if self.is_new_or_init() and not self.func.docstring:
|
||||
pass
|
||||
elif self.func.kind is GETTER:
|
||||
self.methoddef_define = GETTERDEF_PROTOTYPE_DEFINE
|
||||
if self.func.docstring:
|
||||
self.docstring_definition = GETSET_DOCSTRING_PROTOTYPE_STRVAR
|
||||
elif self.func.kind is SETTER:
|
||||
if self.func.docstring:
|
||||
fail("docstrings are only supported for @getter, not @setter")
|
||||
self.return_value_declaration = "int {return_value};"
|
||||
self.methoddef_define = SETTERDEF_PROTOTYPE_DEFINE
|
||||
else:
|
||||
self.docstring_prototype = DOCSTRING_PROTOTYPE_VAR
|
||||
self.docstring_definition = DOCSTRING_PROTOTYPE_STRVAR
|
||||
|
||||
def init_limited_capi(self) -> None:
|
||||
self.limited_capi = self.codegen.limited_capi
|
||||
if self.limited_capi and (self.pseudo_args or
|
||||
(any(p.is_optional() for p in self.parameters) and
|
||||
any(p.is_keyword_only() and not p.is_optional() for p in self.parameters)) or
|
||||
any(c.broken_limited_capi for c in self.converters)):
|
||||
warn(f"Function {self.func.full_name} cannot use limited C API")
|
||||
self.limited_capi = False
|
||||
|
||||
def parser_body(
|
||||
self,
|
||||
*fields: str,
|
||||
declarations: str = ''
|
||||
) -> None:
|
||||
lines = [self.parser_prototype]
|
||||
self.parser_body_fields = fields
|
||||
|
||||
preamble = libclinic.normalize_snippet("""
|
||||
{{
|
||||
{return_value_declaration}
|
||||
{parser_declarations}
|
||||
{declarations}
|
||||
{initializers}
|
||||
""") + "\n"
|
||||
finale = libclinic.normalize_snippet("""
|
||||
{modifications}
|
||||
{lock}
|
||||
{return_value} = {c_basename}_impl({impl_arguments});
|
||||
{unlock}
|
||||
{return_conversion}
|
||||
{post_parsing}
|
||||
|
||||
{exit_label}
|
||||
{cleanup}
|
||||
return return_value;
|
||||
}}
|
||||
""")
|
||||
for field in preamble, *fields, finale:
|
||||
lines.append(field)
|
||||
code = libclinic.linear_format("\n".join(lines),
|
||||
parser_declarations=self.declarations)
|
||||
self.parser_definition = code
|
||||
|
||||
def parse_no_args(self) -> None:
|
||||
parser_code: list[str] | None
|
||||
simple_return = self.use_simple_return()
|
||||
if self.func.kind is GETTER:
|
||||
self.parser_prototype = PARSER_PROTOTYPE_GETTER
|
||||
parser_code = []
|
||||
elif self.func.kind is SETTER:
|
||||
self.parser_prototype = PARSER_PROTOTYPE_SETTER
|
||||
parser_code = []
|
||||
elif not self.requires_defining_class:
|
||||
# no self.parameters, METH_NOARGS
|
||||
self.flags = "METH_NOARGS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_NOARGS
|
||||
parser_code = []
|
||||
else:
|
||||
assert self.fastcall
|
||||
|
||||
self.flags = "METH_METHOD|METH_FASTCALL|METH_KEYWORDS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_DEF_CLASS
|
||||
return_error = ('return NULL;' if simple_return
|
||||
else 'goto exit;')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {{
|
||||
PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments");
|
||||
%s
|
||||
}}
|
||||
""" % return_error, indent=4)]
|
||||
|
||||
if simple_return:
|
||||
self.parser_definition = '\n'.join([
|
||||
self.parser_prototype,
|
||||
'{{',
|
||||
*parser_code,
|
||||
' return {c_basename}_impl({impl_arguments});',
|
||||
'}}'])
|
||||
else:
|
||||
self.parser_body(*parser_code)
|
||||
|
||||
def parse_one_arg(self) -> None:
|
||||
self.flags = "METH_O"
|
||||
|
||||
if (isinstance(self.converters[0], object_converter) and
|
||||
self.converters[0].format_unit == 'O'):
|
||||
meth_o_prototype = METH_O_PROTOTYPE
|
||||
|
||||
if self.use_simple_return():
|
||||
# maps perfectly to METH_O, doesn't need a return converter.
|
||||
# so we skip making a parse function
|
||||
# and call directly into the impl function.
|
||||
self.impl_prototype = ''
|
||||
self.impl_definition = meth_o_prototype
|
||||
else:
|
||||
# SLIGHT HACK
|
||||
# use impl_parameters for the parser here!
|
||||
self.parser_prototype = meth_o_prototype
|
||||
self.parser_body()
|
||||
|
||||
else:
|
||||
argname = 'arg'
|
||||
if self.parameters[0].name == argname:
|
||||
argname += '_'
|
||||
self.parser_prototype = libclinic.normalize_snippet("""
|
||||
static PyObject *
|
||||
{c_basename}({self_type}{self_name}, PyObject *%s)
|
||||
""" % argname)
|
||||
|
||||
displayname = self.parameters[0].get_displayname(0)
|
||||
parsearg: str | None
|
||||
parsearg = self.converters[0].parse_arg(argname, displayname,
|
||||
limited_capi=self.limited_capi)
|
||||
if parsearg is None:
|
||||
self.converters[0].use_converter()
|
||||
parsearg = """
|
||||
if (!PyArg_Parse(%s, "{format_units}:{name}", {parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""" % argname
|
||||
|
||||
parser_code = libclinic.normalize_snippet(parsearg, indent=4)
|
||||
self.parser_body(parser_code)
|
||||
|
||||
def parse_option_groups(self) -> None:
|
||||
# positional parameters with option groups
|
||||
# (we have to generate lots of PyArg_ParseTuple calls
|
||||
# in a big switch statement)
|
||||
|
||||
self.flags = "METH_VARARGS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_VARARGS
|
||||
parser_code = ' {option_group_parsing}'
|
||||
self.parser_body(parser_code)
|
||||
|
||||
def parse_pos_only(self) -> None:
|
||||
if self.fastcall:
|
||||
# positional-only, but no option groups
|
||||
# we only need one call to _PyArg_ParseStack
|
||||
|
||||
self.flags = "METH_FASTCALL"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_FASTCALL
|
||||
nargs = 'nargs'
|
||||
argname_fmt = 'args[%d]'
|
||||
else:
|
||||
# positional-only, but no option groups
|
||||
# we only need one call to PyArg_ParseTuple
|
||||
|
||||
self.flags = "METH_VARARGS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_VARARGS
|
||||
if self.limited_capi:
|
||||
nargs = 'PyTuple_Size(args)'
|
||||
argname_fmt = 'PyTuple_GetItem(args, %d)'
|
||||
else:
|
||||
nargs = 'PyTuple_GET_SIZE(args)'
|
||||
argname_fmt = 'PyTuple_GET_ITEM(args, %d)'
|
||||
|
||||
left_args = f"{nargs} - {self.max_pos}"
|
||||
max_args = NO_VARARG if (self.vararg != NO_VARARG) else self.max_pos
|
||||
if self.limited_capi:
|
||||
parser_code = []
|
||||
if nargs != 'nargs':
|
||||
nargs_def = f'Py_ssize_t nargs = {nargs};'
|
||||
parser_code.append(libclinic.normalize_snippet(nargs_def, indent=4))
|
||||
nargs = 'nargs'
|
||||
if self.min_pos == max_args:
|
||||
pl = '' if self.min_pos == 1 else 's'
|
||||
parser_code.append(libclinic.normalize_snippet(f"""
|
||||
if ({nargs} != {self.min_pos}) {{{{
|
||||
PyErr_Format(PyExc_TypeError, "{{name}} expected {self.min_pos} argument{pl}, got %zd", {nargs});
|
||||
goto exit;
|
||||
}}}}
|
||||
""",
|
||||
indent=4))
|
||||
else:
|
||||
if self.min_pos:
|
||||
pl = '' if self.min_pos == 1 else 's'
|
||||
parser_code.append(libclinic.normalize_snippet(f"""
|
||||
if ({nargs} < {self.min_pos}) {{{{
|
||||
PyErr_Format(PyExc_TypeError, "{{name}} expected at least {self.min_pos} argument{pl}, got %zd", {nargs});
|
||||
goto exit;
|
||||
}}}}
|
||||
""",
|
||||
indent=4))
|
||||
if max_args != NO_VARARG:
|
||||
pl = '' if max_args == 1 else 's'
|
||||
parser_code.append(libclinic.normalize_snippet(f"""
|
||||
if ({nargs} > {max_args}) {{{{
|
||||
PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs});
|
||||
goto exit;
|
||||
}}}}
|
||||
""",
|
||||
indent=4))
|
||||
else:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_CheckPositional()')
|
||||
parser_code = [libclinic.normalize_snippet(f"""
|
||||
if (!_PyArg_CheckPositional("{{name}}", {nargs}, {self.min_pos}, {max_args})) {{{{
|
||||
goto exit;
|
||||
}}}}
|
||||
""", indent=4)]
|
||||
|
||||
has_optional = False
|
||||
use_parser_code = True
|
||||
for i, p in enumerate(self.parameters):
|
||||
if p.is_vararg():
|
||||
if self.fastcall:
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
%s = PyTuple_New(%s);
|
||||
if (!%s) {{
|
||||
goto exit;
|
||||
}}
|
||||
for (Py_ssize_t i = 0; i < %s; ++i) {{
|
||||
PyTuple_SET_ITEM(%s, i, Py_NewRef(args[%d + i]));
|
||||
}}
|
||||
""" % (
|
||||
p.converter.parser_name,
|
||||
left_args,
|
||||
p.converter.parser_name,
|
||||
left_args,
|
||||
p.converter.parser_name,
|
||||
self.max_pos
|
||||
), indent=4))
|
||||
else:
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
%s = PyTuple_GetSlice(%d, -1);
|
||||
""" % (
|
||||
p.converter.parser_name,
|
||||
self.max_pos
|
||||
), indent=4))
|
||||
continue
|
||||
|
||||
displayname = p.get_displayname(i+1)
|
||||
argname = argname_fmt % i
|
||||
parsearg: str | None
|
||||
parsearg = p.converter.parse_arg(argname, displayname, limited_capi=self.limited_capi)
|
||||
if parsearg is None:
|
||||
use_parser_code = False
|
||||
parser_code = []
|
||||
break
|
||||
if has_optional or p.is_optional():
|
||||
has_optional = True
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (%s < %d) {{
|
||||
goto skip_optional;
|
||||
}}
|
||||
""", indent=4) % (nargs, i + 1))
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
|
||||
if use_parser_code:
|
||||
if has_optional:
|
||||
parser_code.append("skip_optional:")
|
||||
else:
|
||||
for parameter in self.parameters:
|
||||
parameter.converter.use_converter()
|
||||
|
||||
if self.limited_capi:
|
||||
self.fastcall = False
|
||||
if self.fastcall:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_ParseStack()')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}",
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
else:
|
||||
self.flags = "METH_VARARGS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_VARARGS
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!PyArg_ParseTuple(args, "{format_units}:{name}",
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
self.parser_body(*parser_code)
|
||||
|
||||
def parse_general(self, clang: CLanguage) -> None:
|
||||
parsearg: str | None
|
||||
deprecated_positionals: dict[int, Parameter] = {}
|
||||
deprecated_keywords: dict[int, Parameter] = {}
|
||||
for i, p in enumerate(self.parameters):
|
||||
if p.deprecated_positional:
|
||||
deprecated_positionals[i] = p
|
||||
if p.deprecated_keyword:
|
||||
deprecated_keywords[i] = p
|
||||
|
||||
has_optional_kw = (
|
||||
max(self.pos_only, self.min_pos) + self.min_kw_only
|
||||
< len(self.converters) - int(self.vararg != NO_VARARG)
|
||||
)
|
||||
|
||||
use_parser_code = True
|
||||
if self.limited_capi:
|
||||
parser_code = []
|
||||
use_parser_code = False
|
||||
self.fastcall = False
|
||||
else:
|
||||
if self.vararg == NO_VARARG:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_UnpackKeywords()')
|
||||
args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % (
|
||||
self.min_pos,
|
||||
self.max_pos,
|
||||
self.min_kw_only
|
||||
)
|
||||
nargs = "nargs"
|
||||
else:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_UnpackKeywordsWithVararg()')
|
||||
args_declaration = "_PyArg_UnpackKeywordsWithVararg", "%s, %s, %s, %s" % (
|
||||
self.min_pos,
|
||||
self.max_pos,
|
||||
self.min_kw_only,
|
||||
self.vararg
|
||||
)
|
||||
nargs = f"Py_MIN(nargs, {self.max_pos})" if self.max_pos else "0"
|
||||
|
||||
if self.fastcall:
|
||||
self.flags = "METH_FASTCALL|METH_KEYWORDS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_FASTCALL_KEYWORDS
|
||||
argname_fmt = 'args[%d]'
|
||||
self.declarations = declare_parser(self.func, codegen=self.codegen)
|
||||
self.declarations += "\nPyObject *argsbuf[%s];" % len(self.converters)
|
||||
if has_optional_kw:
|
||||
self.declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, self.min_pos + self.min_kw_only)
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf);
|
||||
if (!args) {{
|
||||
goto exit;
|
||||
}}
|
||||
""" % args_declaration, indent=4)]
|
||||
else:
|
||||
# positional-or-keyword arguments
|
||||
self.flags = "METH_VARARGS|METH_KEYWORDS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_KEYWORD
|
||||
argname_fmt = 'fastargs[%d]'
|
||||
self.declarations = declare_parser(self.func, codegen=self.codegen)
|
||||
self.declarations += "\nPyObject *argsbuf[%s];" % len(self.converters)
|
||||
self.declarations += "\nPyObject * const *fastargs;"
|
||||
self.declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
|
||||
if has_optional_kw:
|
||||
self.declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, self.min_pos + self.min_kw_only)
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf);
|
||||
if (!fastargs) {{
|
||||
goto exit;
|
||||
}}
|
||||
""" % args_declaration, indent=4)]
|
||||
|
||||
if self.requires_defining_class:
|
||||
self.flags = 'METH_METHOD|' + self.flags
|
||||
self.parser_prototype = PARSER_PROTOTYPE_DEF_CLASS
|
||||
|
||||
if use_parser_code:
|
||||
if deprecated_keywords:
|
||||
code = clang.deprecate_keyword_use(self.func, deprecated_keywords,
|
||||
argname_fmt,
|
||||
codegen=self.codegen,
|
||||
fastcall=self.fastcall)
|
||||
parser_code.append(code)
|
||||
|
||||
add_label: str | None = None
|
||||
for i, p in enumerate(self.parameters):
|
||||
if isinstance(p.converter, defining_class_converter):
|
||||
raise ValueError("defining_class should be the first "
|
||||
"parameter (after clang)")
|
||||
displayname = p.get_displayname(i+1)
|
||||
parsearg = p.converter.parse_arg(argname_fmt % i, displayname, limited_capi=self.limited_capi)
|
||||
if parsearg is None:
|
||||
parser_code = []
|
||||
use_parser_code = False
|
||||
break
|
||||
if add_label and (i == self.pos_only or i == self.max_pos):
|
||||
parser_code.append("%s:" % add_label)
|
||||
add_label = None
|
||||
if not p.is_optional():
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
elif i < self.pos_only:
|
||||
add_label = 'skip_optional_posonly'
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (nargs < %d) {{
|
||||
goto %s;
|
||||
}}
|
||||
""" % (i + 1, add_label), indent=4))
|
||||
if has_optional_kw:
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
noptargs--;
|
||||
""", indent=4))
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
else:
|
||||
if i < self.max_pos:
|
||||
label = 'skip_optional_pos'
|
||||
first_opt = max(self.min_pos, self.pos_only)
|
||||
else:
|
||||
label = 'skip_optional_kwonly'
|
||||
first_opt = self.max_pos + self.min_kw_only
|
||||
if self.vararg != NO_VARARG:
|
||||
first_opt += 1
|
||||
if i == first_opt:
|
||||
add_label = label
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (!noptargs) {{
|
||||
goto %s;
|
||||
}}
|
||||
""" % add_label, indent=4))
|
||||
if i + 1 == len(self.parameters):
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
|
||||
else:
|
||||
add_label = label
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (%s) {{
|
||||
""" % (argname_fmt % i), indent=4))
|
||||
parser_code.append(libclinic.normalize_snippet(parsearg, indent=8))
|
||||
parser_code.append(libclinic.normalize_snippet("""
|
||||
if (!--noptargs) {{
|
||||
goto %s;
|
||||
}}
|
||||
}}
|
||||
""" % add_label, indent=4))
|
||||
|
||||
if use_parser_code:
|
||||
if add_label:
|
||||
parser_code.append("%s:" % add_label)
|
||||
else:
|
||||
for parameter in self.parameters:
|
||||
parameter.converter.use_converter()
|
||||
|
||||
self.declarations = declare_parser(self.func, codegen=self.codegen,
|
||||
hasformat=True)
|
||||
if self.limited_capi:
|
||||
# positional-or-keyword arguments
|
||||
assert not self.fastcall
|
||||
self.flags = "METH_VARARGS|METH_KEYWORDS"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_KEYWORD
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
|
||||
{parse_arguments}))
|
||||
goto exit;
|
||||
""", indent=4)]
|
||||
self.declarations = "static char *_keywords[] = {{{keywords_c} NULL}};"
|
||||
if deprecated_positionals or deprecated_keywords:
|
||||
self.declarations += "\nPy_ssize_t nargs = PyTuple_Size(args);"
|
||||
|
||||
elif self.fastcall:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_ParseStackAndKeywords()')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
else:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_ParseTupleAndKeywordsFast()')
|
||||
parser_code = [libclinic.normalize_snippet("""
|
||||
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
|
||||
{parse_arguments})) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4)]
|
||||
if deprecated_positionals or deprecated_keywords:
|
||||
self.declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
|
||||
if deprecated_keywords:
|
||||
code = clang.deprecate_keyword_use(self.func, deprecated_keywords,
|
||||
codegen=self.codegen,
|
||||
fastcall=self.fastcall)
|
||||
parser_code.append(code)
|
||||
|
||||
if deprecated_positionals:
|
||||
code = clang.deprecate_positional_use(self.func, deprecated_positionals)
|
||||
# Insert the deprecation code before parameter parsing.
|
||||
parser_code.insert(0, code)
|
||||
|
||||
assert self.parser_prototype is not None
|
||||
self.parser_body(*parser_code, declarations=self.declarations)
|
||||
|
||||
def copy_includes(self) -> None:
|
||||
# Copy includes from parameters to Clinic after parse_arg()
|
||||
# has been called above.
|
||||
for converter in self.converters:
|
||||
for include in converter.get_includes():
|
||||
self.codegen.add_include(
|
||||
include.filename,
|
||||
include.reason,
|
||||
condition=include.condition)
|
||||
|
||||
def handle_new_or_init(self) -> None:
|
||||
self.methoddef_define = ''
|
||||
|
||||
if self.func.kind is METHOD_NEW:
|
||||
self.parser_prototype = PARSER_PROTOTYPE_KEYWORD
|
||||
else:
|
||||
self.return_value_declaration = "int return_value = -1;"
|
||||
self.parser_prototype = PARSER_PROTOTYPE_KEYWORD___INIT__
|
||||
|
||||
fields: list[str] = list(self.parser_body_fields)
|
||||
parses_positional = 'METH_NOARGS' not in self.flags
|
||||
parses_keywords = 'METH_KEYWORDS' in self.flags
|
||||
if parses_keywords:
|
||||
assert parses_positional
|
||||
|
||||
if self.requires_defining_class:
|
||||
raise ValueError("Slot methods cannot access their defining class.")
|
||||
|
||||
if not parses_keywords:
|
||||
self.declarations = '{base_type_ptr}'
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_NoKeywords()')
|
||||
fields.insert(0, libclinic.normalize_snippet("""
|
||||
if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4))
|
||||
if not parses_positional:
|
||||
self.codegen.add_include('pycore_modsupport.h',
|
||||
'_PyArg_NoPositional()')
|
||||
fields.insert(0, libclinic.normalize_snippet("""
|
||||
if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{
|
||||
goto exit;
|
||||
}}
|
||||
""", indent=4))
|
||||
|
||||
self.parser_body(*fields, declarations=self.declarations)
|
||||
|
||||
def process_methoddef(self, clang: CLanguage) -> None:
|
||||
methoddef_cast_end = ""
|
||||
if self.flags in ('METH_NOARGS', 'METH_O', 'METH_VARARGS'):
|
||||
methoddef_cast = "(PyCFunction)"
|
||||
elif self.func.kind is GETTER:
|
||||
methoddef_cast = "" # This should end up unused
|
||||
elif self.limited_capi:
|
||||
methoddef_cast = "(PyCFunction)(void(*)(void))"
|
||||
else:
|
||||
methoddef_cast = "_PyCFunction_CAST("
|
||||
methoddef_cast_end = ")"
|
||||
|
||||
if self.func.methoddef_flags:
|
||||
self.flags += '|' + self.func.methoddef_flags
|
||||
|
||||
self.methoddef_define = self.methoddef_define.replace('{methoddef_flags}', self.flags)
|
||||
self.methoddef_define = self.methoddef_define.replace('{methoddef_cast}', methoddef_cast)
|
||||
self.methoddef_define = self.methoddef_define.replace('{methoddef_cast_end}', methoddef_cast_end)
|
||||
|
||||
self.methoddef_ifndef = ''
|
||||
conditional = clang.cpp.condition()
|
||||
if not conditional:
|
||||
self.cpp_if = self.cpp_endif = ''
|
||||
else:
|
||||
self.cpp_if = "#if " + conditional
|
||||
self.cpp_endif = "#endif /* " + conditional + " */"
|
||||
|
||||
if self.methoddef_define and self.codegen.add_ifndef_symbol(self.func.full_name):
|
||||
self.methoddef_ifndef = METHODDEF_PROTOTYPE_IFNDEF
|
||||
|
||||
def finalize(self, clang: CLanguage) -> None:
|
||||
# add ';' to the end of self.parser_prototype and self.impl_prototype
|
||||
# (they mustn't be None, but they could be an empty string.)
|
||||
assert self.parser_prototype is not None
|
||||
if self.parser_prototype:
|
||||
assert not self.parser_prototype.endswith(';')
|
||||
self.parser_prototype += ';'
|
||||
|
||||
if self.impl_prototype is None:
|
||||
self.impl_prototype = self.impl_definition
|
||||
if self.impl_prototype:
|
||||
self.impl_prototype += ";"
|
||||
|
||||
self.parser_definition = self.parser_definition.replace("{return_value_declaration}", self.return_value_declaration)
|
||||
|
||||
compiler_warning = clang.compiler_deprecated_warning(self.func, self.parameters)
|
||||
if compiler_warning:
|
||||
self.parser_definition = compiler_warning + "\n\n" + self.parser_definition
|
||||
|
||||
def create_template_dict(self) -> dict[str, str]:
|
||||
d = {
|
||||
"docstring_prototype" : self.docstring_prototype,
|
||||
"docstring_definition" : self.docstring_definition,
|
||||
"impl_prototype" : self.impl_prototype,
|
||||
"methoddef_define" : self.methoddef_define,
|
||||
"parser_prototype" : self.parser_prototype,
|
||||
"parser_definition" : self.parser_definition,
|
||||
"impl_definition" : self.impl_definition,
|
||||
"cpp_if" : self.cpp_if,
|
||||
"cpp_endif" : self.cpp_endif,
|
||||
"methoddef_ifndef" : self.methoddef_ifndef,
|
||||
}
|
||||
|
||||
# make sure we didn't forget to assign something,
|
||||
# and wrap each non-empty value in \n's
|
||||
d2 = {}
|
||||
for name, value in d.items():
|
||||
assert value is not None, "got a None value for template " + repr(name)
|
||||
if value:
|
||||
value = '\n' + value + '\n'
|
||||
d2[name] = value
|
||||
return d2
|
||||
|
||||
def parse_args(self, clang: CLanguage) -> dict[str, str]:
|
||||
self.select_prototypes()
|
||||
self.init_limited_capi()
|
||||
|
||||
self.flags = ""
|
||||
self.declarations = ""
|
||||
self.parser_prototype = ""
|
||||
self.parser_definition = ""
|
||||
self.impl_prototype = None
|
||||
self.impl_definition = IMPL_DEFINITION_PROTOTYPE
|
||||
|
||||
# parser_body_fields remembers the fields passed in to the
|
||||
# previous call to parser_body. this is used for an awful hack.
|
||||
self.parser_body_fields: tuple[str, ...] = ()
|
||||
|
||||
if not self.parameters:
|
||||
self.parse_no_args()
|
||||
elif self.use_meth_o():
|
||||
self.parse_one_arg()
|
||||
elif self.has_option_groups():
|
||||
self.parse_option_groups()
|
||||
elif (not self.requires_defining_class
|
||||
and self.pos_only == len(self.parameters) - self.pseudo_args):
|
||||
self.parse_pos_only()
|
||||
else:
|
||||
self.parse_general(clang)
|
||||
|
||||
self.copy_includes()
|
||||
if self.is_new_or_init():
|
||||
self.handle_new_or_init()
|
||||
self.process_methoddef(clang)
|
||||
self.finalize(clang)
|
||||
|
||||
return self.create_template_dict()
|
Loading…
Reference in New Issue
Block a user