mcasm 0.4.3__cp315-cp315-win_amd64.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.
mcasm/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from mcasm._core import Assembler, ParserState, Streamer, X86Syntax, mc
2
+ from mcasm.version import __version__ # noqa: F401
3
+
4
+ __all__ = ["Assembler", "ParserState", "Streamer", "X86Syntax", "mc"]
mcasm/__main__.py ADDED
@@ -0,0 +1,306 @@
1
+ import argparse
2
+ import inspect
3
+ import re
4
+ import sys
5
+ from typing import Any, Callable, Dict, List, Optional
6
+
7
+ import mcasm
8
+
9
+ try:
10
+ import rich
11
+ import rich.tree
12
+ except ImportError:
13
+ print("Please install 'mcasm[cli]' to use this command.", file=sys.stderr)
14
+ sys.exit(1)
15
+
16
+ # types.NoneType is too new to rely on, so just declare it here.
17
+ NoneType = type(None)
18
+
19
+ FilterFunc = Callable[[str, mcasm.ParserState], bool]
20
+
21
+
22
+ class PrettyPrintingStreamer(mcasm.Streamer):
23
+ def __init__(self, filter_func: FilterFunc, verbose: bool):
24
+ self._current_tree: Optional[rich.tree.Tree] = None
25
+ self._argnames = self._extract_arg_names()
26
+ self._filter_func = filter_func
27
+ self._verbose = verbose
28
+ super().__init__()
29
+
30
+ def _parse_help_decl(self, method_help: str) -> Optional[List[str]]:
31
+ """
32
+ Parses a pybind11-generated docstr to extract parameter names.
33
+
34
+ This is required because pybind11 methods are not compatible with
35
+ inspect.Signature.
36
+ """
37
+
38
+ decl_re = re.compile(r"[a-zA-Z0-9_]+\((.*)\) -> .+")
39
+ arg_re = re.compile(r"([a-zA-Z0-9_]+): [a-zA-Z0-9_.]+")
40
+
41
+ for line in method_help.splitlines():
42
+ match = decl_re.match(line)
43
+ if match:
44
+ return [
45
+ arg_match.group(1)
46
+ for arg_match in arg_re.finditer(match.group(1))
47
+ ]
48
+
49
+ return None
50
+
51
+ def _extract_arg_names(self) -> Dict[str, List[str]]:
52
+ """
53
+ Generates a map from method name to their argument names.
54
+ """
55
+
56
+ result: Dict[str, List[str]] = {}
57
+
58
+ for name in dir(self):
59
+ if name[0] == "_" or name == "unhandled_event":
60
+ continue
61
+
62
+ attr = getattr(self, name)
63
+ if not inspect.ismethod(attr) or not attr.__doc__:
64
+ continue
65
+
66
+ arg_names = self._parse_help_decl(attr.__doc__)
67
+ if arg_names is not None:
68
+ if arg_names and arg_names[0] == "self":
69
+ del arg_names[0]
70
+ result[name] = arg_names
71
+
72
+ return result
73
+
74
+ def _is_enum_like(self, value: Any) -> bool:
75
+ """
76
+ Determines if a value looks like a pybind11 enum instance.
77
+ """
78
+
79
+ return (
80
+ hasattr(value, "__members__")
81
+ and hasattr(value, "value")
82
+ and hasattr(value, "name")
83
+ )
84
+
85
+ def _is_default_value(self, value: Any) -> bool:
86
+ """
87
+ Is this value the default value for the type?
88
+ """
89
+ if value is None:
90
+ return True
91
+
92
+ if isinstance(value, (int, bool, str, float, bytes, list, dict, set)):
93
+ return value == type(value)()
94
+
95
+ return False
96
+
97
+ def _add_value_node(
98
+ self,
99
+ parent: rich.tree.Tree,
100
+ name: str,
101
+ value: Any,
102
+ force_print: bool = False,
103
+ ) -> None:
104
+ """
105
+ Adds a named value to the tree, potentially creating additional child
106
+ nodes.
107
+ """
108
+
109
+ if (
110
+ not self._verbose
111
+ and not force_print
112
+ and self._is_default_value(value)
113
+ ):
114
+ return
115
+
116
+ if isinstance(value, (int, bool, str, float, bytes, NoneType)):
117
+ parent.add(f"{name} = {value!r}")
118
+ return
119
+
120
+ if self._is_enum_like(value):
121
+ parent.add(f"{name} = {type(value).__qualname__}.{value.name}")
122
+ return
123
+
124
+ if isinstance(value, list):
125
+ if not value:
126
+ parent.add(f"{name} = []")
127
+ return
128
+
129
+ node = parent.add(f"{name} [i](list)[/i]")
130
+ for i, value in enumerate(value):
131
+ self._add_value_node(node, f"[{i}]", value, force_print=True)
132
+ return
133
+
134
+ if isinstance(value, dict):
135
+ if not value:
136
+ parent.add(name + " = {}")
137
+ return
138
+
139
+ node = parent.add(f"{name} [i](dict)[/i]")
140
+ for key, value in value.items():
141
+ self._add_value_node(node, key, value, force_print=True)
142
+ return
143
+
144
+ # Assume all other types should be printed as a class with attributes
145
+ node = parent.add(f"{name} [i]({type(value).__name__})[/i]")
146
+ for name in sorted(dir(type(value))):
147
+ if name[0] == "_":
148
+ continue
149
+
150
+ type_attr = getattr(type(value), name)
151
+ if not isinstance(type_attr, property):
152
+ continue
153
+
154
+ self._add_value_node(node, name, getattr(value, name))
155
+
156
+ def unhandled_event(self, name: str, base_impl, *args, **kwargs):
157
+ if not self._filter_func(name, args[0]):
158
+ return super().unhandled_event(name, base_impl, *args, **kwargs)
159
+
160
+ def make_node(value: str) -> rich.tree.Tree:
161
+ if self._current_tree:
162
+ return self._current_tree.add(value)
163
+ else:
164
+ self._current_tree = rich.tree.Tree(value)
165
+ return self._current_tree
166
+
167
+ root = make_node(f"⚡️ [b]{name}[/b]")
168
+ arg_names = self._argnames.get(name, [])
169
+ for i, (arg_name, arg) in enumerate(zip(arg_names, args)):
170
+ self._add_value_node(
171
+ root, arg_name or f"[{i}]", arg, force_print=True
172
+ )
173
+
174
+ # Call into super here because this may trigger additional events to
175
+ # fire and we want those to be nested in our tree.
176
+ result = super().unhandled_event(name, base_impl, *args, **kwargs)
177
+
178
+ if root == self._current_tree:
179
+ rich.print(root)
180
+ self._current_tree = None
181
+ print()
182
+
183
+ return result
184
+
185
+
186
+ class NameOnlyStreamer(mcasm.Streamer):
187
+ def __init__(self, filter_func: FilterFunc):
188
+ self._filter_func = filter_func
189
+ super().__init__()
190
+
191
+ def unhandled_event(self, name: str, base_impl, *args, **kwargs) -> Any:
192
+ if self._filter_func(name, args[0]):
193
+ print(name)
194
+
195
+ return super().unhandled_event(name, base_impl, *args, **kwargs)
196
+
197
+
198
+ def make_line_filter(start: int, end: Optional[int]) -> FilterFunc:
199
+ """
200
+ Creates a filter function that matches line numbers.
201
+ """
202
+
203
+ def line_filter(event_name: str, state: mcasm.ParserState) -> bool:
204
+ if end is not None:
205
+ return start >= state.loc.lineno >= end
206
+ else:
207
+ return start == state.loc.lineno
208
+
209
+ return line_filter
210
+
211
+
212
+ def make_event_filter(name: str) -> FilterFunc:
213
+ """
214
+ Creates a filter function that matches event names.
215
+ """
216
+
217
+ def event_filter(event_name: str, state: mcasm.ParserState) -> bool:
218
+ return event_name == name
219
+
220
+ return event_filter
221
+
222
+
223
+ def filter_type(value: str) -> FilterFunc:
224
+ """
225
+ An argparse type that accepts filters (event names or line numbers).
226
+ """
227
+ lineno_re = re.compile("([0-9]+)(?:-([0-9]+))")
228
+ name_re = re.compile("([a-z][a-zA-Z0-9_]+)")
229
+
230
+ match = lineno_re.match(value)
231
+ if match:
232
+ start = int(match.group(1))
233
+ end = int(match.group(2)) if match.group(2) is not None else None
234
+ return make_line_filter(start, end)
235
+
236
+ match = name_re.match(value)
237
+ if match:
238
+ return make_event_filter(match.group(1))
239
+
240
+ raise argparse.ArgumentTypeError("unknown filter type")
241
+
242
+
243
+ def main() -> None:
244
+ ap = argparse.ArgumentParser(
245
+ description="parse an assembly file and print out the mc-asm callbacks"
246
+ )
247
+ ap.add_argument(
248
+ "asm", type=argparse.FileType("r"), help="the assembly file to parse"
249
+ )
250
+ ap.add_argument(
251
+ "--target",
252
+ default=mcasm.Assembler.default_triple(),
253
+ help="the target triple to use (default: %(default)s)",
254
+ )
255
+ ap.add_argument(
256
+ "--syntax",
257
+ choices=["intel", "att"],
258
+ default="intel",
259
+ help="the syntax to use (default: %(default)s)",
260
+ )
261
+ ap.add_argument(
262
+ "--names-only",
263
+ action="store_true",
264
+ help="only print the callback names instead of the detailed output",
265
+ )
266
+ ap.add_argument(
267
+ "--filter",
268
+ action="append",
269
+ type=filter_type,
270
+ help="only print out callbacks with a given name or line number",
271
+ )
272
+ ap.add_argument(
273
+ "--verbose", action="store_true", help="print out all object values"
274
+ )
275
+ args = ap.parse_args()
276
+
277
+ if args.syntax == "intel":
278
+ syntax = mcasm.X86Syntax.INTEL
279
+ elif args.syntax == "att":
280
+ syntax = mcasm.X86Syntax.ATT
281
+ else:
282
+ assert False, "invalid syntax name"
283
+
284
+ with args.asm:
285
+ asm = args.asm.read()
286
+
287
+ assembler = mcasm.Assembler(args.target)
288
+ assembler.x86_syntax = syntax
289
+
290
+ def filter_func(event_name: str, state: mcasm.ParserState) -> bool:
291
+ if not args.filter:
292
+ return True
293
+
294
+ return any(f(event_name, state) for f in args.filter)
295
+
296
+ if args.names_only:
297
+ streamer = NameOnlyStreamer(filter_func)
298
+ else:
299
+ streamer = PrettyPrintingStreamer(filter_func, args.verbose)
300
+
301
+ if not assembler.assemble(streamer, asm):
302
+ sys.exit(1)
303
+
304
+
305
+ if __name__ == "__main__":
306
+ main()