def _generate_callback_wrapper( self, m: GeneratorMethod, ): # calling_back_code ret_type = m.ret_type args = m.args arguments_signature = ",".join( [self._to_cpp_variable(i) for i in args]) arg_list = ",".join( ["this", f'"{m.alias}"', *[f"{i.name}" for i in args]]) if m.has_overload: cast_expression = f"static_cast<{m.type}>(&{m.full_name})" else: cast_expression = f"&{m.full_name}" function_code = TextHolder() function_code += ( f"{ret_type} {m.name}({arguments_signature}) override\n") function_code += "{\n" + Indent() calling_method = "call" if m.calling_type == CallingType.Async: calling_method = "async" elif m.calling_type == CallingType.Sync: calling_method = "sync" function_code += ( f"return c2py::callback_wrapper<{cast_expression}>::{calling_method}(" + Indent()) function_code += f"{arg_list}" - IndentLater() function_code += f");" function_code += "}\n" - Indent() return function_code
def _process_function(self, of: GeneratorFunction): wf = of.resolve_wrappers() code = TextHolder() arg_decls = ", ".join([self._variable_with_hint(i) for i in wf.args]) code += f'def {wf.name}({arg_decls})->{self._to_python_type(wf.ret_type)}:' if is_tuple_type(wf.ret_type): code += Indent(self._return_description_for_function(of)) else: code += Indent("...") return code
def _generate_wrappers_for_class(self, c: GeneratorClass, wrapper_code: TextHolder): for ms in c.functions.values(): for m in ms: if m.is_virtual and not m.is_final: function_code = self._generate_callback_wrapper(m, ) wrapper_code += Indent(function_code) for super in c.super: wrapper_code += Indent(f'// methods from {super}') wrapper_code = self._generate_wrappers_for_class( super, wrapper_code) return wrapper_code
def _output_generated_functions(self): """ :return: # of files named 'generated_functions_??.cpp generated """ # declaration decls = TextHolder() for f in self.function_manager.functions: decls += f'void {f.name}({f.arg_type} parent);' self._save_template('generated_functions.h', 'generated_functions.h', includes=self._generate_includes(), declarations=decls) # definitions header_padding_lines = 20 total_lines = header_padding_lines for f in self.function_manager.functions: total_lines += f.body.line_count + 3 prefer_lines_per_file = self.options.max_lines_per_file defs = TextHolder() i = 0 for f in self.function_manager.functions: new_text = TextHolder() new_text += f'void {f.name}({f.arg_type} parent)' new_text += "{" + Indent() new_text += f.body new_text += "}" - Indent() if defs.line_count + new_text.line_count + header_padding_lines >= prefer_lines_per_file: self._save_template( 'generated_functions.cpp', f'generated_functions_{i}.cpp', includes=self._generate_includes(), definitions=defs, ) defs = new_text i += 1 else: defs += new_text if len(str(defs)): self._save_template( 'generated_functions.cpp', f'generated_functions_{i}.cpp', includes=self._generate_includes(), definitions=defs, ) return i + 1 # return the number of generated_functions_.cpp generated
def _process_method(self, of: GeneratorMethod): wf = of.resolve_wrappers() code = TextHolder() arg_decls = ", ".join([self._variable_with_hint(i) for i in wf.args]) self_text = 'self, ' if wf.is_static: code += "@staticmethod" self_text = "" if wf.has_overload: code += "@overload" code += f'def {wf.name}({self_text}{arg_decls})->{self._to_python_type(wf.ret_type)}:' if is_tuple_type(wf.ret_type): code += Indent(self._return_description_for_function(of)) else: code += Indent("...") return code
def _process_sub_namespace(self, ns: GeneratorNamespace, cpp_scope_variable: str, body: TextHolder, pfm: FunctionManager): for n in ns.namespaces.values(): assert n.name, "sub Namespace has no name, someting wrong in Parser or preprocessor" function_name = slugify(f"generate_sub_namespace_{n.full_name}") function_body, fm = self._generate_namespace_body(n, n.name) if self.options.inject_symbol_name: body += f'// {n.full_name}' body += '{' + Indent() body += f'auto m = {cpp_scope_variable}.def_submodule("{n.name}");' body += f'{function_name}(m);' body += '}' - Indent() # todo: generate alias (namespace alias) pfm.add(function_name, "pybind11::module &", function_body) pfm.extend(fm)
def _process_namespace_functions(self, ns: GeneratorNamespace, cpp_scope_variable: str, body: TextHolder, pfm: FunctionManager): namespace_name = ns.name if not namespace_name: # trick: a unnamed namespace should be global namespace. namespace_name = self.module_name max_calls_per_function = self.options.max_template_calls_per_function n = 0 i = 0 sub_body = TextHolder() if ns.functions: for fs in ns.functions.values(): for f in fs: has_overload: bool = False if len(fs) > 1: has_overload = True for m in fs: sub_body += ( f"""{cpp_scope_variable}.def("{m.alias}",""" + Indent()) sub_body += self._generate_calling_wrapper( f, has_overload, append=',') sub_body += f"pybind11::return_value_policy::reference," sub_body += f"pybind11::call_guard<pybind11::gil_scoped_release>()" sub_body += f""");\n""" - Indent() n += 1 if n == max_calls_per_function: n = 0 function_name = f'generate_{namespace_name}_functions_{i}' pfm.add(function_name, "pybind11::module &", sub_body) body += f'{function_name}({cpp_scope_variable});' sub_body = TextHolder() i += 1 function_name = f'generate_{namespace_name}_functions_{i}' pfm.add(function_name, "pybind11::module &", sub_body) body += f'{function_name}({cpp_scope_variable});'
def _process_class_functions(self, c: GeneratorClass, body: TextHolder, my_variable: str): # functions for ms in c.functions.values(): has_overload: bool = False if len(ms) > 1: has_overload = True for m in ms: if self.options.inject_symbol_name: body += f'// {m.full_name}' if m.is_static: body += (f"""{my_variable}.def_static("{m.alias}",""" + Indent()) else: body += (f"""{my_variable}.def("{m.alias}",""" + Indent()) body += self._generate_calling_wrapper(m, has_overload, append=',') body += f"pybind11::return_value_policy::reference," body += f"pybind11::call_guard<pybind11::gil_scoped_release>()" body += f""");\n""" - Indent()
def _process_class(self, c: GeneratorClass): code = TextHolder() # parent_place = f'({c.parent.name})' if c.parent and c.parent.name else '' super_list = ",".join( [self._to_python_type(i.full_name) for i in c.super]) parent_place = f'({super_list})' code += f'class {c.name}{parent_place}:' + Indent() code += self._process_typedefs(c) code += self._process_classes(c) code += self._process_variables(c) code += self._process_enums(c) code += self._process_methods(c) return code
def _output_wrappers(self): wrappers = "" # generate callback wrappers for c in self.objects.values(): if (isinstance(c, GeneratorClass) and self._has_wrapper(c)): py_class_name = "Py" + c.name wrapper_code = TextHolder() for ms in c.functions.values(): for m in ms: if m.is_virtual and not m.is_final: function_code = self._generate_callback_wrapper( m, ) wrapper_code += Indent(function_code) py_class_code = self._render_file("wrapper_class.h", py_class_name=py_class_name, class_fullname=c.full_name, body=wrapper_code) wrappers += py_class_code self._save_template(f"wrappers.hpp", wrappers=wrappers)
def _generate_calling_wrapper(m: GeneratorFunction, has_overload, append=''): code = TextHolder() if m.wrappers: has_this = False if isinstance(m, GeneratorMethod) and not m.is_static: has_this = True if len(m.wrappers) == 1: wi = m.wrappers[0] code += f'c2py::{wi.wrapper.name} < ' + Indent() code += f'c2py::function_constant<{m.address}>,' code += f'std::integral_constant<int, {wi.index}{" + 1/*self*/" if has_this else ""}>' code += f'>::value{append}' - Indent() else: # >= 2 code += f'c2py::apply_function_transform<' + Indent() code += f'c2py::function_constant<{m.address}>,' code += 'brigand::list<' + Indent() lines = [ f'c2py::indexed_transform_holder<' f'c2py::{wi.wrapper.name}, {wi.index}{" + 1/*self*/" if has_this else ""}>' for wi in m.wrappers ] code.append_lines(lines, ',') code += '>' - Indent() code += f'>::value{append}' - Indent() else: if has_overload: code += f'static_cast<{m.type}>(' + Indent() code += f"""&{m.full_name}""" code += f"""),""" - IndentLater() else: code += f"""&{m.full_name},""" return code
def _process_enum(self, e: GeneratorEnum): code = TextHolder() code += f'class {e.name}(Enum):' + Indent() code += self._process_variables(e) return code
def _generate_class_body(self, c: GeneratorClass): body = TextHolder() fm = FunctionManager() my_variable = "c" has_wrapper = self._has_wrapper(c) wrapper_class_name = "Py" + c.name public_destructor = c.destructor is None or c.destructor.access == "public" hold_base_type = c.full_name if not has_wrapper else wrapper_class_name wrapper_field = "" if not has_wrapper else f', {wrapper_class_name}' holder_field = "" if public_destructor else f',std::unique_ptr<{hold_base_type}, pybind11::nodelete>' parents_field = "" if not c.super else f', {", ".join([i.full_name for i in c.super])}' if self.options.inject_symbol_name: body += f'// {c.full_name}' body += f"pybind11::class_<" + Indent() body += f"{c.full_name}" if wrapper_field: body += wrapper_field if holder_field: body += holder_field if parents_field: body += parents_field body += (f"""> {my_variable}(parent, "{c.name}");\n""" - Indent()) # constructor constructor_name = c.full_name if not has_wrapper else wrapper_class_name if c.constructors: arg_list = "" for con in c.constructors: arg_list = ",".join([arg.type for arg in con.args]) comma = ',' if arg_list else '' body += f"""if constexpr (std::is_constructible_v<""" + Indent( ) body += f"""{constructor_name}{comma}{arg_list}""" body += f""">)""" - Indent() body += Indent( f"""{my_variable}.def(pybind11::init<{arg_list}>());\n""") else: body += f"""if constexpr (std::is_default_constructible_v<{constructor_name}>)""" body += Indent(f"""{my_variable}.def(pybind11::init<>());\n""") self._process_class_functions(c, body, my_variable) self._process_class_variables(ns=c, body=body, cpp_scope_variable=my_variable, pfm=fm) self._process_enums(ns=c, body=body, cpp_scope_variable=my_variable, pfm=fm) self._process_classes(ns=c, body=body, cpp_scope_variable=my_variable, pfm=fm) # post_register body += f'AUTOCXXPY_POST_REGISTER_CLASS({self.module_tag}, {c.full_name}, {my_variable});\n' # objects record body += f'{self.module_class}::objects.emplace("{c.full_name}", {my_variable});' return body, fm