Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 117 additions & 96 deletions obidog/bindings/classes.py

Large diffs are not rendered by default.

35 changes: 17 additions & 18 deletions obidog/bindings/enums.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,44 @@
import os
from typing import List

from obidog.bindings.utils import strip_include
import obidog.bindings.flavours.sol3 as flavour
from obidog.bindings.utils import fetch_table
from obidog.utils.string_utils import format_name
from obidog.bindings.utils import fetch_table, strip_include
from obidog.logger import log
from obidog.models.enums import EnumModel
from obidog.utils.string_utils import format_name

def generate_enum_fields(enum_type, enum):

def generate_enum_fields(enum_type: str, enum: EnumModel):
body = []
for enum_field in enum["values"]:
body.append(f'{{"{enum_field["name"]}", {enum_type}::{enum_field["name"]}}}')
for enum_field in enum.values:
body.append(f'{{"{enum_field.name}", {enum_type}::{enum_field.name}}}')
return "{" + ",".join(body) + "}"


def generate_enums_bindings(name, enums):
def generate_enums_bindings(name: str, enums: List[EnumModel]):
includes = []
bindings_functions = []
objects = []
for enum_name, enum in enums.items():
log.info(f" Generating bindings for enum {enum_name}")
enum_path = strip_include(enum["location"]).replace(os.path.sep, "/")
enum_path = strip_include(enum.location).replace(os.path.sep, "/")
includes.append(f"#include <{enum_path}>")
state_view = flavour.STATE_VIEW
export_name = format_name(enum["name"])
binding_function_signature = (
f"void LoadEnum{export_name}({state_view} state)"
)
export_name = format_name(enum.name)
binding_function_signature = f"void LoadEnum{export_name}({state_view} state)"
table_access = fetch_table(name) + "\n"
binding_function_body = table_access + flavour.ENUM_BODY.format(
namespace=name.split("::")[-1],
enum_type=enum_name,
enum_name=enum["name"],
enum_fields=generate_enum_fields(enum_name, enum)
enum_name=enum.name,
enum_fields=generate_enum_fields(enum_name, enum),
)
bindings_functions.append(
f"{binding_function_signature}\n{{\n"
f"{binding_function_body}\n}}"
f"{binding_function_signature}\n{{\n" f"{binding_function_body}\n}}"
)
objects.append(f"Enum{export_name}")
return {
"includes": includes,
"bindings_functions": bindings_functions,
"objects": objects
}
"objects": objects,
}
169 changes: 97 additions & 72 deletions obidog/bindings/functions.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import os
from dataclasses import dataclass
from typing import List, Union

import obidog.bindings.flavours.sol3 as flavour
from obidog.bindings.template import generate_template_specialization
from obidog.bindings.utils import strip_include
from obidog.bindings.utils import fetch_table, strip_include, get_include_file
from obidog.logger import log
from obidog.utils.string_utils import format_name
from obidog.bindings.utils import fetch_table
from obidog.models.functions import FunctionModel, FunctionOverloadModel
from obidog.utils.string_utils import clean_capitalize, format_name

FUNCTION_CAST_TEMPLATE = (
"static_cast<{return_type} (*)" "({parameters}) {qualifiers}>({function_address})"
)

FUNCTION_WITH_DEFAULT_VALUES_LAMBDA_WRAPPER = (
"[]({parameters}) -> {return_type} {{ return {function_call}({parameters_names}); }}"
)
FUNCTION_WITH_DEFAULT_VALUES_LAMBDA_WRAPPER = "[]({parameters}) -> {return_type} {{ return {function_call}({parameters_names}); }}"


def normalize_cpp_type(cpp_type):
stype = cpp_type.split()
Expand Down Expand Up @@ -77,62 +78,70 @@ def normalize_cpp_type(cpp_type):
"new": "allocate",
"delete": "deallocate",
"new[]": "allocate_array",
"delete[]": "deallocate_array"

"delete[]": "deallocate_array",
}

def get_real_function_name(function_name, function):

