Skip to content
Closed
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
111 changes: 106 additions & 5 deletions scripts/cxx-api/parser/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
EnumMember,
FriendMember,
FunctionMember,
PropertyMember,
TypedefMember,
VariableMember,
)
from .scope import StructLikeScopeKind
from .scope import ProtocolScopeKind, StructLikeScopeKind
from .snapshot import Snapshot
from .template import Template
from .utils import Argument, extract_qualifiers, parse_qualified_path
Expand Down Expand Up @@ -119,6 +120,15 @@ def _qualify_text_with_refid(text: str, refid: str) -> str:
return prepend + "::" + text


def normalize_angle_brackets(text: str) -> str:
"""Doxygen adds spaces around < and > to avoid XML ambiguity.
e.g. "NSArray< id< RCTBridgeMethod > > *" -> "NSArray<id<RCTBridgeMethod>> *"
"""
text = re.sub(r"<\s+", "<", text)
text = re.sub(r"\s+>", ">", text)
return text


def extract_namespace_from_refid(refid: str) -> str:
"""Extract the namespace prefix from a doxygen refid.
e.g. 'namespacefacebook_1_1yoga_1a...' -> 'facebook::yoga'
Expand Down Expand Up @@ -202,12 +212,13 @@ def resolve_linked_text_name(
initialier_type = InitializerType.BRACE
name = name[1:-1].strip()

return (name.strip(), initialier_type)
return (normalize_angle_brackets(name.strip()), initialier_type)


def get_base_classes(
compound_object: compound.CompounddefType,
) -> [StructLikeScopeKind.Base]:
base_class=StructLikeScopeKind.Base,
) -> list:
"""
Get the base classes of a compound object.
"""
Expand All @@ -229,7 +240,7 @@ def get_base_classes(
continue

base_classes.append(
StructLikeScopeKind.Base(
base_class(
base_name,
base_prot,
base_virt == "virtual",
Expand Down Expand Up @@ -484,6 +495,31 @@ def get_concept_member(
return concept


def get_property_member(
member_def: compound.MemberdefType,
visibility: str,
is_static: bool = False,
) -> PropertyMember:
"""
Get the property member from a member definition.
"""
property_name = member_def.get_name()
property_type = resolve_linked_text_name(member_def.get_type())[0].strip()
accessor = member_def.accessor if hasattr(member_def, "accessor") else None
is_readable = getattr(member_def, "readable", "no") == "yes"
is_writable = getattr(member_def, "writable", "no") == "yes"

return PropertyMember(
property_name,
property_type,
visibility,
is_static,
accessor,
is_readable,
is_writable,
)


def create_enum_scope(snapshot: Snapshot, enum_def: compound.EnumdefType):
"""
Create an enum scope in the snapshot.
Expand All @@ -507,6 +543,71 @@ def create_enum_scope(snapshot: Snapshot, enum_def: compound.EnumdefType):
)


def create_protocol_scope(snapshot: Snapshot, scope_def: compound.CompounddefType):
"""
Create a protocol scope in the snapshot.
"""
# Doxygen appends "-p" to ObjC protocol compound names
protocol_name = scope_def.compoundname
if protocol_name.endswith("-p"):
protocol_name = protocol_name[:-2]

protocol_scope = snapshot.create_protocol(protocol_name)
base_classes = get_base_classes(scope_def, base_class=ProtocolScopeKind.Base)
for base in base_classes:
base.name = base.name.strip("<>")
protocol_scope.kind.add_base(base_classes)
protocol_scope.location = scope_def.location.file

for section_def in scope_def.sectiondef:
kind = section_def.kind
parts = kind.split("-")
visibility = parts[0]
is_static = "static" in parts
member_type = parts[-1]

