demangle 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- demangle/__init__.py +103 -0
- demangle/_signature.py +623 -0
- demangle/api.py +749 -0
- demangle/cli.py +521 -0
- demangle/core/__init__.py +20 -0
- demangle/core/ast.py +899 -0
- demangle/core/builder.py +160 -0
- demangle/core/cache.py +70 -0
- demangle/core/decorations.py +73 -0
- demangle/core/errors.py +75 -0
- demangle/core/limits.py +50 -0
- demangle/core/plugin.py +98 -0
- demangle/core/reader.py +247 -0
- demangle/core/registry.py +221 -0
- demangle/core/spelling.py +470 -0
- demangle/core/style.py +192 -0
- demangle/filter.py +226 -0
- demangle/py.typed +0 -0
- demangle/schemes/__init__.py +7 -0
- demangle/schemes/ada/__init__.py +175 -0
- demangle/schemes/ada/_parser.py +313 -0
- demangle/schemes/ada/nodes.py +89 -0
- demangle/schemes/codewarrior/__init__.py +215 -0
- demangle/schemes/codewarrior/_parser.py +583 -0
- demangle/schemes/codewarrior/nodes.py +131 -0
- demangle/schemes/codewarrior/options.py +33 -0
- demangle/schemes/d/__init__.py +118 -0
- demangle/schemes/d/_parser.py +1616 -0
- demangle/schemes/d/nodes.py +93 -0
- demangle/schemes/delphi/__init__.py +79 -0
- demangle/schemes/delphi/_parser.py +897 -0
- demangle/schemes/delphi/nodes.py +128 -0
- demangle/schemes/gnuv2/__init__.py +325 -0
- demangle/schemes/gnuv2/_parser.py +2487 -0
- demangle/schemes/gnuv2/nodes.py +145 -0
- demangle/schemes/gnuv2/options.py +54 -0
- demangle/schemes/go/__init__.py +127 -0
- demangle/schemes/go/_parser.py +278 -0
- demangle/schemes/go/nodes.py +138 -0
- demangle/schemes/itanium/__init__.py +61 -0
- demangle/schemes/itanium/options.py +332 -0
- demangle/schemes/itanium/parser.py +5456 -0
- demangle/schemes/itanium/substitutions.py +362 -0
- demangle/schemes/itanium/tables.py +376 -0
- demangle/schemes/jni/__init__.py +96 -0
- demangle/schemes/jni/_parser.py +226 -0
- demangle/schemes/jni/nodes.py +91 -0
- demangle/schemes/msvc/__init__.py +291 -0
- demangle/schemes/msvc/_parser.py +2024 -0
- demangle/schemes/msvc/nodes.py +450 -0
- demangle/schemes/msvc/options.py +140 -0
- demangle/schemes/nim/__init__.py +81 -0
- demangle/schemes/nim/_parser.py +470 -0
- demangle/schemes/nim/nodes.py +81 -0
- demangle/schemes/objc/__init__.py +119 -0
- demangle/schemes/objc/_parser.py +823 -0
- demangle/schemes/objc/nodes.py +122 -0
- demangle/schemes/pascal/__init__.py +85 -0
- demangle/schemes/pascal/_parser.py +428 -0
- demangle/schemes/pascal/nodes.py +131 -0
- demangle/schemes/rust/__init__.py +244 -0
- demangle/schemes/rust/_dispatch.py +85 -0
- demangle/schemes/rust/_legacy.py +266 -0
- demangle/schemes/rust/_v0.py +1867 -0
- demangle/schemes/rust/nodes.py +194 -0
- demangle/schemes/rust/options.py +36 -0
- demangle/schemes/swift/__init__.py +247 -0
- demangle/schemes/swift/_demangler.py +3224 -0
- demangle/schemes/swift/_node.py +644 -0
- demangle/schemes/swift/_old_demangler.py +1525 -0
- demangle/schemes/swift/_printer.py +2737 -0
- demangle/schemes/swift/_punycode.py +116 -0
- demangle/schemes/swift/nodes.py +143 -0
- demangle/schemes/swift/options.py +155 -0
- demangle/schemes/swift/resolve.py +577 -0
- demangle/schemes/swift/symbolic.py +201 -0
- demangle-0.3.0.dist-info/METADATA +382 -0
- demangle-0.3.0.dist-info/RECORD +82 -0
- demangle-0.3.0.dist-info/WHEEL +4 -0
- demangle-0.3.0.dist-info/entry_points.txt +2 -0
- demangle-0.3.0.dist-info/licenses/LICENSE +21 -0
- demangle-0.3.0.dist-info/licenses/NOTICE +178 -0
demangle/_signature.py
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
"""The parts of a symbol, in one shape, for every scheme.
|
|
2
|
+
|
|
3
|
+
`demangle()` answers "what does this name say"; this answers "what are its pieces". A
|
|
4
|
+
disassembler labelling a call site wants the base name without its namespace; a
|
|
5
|
+
cross-reference wants the namespace without the base name; a signature matcher wants the
|
|
6
|
+
parameter types and nothing else. All three can be had by splitting the spelling, and
|
|
7
|
+
all three get it wrong the same way, because `::` and `,` and `(` occur inside template
|
|
8
|
+
arguments and inside function types as well as between the parts a caller means.
|
|
9
|
+
|
|
10
|
+
So the split is done on the tree the parser already built. Two shapes of tree, because
|
|
11
|
+
the schemes come in two kinds:
|
|
12
|
+
|
|
13
|
+
* C++, both manglings, build a declaration -- a function node with its name inside its
|
|
14
|
+
own type, or a declarator standing beside one. The fields are read off it directly.
|
|
15
|
+
* Swift, D, Go, Nim, Free Pascal and Delphi build a node that *is* its own fragments, in
|
|
16
|
+
output order: `parts` interleaves the literal text the printer wrote with the subtrees
|
|
17
|
+
it wrote between them. That interleaving is the structure -- a `.` in `parts`
|
|
18
|
+
separates two components and a `.` inside a name does not -- so reading it gives the
|
|
19
|
+
same answer the printer gave, which splitting the printer's output could not.
|
|
20
|
+
|
|
21
|
+
Text is the last resort, for the places where a tree has already flattened the answer,
|
|
22
|
+
and even there with a reader that counts brackets and that declines a spelling which is
|
|
23
|
+
a phrase rather than a name.
|
|
24
|
+
|
|
25
|
+
What each scheme can say still differs, and the difference is real rather than a gap to
|
|
26
|
+
be filled in later:
|
|
27
|
+
|
|
28
|
+
* C++ encodes parameter types, the return type where the ABI writes one, cv- and
|
|
29
|
+
ref-qualifiers, and for MSVC the calling convention and the declared access.
|
|
30
|
+
* Swift encodes the whole function type, so parameters, result and `throws` all come
|
|
31
|
+
back. D, Free Pascal and Delphi encode their parameter types, and Free Pascal a
|
|
32
|
+
function's result.
|
|
33
|
+
* Rust, Go, Nim and Objective-C encode a path, or a class and a selector, and no
|
|
34
|
+
signature at all. `parameters` is `None` for them, which is not the same as `()`: one
|
|
35
|
+
says "the name does not carry this", the other says "it carries an empty list".
|
|
36
|
+
|
|
37
|
+
The `None`s are the point. A field that guesses is worse than a field that declines.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from typing import NamedTuple
|
|
42
|
+
|
|
43
|
+
from .api import _decode, _resolve
|
|
44
|
+
from .api import detect as _detect
|
|
45
|
+
from .api import parse as _parse
|
|
46
|
+
from .core.limits import DEFAULT_LIMITS, Limits
|
|
47
|
+
from .core.style import DEFAULT_STYLE, Style
|
|
48
|
+
|
|
49
|
+
__all__ = ["Signature", "signature", "signatureb"]
|
|
50
|
+
|
|
51
|
+
#: What separates the components of a qualified name, by scheme. Objective-C's is a
|
|
52
|
+
#: space, because a method is a class and a selector; a category -- the `(Store247)` in
|
|
53
|
+
#: `+[A_B209(Store247) andThen:do:]` -- says which chunk of source declared the method
|
|
54
|
+
#: and is not part of its name, so it stays in `demangled` and out of `qualified_name`.
|
|
55
|
+
_SEPARATORS = {
|
|
56
|
+
"itanium": "::",
|
|
57
|
+
"msvc": "::",
|
|
58
|
+
"rust": "::",
|
|
59
|
+
"delphi": "::",
|
|
60
|
+
"swift": ".",
|
|
61
|
+
"d": ".",
|
|
62
|
+
"go": ".",
|
|
63
|
+
"nim": ".",
|
|
64
|
+
"pascal": ".",
|
|
65
|
+
"objc": " ",
|
|
66
|
+
"jni": ".",
|
|
67
|
+
# `gnuv2` and `codewarrior` are absent on purpose rather than by oversight: both
|
|
68
|
+
# spell C++, so both take the `::` default above.
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#: Words that trail a declaration and qualify it rather than being part of its type.
|
|
72
|
+
_TRAILING_QUALIFIERS = frozenset({"const", "volatile", "restrict", "__restrict", "&", "&&", "noexcept"})
|
|
73
|
+
|
|
74
|
+
#: What MSVC writes before a declaration: the access it was declared with, and whether it
|
|
75
|
+
#: is static or virtual. Kept because a tool reading a binary wants to know, and nothing
|
|
76
|
+
#: else in the structured view records it.
|
|
77
|
+
_LEADING_QUALIFIERS = frozenset({"private", "protected", "public", "static", "virtual"})
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True, slots=True)
|
|
81
|
+
class Signature:
|
|
82
|
+
"""What a mangled name says about the entity it names.
|
|
83
|
+
|
|
84
|
+
Every field is either what the name encodes or `None`/empty where it encodes
|
|
85
|
+
nothing. Nothing here is inferred from a spelling that does not say it.
|
|
86
|
+
|
|
87
|
+
The three name fields hold to one invariant, which is what makes them safe to
|
|
88
|
+
recombine: where `namespace` is not empty, joining it to `base_name` with the
|
|
89
|
+
scheme's separator gives `qualified_name` exactly. Template arguments therefore stay
|
|
90
|
+
on `base_name` -- `sort<int*>`, not `sort` -- because they are part of the component
|
|
91
|
+
they belong to.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
language: str
|
|
95
|
+
"""The scheme that read the name: `"itanium"`, `"msvc"`, `"rust"` and so on."""
|
|
96
|
+
|
|
97
|
+
demangled: str
|
|
98
|
+
"""The whole readable spelling, the same string `demangle()` returns."""
|
|
99
|
+
|
|
100
|
+
qualified_name: str
|
|
101
|
+
"""The entity's name with its scope and without its type."""
|
|
102
|
+
|
|
103
|
+
base_name: str
|
|
104
|
+
"""The last component of that. Never empty for a name that parsed."""
|
|
105
|
+
|
|
106
|
+
namespace: str
|
|
107
|
+
"""Everything before the last component. Empty at the top level."""
|
|
108
|
+
|
|
109
|
+
parameters: tuple[str, ...] | None
|
|
110
|
+
"""The parameter types, spelled, or `None` where the name encodes no parameter list.
|
|
111
|
+
|
|
112
|
+
`()` and `None` mean different things: a C++ function taking no arguments has `()`,
|
|
113
|
+
and a Rust path has `None` because Rust does not put the signature in the symbol.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
return_type: str | None
|
|
117
|
+
"""The return type, spelled, or `None` where the name does not encode one.
|
|
118
|
+
|
|
119
|
+
Most C++ functions do not: the ABI omits it, because overloads cannot differ by it.
|
|
120
|
+
A template specialisation does. For a data symbol this is the type of the object.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
calling_convention: str | None
|
|
124
|
+
"""`__cdecl`, `__stdcall`, ... . MSVC only; no other scheme writes one."""
|
|
125
|
+
|
|
126
|
+
qualifiers: tuple[str, ...] = ()
|
|
127
|
+
"""What qualifies the declaration rather than its type.
|
|
128
|
+
|
|
129
|
+
The trailing cv- and ref-qualifiers of a member function -- `const`, `&&`,
|
|
130
|
+
`noexcept` -- for MSVC the leading access and storage words `private`, `static`,
|
|
131
|
+
`virtual`, and for Swift the effects a function declares: `throws`, `async`.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
special: str | None = None
|
|
135
|
+
"""What the symbol is *about*, where it is about something rather than being it.
|
|
136
|
+
|
|
137
|
+
`vtable for`, `typeinfo for`, `guard variable for`, `non-virtual thunk to`; D's
|
|
138
|
+
`initializer for`; Swift's `protocol requirements base descriptor for`. The other
|
|
139
|
+
fields then describe the entity the symbol is about, so `base_name` on a
|
|
140
|
+
`vtable for std::ostream` is `ostream`.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
decoration: str = ""
|
|
144
|
+
"""What the symbol table appended, separator included: `"@@GLIBCXX_3.4"`, `".cold"`."""
|
|
145
|
+
|
|
146
|
+
is_function: bool = False
|
|
147
|
+
"""Whether the name says it encodes a function.
|
|
148
|
+
|
|
149
|
+
False where the name does not say. A Rust or Go symbol is a path, and a path to a
|
|
150
|
+
function reads exactly like a path to a static, so neither is claimed to be either.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
is_data: bool = False
|
|
154
|
+
"""Whether the name encodes an object rather than a function."""
|
|
155
|
+
|
|
156
|
+
is_ctor_or_dtor: bool = False
|
|
157
|
+
"""Whether it is a constructor or a destructor of the type it sits in.
|
|
158
|
+
|
|
159
|
+
C++ repeats the class's own name, or negates it; Swift writes `init` and `deinit`.
|
|
160
|
+
No other scheme here marks the two, so no other scheme reports them.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def is_special(self) -> bool:
|
|
165
|
+
"""Whether the symbol is about an entity rather than being one."""
|
|
166
|
+
return self.special is not None
|
|
167
|
+
|
|
168
|
+
def __str__(self) -> str:
|
|
169
|
+
return self.demangled
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def signature(
|
|
173
|
+
mangled: str,
|
|
174
|
+
*,
|
|
175
|
+
language: str | None = None,
|
|
176
|
+
style: str | Style | None = DEFAULT_STYLE,
|
|
177
|
+
limits: Limits = DEFAULT_LIMITS,
|
|
178
|
+
) -> Signature:
|
|
179
|
+
"""The parts of `mangled`.
|
|
180
|
+
|
|
181
|
+
Raises the same errors as `demangle_strict`: a name that cannot be read has no
|
|
182
|
+
parts, and a `Signature` full of `None` would say that it did and that they were
|
|
183
|
+
all empty.
|
|
184
|
+
"""
|
|
185
|
+
tree = _parse(mangled, language=language, style=style, limits=limits)
|
|
186
|
+
# Through the registry, because `language` is whatever the caller wrote and the
|
|
187
|
+
# aliases are documented: `signature(..., language="objective-c")` must read the same
|
|
188
|
+
# as `language="objc"`, and keyed by the alias it found no separator and reported the
|
|
189
|
+
# whole spelling as the base name.
|
|
190
|
+
scheme = _resolve(language).name if language is not None else (_detect(mangled) or "")
|
|
191
|
+
return _extract(_Reading(scheme, _SEPARATORS.get(scheme, "::"), style), tree)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def signatureb(
|
|
195
|
+
mangled: bytes,
|
|
196
|
+
*,
|
|
197
|
+
language: str | None = None,
|
|
198
|
+
style: str | Style | None = DEFAULT_STYLE,
|
|
199
|
+
limits: Limits = DEFAULT_LIMITS,
|
|
200
|
+
) -> Signature:
|
|
201
|
+
"""`signature()` over bytes. Its fields are `str`, as a tree's spellings are.
|
|
202
|
+
|
|
203
|
+
The form to use when the names come from a symbol table: an ELF or Mach-O string
|
|
204
|
+
table holds bytes, and a tool that has read one should not have to guess an encoding
|
|
205
|
+
to ask what a name's parts are.
|
|
206
|
+
"""
|
|
207
|
+
return signature(_decode(mangled), language=language, style=style, limits=limits)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class _Reading(NamedTuple):
|
|
211
|
+
"""What every step of the read needs to know, and nothing else.
|
|
212
|
+
|
|
213
|
+
`style` travels with the rest because a subtree has to be spelled the way the whole
|
|
214
|
+
name was: a tree parsed under one style and spelled under another is a mixture of
|
|
215
|
+
the two, and `namespace` would then disagree with `demangled` about the same name.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
scheme: str
|
|
219
|
+
separator: str
|
|
220
|
+
style: str | Style | None
|
|
221
|
+
|
|
222
|
+
def spell(self, node):
|
|
223
|
+
return node.spell(style=self.style)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _extract(reading, tree):
|
|
227
|
+
whole = demangled = reading.spell(tree)
|
|
228
|
+
decoration = ""
|
|
229
|
+
special = None
|
|
230
|
+
|
|
231
|
+
# `whole` is the current tree's spelling, refreshed each time a wrapper comes off:
|
|
232
|
+
# the `symbol` test below compares a child against it, and rendering the tree twice
|
|
233
|
+
# to ask is rendering it twice per name.
|
|
234
|
+
while True:
|
|
235
|
+
kind = tree.kind
|
|
236
|
+
if kind == "decorated":
|
|
237
|
+
decoration = tree.decoration + decoration
|
|
238
|
+
tree = tree.inner
|
|
239
|
+
elif kind == "special":
|
|
240
|
+
# `vtable for X` describes X, so the rest of the fields describe X. A chain
|
|
241
|
+
# of them -- `guard variable for reference temporary for x` -- keeps the
|
|
242
|
+
# outermost label, which is what the symbol *is*.
|
|
243
|
+
if special is None:
|
|
244
|
+
special = tree.label.strip()
|
|
245
|
+
tree = tree.inner
|
|
246
|
+
elif kind == "symbol" and len(tree.children()) == 1 and reading.spell(tree.children()[0]) == whole:
|
|
247
|
+
# Rust, D and Swift wrap the whole thing in a `symbol` node, which carries
|
|
248
|
+
# the spelling and nothing the parts need. Only where the child *is* the
|
|
249
|
+
# spelling: Go's `go:buildinfo` and D's `initializer for demangle.test` are
|
|
250
|
+
# one node inside a wrapper that adds text of its own, and unwrapping them
|
|
251
|
+
# would drop it.
|
|
252
|
+
tree = tree.children()[0]
|
|
253
|
+
else:
|
|
254
|
+
break
|
|
255
|
+
whole = reading.spell(tree)
|
|
256
|
+
|
|
257
|
+
found = _parts_of(reading, tree)
|
|
258
|
+
if special is None:
|
|
259
|
+
special = found["label"]
|
|
260
|
+
namespace, base = found["namespace"], found["base_name"]
|
|
261
|
+
if namespace is None:
|
|
262
|
+
namespace, base = _split_last(found["qualified_name"], reading.separator)
|
|
263
|
+
|
|
264
|
+
return Signature(
|
|
265
|
+
language=reading.scheme,
|
|
266
|
+
demangled=demangled,
|
|
267
|
+
qualified_name=found["qualified_name"],
|
|
268
|
+
base_name=base,
|
|
269
|
+
namespace=namespace,
|
|
270
|
+
parameters=found["parameters"],
|
|
271
|
+
return_type=found["return_type"],
|
|
272
|
+
calling_convention=found["calling_convention"],
|
|
273
|
+
qualifiers=found["qualifiers"],
|
|
274
|
+
special=special,
|
|
275
|
+
decoration=decoration,
|
|
276
|
+
is_function=bool(found["is_function"]),
|
|
277
|
+
is_data=found["is_data"],
|
|
278
|
+
is_ctor_or_dtor=_is_structor(reading, namespace, base),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _blank(reading, tree):
|
|
283
|
+
return {
|
|
284
|
+
"qualified_name": reading.spell(tree),
|
|
285
|
+
"namespace": None,
|
|
286
|
+
"base_name": "",
|
|
287
|
+
"parameters": None,
|
|
288
|
+
"return_type": None,
|
|
289
|
+
"calling_convention": None,
|
|
290
|
+
"qualifiers": (),
|
|
291
|
+
"is_function": None,
|
|
292
|
+
"is_data": False,
|
|
293
|
+
"label": None,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _parts_of(reading, tree):
|
|
298
|
+
"""Read what the tree says, by the shape it has rather than by the scheme that built it."""
|
|
299
|
+
found = _blank(reading, tree)
|
|
300
|
+
kind = tree.kind
|
|
301
|
+
|
|
302
|
+
if kind == "function" and getattr(tree, "name", None) is not None:
|
|
303
|
+
# A C++ function declaration: the name sits inside its own type.
|
|
304
|
+
_name_from(reading, found, tree.name)
|
|
305
|
+
found["parameters"] = tuple(reading.spell(parameter) for parameter in tree.parameters)
|
|
306
|
+
found["return_type"] = reading.spell(tree.returns) if tree.returns is not None else None
|
|
307
|
+
found["qualifiers"] = _trailing(getattr(tree, "suffix", ""))
|
|
308
|
+
found["is_function"] = True
|
|
309
|
+
return found
|
|
310
|
+
|
|
311
|
+
if kind == "declaration":
|
|
312
|
+
# MSVC: the declarator is the name, and the type stands beside it.
|
|
313
|
+
_name_from(reading, found, tree.declarator)
|
|
314
|
+
# `access` and `member_type` hold what `prefix` used to hold as one string, so
|
|
315
|
+
# that each can be suppressed on its own; the qualifiers a caller asks for are
|
|
316
|
+
# still all of them.
|
|
317
|
+
lead = tree.prefix + getattr(tree, "access", "") + getattr(tree, "member_type", "")
|
|
318
|
+
found["qualifiers"] = _leading(lead) + _trailing(tree.suffix)
|
|
319
|
+
declared = tree.type
|
|
320
|
+
# A *pointer* to a function is data; only a declared function type is a
|
|
321
|
+
# function, so this looks through nothing.
|
|
322
|
+
if declared is not None and declared.kind == "function":
|
|
323
|
+
found["parameters"] = tuple(reading.spell(parameter) for parameter in declared.parameters)
|
|
324
|
+
found["return_type"] = reading.spell(declared.returns) if declared.returns is not None else None
|
|
325
|
+
found["calling_convention"] = getattr(declared, "convention", None) or None
|
|
326
|
+
found["qualifiers"] += _trailing(" ".join(getattr(declared, "member_cv", ()) or ()))
|
|
327
|
+
found["is_function"] = True
|
|
328
|
+
else:
|
|
329
|
+
found["is_data"] = True
|
|
330
|
+
found["is_function"] = False
|
|
331
|
+
found["return_type"] = reading.spell(declared) if declared is not None else None
|
|
332
|
+
return found
|
|
333
|
+
|
|
334
|
+
if kind == "symbol" and reading.scheme == "objc":
|
|
335
|
+
# A method is a class and a selector, and the selector's colons say how many
|
|
336
|
+
# arguments it takes without saying what any of them is.
|
|
337
|
+
names = [reading.spell(child) for child in tree.children() if child.kind == "name"]
|
|
338
|
+
if len(names) == 2:
|
|
339
|
+
found["namespace"], found["base_name"] = names
|
|
340
|
+
found["qualified_name"] = " ".join(names)
|
|
341
|
+
found["is_function"] = True
|
|
342
|
+
else:
|
|
343
|
+
# A module constructor or a class reference, not a method. There is no class
|
|
344
|
+
# and no selector to hand back, and the spelling is prose -- splitting
|
|
345
|
+
# `Objective-C module constructor` at its last space would invent both.
|
|
346
|
+
found["namespace"], found["base_name"] = "", found["qualified_name"]
|
|
347
|
+
return found
|
|
348
|
+
|
|
349
|
+
if kind in ("path", "qualified"):
|
|
350
|
+
_name_from(reading, found, tree)
|
|
351
|
+
return found
|
|
352
|
+
|
|
353
|
+
if _from_parts(reading, found, tree):
|
|
354
|
+
return found
|
|
355
|
+
|
|
356
|
+
if kind == "variable":
|
|
357
|
+
# For a tree that names its kinds but is not built out of fragments -- which a
|
|
358
|
+
# registered plugin's may not be. Every scheme shipped here reaches the reader
|
|
359
|
+
# above instead.
|
|
360
|
+
found["is_data"] = True
|
|
361
|
+
found["is_function"] = False
|
|
362
|
+
return found
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _name_from(reading, found, node):
|
|
366
|
+
"""Fill the three name fields from a name node.
|
|
367
|
+
|
|
368
|
+
Splitting a *node* is what makes the answer right: `A::operator<` and
|
|
369
|
+
`map<int, string>::at` both hold the separator's characters in places that are not
|
|
370
|
+
separators, and only the tree knows which is which. Where the components do not
|
|
371
|
+
reproduce the spelling -- Rust writes `::` into a closure's own name, so joining
|
|
372
|
+
them would double it -- the components are the wrong answer and are dropped, leaving
|
|
373
|
+
the bracket-counting reader in `_extract` to do it.
|
|
374
|
+
"""
|
|
375
|
+
found["qualified_name"] = reading.spell(node)
|
|
376
|
+
if node.kind not in ("path", "qualified"):
|
|
377
|
+
return
|
|
378
|
+
components = [reading.spell(child) for child in node.children()]
|
|
379
|
+
if not components or not all(components):
|
|
380
|
+
return
|
|
381
|
+
if reading.separator.join(components) != found["qualified_name"]:
|
|
382
|
+
return
|
|
383
|
+
found["namespace"] = reading.separator.join(components[:-1])
|
|
384
|
+
found["base_name"] = components[-1]
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
#: What a printer writes between a declaration and its result. Swift spells a function's
|
|
388
|
+
#: with `->` and a variable's with `:`; Free Pascal writes a routine's with `:`.
|
|
389
|
+
_RESULT_MARKERS = frozenset({"->", ":"})
|
|
390
|
+
|
|
391
|
+
#: Words a printer writes after the parameter list that qualify the declaration.
|
|
392
|
+
_DECLARATION_WORDS = frozenset({"async", "mutating", "nonmutating", "rethrows", "throws"})
|
|
393
|
+
|
|
394
|
+
#: What Free Pascal writes where the parameter list would go when the compiler dropped
|
|
395
|
+
#: it. A note is not a parameter, so a list that is only this one is no list at all.
|
|
396
|
+
_ELIDED = "<parameters elided by the compiler>"
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _from_parts(reading, found, node):
|
|
400
|
+
"""Read a node that is its own fragments, in output order.
|
|
401
|
+
|
|
402
|
+
Swift, D, Go, Nim, Free Pascal and Delphi all build their trees this way: `parts`
|
|
403
|
+
interleaves the literal text a printer emitted with the subtrees it emitted between
|
|
404
|
+
them. That interleaving is the structure -- a `.` in `parts` separates two
|
|
405
|
+
components and a `.` inside a name does not -- so reading it gives the same answer
|
|
406
|
+
the printer gave, which splitting its output could not.
|
|
407
|
+
"""
|
|
408
|
+
parts = getattr(node, "parts", None)
|
|
409
|
+
if not parts:
|
|
410
|
+
return False
|
|
411
|
+
|
|
412
|
+
components = [""]
|
|
413
|
+
parameters = None
|
|
414
|
+
result = None
|
|
415
|
+
qualifiers = []
|
|
416
|
+
label = None
|
|
417
|
+
at = 0
|
|
418
|
+
|
|
419
|
+
# A leading literal is what the printer said this symbol is *about*: `type info for`,
|
|
420
|
+
# `default associated conformance accessor for`. It labels the entity the rest of the
|
|
421
|
+
# parts describe, which is what `special` records. The trailing space is what makes it
|
|
422
|
+
# a label rather than a prefix of the name: Go's `go:buildinfo` is one word.
|
|
423
|
+
if isinstance(parts[0], str) and parts[0].strip() and parts[0].endswith(" ") and _holds_a_node(parts[1:]):
|
|
424
|
+
label = parts[0].strip()
|
|
425
|
+
at = 1
|
|
426
|
+
|
|
427
|
+
region = "name"
|
|
428
|
+
while at < len(parts):
|
|
429
|
+
part = parts[at]
|
|
430
|
+
at += 1
|
|
431
|
+
if not isinstance(part, str):
|
|
432
|
+
if region == "result":
|
|
433
|
+
result += reading.spell(part)
|
|
434
|
+
elif region == "name":
|
|
435
|
+
components[-1] += reading.spell(part)
|
|
436
|
+
continue
|
|
437
|
+
if region == "name" and part == reading.separator:
|
|
438
|
+
components.append("")
|
|
439
|
+
elif region == "name" and components[-1] and part.startswith("("):
|
|
440
|
+
# A fragment that *opens* with `(` after a component that has started is the
|
|
441
|
+
# parameter list. A `(` anywhere else in a fragment is text: Swift writes a
|
|
442
|
+
# file-private routine as `Foundation.FileHandle.(_check in _2DF8)()`, where
|
|
443
|
+
# only the second bracket is a call, and an LLDB expression as
|
|
444
|
+
# `__lldb_expr_1.(unknown context at $10016c2d8)`, where neither is. D writes
|
|
445
|
+
# the whole list as one fragment, `(int)`, which does open with the bracket.
|
|
446
|
+
parameters, at = _parameter_list(reading, parts, at, part[1:])
|
|
447
|
+
region = "signed"
|
|
448
|
+
elif region != "result" and part.strip() in _RESULT_MARKERS:
|
|
449
|
+
region, result = "result", ""
|
|
450
|
+
elif region == "name":
|
|
451
|
+
components[-1] += part
|
|
452
|
+
elif region == "result":
|
|
453
|
+
result += part
|
|
454
|
+
else:
|
|
455
|
+
qualifiers.extend(word for word in part.split() if word in _DECLARATION_WORDS)
|
|
456
|
+
|
|
457
|
+
found["qualified_name"] = reading.separator.join(components)
|
|
458
|
+
if len(components) > 1:
|
|
459
|
+
found["namespace"] = reading.separator.join(components[:-1])
|
|
460
|
+
found["base_name"] = components[-1]
|
|
461
|
+
# One component is not a split. `_TtBf32_` spells `Builtin.FPIEEE32` out of a single
|
|
462
|
+
# literal, and D writes `initializer for demangle.test` as a label and one node --
|
|
463
|
+
# in both the components are all the tree has, so the reader in `_extract` takes
|
|
464
|
+
# over, which at least counts brackets.
|
|
465
|
+
found["parameters"] = None if parameters == (_ELIDED,) else parameters
|
|
466
|
+
found["return_type"] = result or None
|
|
467
|
+
found["qualifiers"] = tuple(qualifiers)
|
|
468
|
+
found["label"] = label
|
|
469
|
+
if node.kind == "variable":
|
|
470
|
+
found["is_data"] = True
|
|
471
|
+
found["is_function"] = False
|
|
472
|
+
elif parameters is not None or node.kind == "function" or (result is not None and label is None):
|
|
473
|
+
# A labelled symbol is *about* an entity rather than being one, and the type
|
|
474
|
+
# after its `:` is what the label names -- an associated type, not a result --
|
|
475
|
+
# so it says nothing about whether anything here is callable.
|
|
476
|
+
found["is_function"] = True
|
|
477
|
+
return True
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _holds_a_node(parts):
|
|
481
|
+
"""Whether any of `parts` is a subtree rather than literal text."""
|
|
482
|
+
return any(not isinstance(part, str) for part in parts)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _parameter_list(reading, parts, at, opening=""):
|
|
486
|
+
"""The parameters between a `(` already consumed and its matching `)`.
|
|
487
|
+
|
|
488
|
+
Counted rather than split: a parameter can be a function type of its own, so the
|
|
489
|
+
comma that separates two parameters is the one at bracket depth one. Free Pascal and
|
|
490
|
+
Delphi hand the whole list over as a single `parameters` node, which says the arity
|
|
491
|
+
outright and is taken as it stands.
|
|
492
|
+
"""
|
|
493
|
+
taken = _Parameters()
|
|
494
|
+
if taken.text(opening):
|
|
495
|
+
return tuple(taken.found), at
|
|
496
|
+
while at < len(parts):
|
|
497
|
+
part = parts[at]
|
|
498
|
+
at += 1
|
|
499
|
+
if not isinstance(part, str):
|
|
500
|
+
if part.kind == "parameters":
|
|
501
|
+
taken.found.extend(spelled for child in part.children() if (spelled := reading.spell(child)))
|
|
502
|
+
else:
|
|
503
|
+
taken.current += reading.spell(part)
|
|
504
|
+
continue
|
|
505
|
+
if taken.text(part):
|
|
506
|
+
return tuple(taken.found), at
|
|
507
|
+
taken.end()
|
|
508
|
+
return tuple(taken.found), at
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class _Parameters:
|
|
512
|
+
"""A parameter list being read, a fragment at a time.
|
|
513
|
+
|
|
514
|
+
The state has to survive between fragments because a parameter's text can span
|
|
515
|
+
several of them -- a subtree, then the literal that follows it -- and the bracket
|
|
516
|
+
depth that decides which comma is a separator spans them too.
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
__slots__ = ("current", "depth", "found")
|
|
520
|
+
|
|
521
|
+
def __init__(self):
|
|
522
|
+
self.current = ""
|
|
523
|
+
self.found = []
|
|
524
|
+
# One, for the `(` the caller has already read.
|
|
525
|
+
self.depth = 1
|
|
526
|
+
|
|
527
|
+
def text(self, fragment):
|
|
528
|
+
"""Read literal text. True once the bracket the list opened with has closed."""
|
|
529
|
+
for char in fragment:
|
|
530
|
+
if char in "([{":
|
|
531
|
+
self.depth += 1
|
|
532
|
+
elif char in ")]}":
|
|
533
|
+
self.depth -= 1
|
|
534
|
+
if not self.depth:
|
|
535
|
+
self.end()
|
|
536
|
+
return True
|
|
537
|
+
elif char == "," and self.depth == 1:
|
|
538
|
+
self.end()
|
|
539
|
+
continue
|
|
540
|
+
self.current += char
|
|
541
|
+
return False
|
|
542
|
+
|
|
543
|
+
def end(self):
|
|
544
|
+
"""End the parameter being read, where anything was read."""
|
|
545
|
+
if self.current.strip():
|
|
546
|
+
self.found.append(self.current.strip())
|
|
547
|
+
self.current = ""
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def _trailing(text):
|
|
551
|
+
return tuple(word for word in text.split() if word in _TRAILING_QUALIFIERS)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def _leading(text):
|
|
555
|
+
return tuple(word.rstrip(":") for word in text.split() if word.rstrip(":") in _LEADING_QUALIFIERS)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _split_last(text, separator):
|
|
559
|
+
"""Split a qualified name at its last separator, counting brackets.
|
|
560
|
+
|
|
561
|
+
`std::map<int, std::string>::at` splits after `>`, not inside the argument list, and
|
|
562
|
+
`A::operator->` does not open a bracket it never closes -- the depth is clamped, so
|
|
563
|
+
an unbalanced `>` from an operator name cannot swallow the rest of the string.
|
|
564
|
+
|
|
565
|
+
A space outside every bracket means this is not a qualified name at all but a phrase
|
|
566
|
+
the printer wrote -- `inout Swift.Int`, `operator new`, `__thunk__ [B,0,1,0]` -- and
|
|
567
|
+
the separators in a phrase do not separate components. Those are left whole, which
|
|
568
|
+
is the only answer that is not invented.
|
|
569
|
+
"""
|
|
570
|
+
if not separator or (separator.strip() and _phrase(text)):
|
|
571
|
+
return "", text
|
|
572
|
+
depth = 0
|
|
573
|
+
cut = -1
|
|
574
|
+
at = 0
|
|
575
|
+
width = len(separator)
|
|
576
|
+
while at < len(text):
|
|
577
|
+
char = text[at]
|
|
578
|
+
if char in "<([":
|
|
579
|
+
depth += 1
|
|
580
|
+
elif char in ">)]":
|
|
581
|
+
depth = depth - 1 if depth else 0
|
|
582
|
+
elif depth == 0 and text.startswith(separator, at):
|
|
583
|
+
cut = at
|
|
584
|
+
at += width
|
|
585
|
+
continue
|
|
586
|
+
at += 1
|
|
587
|
+
if cut < 0:
|
|
588
|
+
return "", text
|
|
589
|
+
return text[:cut], text[cut + width :]
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _phrase(text):
|
|
593
|
+
"""Whether `text` holds a space at bracket depth zero."""
|
|
594
|
+
depth = 0
|
|
595
|
+
for char in text:
|
|
596
|
+
if char in "<([":
|
|
597
|
+
depth += 1
|
|
598
|
+
elif char in ">)]":
|
|
599
|
+
depth = depth - 1 if depth else 0
|
|
600
|
+
elif char == " " and depth == 0:
|
|
601
|
+
return True
|
|
602
|
+
return False
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
#: What Swift calls the two, where C++ repeats the class's own name.
|
|
606
|
+
_SWIFT_STRUCTORS = frozenset({"__allocating_init", "__deallocating_deinit", "deinit", "init"})
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _is_structor(reading, namespace, base):
|
|
610
|
+
"""Whether a name is a constructor or a destructor of the class it sits in."""
|
|
611
|
+
if reading.scheme == "swift":
|
|
612
|
+
return bool(namespace) and base in _SWIFT_STRUCTORS
|
|
613
|
+
if base.startswith("~"):
|
|
614
|
+
return True
|
|
615
|
+
if not namespace:
|
|
616
|
+
return False
|
|
617
|
+
_, enclosing = _split_last(namespace, reading.separator)
|
|
618
|
+
# `Foo<int>::Foo` is a constructor: the class carries its arguments and the
|
|
619
|
+
# constructor does not, so the comparison drops them.
|
|
620
|
+
cut = enclosing.find("<")
|
|
621
|
+
if cut > 0:
|
|
622
|
+
enclosing = enclosing[:cut]
|
|
623
|
+
return bool(enclosing) and enclosing == base
|