def get_real_function_name(
function_name, function: Union[FunctionModel, FunctionOverloadModel]
):
if function_name.startswith("operator"):
short_operator = function_name.split("operator")[1]
if isinstance(OPERATOR_TABLE[short_operator], str):
if function["__type__"] == "function_overload":
function = function["overloads"][0]
type_before = clean_capitalize(normalize_cpp_type(
function["parameters"][0]["type"]
))
if isinstance(function, FunctionOverloadModel):
function = function.overloads[0]
type_before = clean_capitalize(
normalize_cpp_type(function.parameters[0].type)
)
operator_name = "".join(
clean_capitalize(name_part)
for name_part in
OPERATOR_TABLE[short_operator].split("_")
for name_part in OPERATOR_TABLE[short_operator].split("_")
)
type_after = ""
if len(function["parameters"]) > 1:
type_after = clean_capitalize(normalize_cpp_type(
function["parameters"][1]["type"]
))
if len(function.parameters) > 1:
type_after = clean_capitalize(
normalize_cpp_type(function.parameters[1].type)
)
return "Operator", f"{type_before}{operator_name}{type_after}"
else:
if function["__type__"] == "function_overload":
function = function["overloads"][0]
if isinstance(function, FunctionOverloadModel):
function = function.overloads[0]
raise NotImplementedError()
return "Function", function_name

def create_all_default_overloads(function):

@dataclass
class DefaultOverloadModel:
definition: str
name: str


def create_all_default_overloads(function: FunctionModel) -> List[DefaultOverloadModel]:
function_definitions = []
static_part_index = 0
for parameter in function["parameters"]:
if "default" in parameter:
for parameter in function.parameters:
if parameter.default:
break
static_part_index += 1
static_part = [
{"full": f"{parameter['type']} {parameter['name']}", "name": parameter["name"]}
for parameter in function["parameters"][0:static_part_index]
DefaultOverloadModel(f"{parameter.type} {parameter.name}", parameter.name)
for parameter in function.parameters[0:static_part_index]
]
function_definitions.append(static_part)
for i in range(static_part_index, len(function["parameters"])):
for i in range(static_part_index, len(function.parameters)):
function_definitions.append(
static_part
+ [
{
"full": f"{parameter['type']} {parameter['name']}",
"name": parameter["name"],
}
for parameter in function["parameters"][static_part_index : i + 1]
DefaultOverloadModel(
f"{parameter.type} {parameter.name}", parameter.name
)
for parameter in function.parameters[static_part_index : i + 1]
]
)
return function_definitions

def generate_function_definitions(function_name, function):