if visibility == "private":
pass
elif visibility in ("public", "protected"):
if member_type == "attrib":
for member_def in section_def.memberdef:
if member_def.kind == "variable":
protocol_scope.add_member(
get_variable_member(member_def, visibility, is_static)
)
elif member_type == "func":
for function_def in section_def.memberdef:
protocol_scope.add_member(
get_function_member(function_def, visibility, is_static)
)
elif member_type == "type":
for member_def in section_def.memberdef:
if member_def.kind == "enum":
create_enum_scope(snapshot, member_def)
elif member_def.kind == "typedef":
protocol_scope.add_member(
get_typedef_member(member_def, visibility)
)
else:
print(
f"Unknown section member kind: {member_def.kind} in {scope_def.location.file}"
)
else:
print(
f"Unknown protocol section kind: {kind} in {scope_def.location.file}"
)
elif visibility == "property":
for member_def in section_def.memberdef:
if member_def.kind == "property":
protocol_scope.add_member(
get_property_member(member_def, "public", is_static)
)
else:
print(
f"Unknown protocol visibility: {visibility} in {scope_def.location.file}"
)


def build_snapshot(xml_dir: str) -> Snapshot:
"""
Reads the Doxygen XML output and builds a snapshot of the C++ API.
Expand Down Expand Up @@ -679,7 +780,7 @@ def build_snapshot(xml_dir: str) -> Snapshot:
# Contains deprecation info
pass
elif compound_object.kind == "protocol":
print(f"Protocol not supported: {compound_object.compoundname}")
create_protocol_scope(snapshot, compound_object)
else:
print(f"Unknown compound kind: {compound_object.kind}")

Expand Down
50 changes: 50 additions & 0 deletions scripts/cxx-api/parser/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,56 @@ def to_string(
return result


class PropertyMember(Member):
def __init__(
self,
name: str,
type: str,
visibility: str,
is_static: bool,
accessor: str | None,
is_readable: bool,
is_writable: bool,
) -> None:
super().__init__(name, visibility)
self.type: str = type
self.is_static: bool = is_static
self.accessor: str | None = accessor
self.is_readable: bool = is_readable
self.is_writable: bool = is_writable

@property
def member_kind(self) -> MemberKind:
return MemberKind.VARIABLE

def to_string(
self,
indent: int = 0,
qualification: str | None = None,
hide_visibility: bool = False,
) -> str:
name = self._get_qualified_name(qualification)
result = " " * indent

if not hide_visibility:
result += self.visibility + " "

attributes = []
if self.accessor:
attributes.append(self.accessor)
if not self.is_writable and self.is_readable:
attributes.append("readonly")

attrs_str = f"({', '.join(attributes)}) " if attributes else ""

if self.is_static:
result += "static "

result += f"@property {attrs_str}{self.type} {name};"

return result


class ConceptMember(Member):
def __init__(
self,
Expand Down
49 changes: 49 additions & 0 deletions scripts/cxx-api/parser/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,55 @@ def to_string(self, scope: Scope) -> str:
return result


class ProtocolScopeKind(ScopeKind):
class Base:
def __init__(
self, name: str, protection: str, virtual: bool, refid: str
) -> None:
self.name: str = name
self.protection: str = protection
self.virtual: bool = virtual
self.refid: str = refid

def __init__(self) -> None:
super().__init__("protocol")
self.base_classes: [ProtocolScopeKind.Base] = []

def add_base(self, base: ProtocolScopeKind.Base | [ProtocolScopeKind.Base]) -> None:
if isinstance(base, list):
for b in base:
self.base_classes.append(b)
else:
self.base_classes.append(base)

def to_string(self, scope: Scope) -> str:
result = ""

bases = []
for base in self.base_classes:
base_text = [base.protection]
if base.virtual:
base_text.append("virtual")
base_text.append(base.name)
bases.append(" ".join(base_text))

inheritance_string = " : " + ", ".join(bases) if bases else ""

result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{"

stringified_members = []
for member in scope.get_members():
stringified_members.append(member.to_string(2))
stringified_members = natsorted(stringified_members)
result += ("\n" if len(stringified_members) > 0 else "") + "\n".join(
stringified_members
)

result += "\n}"

return result


class TemporaryScopeKind(ScopeKind):
def __init__(self) -> None:
super().__init__("temporary")
Expand Down
25 changes: 25 additions & 0 deletions scripts/cxx-api/parser/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .scope import (
EnumScopeKind,
NamespaceScopeKind,
ProtocolScopeKind,
Scope,
StructLikeScopeKind,
TemporaryScopeKind,
Expand Down Expand Up @@ -84,6 +85,30 @@ def create_or_get_namespace(self, qualified_name: str) -> Scope[NamespaceScopeKi
current_scope.inner_scopes[namespace_name] = new_scope
return new_scope

def create_protocol(self, qualified_name: str) -> Scope[ProtocolScopeKind]:
"""
Create a protocol in the snapshot.
"""
path = parse_qualified_path(qualified_name)
scope_path = path[0:-1]
scope_name = path[-1]
current_scope = self.ensure_scope(scope_path)

if scope_name in current_scope.inner_scopes:
scope = current_scope.inner_scopes[scope_name]
if scope.kind.name == "temporary":
scope.kind = ProtocolScopeKind()
else:
raise RuntimeError(
f"Identifier {scope_name} already exists in scope {current_scope.name}"
)
return scope
else:
new_scope = Scope(ProtocolScopeKind(), scope_name)
new_scope.parent_scope = current_scope
current_scope.inner_scopes[scope_name] = new_scope
return new_scope

def create_enum(self, qualified_name: str) -> Scope[EnumScopeKind]:
"""
Create an enum in the snapshot.
Expand Down
2 changes: 1 addition & 1 deletion scripts/cxx-api/parser/utils/type_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def qualify_type_str(type_str: str, scope: Scope) -> str:
# Split template arguments and qualify each one
args = _split_arguments(template_args)
qualified_args = [qualify_type_str(arg.strip(), scope) for arg in args]
qualified_template = "< " + ", ".join(qualified_args) + " >"
qualified_template = "<" + ", ".join(qualified_args) + ">"

