Skip to content

Commit 108848f

Browse files
committed
feat: allow typedefs and multiple structs in definition_to_type
1 parent dd253da commit 108848f

1 file changed

Lines changed: 45 additions & 11 deletions

File tree

libdestruct/c/struct_parser.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,44 @@
2222
from libdestruct.common.obj import obj
2323

2424

25+
PARSED_STRUCT = {}
26+
"""A cache for parsed struct definitions, indexed by name."""
27+
28+
TYPEDEFS = {}
29+
"""A cache for parsed type definitions, indexed by name."""
30+
2531
def definition_to_type(definition: str) -> type[obj]:
2632
"""Converts a C struct definition to a struct object."""
2733
parser = c_parser.CParser()
2834

2935
# If the definition contains includes, we must expand them.
3036
if "#include" in definition:
3137
definition = cleanup_attributes(expand_includes(definition))
32-
force_more_tops = True
33-
elif "typedef" in definition:
34-
force_more_tops = True
35-
else:
36-
force_more_tops = False
3738

3839
try:
3940
ast = parser.parse(definition)
4041
except c_parser.ParseError as e:
4142
raise ValueError("Invalid definition. Please add the necessary includes if using non-standard type definitions.") from e
4243

43-
if not force_more_tops and len(ast.ext) != 1:
44-
raise ValueError("Definition must contain exactly one top object.")
45-
46-
# If force_more_tops is True, we take the last top object.
47-
# This is useful when a struct definition is preceded by typedefs.
48-
root = ast.ext[-1].type if force_more_tops else ast.ext[0].type
44+
# We assume that the root declaration is the last one.
45+
root = ast.ext[-1].type
4946

5047
if not isinstance(root, c_ast.Struct):
5148
raise TypeError("Definition must be a struct.")
5249

50+
# We parse each declaration in the definition, except the last one, if it is a struct.
51+
for decl in ast.ext[:-1]:
52+
if isinstance(decl.type, c_ast.Struct):
53+
struct_node = decl.type
54+
55+
if struct_node.name:
56+
PARSED_STRUCT[struct_node.name] = struct_to_type(struct_node)
57+
elif isinstance(decl, c_ast.Typedef):
58+
name, definition = typedef_to_pair(decl)
59+
TYPEDEFS[name] = definition
60+
61+
print(TYPEDEFS)
62+
5363
return struct_to_type(root)
5464

5565

@@ -60,6 +70,12 @@ def struct_to_type(struct_node: c_ast.Struct) -> type[struct]:
6070

6171
fields = {}
6272

73+
if not struct_node.decls and struct_node.name in PARSED_STRUCT:
74+
# We can check if the struct is already parsed.
75+
return PARSED_STRUCT[struct_node.name]
76+
elif not struct_node.decls:
77+
raise ValueError("Struct must have fields.")
78+
6379
for decl in struct_node.decls:
6480
name = decl.name
6581
typ = type_decl_to_type(decl.type, struct_node)
@@ -122,6 +138,20 @@ def type_decl_to_type(decl: c_ast.TypeDecl, parent: c_ast.Struct | None = None)
122138
raise TypeError("Unsupported type.")
123139

124140

141+
def typedef_to_pair(typedef: c_ast.Typedef) -> tuple[str, type[obj]]:
142+
"""Converts a C typedef to a pair of name and definition."""
143+
if not isinstance(typedef, c_ast.Typedef):
144+
raise TypeError("Definition must be a typedef.")
145+
146+
if not isinstance(typedef.type, c_ast.TypeDecl):
147+
raise TypeError("Definition must be a type declaration.")
148+
149+
name = "".join(typedef.name)
150+
definition = type_decl_to_type(typedef.type)
151+
152+
return name, definition
153+
154+
125155
def to_uniform_name(name: str) -> str:
126156
"""Converts a name to a uniform name."""
127157
name = name.replace("unsigned", "u")
@@ -182,4 +212,8 @@ def identifier_to_type(identifier: c_ast.IdentifierType) -> type[obj]:
182212
if hasattr(ctypes, ctypes_name):
183213
return getattr(ctypes, ctypes_name)
184214

215+
# Check if we have a typedef to resolve this
216+
if identifier_name in TYPEDEFS:
217+
return TYPEDEFS[identifier_name]
218+
185219
raise ValueError(f"Unsupported identifier: {identifier_name}.")

0 commit comments

Comments
 (0)