def _generate_calling_wrapper(m: GeneratorFunction, has_overload, append=''): code = TextHolder() code += f'autocxxpy::apply_function_transform<' + Indent() code += f'autocxxpy::function_constant<' + Indent() if has_overload: code += f'static_cast<{m.type}>(' + Indent() code += f"""&{m.full_name}""" if has_overload: code += f""")""" - IndentLater() code += '>, ' - Indent() code += 'brigand::list<' + Indent() has_this = False if isinstance(m, GeneratorMethod) and not m.is_static: has_this = True lines = [ f'autocxxpy::indexed_transform_holder<' f'autocxxpy::{wi.wrapper.name}, {wi.index + 1 if has_this else wi.index}>' for wi in m.wrappers ] code.append_lines(lines, ',') code += '>' - Indent() code += f'>::value{append}' - Indent() return 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)}:' code += Indent("...") return code
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 autocxxpy::callback_wrapper<{cast_expression}>::{calling_method}(" + Indent()) function_code += f"{arg_list}" - IndentLater() function_code += f");" function_code += "}\n" - Indent() return function_code
def batch_process(self, ns: GeneratorNamespace, attr_name: str, func: Callable): code = TextHolder() container: dict = getattr(ns, attr_name) for v in container.values(): code += func(v) return code
def _generate_namespace_body(self, ns: GeneratorNamespace): fm = FunctionManager() body = TextHolder() cpp_scope_variable = "parent" self._process_sub_namespace(ns=ns, cpp_scope_variable=cpp_scope_variable, body=body, pfm=fm) self._process_classes(ns=ns, cpp_scope_variable=cpp_scope_variable, body=body, pfm=fm) self._process_enums(ns=ns, cpp_scope_variable=cpp_scope_variable, body=body, pfm=fm) self._process_namespace_functions( ns=ns, cpp_scope_variable=cpp_scope_variable, body=body, pfm=fm) self._process_namespace_variables( ns=ns, cpp_scope_variable=cpp_scope_variable, body=body, pfm=fm) self._process_typedefs(ns=ns, cpp_scope_variable=cpp_scope_variable, body=body, pfm=fm) self._process_caster(ns=ns, cpp_scope_variable=cpp_scope_variable, body=body, pfm=fm) return body, fm
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 total_lines = 0 for f in self.function_manager.functions: total_lines += f.body.line_count prefer_lines_per_file = self.options.max_lines_per_file - 100 if total_lines > self.options.max_lines_per_file: prefer_lines_per_file = total_lines / int( total_lines / self.options.max_lines_per_file) defs = TextHolder() i = 0 for f in self.function_manager.functions: defs += f'void {f.name}({f.arg_type} parent)' defs += "{" + Indent() defs += f.body defs += "}" - Indent() if defs.line_count >= prefer_lines_per_file: self._save_template( 'generated_functions.cpp', f'generated_functions_{i}.cpp', includes=self._generate_includes(), definitions=defs, ) defs = TextHolder() i += 1 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 _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 if has_wrapper: if (c.destructor is None or c.destructor.access == "public"): body += f"""pybind11::class_<{c.full_name}, {wrapper_class_name}> {my_variable}(parent, "{c.name}");\n""" else: body += f"pybind11::class_<" + Indent() body += f"{c.full_name}," body += f"std::unique_ptr<{c.full_name}, pybind11::nodelete>," body += f"{wrapper_class_name}" body += (f"""> {my_variable}(parent, "{c.name}");\n""" - Indent()) else: body += f"""pybind11::class_<{c.full_name}> {my_variable}(parent, "{c.name}");\n""" # 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
def _output_config(self): code = TextHolder() windows = self.options.string_encoding_windows linux = self.options.string_encoding_linux if windows == "utf-8" and linux == "utf-8": code += '#define AUTOCXXPY_ENCODING_UTF8' else: code += f'#define AUTOCXXPY_ENCODING_CUSTOM' code += f'#define AUTOCXXPY_ENCODING_CUSTOM_WINDOWS "{windows}"' code += f'#define AUTOCXXPY_ENCODING_CUSTOM_LINUX "{linux}"' self._save_template("config.h", "config.h", body=code)
def _return_description_for_function(self, of: GeneratorFunction): code = TextHolder() return_elements = ['"retv"', ] wf = of for wi in wf.wrappers: arg = wf.args[wi.index] return_elements.append(f'"{arg.name}"') wf = wi.wrapper.wrap(f=wf, index=wi.index, wrapper_info=wi) return_str = ",".join(return_elements) code += f'return {return_str}' return code
def _generate_caster_body(self, ns: GeneratorNamespace): fm = FunctionManager() body = TextHolder() cpp_scope_variable = "c" body += "struct caster: autocxxpy::caster{};" body += f"""auto {cpp_scope_variable} = autocxxpy::caster::bind<caster>(parent, "{self.options.caster_class_name}"); """ for c in ns.classes.values(): body += f'autocxxpy::caster::try_generate<{c.full_name}>({cpp_scope_variable}, "to{c.name})");' for p in ns.typedefs.values(): body += f'autocxxpy::caster::try_generate<{p.full_name}>({cpp_scope_variable}, "to{p.name})");' return body, fm
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_module(self): function_name = slugify(f'generate_{self.module_name}') function_body, fm = self._generate_namespace_body(self.options.g) module_body = TextHolder() module_body += 1 module_body += f'{function_name}(m);' self._save_template( "module.cpp", "module.cpp", module_body=module_body, module_tag=self.module_tag, ) self.function_manager.add(function_name, "pybind11::module &", function_body) self.function_manager.extend(fm)
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)}:' code += Indent("...") 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 _process_namespace(self, ns: GeneratorNamespace): code = TextHolder() # import sub modules first code_filename_base = self._module_filename_base(ns) code += self._process_namespaces(ns) code += self._process_classes(ns) code += self._process_enums(ns) code += self._process_typedefs(ns) code += self._process_variables(ns) code += self._process_functions(ns) self._save_template( "hint.py.in", f'{code_filename_base}.pyi', hint_code=code, ) return f"from . import {code_filename_base} as {ns.name}"
def _generate_enum_body(self, e: GeneratorEnum): fm = FunctionManager() body = TextHolder() my_variable = "e" if self.options.arithmetic_enum: arithmetic_enum_code = ", pybind11::arithmetic()" else: arithmetic_enum_code = "" body += ( f'pybind11::enum_<{e.full_name}> {my_variable}(parent, "{e.alias}"{arithmetic_enum_code});' ) for v in e.variables.values(): body += f'{my_variable}.value("{v.alias}", {v.full_name});' if not e.is_strong_typed: body += f'{my_variable}.export_values();' # objects record body += f'{self.module_class}::objects.emplace("{e.full_name}", {my_variable});' return body, fm
def _process_enum(self, e: GeneratorEnum): code = TextHolder() code += f'class {e.name}(Enum):' + Indent() code += self._process_variables(e) return code
def _process_namespaces(self, ns: GeneratorNamespace): code = TextHolder() for n in ns.namespaces.values(): code += self._process_namespace(n) return code
def _process_functions(self, ns: GeneratorNamespace): code = TextHolder() for ms in ns.functions.values(): for m in ms: code += self._process_function(m) return code
def _process_methods(self, ns: GeneratorClass): code = TextHolder() for ms in ns.functions.values(): for m in ms: code += self._process_method(m) return code
def __init__(self): self.body: TextHolder = TextHolder() self.function_manager = FunctionManager()