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/cli.py ADDED
@@ -0,0 +1,521 @@
1
+ """The `demangle` command.
2
+
3
+ Reads names from arguments or standard input and writes their expansions, so it drops
4
+ into a pipeline the way `c++filt` does:
5
+
6
+ nm -a libfoo.so | demangle
7
+ demangle _ZNSt6vectorIiSaIiEE9push_backERKi
8
+
9
+ Names that cannot be read pass through unchanged, which is what makes it safe to run
10
+ over a whole symbol table.
11
+
12
+ On reading a stream
13
+ -------------------
14
+ With no arguments this is a *filter*, not a line reader: every symbol-shaped word in the
15
+ input is a candidate and everything around it is copied through untouched. That is what
16
+ `c++filt`, `demumble` and `rustfilt` all do, and it is the only behaviour under which
17
+ the first example above works. `nm` writes an address and a type letter before the name,
18
+ so treating the whole line as one symbol demangled nothing at all:
19
+
20
+ $ printf '0000000000001139 T _ZN3foo3barEv\\n' | demangle
21
+ 0000000000001139 T _ZN3foo3barEv # every line, unchanged
22
+
23
+ Encoding
24
+ --------
25
+ A symbol table holds bytes, and they are not reliably UTF-8. Both streams are read and
26
+ written with `surrogateescape`, so a name this package cannot read comes back byte for
27
+ byte -- and so a non-UTF-8 symbol does not end the run with a `UnicodeDecodeError`
28
+ traceback, which is what happened wherever the interpreter's error handler was `strict`.
29
+ """
30
+
31
+ import argparse
32
+ import contextlib
33
+ import json
34
+ import os
35
+ import sys
36
+
37
+ from . import __version__
38
+ from ._signature import signature
39
+ from .api import demangle, demangle_strict, demangle_type, detect, languages, parse, parse_type, style, styles
40
+ from .core.ast import Function
41
+ from .core.errors import DemanglingError
42
+ from .core.limits import DEFAULT_LIMITS, RELAXED_LIMITS, Limits
43
+ from .filter import TOKEN, TOKEN_MUST_HOLD
44
+
45
+ #: The token scan, shared with `demangle.filter` so there is one of it rather than
46
+ #: two. That module is the library form of this command's default behaviour; see its
47
+ #: docstring for what the two patterns are for and why the second one is needed.
48
+ _TOKEN = TOKEN
49
+ _TOKEN_MUST_HOLD = TOKEN_MUST_HOLD
50
+
51
+
52
+ def build_parser():
53
+ parser = argparse.ArgumentParser(
54
+ prog="demangle",
55
+ description="Demangle C++, Rust, Swift, MSVC and other symbol names.",
56
+ epilog=(
57
+ "With no NAME arguments, names are read from standard input: every "
58
+ "symbol-shaped word is demangled and the text around it is copied through."
59
+ ),
60
+ )
61
+ parser.add_argument("names", nargs="*", metavar="NAME", help="symbol names to demangle")
62
+ parser.add_argument("-l", "--language", help="force a scheme instead of detecting (aliases accepted)")
63
+ parser.add_argument("-s", "--style", default="llvm", help="output style (default: llvm)")
64
+ parser.add_argument("-d", "--detect", action="store_true", help="print the detected scheme, not the expansion")
65
+ parser.add_argument("-t", "--tree", action="store_true", help="print the parse tree")
66
+ parser.add_argument("--json", action="store_true", help="print the parse tree as JSON")
67
+ parser.add_argument(
68
+ "--types",
69
+ action="store_true",
70
+ help="read each NAME as a bare type encoding, not a symbol (needs --language)",
71
+ )
72
+ parser.add_argument(
73
+ "-_",
74
+ "--strip-underscore",
75
+ action="store_true",
76
+ help="ignore one leading underscore, as `c++filt --strip-underscore` does",
77
+ )
78
+ parser.add_argument("--strict", action="store_true", help="report failures instead of echoing the input")
79
+ parser.add_argument(
80
+ "-b", "--both", action="store_true", help="print the mangled name and its expansion, as `mangled ==> demangled`"
81
+ )
82
+ parser.add_argument(
83
+ "-m", "--only-demangled", action="store_true", help="print only what demangled, skipping the rest"
84
+ )
85
+ # One name, one part: asking for two of these would print one of them and drop the
86
+ # other, and a flag that is silently ignored is worse than an error.
87
+ parts = parser.add_mutually_exclusive_group()
88
+ parts.add_argument(
89
+ "-p",
90
+ "--no-params",
91
+ action="store_true",
92
+ help="print the name without its parameter list or return type, as `c++filt -p` does",
93
+ )
94
+ parts.add_argument(
95
+ "--base-name", action="store_true", help="print only the last component of the name, without its scope"
96
+ )
97
+ parts.add_argument(
98
+ "--no-return-type", action="store_true", help="print the whole declaration except the return type"
99
+ )
100
+ parts.add_argument(
101
+ "--ret-postfix",
102
+ action="store_true",
103
+ help="print the return type after the parameter list, as libiberty's `DMGL_RET_POSTFIX` does",
104
+ )
105
+ less = parser.add_argument_group(
106
+ "printing less of a name",
107
+ "MSVC decorated names expand to a great deal more than the name. "
108
+ "`--no-calling-convention`, `--no-access-specifier`, `--no-member-type` and "
109
+ "`--no-variable-type` are `llvm-undname`'s flags and mean the same, and apply to "
110
+ "the declaration. `--no-ms-keywords`, `--no-leading-underscores`, "
111
+ "`--no-this-type` and `--no-tag-kind` are `UnDecorateSymbolName` mask bits it has "
112
+ "no flag for, and reach every occurrence of what they name -- inside a template "
113
+ "argument, and inside the symbol a local name is scoped by. `--no-return-type` is "
114
+ "shared with the selection above and applies to every scheme.",
115
+ )
116
+ less.add_argument("--no-calling-convention", action="store_true", help="omit `__cdecl` and its siblings")
117
+ less.add_argument("--no-access-specifier", action="store_true", help="omit `public: `, `private: `")
118
+ less.add_argument("--no-member-type", action="store_true", help="omit `static ` and `virtual `")
119
+ less.add_argument("--no-variable-type", action="store_true", help="print a data symbol as its name alone")
120
+ less.add_argument("--no-ms-keywords", action="store_true", help="omit every Microsoft keyword, wherever it stands")
121
+ less.add_argument(
122
+ "--no-leading-underscores", action="store_true", help="spell those keywords as `cdecl`, `restrict`"
123
+ )
124
+ less.add_argument("--no-this-type", action="store_true", help="omit what a member function writes after `()`")
125
+ less.add_argument("--no-tag-kind", action="store_true", help="omit `class`, `struct`, `union`, `enum`")
126
+ less.add_argument(
127
+ "--simplified",
128
+ action="store_true",
129
+ help="Swift names the way Xcode shows them, as `swift-demangle --simplified`",
130
+ )
131
+ parser.add_argument(
132
+ "--keep-hash",
133
+ action="store_true",
134
+ help="spell the hash Rust writes to keep a symbol unique, as rustc-demangle's `{}` does",
135
+ )
136
+ parser.add_argument("--relaxed", action="store_true", help="raise the resource bounds, for input you trust")
137
+ parser.add_argument("--max-input", type=int, metavar="N", help="characters of input to consider")
138
+ parser.add_argument("--max-output", type=int, metavar="N", help="characters of output to allow")
139
+ parser.add_argument("--max-depth", type=int, metavar="N", help="nesting depth to allow")
140
+ parser.add_argument("--list-languages", action="store_true", help="list supported schemes and exit")
141
+ parser.add_argument("--list-styles", action="store_true", help="list output styles and exit")
142
+ parser.add_argument("--version", action="version", version=f"demangle {__version__}")
143
+ return parser
144
+
145
+
146
+ #: `--flag` to the MSVC option it turns off. `--no-return-type` and `--ret-postfix` are
147
+ #: shared with the selection flags -- both need the declaration without its return type,
148
+ #: and differ only in whether the type is then written after it. For MSVC that has to be
149
+ #: an option, because a return type there wraps *around* the declarator:
150
+ #: `int (__cdecl * __cdecl fn(void))(int)` has no prefix to strip. Itanium has the same
151
+ #: shape in `int (*g<int>(int))(int)` and answers it from the tree instead; see
152
+ #: `_without_return_type`.
153
+ _MSVC_SUPPRESSIONS = {
154
+ "no_calling_convention": "calling_convention",
155
+ "no_access_specifier": "access_specifier",
156
+ "no_member_type": "member_type",
157
+ "no_variable_type": "variable_type",
158
+ "no_return_type": "return_type",
159
+ "ret_postfix": "return_type",
160
+ "no_ms_keywords": "ms_keywords",
161
+ "no_leading_underscores": "leading_underscores",
162
+ "no_this_type": "this_type",
163
+ "no_tag_kind": "tag_kind",
164
+ }
165
+
166
+
167
+ def _style_from(arguments):
168
+ """The style this run spells with: the named one, plus whatever it is to leave out."""
169
+ changes = {}
170
+ off = {option: False for flag, option in _MSVC_SUPPRESSIONS.items() if getattr(arguments, flag)}
171
+ if off:
172
+ changes["msvc"] = off
173
+ if arguments.simplified:
174
+ from .schemes.swift.options import SIMPLIFIED_OPTIONS
175
+
176
+ changes["swift"] = SIMPLIFIED_OPTIONS
177
+ if arguments.keep_hash:
178
+ changes["rust"] = {"keep_hash": True}
179
+ if not changes:
180
+ return arguments.style
181
+ return style(arguments.style, **changes)
182
+
183
+
184
+ def _limits_from(arguments):
185
+ """The bounds this run should use.
186
+
187
+ `--relaxed` moves the floor; the three explicit flags override whatever is under
188
+ them, so `--relaxed --max-output 4096` means what it reads as.
189
+ """
190
+ base = RELAXED_LIMITS if arguments.relaxed else DEFAULT_LIMITS
191
+ if arguments.max_input is arguments.max_output is arguments.max_depth is None:
192
+ return base
193
+ return Limits(
194
+ max_depth=base.max_depth if arguments.max_depth is None else arguments.max_depth,
195
+ max_output=base.max_output if arguments.max_output is None else arguments.max_output,
196
+ max_substitutions=base.max_substitutions,
197
+ max_input=base.max_input if arguments.max_input is None else arguments.max_input,
198
+ )
199
+
200
+
201
+ def _dump(node, indent=0):
202
+ """Render a parse tree, one node per line."""
203
+ pad = " " * indent
204
+ detail = ""
205
+ # Every slot in the hierarchy, not just the most-derived class's: a node whose text
206
+ # lives on a shared base declares none of its own and would print no detail at all.
207
+ for slot in node._fields() if hasattr(node, "_fields") else getattr(node, "__slots__", ()):
208
+ value = getattr(node, slot, None)
209
+ if isinstance(value, str) and value:
210
+ detail = f" {value!r}"
211
+ break
212
+ yield f"{pad}{node.kind}{detail}"
213
+ for child in node.children():
214
+ yield from _dump(child, indent + 1)
215
+
216
+
217
+ def _reconfigure(stream, **kwargs):
218
+ """Set an error handler on a stream, where the stream has one to set.
219
+
220
+ Under `pytest`'s capture, and behind some redirections, the standard streams are
221
+ substitutes with no `reconfigure`. Nothing here is load-bearing for correctness --
222
+ it decides how undecodable bytes are handled -- so a stream that cannot be
223
+ reconfigured is left alone.
224
+ """
225
+ reconfigure = getattr(stream, "reconfigure", None)
226
+ if reconfigure is not None:
227
+ with contextlib.suppress(ValueError, OSError):
228
+ reconfigure(**kwargs)
229
+
230
+
231
+ def main(argv=None):
232
+ parser = build_parser()
233
+ arguments = parser.parse_args(argv)
234
+
235
+ if arguments.list_languages:
236
+ from .core.registry import available
237
+
238
+ for plugin in available():
239
+ aliases = f" (aliases: {', '.join(plugin.aliases)})" if plugin.aliases else ""
240
+ print(f"{plugin.name:10} {plugin.description}{aliases}")
241
+ return 0
242
+
243
+ if arguments.list_styles:
244
+ for name in styles():
245
+ print(name)
246
+ return 0
247
+
248
+ # Validated through the registry rather than against `languages()`, so the aliases
249
+ # that `--list-languages` advertises are actually accepted.
250
+ if arguments.language:
251
+ from .core.registry import aliases, get
252
+
253
+ try:
254
+ get(arguments.language)
255
+ except KeyError:
256
+ known = ", ".join(sorted(set(languages()) | set(aliases())))
257
+ parser.error(f"unknown language {arguments.language!r}; choose from {known}")
258
+ if arguments.json and arguments.tree:
259
+ parser.error("--tree and --json are two spellings of the same answer; choose one")
260
+ if arguments.types:
261
+ if not arguments.language:
262
+ parser.error("--types needs --language: a type encoding carries no marker to detect on")
263
+ # Checked here rather than per name: a scheme with no type grammar fails on every
264
+ # line, and one message about the run beats one message per symbol.
265
+ from .core.registry import get as _get_plugin
266
+
267
+ if _get_plugin(arguments.language).parse_type is None:
268
+ readable = ", ".join(sorted(name for name in languages() if _get_plugin(name).parse_type is not None))
269
+ parser.error(f"{arguments.language} has no type grammar of its own; {readable} do")
270
+ if arguments.detect:
271
+ parser.error("--types and --detect ask different questions; --types already names the scheme")
272
+ if arguments.no_params or arguments.base_name or arguments.no_return_type or arguments.ret_postfix:
273
+ parser.error("--types reads a type, which has no name, parameters or return type to select")
274
+ if arguments.style not in styles():
275
+ parser.error(f"unknown style {arguments.style!r}; choose from {', '.join(styles())}")
276
+ for flag in ("max_input", "max_output", "max_depth"):
277
+ value = getattr(arguments, flag)
278
+ if value is not None and value < 1:
279
+ parser.error(f"--{flag.replace('_', '-')} must be positive")
280
+
281
+ arguments.style = _style_from(arguments)
282
+
283
+ _reconfigure(sys.stdin, errors="surrogateescape")
284
+ _reconfigure(sys.stdout, errors="surrogateescape")
285
+
286
+ try:
287
+ if arguments.types:
288
+ return _run_types(arguments.names or sys.stdin, arguments)
289
+ if arguments.names:
290
+ return _run_names(arguments.names, arguments)
291
+ return _run_stream(sys.stdin, arguments)
292
+ except BrokenPipeError:
293
+ # `demangle | head` closes the pipe on us. This has to be caught outside the
294
+ # loop: caught per name it prints one error for every remaining symbol, thousands
295
+ # of them. Point stdout at the null device so the interpreter's shutdown flush
296
+ # does not raise it again, and exit cleanly.
297
+ _silence_stdout()
298
+ return 0
299
+
300
+
301
+ def _silence_stdout():
302
+ """Point stdout at the null device, so the shutdown flush has somewhere to go."""
303
+ try:
304
+ null = os.open(os.devnull, os.O_WRONLY)
305
+ except OSError: # pragma: no cover - no /dev/null
306
+ return
307
+ try:
308
+ os.dup2(null, sys.stdout.fileno())
309
+ except (OSError, ValueError, AttributeError): # pragma: no cover - not a real stream
310
+ pass
311
+ finally:
312
+ # `dup2` duplicated the descriptor, so this one has done its job either way.
313
+ # Left open it leaked -- which mattered not at all for one exit, and would have
314
+ # mattered for a library caller invoking `main()` in a loop.
315
+ os.close(null)
316
+
317
+
318
+ def _expand(name, arguments, limits):
319
+ """What this run has to say about one name, after any leading underscore is dealt with.
320
+
321
+ `--strip-underscore` is for the targets whose assembler prepends one, where the
322
+ symbol in the table is `__Z1fv` and the name the compiler mangled is `_Z1fv`. Both
323
+ references drop exactly one and, where what is left does not read, print the name
324
+ they were *given* rather than the stripped form -- so a table full of `_foo` comes
325
+ back untouched instead of a character short.
326
+ """
327
+ if arguments.strip_underscore and name.startswith("_"):
328
+ answer = _expand_read(name[1:], arguments, limits)
329
+ return name if answer == name[1:] else answer
330
+ return _expand_read(name, arguments, limits)
331
+
332
+
333
+ def _expand_read(name, arguments, limits):
334
+ """What this run has to say about one name."""
335
+ if arguments.detect:
336
+ return arguments.language or detect(name) or "-"
337
+ if arguments.types:
338
+ if arguments.json or arguments.tree:
339
+ node = parse_type(name, language=arguments.language, style=arguments.style, limits=limits)
340
+ return json.dumps(node.to_dict()) if arguments.json else "\n".join(_dump(node))
341
+ return demangle_type(name, language=arguments.language, style=arguments.style, limits=limits)
342
+ if arguments.json:
343
+ return json.dumps(parse(name, language=arguments.language, style=arguments.style, limits=limits).to_dict())
344
+ if arguments.tree:
345
+ return "\n".join(_dump(parse(name, language=arguments.language, style=arguments.style, limits=limits)))
346
+ if arguments.no_params or arguments.base_name or arguments.no_return_type or arguments.ret_postfix:
347
+ return _part_of(name, arguments, limits)
348
+ if arguments.strict:
349
+ return demangle_strict(name, language=arguments.language, style=arguments.style, limits=limits)
350
+ return demangle(name, language=arguments.language, style=arguments.style, limits=limits)
351
+
352
+
353
+ def _part_of(name, arguments, limits):
354
+ """One piece of a name rather than the whole spelling.
355
+
356
+ A name that cannot be read has no pieces, so without `--strict` it comes back
357
+ unchanged -- the same bargain `demangle` makes, because a filter over a symbol table
358
+ meets far more names that are not mangled than names that are.
359
+ """
360
+ try:
361
+ parts = signature(name, language=arguments.language, style=arguments.style, limits=limits)
362
+ except Exception:
363
+ if arguments.strict:
364
+ raise
365
+ return name
366
+ if arguments.base_name:
367
+ return parts.base_name
368
+ if arguments.no_params:
369
+ # What `c++filt -p` prints: the name with its scope, and neither the signature
370
+ # around it nor the qualifiers after it. A `vtable for` still says so.
371
+ lead = f"{parts.special} " if parts.special else ""
372
+ return f"{lead}{parts.qualified_name}{parts.decoration}"
373
+ without = _without_return_type(name, arguments, limits, parts)
374
+ if arguments.ret_postfix and parts.return_type:
375
+ # libiberty writes the return type straight after the parameter list with no
376
+ # space between them -- `f<int>(int)int` -- because the space it puts between a
377
+ # return type and a signature belongs to the prefix form. Checked against
378
+ # `cplus_demangle_v3(name, DMGL_PARAMS | DMGL_RET_POSTFIX)` rather than assumed.
379
+ return f"{without}{parts.return_type}"
380
+ return without
381
+
382
+
383
+ def _without_return_type(name, arguments, limits, parts):
384
+ """The whole declaration with the return type taken out of it.
385
+
386
+ Cutting it off the front of the spelling is right only where it *is* a prefix. A
387
+ return type that wraps the declarator has none to cut -- `int (*g<int>(int))(int)`,
388
+ a function returning a pointer to a function -- and the cut then left the return type
389
+ in place and reported success, which is the silently ignored flag this file warns
390
+ about a few lines up. MSVC met the same shape first and answers it with a scheme
391
+ option; an Itanium `function` node answers it directly, by spelling the same node
392
+ with nothing where the return type was. `libiberty`'s own `DMGL_RET_DROP` agrees.
393
+ """
394
+ if parts.return_type:
395
+ try:
396
+ tree = parse(name, language=arguments.language, style=arguments.style, limits=limits)
397
+ except Exception: # the cut below is the answer when there is no tree
398
+ tree = None
399
+ if isinstance(tree, Function) and tree.returns is not None:
400
+ bare = Function(returns=None, parameters=tree.parameters, suffix=tree.suffix, name=tree.name)
401
+ return bare.spell(style=arguments.style)
402
+ # Cut by what the return type is rather than at the first space, so one with spaces
403
+ # in it goes whole.
404
+ spelling = parts.demangled
405
+ prefix = f"{parts.return_type} " if parts.return_type else ""
406
+ return spelling[len(prefix) :] if prefix and spelling.startswith(prefix) else spelling
407
+
408
+
409
+ def _run_names(names, arguments):
410
+ """One name per argument: the whole argument is the name, whatever it holds.
411
+
412
+ No word-splitting here. A caller who typed a name meant that name, and an
413
+ Objective-C method or a Go symbol has spaces and slashes in it.
414
+ """
415
+ limits = _limits_from(arguments)
416
+ status = 0
417
+ out = sys.stdout
418
+ for name in names:
419
+ if not name:
420
+ out.write("\n")
421
+ continue
422
+ try:
423
+ expanded = _expand(name, arguments, limits)
424
+ except BrokenPipeError:
425
+ raise
426
+ except Exception as exc:
427
+ print(f"{name}: {exc}", file=sys.stderr)
428
+ status = 1
429
+ continue
430
+ if arguments.only_demangled and expanded == name:
431
+ continue
432
+ out.write(f"{name} ==> {expanded}\n" if arguments.both else f"{expanded}\n")
433
+ return status
434
+
435
+
436
+ def _run_types(names, arguments):
437
+ """`--types`: one *type* encoding per argument, or per line of standard input.
438
+
439
+ Not the filter the default path is, and it cannot be. A type encoding is not
440
+ symbol-shaped -- `Pi`, `H`, `Si` are ordinary words -- so picking them out of mixed
441
+ text would mean turning `I like Pi` into `I like int*`, which is the very thing
442
+ `_TOKEN_MUST_HOLD` exists to prevent. Each input is one encoding, whole.
443
+
444
+ An encoding that does not parse comes back unchanged, the bargain `demangle()`
445
+ makes, unless `--strict` asks to hear about it instead.
446
+ """
447
+ limits = _limits_from(arguments)
448
+ status = 0
449
+ out = sys.stdout
450
+ for raw in names:
451
+ # A line off stdin carries its newline, and an argument does not. No type
452
+ # encoding in any of these grammars holds a `\r` either, so a file with CRLF
453
+ # endings reads the same as one without.
454
+ name = raw.rstrip("\r\n")
455
+ if not name:
456
+ out.write("\n")
457
+ continue
458
+ try:
459
+ expanded = _expand(name, arguments, limits)
460
+ except BrokenPipeError:
461
+ raise
462
+ except DemanglingError as exc:
463
+ if arguments.strict:
464
+ print(f"{name}: {exc}", file=sys.stderr)
465
+ status = 1
466
+ continue
467
+ expanded = name
468
+ except Exception as exc:
469
+ print(f"{name}: {exc}", file=sys.stderr)
470
+ status = 1
471
+ continue
472
+ if arguments.only_demangled and expanded == name:
473
+ continue
474
+ out.write(f"{name} ==> {expanded}\n" if arguments.both else f"{expanded}\n")
475
+ return status
476
+
477
+
478
+ def _run_stream(stream, arguments):
479
+ """A filter: substitute every symbol-shaped word, copy everything else through.
480
+
481
+ Line by line rather than all at once, so `demangle` in a pipe stays a pipe: someone
482
+ watching `nm ... | demangle` should not have to wait for the input to end.
483
+ """
484
+ limits = _limits_from(arguments)
485
+ status = 0
486
+ out = sys.stdout
487
+
488
+ for line in stream:
489
+ pieces = []
490
+ demangled = []
491
+ end = 0
492
+ for match in _TOKEN.finditer(line):
493
+ word = match.group()
494
+ if not _TOKEN_MUST_HOLD.search(word):
495
+ continue
496
+ try:
497
+ expanded = _expand(word, arguments, limits)
498
+ except BrokenPipeError:
499
+ raise
500
+ except Exception as exc:
501
+ print(f"{word}: {exc}", file=sys.stderr)
502
+ status = 1
503
+ continue
504
+ if expanded == word and not arguments.detect:
505
+ continue
506
+ replacement = f"{word} ==> {expanded}" if arguments.both else expanded
507
+ demangled.append(replacement)
508
+ pieces.append(line[end : match.start()])
509
+ pieces.append(replacement)
510
+ end = match.end()
511
+ if arguments.only_demangled:
512
+ for replacement in demangled:
513
+ out.write(replacement + "\n")
514
+ continue
515
+ pieces.append(line[end:])
516
+ out.write("".join(pieces))
517
+ return status
518
+
519
+
520
+ if __name__ == "__main__": # pragma: no cover
521
+ sys.exit(main())
@@ -0,0 +1,20 @@
1
+ """The scheme-agnostic half of the library.
2
+
3
+ Nothing here imports a mangling scheme, and a new scheme is written against these
4
+ modules and nothing else:
5
+
6
+ - `builder` -- the contract between a parser and its output, and the reason one parser
7
+ can serve both `demangle()` and `parse()` with no second implementation to drift.
8
+ - `spelling` -- C-family declarator placement, shared by every scheme that spells its
9
+ types the way C does.
10
+ - `ast` -- the tree `parse()` returns.
11
+ - `reader` -- a bounds-checked cursor; the only input primitive parsers use.
12
+ - `errors`, `limits` -- the failure and resource contracts.
13
+ - `plugin`, `registry` -- how a scheme announces itself.
14
+ - `decorations` -- what a symbol table appends to a name, which belongs to no scheme.
15
+ - `style` -- the choices on which the reference implementations legitimately differ.
16
+
17
+ An explicit `__init__` rather than a namespace package: an implicit one silently merges
18
+ with any same-named directory another installed distribution happens to ship, which for
19
+ a package called `core` is not a remote possibility.
20
+ """