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.
Files changed (82) hide show
  1. demangle/__init__.py +103 -0
  2. demangle/_signature.py +623 -0
  3. demangle/api.py +749 -0
  4. demangle/cli.py +521 -0
  5. demangle/core/__init__.py +20 -0
  6. demangle/core/ast.py +899 -0
  7. demangle/core/builder.py +160 -0
  8. demangle/core/cache.py +70 -0
  9. demangle/core/decorations.py +73 -0
  10. demangle/core/errors.py +75 -0
  11. demangle/core/limits.py +50 -0
  12. demangle/core/plugin.py +98 -0
  13. demangle/core/reader.py +247 -0
  14. demangle/core/registry.py +221 -0
  15. demangle/core/spelling.py +470 -0
  16. demangle/core/style.py +192 -0
  17. demangle/filter.py +226 -0
  18. demangle/py.typed +0 -0
  19. demangle/schemes/__init__.py +7 -0
  20. demangle/schemes/ada/__init__.py +175 -0
  21. demangle/schemes/ada/_parser.py +313 -0
  22. demangle/schemes/ada/nodes.py +89 -0
  23. demangle/schemes/codewarrior/__init__.py +215 -0
  24. demangle/schemes/codewarrior/_parser.py +583 -0
  25. demangle/schemes/codewarrior/nodes.py +131 -0
  26. demangle/schemes/codewarrior/options.py +33 -0
  27. demangle/schemes/d/__init__.py +118 -0
  28. demangle/schemes/d/_parser.py +1616 -0
  29. demangle/schemes/d/nodes.py +93 -0
  30. demangle/schemes/delphi/__init__.py +79 -0
  31. demangle/schemes/delphi/_parser.py +897 -0
  32. demangle/schemes/delphi/nodes.py +128 -0
  33. demangle/schemes/gnuv2/__init__.py +325 -0
  34. demangle/schemes/gnuv2/_parser.py +2487 -0
  35. demangle/schemes/gnuv2/nodes.py +145 -0
  36. demangle/schemes/gnuv2/options.py +54 -0
  37. demangle/schemes/go/__init__.py +127 -0
  38. demangle/schemes/go/_parser.py +278 -0
  39. demangle/schemes/go/nodes.py +138 -0
  40. demangle/schemes/itanium/__init__.py +61 -0
  41. demangle/schemes/itanium/options.py +332 -0
  42. demangle/schemes/itanium/parser.py +5456 -0
  43. demangle/schemes/itanium/substitutions.py +362 -0
  44. demangle/schemes/itanium/tables.py +376 -0
  45. demangle/schemes/jni/__init__.py +96 -0
  46. demangle/schemes/jni/_parser.py +226 -0
  47. demangle/schemes/jni/nodes.py +91 -0
  48. demangle/schemes/msvc/__init__.py +291 -0
  49. demangle/schemes/msvc/_parser.py +2024 -0
  50. demangle/schemes/msvc/nodes.py +450 -0
  51. demangle/schemes/msvc/options.py +140 -0
  52. demangle/schemes/nim/__init__.py +81 -0
  53. demangle/schemes/nim/_parser.py +470 -0
  54. demangle/schemes/nim/nodes.py +81 -0
  55. demangle/schemes/objc/__init__.py +119 -0
  56. demangle/schemes/objc/_parser.py +823 -0
  57. demangle/schemes/objc/nodes.py +122 -0
  58. demangle/schemes/pascal/__init__.py +85 -0
  59. demangle/schemes/pascal/_parser.py +428 -0
  60. demangle/schemes/pascal/nodes.py +131 -0
  61. demangle/schemes/rust/__init__.py +244 -0
  62. demangle/schemes/rust/_dispatch.py +85 -0
  63. demangle/schemes/rust/_legacy.py +266 -0
  64. demangle/schemes/rust/_v0.py +1867 -0
  65. demangle/schemes/rust/nodes.py +194 -0
  66. demangle/schemes/rust/options.py +36 -0
  67. demangle/schemes/swift/__init__.py +247 -0
  68. demangle/schemes/swift/_demangler.py +3224 -0
  69. demangle/schemes/swift/_node.py +644 -0
  70. demangle/schemes/swift/_old_demangler.py +1525 -0
  71. demangle/schemes/swift/_printer.py +2737 -0
  72. demangle/schemes/swift/_punycode.py +116 -0
  73. demangle/schemes/swift/nodes.py +143 -0
  74. demangle/schemes/swift/options.py +155 -0
  75. demangle/schemes/swift/resolve.py +577 -0
  76. demangle/schemes/swift/symbolic.py +201 -0
  77. demangle-0.3.0.dist-info/METADATA +382 -0
  78. demangle-0.3.0.dist-info/RECORD +82 -0
  79. demangle-0.3.0.dist-info/WHEEL +4 -0
  80. demangle-0.3.0.dist-info/entry_points.txt +2 -0
  81. demangle-0.3.0.dist-info/licenses/LICENSE +21 -0
  82. 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