212 lines
7.2 KiB
Python
212 lines
7.2 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
from dataclasses import dataclass
|
|
|
|
import logging
|
|
|
|
import clang.cindex
|
|
from clang.cindex import CursorKind
|
|
|
|
from .comment_commands import CommentCommands
|
|
from .membertype import MemberType
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def get_type_name(type: clang.cindex.Type, namespace: tuple[str, ...]) -> str:
|
|
if namespace:
|
|
namespace_str = f"{'::'.join(namespace)}::"
|
|
else:
|
|
namespace_str = ""
|
|
type_name = type.spelling
|
|
|
|
if type.is_const_qualified():
|
|
type_name = type_name.replace("const", "").strip()
|
|
|
|
type_name = type_name.removeprefix(namespace_str)
|
|
|
|
return type_name
|
|
|
|
|
|
def _get_template_name(type: clang.cindex.Type) -> str:
|
|
type = type.get_canonical()
|
|
if type.get_num_template_arguments() < 1:
|
|
return type.spelling
|
|
name: str = type.spelling
|
|
if type.is_const_qualified():
|
|
name.removeprefix("const ")
|
|
return name[: name.index("<")]
|
|
|
|
|
|
def _is_chrono_like_type(type: clang.cindex.Type) -> bool:
|
|
return _get_template_name(type) in ("std::chrono::time_point", "std::chrono::duration")
|
|
|
|
|
|
# clang's C API doesn't expose this, so we emulate it
|
|
def _is_trivially_copyable(type: clang.cindex.Type) -> bool:
|
|
# remove optional wrapper(s)
|
|
type = type.get_canonical()
|
|
while type.get_num_template_arguments() and _get_template_name(type) == "std::optional":
|
|
type = type.get_template_argument_type(0).get_canonical()
|
|
|
|
if type.is_pod():
|
|
return True
|
|
return _is_chrono_like_type(type)
|
|
|
|
|
|
def _has_no_fields(type: clang.cindex.Type) -> bool:
|
|
for _ in type.get_fields():
|
|
return False
|
|
return True
|
|
|
|
|
|
@dataclass
|
|
class VariantType:
|
|
name: str
|
|
trivial: bool
|
|
empty: bool
|
|
|
|
|
|
class Member:
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
member_type: MemberType = MemberType.BASIC,
|
|
type_name: str = "?",
|
|
trivial: bool = False,
|
|
) -> None:
|
|
self.name = name
|
|
self.json_name = name
|
|
self.member_type = member_type
|
|
self.type_name = type_name
|
|
self.tag: Optional[str] = None
|
|
self.trivial = trivial
|
|
self.variant_types: list[VariantType] | None = None
|
|
self.variant_fallback: str | None = None
|
|
|
|
self.dont_fail_on_deserialization: bool = False
|
|
|
|
def apply_comment_commands(self, comment_commands: CommentCommands) -> None:
|
|
self.json_name = comment_commands.apply_name_transform(self.json_name)
|
|
self.tag = comment_commands.tag
|
|
self.dont_fail_on_deserialization = comment_commands.dont_fail_on_deserialization
|
|
|
|
@staticmethod
|
|
def from_field(
|
|
node: clang.cindex.Cursor,
|
|
comment_commands: CommentCommands,
|
|
namespace: tuple[str, ...],
|
|
) -> Member:
|
|
assert node.type is not None
|
|
|
|
name = node.spelling
|
|
member_type = MemberType.BASIC
|
|
type_name = get_type_name(node.type, namespace)
|
|
|
|
log.debug(f"{node.spelling} - {type_name} - {node.type.is_const_qualified()}")
|
|
|
|
ntargs = node.type.get_num_template_arguments()
|
|
if ntargs > 0:
|
|
overwrite_member_type: Optional[MemberType] = None
|
|
|
|
# log.debug(node.type.get_template_argument_type(0).kind)
|
|
# log.debug(node.type.get_template_argument_type(0).spelling)
|
|
# log.debug(node.type.get_template_argument_type(0).get_named_type().spelling)
|
|
# log.debug(node.type.get_template_argument_type(0).get_class_type().spelling)
|
|
|
|
type_name = get_type_name(node.type.get_template_argument_type(0), namespace)
|
|
|
|
for xd in node.get_children():
|
|
match xd.kind:
|
|
case CursorKind.NAMESPACE_REF:
|
|
# Ignore namespaces
|
|
pass
|
|
|
|
case CursorKind.TEMPLATE_REF:
|
|
match xd.spelling:
|
|
case "optional":
|
|
match overwrite_member_type:
|
|
case None:
|
|
overwrite_member_type = MemberType.OPTIONAL
|
|
case other:
|
|
log.warning(f"Optional cannot be added on top of other member type: {other}")
|
|
|
|
case "vector":
|
|
match overwrite_member_type:
|
|
case None:
|
|
overwrite_member_type = MemberType.VECTOR
|
|
case MemberType.OPTIONAL:
|
|
overwrite_member_type = MemberType.OPTIONAL_VECTOR
|
|
case other:
|
|
log.warning(f"Vector cannot be added on top of other member type: {other}")
|
|
case "variant":
|
|
overwrite_member_type = MemberType.VARIANT
|
|
case other:
|
|
log.warning(f"Unhandled template type: {other}")
|
|
|
|
case CursorKind.TYPE_REF:
|
|
type_name = get_type_name(xd.type, namespace)
|
|
|
|
case other:
|
|
log.debug(f"Unhandled child kind type: {other}")
|
|
|
|
if overwrite_member_type is not None:
|
|
member_type = overwrite_member_type
|
|
|
|
member = Member(name, member_type, type_name, _is_trivially_copyable(node.type))
|
|
|
|
if node.raw_comment is not None:
|
|
comment_commands.parse(node.raw_comment)
|
|
|
|
member.apply_comment_commands(comment_commands)
|
|
|
|
if member.member_type == MemberType.VARIANT:
|
|
member.apply_variant(node.type, namespace)
|
|
|
|
return member
|
|
|
|
def apply_variant(self, type: clang.cindex.Type, namespace: tuple[str, ...]):
|
|
self.variant_types = []
|
|
for idx in range(type.get_num_template_arguments()):
|
|
inner = type.get_template_argument_type(idx)
|
|
name = get_type_name(inner, namespace)
|
|
if name == "std::string" or name == "String":
|
|
assert not self.variant_fallback
|
|
self.variant_fallback = name
|
|
continue
|
|
self.variant_types.append(
|
|
VariantType(
|
|
name=name,
|
|
trivial=_is_trivially_copyable(inner),
|
|
empty=_has_no_fields(inner),
|
|
)
|
|
)
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if not isinstance(other, self.__class__):
|
|
return False
|
|
|
|
if self.name != other.name:
|
|
return False
|
|
if self.member_type != other.member_type:
|
|
return False
|
|
if self.type_name != other.type_name:
|
|
return False
|
|
|
|
return True
|
|
|
|
def __repr__(self) -> str:
|
|
match self.member_type:
|
|
case MemberType.BASIC:
|
|
return f"{self.type_name} {self.name}"
|
|
|
|
case MemberType.VECTOR:
|
|
return f"std::vector<{self.type_name}> {self.name}"
|
|
|
|
case MemberType.OPTIONAL:
|
|
return f"std::optional<{self.type_name}> {self.name}"
|
|
|
|
case MemberType.OPTIONAL_VECTOR:
|
|
return f"std::optional<std::vector<{self.type_name}>> {self.name}"
|