| from __future__ import annotations |
| from typing import TYPE_CHECKING, Final, Literal |
| |
| import libclinic |
| from libclinic import fail, warn, unspecified, Sentinels |
| from libclinic.function import ( |
| Function, Parameter, ParamTuple, |
| count_required, group_to_variable_name, permute_optional_groups, |
| GETTER, SETTER, METHOD_NEW, |
| ACCESSORS, SETTERS) |
| 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 p.is_positional_or_keyword() or p.is_keyword_only() |
| ]) |
| |
| condition = '#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)' |
| 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 |
| """ |
| |
| codegen.add_include('pycore_runtime.h', '_Py_SINGLETON()', |
| condition=condition) |
| else: |
| # XXX Why do we not statically allocate the tuple |
| # for non-builtin modules? |
| 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 |
| Py_hash_t ob_hash; |
| PyObject *ob_item[NUM_KEYWORDS]; |
| }} _kwtuple = {{ |
| .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) |
| .ob_hash = -1, |
| .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 |
| |
| 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}({self_type}{self_name}, {parser_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}); |
| #if defined({getset_basename}_DOCSTR) |
| # undef {getset_basename}_DOCSTR |
| #endif |
| #define {getset_basename}_DOCSTR {getset_basename}__doc__ |
| """) |
| 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}_DOCSTR) |
| # 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_basename}_DOCSTR) |
| # 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}) */ |
| """) |
| GETSETDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet(""" |
| #ifndef {getset_name}_GETSETDEF |
| #define {getset_name}_GETSETDEF |
| #endif /* !defined({getset_name}_GETSETDEF) */ |
| """) |
| # The setter is called with NULL to delete the attribute. Unless @deleter is |
| # applied to it, deletion is rejected before the implementation is called. |
| SETTER_PREAMBLE: Final[str] = libclinic.normalize_snippet(""" |
| if (value == NULL) {{ |
| PyErr_Format(PyExc_AttributeError, |
| "attribute '{name}' of '%.100s' objects cannot be deleted", |
| Py_TYPE({self_name})->tp_name); |
| return -1; |
| }} |
| """, indent=4) |
| |
| |
| class ParseArgsCodeGen: |
| func: Function |
| codegen: CodeGen |
| limited_capi: bool = False |
| |
| # Function parameters |
| parameters: list[Parameter] |
| self_parameter_converter: self_converter |
| 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 |
| varpos: Parameter | None = None |
| var_keyword: Parameter | None = None |
| |
| 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()) |
| self_parameter = self.parameters.pop(0) |
| if not isinstance(self_parameter.converter, self_converter): |
| raise ValueError("the first parameter must use self_converter") |
| self.self_parameter_converter = self_parameter.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] |
| |
| for i, p in enumerate(self.parameters): |
| if p.is_vararg(): |
| self.varpos = p |
| del self.parameters[i] |
| break |
| |
| for i, p in enumerate(self.parameters): |
| if p.is_var_keyword(): |
| self.var_keyword = p |
| del self.parameters[i] |
| break |
| |
| 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()') |
| |
| # Use fastcall if not disabled, except if in a __new__ or |
| # __init__ method, or if there is a **kwargs parameter. |
| if self.func.disable_fastcall: |
| self.fastcall = False |
| elif self.var_keyword is not None: |
| self.fastcall = False |
| else: |
| 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 |
| 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 |
| else: |
| 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.has_option_groups() |
| and not self.converters[0].is_optional() |
| and not self.varpos |
| 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 use_pyobject_self(self) -> bool: |
| return self.self_parameter_converter.use_pyobject_self(self.func) |
| |
| def select_prototypes(self) -> None: |
| self.docstring_prototype = '' |
| self.docstring_definition = '' |
| self.methoddef_define = METHODDEF_PROTOTYPE_DEFINE |
| self.return_value_declaration = "PyObject *{parser_retval} = 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 in SETTERS: |
| if self.func.docstring: |
| fail("docstrings are only supported for @getter, not @setter", |
| line_number=self.func.line_number) |
| self.return_value_declaration = "int {parser_retval};" |
| 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.varpos and self.pos_only < len(self.parameters)) 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 {parser_retval}; |
| }} |
| """) |
| 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 in SETTERS: |
| self.parser_prototype = PARSER_PROTOTYPE_SETTER |
| if self.func.kind is SETTER: |
| parser_code = [SETTER_PREAMBLE] |
| else: |
| 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() and self.use_pyobject_self(): |
| # 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 option_group_subsets(self) -> dict[int, ParamTuple]: |
| # positional only, grouped, optional arguments! |
| # can be optional on the left or right. |
| # here's an example: |
| # |
| # [ [ [ A1 A2 ] B1 B2 B3 ] C1 C2 ] D1 D2 D3 [ E1 E2 E3 [ F1 F2 F3 ] ] |
| # |
| # Here group D are required, and all other groups are optional. |
| # (Group D's "group" is actually None.) |
| # We can figure out which sets of arguments we have based on |
| # how many arguments are in the tuple. |
| # |
| # Note that you need to count up on both sides. For example, |
| # you could have groups C+D, or C+D+E, or C+D+E+F. |
| # |
| # What if the number of arguments leads us to an ambiguous result? |
| # Clinic prefers groups on the left. So in the above example, |
| # five arguments would map to B+C, not C+D. |
| # |
| # A nested group can only be omitted together with the group |
| # containing it, but groups on the same level, like G and H in |
| # |
| # [ G1 G2 ] [ H1 ] I1 I2 |
| # |
| # can be omitted independently of each other. |
| |
| # Groups are collected into chains of nested groups. A group which |
| # is not nested in the preceding one starts a new chain. |
| group: list[Parameter] | None = None |
| left: list[list[list[Parameter]]] = [] |
| right: list[list[list[Parameter]]] = [] |
| required: list[Parameter] = [] |
| last: int | Literal[Sentinels.unspecified] = unspecified |
| last_depth = 0 |
| |
| for p in self.parameters: |
| group_id = p.group |
| if group_id != last: |
| last = group_id |
| group = [] |
| if group_id == 0: |
| group = required |
| else: |
| chains = left if group_id < 0 else right |
| nested = ((p.group_depth < last_depth) if group_id < 0 |
| else (p.group_depth > last_depth)) |
| if chains and nested: |
| chains[-1].append(group) |
| else: |
| chains.append([group]) |
| last_depth = p.group_depth |
| assert group is not None |
| group.append(p) |
| |
| # Map the number of arguments to the subset which accepts it. |
| subsets: dict[int, ParamTuple] = {} |
| for subset in permute_optional_groups(left, required, right): |
| for count in range(count_required(subset), len(subset) + 1): |
| if count in subsets: |
| fail(f"Function {self.func.full_name!r} has an ambiguous " |
| f"group configuration: a call with {count} " |
| f"argument(s) can be parsed in more than one way.", |
| line_number=self.func.line_number) |
| subsets[count] = subset |
| return subsets |
| |
| def parse_option_groups(self) -> None: |
| # positional parameters with option groups |
| # (which groups are passed is determined by the number of arguments) |
| |
| nargs, argname_fmt = self.select_positional_convention() |
| # With the stack convention "nargs" is already a parameter. |
| size_expr = None if self.fastcall else nargs |
| |
| subsets = self.option_group_subsets() |
| parser_code = self.render_option_groups(subsets, argname_fmt, size_expr) |
| if parser_code is None: |
| # The code of some converter cannot be inlined, so the |
| # arguments of every combination of groups are parsed at once. |
| parser_code = self.render_option_groups_parse_tuple(subsets) |
| self.parser_body(*parser_code) |
| |
| def render_option_groups( |
| self, |
| subsets: dict[int, ParamTuple], |
| argname_fmt: str, |
| size_expr: str | None, |
| ) -> list[str] | None: |
| """Return the code which parses the arguments of all groups. |
| |
| Every parameter is parsed once, in a block guarded by the number of |
| arguments. Return None if the code of some converter cannot be |
| inlined. |
| """ |
| # Split the parameters into the groups, in the order in which |
| # they are passed. |
| groups: list[tuple[int, list[Parameter]]] = [] |
| for p in self.parameters: |
| if not groups or groups[-1][0] != p.group: |
| groups.append((p.group, [])) |
| groups[-1][1].append(p) |
| |
| indices, shifting = self.render_option_group_indices(subsets, groups) |
| |
| parser_code = [] |
| if size_expr is not None: |
| parser_code.append(libclinic.normalize_snippet( |
| f"Py_ssize_t nargs = {size_expr};", indent=4)) |
| if shifting: |
| parser_code.append(libclinic.normalize_snippet( |
| "Py_ssize_t offset = 0;", indent=4)) |
| parser_code.append(self.render_option_group_check(subsets)) |
| |
| has_optional = False |
| for group_id, params in groups: |
| body = [] |
| for p in params: |
| index_expr = indices[p.name] |
| if index_expr.isdigit(): |
| displayname = p.get_displayname(int(index_expr) + 1) |
| else: |
| # The position of the argument differs between the calls. |
| displayname = p.get_displayname(0) |
| parsearg = p.converter.parse_arg(argname_fmt % index_expr, |
| displayname, |
| limited_capi=self.limited_capi) |
| if parsearg is None: |
| return None |
| if not group_id and (has_optional or p.is_optional()): |
| # Such parameter is omitted on its own, and nothing |
| # can follow it, so the rest is simply skipped. |
| has_optional = True |
| body.append(libclinic.normalize_snippet(""" |
| if (nargs <= %s) {{ |
| goto skip_optional; |
| }} |
| """, indent=4) % index_expr) |
| body.append(libclinic.normalize_snippet(parsearg, indent=4)) |
| |
| if not group_id: |
| parser_code.extend(body) |
| continue |
| |
| if group_id in shifting: |
| # The arguments of this group shift the following ones. |
| body.append(libclinic.normalize_snippet( |
| f"offset += {len(params)};", indent=4)) |
| body.append(libclinic.normalize_snippet( |
| f"{group_to_variable_name(group_id)} = 1;", indent=4)) |
| condition = self.render_option_group_condition(subsets, group_id) |
| parser_code.append(libclinic.normalize_snippet( |
| f"if ({condition}) {{{{", indent=4)) |
| parser_code.extend(libclinic.normalize_snippet(line, indent=8) |
| for line in body) |
| parser_code.append(libclinic.normalize_snippet("}}", indent=4)) |
| |
| if has_optional: |
| parser_code.append("skip_optional:") |
| return parser_code |
| |
| @staticmethod |
| def render_option_group_indices( |
| subsets: dict[int, ParamTuple], |
| groups: list[tuple[int, list[Parameter]]], |
| ) -> tuple[dict[str, str], set[int]]: |
| """Return the index of the argument of every parameter, as C code, |
| and the groups which have to add their size to "offset". |
| |
| A group which is not passed shifts the following arguments. Their |
| index can still be fixed if it is counted from the end; otherwise it |
| is counted at run time, in "offset". |
| """ |
| forward: dict[str, set[int]] = {} |
| backward: dict[str, set[int]] = {} |
| for count, subset in subsets.items(): |
| for i, p in enumerate(subset[:count]): |
| forward.setdefault(p.name, set()).add(i) |
| backward.setdefault(p.name, set()).add(count - i) |
| # A parameter with a default value can be omitted on its own, so |
| # its index from the end is not fixed. |
| for group_id, params in groups: |
| if not group_id: |
| for p in params: |
| if p.is_optional(): |
| backward.pop(p.name) |
| |
| def counted_at_run_time(p: Parameter) -> bool: |
| return (len(forward[p.name]) != 1 |
| and len(backward.get(p.name, ())) != 1) |
| |
| # A group adds its size to "offset" if it shifts an argument |
| # which is counted at run time. |
| shifting: set[int] = set() |
| seen: list[int] = [] |
| for group_id, params in groups: |
| if any(counted_at_run_time(p) for p in params): |
| shifting.update(g for g in seen if g) |
| seen.append(group_id) |
| |
| indices = {} |
| # The number of the parameters before the current one, and how many |
| # of them are counted in "offset". |
| pos = 0 |
| in_offset = 0 |
| for group_id, params in groups: |
| for i, p in enumerate(params): |
| if not counted_at_run_time(p): |
| if len(forward[p.name]) == 1: |
| indices[p.name] = str(min(forward[p.name])) |
| else: |
| indices[p.name] = f"nargs - {min(backward[p.name])}" |
| else: |
| index = pos + i - in_offset |
| indices[p.name] = ("offset" if index == 0 |
| else f"offset + {index}") |
| pos += len(params) |
| if group_id in shifting: |
| in_offset += len(params) |
| return indices, shifting |
| |
| @staticmethod |
| def render_option_group_condition( |
| subsets: dict[int, ParamTuple], |
| group_id: int, |
| ) -> str: |
| """Return the condition on which the group is passed.""" |
| counts = sorted(count for count, subset in subsets.items() |
| if any(p.group == group_id for p in subset)) |
| if counts == list(range(counts[0], max(subsets) + 1)): |
| return f"nargs >= {counts[0]}" |
| return " || ".join(f"nargs == {count}" for count in counts) |
| |
| def render_option_group_check( |
| self, |
| subsets: dict[int, ParamTuple], |
| ) -> str: |
| """Return the code which rejects an unsupported number of arguments.""" |
| error = ('PyErr_SetString(PyExc_TypeError, "%s requires %d to %d ' |
| 'arguments");' % (self.func.full_name, min(subsets), |
| max(subsets))) |
| counts = sorted(subsets) |
| if counts == list(range(counts[0], counts[-1] + 1)): |
| tests = [f"nargs > {counts[-1]}"] |
| if counts[0]: |
| tests.insert(0, f"nargs < {counts[0]}") |
| lines = [ |
| f"if ({' || '.join(tests)}) {{{{", |
| f" {error}", |
| " goto exit;", |
| "}}", |
| ] |
| else: |
| # Some numbers of arguments in between are not supported. |
| lines = ["switch (nargs) {{"] |
| lines += [f" case {count}:" for count in counts] |
| lines += [ |
| " break;", |
| " default:", |
| f" {error}", |
| " goto exit;", |
| "}}", |
| ] |
| return libclinic.normalize_snippet("\n".join(lines), indent=4) |
| |
| def render_option_groups_parse_tuple( |
| self, |
| subsets: dict[int, ParamTuple], |
| ) -> list[str]: |
| """Return the code which parses the arguments of every group at once.""" |
| parse_call = self.render_parse_all_arguments() |
| # The convention can have changed, so ask for it again. |
| size_expr, _ = self.select_positional_convention() |
| parser_code = [libclinic.normalize_snippet(f"switch ({size_expr}) {{{{", |
| indent=4)] |
| for count, subset in sorted(subsets.items()): |
| parser_code.append(f" case {count}:") |
| if count != len(subset): |
| # The omitted parameters are parsed by the following case. |
| continue |
| if count: |
| format_units = [p.converter.format_unit for p in subset] |
| n_required = count_required(subset) |
| if n_required < count: |
| format_units.insert(n_required, '|') |
| parse_arguments: list[str] = [] |
| for p in subset: |
| p.converter.parse_argument(parse_arguments) |
| parser_code.append(libclinic.normalize_snippet(""" |
| if (!%s"%s:%s", %s)) {{ |
| goto exit; |
| }} |
| """, indent=12) % (parse_call, "".join(format_units), |
| self.func.name, |
| ", ".join(parse_arguments))) |
| # A set would eliminate duplicates too, but the iteration |
| # order of small negative integers depends on the platform. |
| group_ids = dict.fromkeys(p.group for p in subset) |
| group_ids.pop(0, None) |
| for group_id in group_ids: |
| parser_code.append( |
| f" {group_to_variable_name(group_id)} = 1;") |
| parser_code.append(" break;") |
| parser_code.append(" default:") |
| parser_code.append( |
| ' PyErr_SetString(PyExc_TypeError, "%s requires %d to %d arguments");' |
| % (self.func.full_name, min(subsets), max(subsets))) |
| parser_code.append(" goto exit;") |
| parser_code.append(" }}") |
| return parser_code |
| |
| def _parse_vararg(self) -> str: |
| assert self.varpos is not None |
| c = self.varpos.converter |
| assert isinstance(c, libclinic.converters.VarPosCConverter) |
| return c.parse_vararg(pos_only=self.pos_only, |
| min_pos=self.min_pos, |
| max_pos=self.max_pos, |
| fastcall=self.fastcall, |
| limited_capi=self.limited_capi) |
| |
| def _parse_kwarg(self) -> str: |
| assert self.var_keyword is not None |
| c = self.var_keyword.converter |
| assert isinstance(c, libclinic.converters.VarKeywordCConverter) |
| return c.parse_var_keyword() |
| |
| def select_positional_convention(self) -> tuple[str, str]: |
| """Select the calling convention of a positional-only function. |
| |
| Set the method flags and the prototype, and return the expression of |
| the number of arguments and the format of an argument expression. |
| """ |
| if self.fastcall: |
| self.flags = "METH_FASTCALL" |
| self.parser_prototype = PARSER_PROTOTYPE_FASTCALL |
| return 'nargs', 'args[%s]' |
| |
| self.flags = "METH_VARARGS" |
| self.parser_prototype = PARSER_PROTOTYPE_VARARGS |
| if self.limited_capi: |
| return 'PyTuple_Size(args)', 'PyTuple_GetItem(args, %s)' |
| return 'PyTuple_GET_SIZE(args)', 'PyTuple_GET_ITEM(args, %s)' |
| |
| def render_parse_all_arguments(self) -> str: |
| """Return the beginning of the call which parses all arguments. |
| |
| Fall back to the tuple convention if the stack one cannot be used. |
| """ |
| for p in self.parameters: |
| p.converter.use_converter() |
| if self.limited_capi: |
| # _PyArg_ParseStack() is not part of the limited C API. |
| self.fastcall = False |
| if self.fastcall: |
| self.codegen.add_include('pycore_modsupport.h', |
| '_PyArg_ParseStack()') |
| return '_PyArg_ParseStack(args, nargs, ' |
| self.flags = "METH_VARARGS" |
| self.parser_prototype = PARSER_PROTOTYPE_VARARGS |
| return 'PyArg_ParseTuple(args, ' |
| |
| def parse_pos_only(self) -> None: |
| # positional-only, but no option groups: we only need one call |
| # to _PyArg_ParseStack or PyArg_ParseTuple |
| nargs, argname_fmt = self.select_positional_convention() |
| |
| parser_code = [] |
| max_args = NO_VARARG if self.varpos else self.max_pos |
| if self.limited_capi: |
| 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)) |
| elif self.min_pos or max_args != NO_VARARG: |
| self.codegen.add_include('pycore_modsupport.h', |
| '_PyArg_CheckPositional()') |
| parser_code.append(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): |
| 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: |
| if self.varpos: |
| raise ValueError( |
| f"Using converter {p.converter} is not supported " |
| f"in function with var-positional parameter") |
| 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:") |
| if self.varpos: |
| parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4)) |
| elif self.var_keyword: |
| parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), indent=4)) |
| else: |
| parse_call = self.render_parse_all_arguments() |
| parser_code = [libclinic.normalize_snippet(""" |
| if (!%s"{format_units}:{name}", |
| {parse_arguments})) {{ |
| goto exit; |
| }} |
| """, indent=4) % parse_call] |
| self.parser_body(*parser_code) |
| |
| def parse_var_keyword(self) -> None: |
| self.flags = "METH_VARARGS|METH_KEYWORDS" |
| self.parser_prototype = PARSER_PROTOTYPE_KEYWORD |
| nargs = 'PyTuple_GET_SIZE(args)' |
| |
| parser_code = [] |
| max_args = NO_VARARG if self.varpos else self.max_pos |
| if self.varpos is None and self.min_pos == self.max_pos == 0: |
| self.codegen.add_include('pycore_modsupport.h', |
| '_PyArg_NoPositional()') |
| parser_code.append(libclinic.normalize_snippet(""" |
| if (!_PyArg_NoPositional("{name}", args)) {{ |
| goto exit; |
| }} |
| """, indent=4)) |
| elif self.min_pos or max_args != NO_VARARG: |
| self.codegen.add_include('pycore_modsupport.h', |
| '_PyArg_CheckPositional()') |
| parser_code.append(libclinic.normalize_snippet(f""" |
| if (!_PyArg_CheckPositional("{{name}}", {nargs}, {self.min_pos}, {max_args})) {{{{ |
| goto exit; |
| }}}} |
| """, indent=4)) |
| |
| has_optional = False |
| for i, p in enumerate(self.parameters): |
| parse_arg = p.converter.parse_arg( |
| f'PyTuple_GET_ITEM(args, {i})', |
| p.get_displayname(i+1), |
| limited_capi=self.limited_capi, |
| ) |
| assert parse_arg is not None |
| 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(parse_arg, indent=4)) |
| if has_optional: |
| parser_code.append("skip_optional:") |
| |
| if self.varpos: |
| parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4)) |
| if self.var_keyword: |
| parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), 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) |
| ) |
| |
| use_parser_code = True |
| if self.limited_capi: |
| parser_code = [] |
| use_parser_code = False |
| self.fastcall = False |
| else: |
| self.codegen.add_include('pycore_modsupport.h', |
| '_PyArg_UnpackKeywords()') |
| if not self.varpos: |
| nargs = "nargs" |
| else: |
| 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 |
| self.declarations = declare_parser(self.func, codegen=self.codegen) |
| self.declarations += "\nPyObject *argsbuf[%s];" % (len(self.converters) or 1) |
| if self.varpos: |
| self.declarations += "\nPyObject * const *fastargs;" |
| argsname = 'fastargs' |
| argname_fmt = 'fastargs[%d]' |
| else: |
| argsname = 'args' |
| argname_fmt = 'args[%d]' |
| 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) |
| unpack_args = 'args, nargs, NULL, kwnames' |
| else: |
| # positional-or-keyword arguments |
| self.flags = "METH_VARARGS|METH_KEYWORDS" |
| self.parser_prototype = PARSER_PROTOTYPE_KEYWORD |
| argsname = 'fastargs' |
| argname_fmt = 'fastargs[%d]' |
| self.declarations = declare_parser(self.func, codegen=self.codegen) |
| self.declarations += "\nPyObject *argsbuf[%s];" % (len(self.converters) or 1) |
| 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) |
| unpack_args = '_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL' |
| parser_code = [libclinic.normalize_snippet(f""" |
| {argsname} = _PyArg_UnpackKeywords({unpack_args}, &_parser, |
| /*minpos*/ {self.min_pos}, /*maxpos*/ {self.max_pos}, /*minkw*/ {self.min_kw_only}, /*varpos*/ {1 if self.varpos else 0}, argsbuf); |
| if (!{argsname}) {{{{ |
| goto exit; |
| }}}} |
| """, 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 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) |
| if self.varpos: |
| parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4)) |
| 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. |
| converters = self.converters |
| if self.varpos: |
| converters = converters + [self.varpos.converter] |
| for converter in 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 {parser_retval} = -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): |
| if self.func.kind in ACCESSORS: |
| self.methoddef_ifndef = GETSETDEF_PROTOTYPE_IFNDEF |
| else: |
| 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 and not self.varpos and not self.var_keyword: |
| self.parse_no_args() |
| elif self.use_meth_o(): |
| self.parse_one_arg() |
| elif self.has_option_groups(): |
| self.parse_option_groups() |
| elif self.var_keyword is not None: |
| self.parse_var_keyword() |
| elif (not self.requires_defining_class |
| and self.pos_only == len(self.parameters)): |
| 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() |