def generate_function_definitions(function_name: str, function: FunctionModel):
"""This function generates all possible combinations of a function with default parameters
If a function has 2 mandatory parameters and 3 default ones, it will generate 4 function
wrappers
Expand All @@ -144,80 +153,96 @@ def generate_function_definitions(function_name, function):
overloads.append(
FUNCTION_WITH_DEFAULT_VALUES_LAMBDA_WRAPPER.format(
parameters=",".join(
parameter["full"] for parameter in function_definition
parameter.definition for parameter in function_definition
),
return_type=function["return_type"],
return_type=function.return_type,
function_call=function_name,
parameters_names=",".join(
parameter["name"] for parameter in function_definition
parameter.name for parameter in function_definition
),
)
)
return overloads

def get_overload_static_cast(function_name, function_value):

def get_overload_static_cast(function_name: str, function_value: FunctionModel):
return FUNCTION_CAST_TEMPLATE.format(
return_type=function_value["return_type"],
parameters=",".join(parameter["type"] for parameter in function_value["parameters"]),
qualifiers=" ".join(function_value["qualifiers"]),
function_address=function_name
return_type=function_value.return_type,
parameters=",".join(parameter.type for parameter in function_value.parameters),
qualifiers=" ".join(
[
"const" if function_value.qualifiers.const else "",
"volatile" if function_value.qualifiers.volatile else "",
]
),
function_address=function_name,
)

def generate_function_bindings(function_name, function_value):

def generate_function_bindings(
function_name: str, function_value: Union[FunctionModel, FunctionOverloadModel]
):
namespace_splitted = function_name.split("::")[:-1]
function_ptr = function_name
function_list = []
if function_value["__type__"] == "function" and function_value["template"]:
if "template_hints" in function_value:
if isinstance(function_value, FunctionModel) and function_value.template:
if function_value.flags.template_hints:
full_body = ""
for bind_name, template_hints in function_value["template_hints"].items():
for (
bind_name,
template_hints,
) in function_value.flags.template_hints.items():
new_name = bind_name
if len(function_name.split("::")) > 1:
new_name = "::".join(function_name.split("::")[:-1]) + "::" + bind_name
new_name = (
"::".join(function_name.split("::")[:-1]) + "::" + bind_name
)
if len(template_hints) == 1:
new_func = generate_template_specialization(function_value, bind_name, template_hints[0])
new_func = generate_template_specialization(
function_value, bind_name, template_hints[0]
)
full_body += generate_function_bindings(new_name, new_func)
else:
overloads = [
generate_template_specialization(function_value, bind_name, template_hint)
generate_template_specialization(
function_value, bind_name, template_hint
)
for template_hint in template_hints
]
funcs = {
"__type__": "function_overload",
"name": new_name,
"overloads": overloads
}
funcs = FunctionOverloadModel(new_name, overloads)
full_body += generate_function_bindings(new_name, funcs)
return full_body
else:
print(f"[WARNING] No template hints found for function {function_name}")
return ""
if function_value["__type__"].endswith("_overload"):
function_list = function_value["overloads"]
if isinstance(function_value, FunctionOverloadModel):
function_list = function_value.overloads
all_overloads = []
for function_overload in function_list:
if any("default" in parameter for parameter in function_overload["parameters"]):
all_overloads += generate_function_definitions(function_name, function_value)
if any(parameter.default for parameter in function_overload.parameters):
all_overloads += generate_function_definitions(
function_name, function_value
)
else:
all_overloads += [get_overload_static_cast(function_name, function_overload)]
all_overloads += [
get_overload_static_cast(function_name, function_overload)
]
if all_overloads:
function_ptr = flavour.FUNCTION_OVERLOAD.format(
overloads=",".join(
all_overloads
)
overloads=",".join(all_overloads)
)

binding_body = fetch_table("::".join(namespace_splitted)) + "\n" + flavour.FUNCTION_BODY.format(
namespace=namespace_splitted[-1],
function_name=function_value["name"],
function_ptr=function_ptr,
binding_body = (
fetch_table("::".join(namespace_splitted))
+ "\n"
+ flavour.FUNCTION_BODY.format(
namespace=namespace_splitted[-1],
function_name=function_value.name,
function_ptr=function_ptr,
)
)
return f"{binding_body}"

def get_include_file(function_value):
include_path = strip_include(function_value["location"])
include_path = include_path.replace(os.path.sep, "/")
return f"#include <{include_path}>"

# LATER: Catch operator function and assign them to correct classes metafunctions
def generate_functions_bindings(functions):
Expand All @@ -231,8 +256,8 @@ def generate_functions_bindings(functions):
)
real_function_name = format_name(short_name)
objects.append(f"{func_type}{real_function_name}")
if function_value["__type__"].endswith("_overload"):
for overload in function_value["overloads"]:
if isinstance(function_value, FunctionOverloadModel):
for overload in function_value.overloads:
includes.append(get_include_file(overload))
else:
includes.append(get_include_file(function_value))
Expand Down
41 changes: 17 additions & 24 deletions obidog/bindings/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from obidog.logger import log
from obidog.wrappers.clangformat_wrapper import clang_format_files
from obidog.utils.string_utils import clean_capitalize
from obidog.models.functions import PlaceholderFunctionModel
import os
import inflection
import re
Expand Down Expand Up @@ -248,37 +249,29 @@ def apply_proxies(cpp_db, functions):
f"{class_name}::{method_name}": method_value
for class_name, class_value in cpp_db.classes.items()
for method_name, method_value
in class_value["methods"].items()
in class_value.methods.items()
}
}
for function_name, function_value in functions.items():
if "proxy" in function_value:
patch = all_functions[function_value["proxy"]]
patch.update({
"definition": function_value["definition"],
"parameters": function_value["parameters"],
"return_type": function_value["return_type"],
"location": function_value["location"],
"replacement": function_value["definition"].split()[1]
})
print()
for function_value in functions.values():
if function_value.flags.proxy:
patch = all_functions[function_value.flags.proxy]
patch.definition = function_value.definition
patch.parameters = function_value.parameters
patch.return_type = function_value.return_type
patch.location = function_value.location
patch.replacement = function_value.definition.split()[1]

def discard_placeholders(cpp_db):
for class_value in cpp_db.classes.values():
class_value["methods"] = {
key: value for
key, value in class_value["methods"].items()
if not value["__type__"] == "placeholder"
}
class_value["static_methods"] = {
key: value for
key, value in class_value["static_methods"].items()
if not value["__type__"] == "placeholder"
class_value.methods = {
method_name: method for
method_name, method in class_value.methods.items()
if not isinstance(method, PlaceholderFunctionModel)
}
cpp_db.functions = {
key: value for
key, value in cpp_db.functions.items()
if not value["__type__"] == "placeholder"
function_name: function for
function_name, function in cpp_db.functions.items()
if not isinstance(function, PlaceholderFunctionModel)
}

# LATER: Add a tag in Doxygen to allow custom name / namespace binding
Expand Down
Loading