# Recursively qualify the suffix (handles nested templates, pointers, etc.)
qualified_suffix = qualify_type_str(suffix, scope) if suffix else ""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
struct test::Node {
public void setArray(int(&arr)[10]);
template <size_t N>
public static std::vector< test::PropNameID > names(test::PropNameID(&&propertyNames)[N]);
public static std::vector<test::PropNameID> names(test::PropNameID(&&propertyNames)[N]);
}

struct test::PropNameID {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
struct test::Config {
public std::optional< bool > expanded;
public std::optional< std::string > label;
public std::optional<bool> expanded;
public std::optional<std::string> label;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ class test::Clss {
public virtual void fn16() = 0;
public void fn0(int32_t arg1);
public void fn1(int32_t arg1, int32_t arg2);
public void fn2(std::function< int32_t(int32_t) > arg1);
public void fn3(std::function< int32_t(int32_t) > arg1, std::function< int32_t(int32_t, int32_t) > arg2);
public void fn4(std::map< std::string, int > m);
public void fn5(std::unordered_map< K, std::vector< V > > m);
public void fn6(std::tuple< int, float, std::string > t);
public void fn7(std::vector< std::vector< std::pair< int, int > > > v);
public void fn8(std::map< K, std::function< void(A, B) > > m);
public void fn2(std::function<int32_t(int32_t)> arg1);
public void fn3(std::function<int32_t(int32_t)> arg1, std::function<int32_t(int32_t, int32_t)> arg2);
public void fn4(std::map<std::string, int> m);
public void fn5(std::unordered_map<K, std::vector<V>> m);
public void fn6(std::tuple<int, float, std::string> t);
public void fn7(std::vector<std::vector<std::pair<int, int>>> v);
public void fn8(std::map<K, std::function<void(A, B)>> m);
public void fn9(int(*)(int, int) callback);
public void fn10(void(*)(const char *, size_t) handler);
public void fn11(int(*(*fp)(int))(double));
public void fn12(int x = 5, std::string s = "default");
public void fn13(std::function< void() > f = nullptr);
public void fn14(std::vector< int > v = {1, 2, 3});
public void fn13(std::function<void()> f = nullptr);
public void fn14(std::vector<int> v = {1, 2, 3});
public void fn15() noexcept;
public void fn17() = default;
public void fn18() = delete;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
protocol RCTEmptyProtocol {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

@protocol RCTEmptyProtocol

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
protocol RCTBaseProtocol {
public virtual void baseMethod();
}

protocol RCTChildProtocol : public RCTBaseProtocol {
public virtual void childMethod();
}
Loading