pyflashkit 1.0.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 (48) hide show
  1. flashkit/__init__.py +54 -0
  2. flashkit/abc/__init__.py +79 -0
  3. flashkit/abc/builder.py +847 -0
  4. flashkit/abc/constants.py +198 -0
  5. flashkit/abc/disasm.py +364 -0
  6. flashkit/abc/parser.py +434 -0
  7. flashkit/abc/types.py +275 -0
  8. flashkit/abc/writer.py +230 -0
  9. flashkit/analysis/__init__.py +28 -0
  10. flashkit/analysis/call_graph.py +317 -0
  11. flashkit/analysis/inheritance.py +267 -0
  12. flashkit/analysis/references.py +371 -0
  13. flashkit/analysis/strings.py +299 -0
  14. flashkit/cli/__init__.py +75 -0
  15. flashkit/cli/_util.py +52 -0
  16. flashkit/cli/build.py +36 -0
  17. flashkit/cli/callees.py +30 -0
  18. flashkit/cli/callers.py +30 -0
  19. flashkit/cli/class_cmd.py +83 -0
  20. flashkit/cli/classes.py +71 -0
  21. flashkit/cli/disasm.py +77 -0
  22. flashkit/cli/extract.py +36 -0
  23. flashkit/cli/info.py +41 -0
  24. flashkit/cli/packages.py +30 -0
  25. flashkit/cli/refs.py +31 -0
  26. flashkit/cli/strings.py +58 -0
  27. flashkit/cli/tags.py +32 -0
  28. flashkit/cli/tree.py +52 -0
  29. flashkit/errors.py +33 -0
  30. flashkit/info/__init__.py +31 -0
  31. flashkit/info/class_info.py +176 -0
  32. flashkit/info/member_info.py +275 -0
  33. flashkit/info/package_info.py +60 -0
  34. flashkit/search/__init__.py +16 -0
  35. flashkit/search/search.py +456 -0
  36. flashkit/swf/__init__.py +66 -0
  37. flashkit/swf/builder.py +283 -0
  38. flashkit/swf/parser.py +164 -0
  39. flashkit/swf/tags.py +120 -0
  40. flashkit/workspace/__init__.py +20 -0
  41. flashkit/workspace/resource.py +189 -0
  42. flashkit/workspace/workspace.py +232 -0
  43. pyflashkit-1.0.0.dist-info/METADATA +281 -0
  44. pyflashkit-1.0.0.dist-info/RECORD +48 -0
  45. pyflashkit-1.0.0.dist-info/WHEEL +5 -0
  46. pyflashkit-1.0.0.dist-info/entry_points.txt +2 -0
  47. pyflashkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  48. pyflashkit-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,847 @@
1
+ """
2
+ Programmatic ABC (ActionScript Byte Code) builder.
3
+
4
+ Construct an ``AbcFile`` step-by-step: add strings, namespaces,
5
+ multinames, methods, classes, and method bodies through a high-level API.
6
+ The resulting ``AbcFile`` can be serialized with ``serialize_abc()``.
7
+
8
+ Usage::
9
+
10
+ from flashkit.abc.builder import AbcBuilder
11
+ from flashkit.abc.writer import serialize_abc
12
+
13
+ b = AbcBuilder()
14
+
15
+ # Build constant pools
16
+ cls_str = b.string("MyClass")
17
+ pkg_str = b.string("com.example")
18
+ ns = b.package_namespace(pkg_str)
19
+ cls_mn = b.qname(ns, cls_str)
20
+
21
+ # Build a method
22
+ ctor = b.method()
23
+ b.method_body(ctor, code=b.asm(
24
+ b.op_getlocal_0(),
25
+ b.op_pushscope(),
26
+ b.op_returnvoid(),
27
+ ))
28
+
29
+ # Build a class
30
+ b.define_class(name=cls_mn, super_name=0, constructor=ctor)
31
+
32
+ abc = b.build()
33
+ raw = serialize_abc(abc)
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from .types import (
39
+ AbcFile, NamespaceInfo, NsSetInfo, MultinameInfo,
40
+ MethodInfo, MetadataInfo, TraitInfo, InstanceInfo,
41
+ ClassInfo as AbcClassInfo, ScriptInfo, ExceptionInfo, MethodBodyInfo,
42
+ )
43
+ from .parser import write_u30
44
+ from .constants import (
45
+ CONSTANT_QName, CONSTANT_QNameA,
46
+ CONSTANT_RTQName, CONSTANT_RTQNameA,
47
+ CONSTANT_Multiname, CONSTANT_MultinameA,
48
+ CONSTANT_MultinameL, CONSTANT_MultinameLA,
49
+ CONSTANT_TypeName,
50
+ CONSTANT_Namespace, CONSTANT_PackageNamespace, CONSTANT_PackageInternalNs,
51
+ CONSTANT_ProtectedNamespace, CONSTANT_ExplicitNamespace,
52
+ CONSTANT_StaticProtectedNs, CONSTANT_PrivateNs,
53
+ TRAIT_Slot, TRAIT_Method, TRAIT_Getter, TRAIT_Setter,
54
+ TRAIT_Class, TRAIT_Function, TRAIT_Const,
55
+ ATTR_Final, ATTR_Override, ATTR_Metadata,
56
+ METHOD_HasOptional, METHOD_HasParamNames,
57
+ METHOD_NeedArguments, METHOD_NeedActivation, METHOD_NeedRest,
58
+ INSTANCE_Sealed, INSTANCE_Final, INSTANCE_Interface, INSTANCE_ProtectedNs,
59
+ OP_getlocal_0, OP_pushscope, OP_returnvoid, OP_returnvalue,
60
+ OP_constructsuper, OP_pushstring, OP_callpropvoid, OP_callproperty,
61
+ OP_getproperty, OP_setproperty, OP_getlex, OP_findpropstrict,
62
+ OP_constructprop, OP_newarray, OP_newclass, OP_coerce,
63
+ OP_pop, OP_dup, OP_swap, OP_pushtrue, OP_pushfalse, OP_pushnull,
64
+ OP_pushundefined, OP_pushbyte, OP_pushshort, OP_pushint, OP_pushuint,
65
+ OP_pushdouble, OP_convert_i, OP_convert_s, OP_convert_d,
66
+ OP_coerce_a, OP_coerce_s, OP_initproperty, OP_getlocal,
67
+ OP_setlocal, OP_getlocal_1, OP_getlocal_2, OP_getlocal_3,
68
+ OP_setlocal_0, OP_setlocal_1, OP_setlocal_2, OP_setlocal_3,
69
+ OP_newfunction, OP_call, OP_construct,
70
+ OP_jump, OP_iftrue, OP_iffalse,
71
+ OP_add, OP_subtract, OP_multiply, OP_divide,
72
+ OP_equals, OP_strictequals, OP_lessthan, OP_greaterequals,
73
+ OP_not, OP_nop, OP_label, OP_throw, OP_debugfile, OP_debugline,
74
+ )
75
+
76
+
77
+ def _encode_s24(value: int) -> bytes:
78
+ """Encode a signed 24-bit offset (little-endian)."""
79
+ if value < 0:
80
+ value += 1 << 24
81
+ return bytes([value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF])
82
+
83
+
84
+ class AbcBuilder:
85
+ """High-level builder for constructing an AbcFile programmatically.
86
+
87
+ Pools are managed automatically — adding the same string twice
88
+ returns the same index. Index 0 in each pool is reserved for
89
+ the implicit default.
90
+ """
91
+
92
+ def __init__(self) -> None:
93
+ self._int_pool: list[int] = [0]
94
+ self._uint_pool: list[int] = [0]
95
+ self._double_pool: list[float] = [0.0]
96
+ self._string_pool: list[str] = [""]
97
+ self._namespace_pool: list[NamespaceInfo] = [NamespaceInfo(0, 0)]
98
+ self._ns_set_pool: list[NsSetInfo] = [NsSetInfo([])]
99
+ self._multiname_pool: list[MultinameInfo] = [MultinameInfo(0)]
100
+
101
+ self._methods: list[MethodInfo] = []
102
+ self._metadata: list[MetadataInfo] = []
103
+ self._instances: list[InstanceInfo] = []
104
+ self._classes: list[AbcClassInfo] = []
105
+ self._scripts: list[ScriptInfo] = []
106
+ self._method_bodies: list[MethodBodyInfo] = []
107
+
108
+ # Dedup caches
109
+ self._string_cache: dict[str, int] = {"": 0}
110
+
111
+ # ── Constant pool: strings ─────────────────────────────────────────
112
+
113
+ def string(self, value: str) -> int:
114
+ """Add a string to the pool (or return existing index).
115
+
116
+ Args:
117
+ value: The string value.
118
+
119
+ Returns:
120
+ Index into the string pool.
121
+ """
122
+ if value in self._string_cache:
123
+ return self._string_cache[value]
124
+ idx = len(self._string_pool)
125
+ self._string_pool.append(value)
126
+ self._string_cache[value] = idx
127
+ return idx
128
+
129
+ # ── Constant pool: integers / doubles ──────────────────────────────
130
+
131
+ def integer(self, value: int) -> int:
132
+ """Add a signed integer to the int pool.
133
+
134
+ Returns:
135
+ Index into the int pool.
136
+ """
137
+ # Check for existing (skip default at 0)
138
+ for i in range(1, len(self._int_pool)):
139
+ if self._int_pool[i] == value:
140
+ return i
141
+ idx = len(self._int_pool)
142
+ self._int_pool.append(value)
143
+ return idx
144
+
145
+ def uint(self, value: int) -> int:
146
+ """Add an unsigned integer to the uint pool.
147
+
148
+ Returns:
149
+ Index into the uint pool.
150
+ """
151
+ for i in range(1, len(self._uint_pool)):
152
+ if self._uint_pool[i] == value:
153
+ return i
154
+ idx = len(self._uint_pool)
155
+ self._uint_pool.append(value)
156
+ return idx
157
+
158
+ def double(self, value: float) -> int:
159
+ """Add a double to the double pool.
160
+
161
+ Returns:
162
+ Index into the double pool.
163
+ """
164
+ for i in range(1, len(self._double_pool)):
165
+ if self._double_pool[i] == value:
166
+ return i
167
+ idx = len(self._double_pool)
168
+ self._double_pool.append(value)
169
+ return idx
170
+
171
+ # ── Constant pool: namespaces ──────────────────────────────────────
172
+
173
+ def namespace(self, kind: int, name: int) -> int:
174
+ """Add a namespace to the pool.
175
+
176
+ Args:
177
+ kind: Namespace kind constant (CONSTANT_Namespace, etc.).
178
+ name: String pool index for the namespace name.
179
+
180
+ Returns:
181
+ Index into the namespace pool.
182
+ """
183
+ for i in range(1, len(self._namespace_pool)):
184
+ ns = self._namespace_pool[i]
185
+ if ns.kind == kind and ns.name == name:
186
+ return i
187
+ idx = len(self._namespace_pool)
188
+ self._namespace_pool.append(NamespaceInfo(kind, name))
189
+ return idx
190
+
191
+ def package_namespace(self, name: int | str) -> int:
192
+ """Add a public package namespace.
193
+
194
+ Args:
195
+ name: String pool index, or a string (auto-added to pool).
196
+
197
+ Returns:
198
+ Namespace pool index.
199
+ """
200
+ if isinstance(name, str):
201
+ name = self.string(name)
202
+ return self.namespace(CONSTANT_PackageNamespace, name)
203
+
204
+ def private_namespace(self, name: int | str = 0) -> int:
205
+ """Add a private namespace.
206
+
207
+ Args:
208
+ name: String pool index, or a string. Default 0 (empty).
209
+
210
+ Returns:
211
+ Namespace pool index.
212
+ """
213
+ if isinstance(name, str):
214
+ name = self.string(name)
215
+ return self.namespace(CONSTANT_PrivateNs, name)
216
+
217
+ def internal_namespace(self, name: int | str) -> int:
218
+ """Add a package-internal namespace.
219
+
220
+ Args:
221
+ name: String pool index, or a string.
222
+
223
+ Returns:
224
+ Namespace pool index.
225
+ """
226
+ if isinstance(name, str):
227
+ name = self.string(name)
228
+ return self.namespace(CONSTANT_PackageInternalNs, name)
229
+
230
+ def protected_namespace(self, name: int | str) -> int:
231
+ """Add a protected namespace.
232
+
233
+ Args:
234
+ name: String pool index, or a string.
235
+
236
+ Returns:
237
+ Namespace pool index.
238
+ """
239
+ if isinstance(name, str):
240
+ name = self.string(name)
241
+ return self.namespace(CONSTANT_ProtectedNamespace, name)
242
+
243
+ # ── Constant pool: namespace sets ──────────────────────────────────
244
+
245
+ def ns_set(self, namespaces: list[int]) -> int:
246
+ """Add a namespace set to the pool.
247
+
248
+ Args:
249
+ namespaces: List of namespace pool indices.
250
+
251
+ Returns:
252
+ Index into the namespace set pool.
253
+ """
254
+ idx = len(self._ns_set_pool)
255
+ self._ns_set_pool.append(NsSetInfo(list(namespaces)))
256
+ return idx
257
+
258
+ # ── Constant pool: multinames ──────────────────────────────────────
259
+
260
+ def qname(self, ns: int, name: int | str) -> int:
261
+ """Add a QName (qualified name) multiname.
262
+
263
+ Args:
264
+ ns: Namespace pool index.
265
+ name: String pool index, or a string.
266
+
267
+ Returns:
268
+ Multiname pool index.
269
+ """
270
+ if isinstance(name, str):
271
+ name = self.string(name)
272
+ for i in range(1, len(self._multiname_pool)):
273
+ mn = self._multiname_pool[i]
274
+ if mn.kind == CONSTANT_QName and mn.ns == ns and mn.name == name:
275
+ return i
276
+ idx = len(self._multiname_pool)
277
+ self._multiname_pool.append(MultinameInfo(
278
+ kind=CONSTANT_QName, ns=ns, name=name))
279
+ return idx
280
+
281
+ def multiname(self, name: int | str, ns_set: int) -> int:
282
+ """Add a Multiname (name + namespace set).
283
+
284
+ Args:
285
+ name: String pool index, or a string.
286
+ ns_set: Namespace set pool index.
287
+
288
+ Returns:
289
+ Multiname pool index.
290
+ """
291
+ if isinstance(name, str):
292
+ name = self.string(name)
293
+ idx = len(self._multiname_pool)
294
+ self._multiname_pool.append(MultinameInfo(
295
+ kind=CONSTANT_Multiname, name=name, ns_set=ns_set))
296
+ return idx
297
+
298
+ def rtqname(self, name: int | str) -> int:
299
+ """Add a runtime-qualified name (namespace from stack).
300
+
301
+ Args:
302
+ name: String pool index, or a string.
303
+
304
+ Returns:
305
+ Multiname pool index.
306
+ """
307
+ if isinstance(name, str):
308
+ name = self.string(name)
309
+ idx = len(self._multiname_pool)
310
+ self._multiname_pool.append(MultinameInfo(
311
+ kind=CONSTANT_RTQName, name=name))
312
+ return idx
313
+
314
+ # ── Methods ────────────────────────────────────────────────────────
315
+
316
+ def method(
317
+ self,
318
+ params: list[int] | None = None,
319
+ return_type: int = 0,
320
+ name: int | str = 0,
321
+ flags: int = 0,
322
+ param_names: list[int | str] | None = None,
323
+ options: list[tuple[int, int]] | None = None,
324
+ ) -> int:
325
+ """Add a method signature.
326
+
327
+ Args:
328
+ params: List of multiname indices for parameter types.
329
+ return_type: Multiname index for return type (0 = any).
330
+ name: String pool index or string for method name.
331
+ flags: Method flags bitmask.
332
+ param_names: String pool indices or strings for debug param names.
333
+ options: Default values as (value_index, value_kind) pairs.
334
+
335
+ Returns:
336
+ Method index.
337
+ """
338
+ params = params or []
339
+ if isinstance(name, str):
340
+ name = self.string(name) if name else 0
341
+
342
+ resolved_flags = flags
343
+ resolved_param_names: list[int] = []
344
+ if param_names:
345
+ resolved_flags |= METHOD_HasParamNames
346
+ for pn in param_names:
347
+ if isinstance(pn, str):
348
+ resolved_param_names.append(self.string(pn))
349
+ else:
350
+ resolved_param_names.append(pn)
351
+
352
+ resolved_options: list[tuple[int, int]] = []
353
+ if options:
354
+ resolved_flags |= METHOD_HasOptional
355
+ resolved_options = list(options)
356
+
357
+ mi = MethodInfo(
358
+ param_count=len(params),
359
+ return_type=return_type,
360
+ param_types=list(params),
361
+ name=name,
362
+ flags=resolved_flags,
363
+ options=resolved_options,
364
+ param_names=resolved_param_names,
365
+ )
366
+ idx = len(self._methods)
367
+ self._methods.append(mi)
368
+ return idx
369
+
370
+ # ── Method bodies ──────────────────────────────────────────────────
371
+
372
+ def method_body(
373
+ self,
374
+ method: int,
375
+ code: bytes,
376
+ max_stack: int = 2,
377
+ local_count: int = 1,
378
+ init_scope_depth: int = 0,
379
+ max_scope_depth: int = 1,
380
+ exceptions: list[ExceptionInfo] | None = None,
381
+ ) -> int:
382
+ """Add a method body (bytecode) for a method.
383
+
384
+ Args:
385
+ method: Method index this body belongs to.
386
+ code: Raw AVM2 bytecode bytes (use asm() or op_*() to build).
387
+ max_stack: Maximum operand stack depth.
388
+ local_count: Number of local registers.
389
+ init_scope_depth: Initial scope depth.
390
+ max_scope_depth: Maximum scope depth.
391
+ exceptions: Exception handler table.
392
+
393
+ Returns:
394
+ Index into the method bodies array.
395
+ """
396
+ mb = MethodBodyInfo(
397
+ method=method,
398
+ max_stack=max_stack,
399
+ local_count=local_count,
400
+ init_scope_depth=init_scope_depth,
401
+ max_scope_depth=max_scope_depth,
402
+ code=code,
403
+ exceptions=exceptions or [],
404
+ )
405
+ idx = len(self._method_bodies)
406
+ self._method_bodies.append(mb)
407
+ return idx
408
+
409
+ # ── Traits ─────────────────────────────────────────────────────────
410
+
411
+ @staticmethod
412
+ def trait_slot(
413
+ name: int,
414
+ type_mn: int = 0,
415
+ slot_id: int = 0,
416
+ default_value: int = 0,
417
+ default_kind: int = 0,
418
+ is_const: bool = False,
419
+ ) -> TraitInfo:
420
+ """Build a slot/const trait (field).
421
+
422
+ Args:
423
+ name: Multiname index for the field name.
424
+ type_mn: Multiname index for the field type (0 = any).
425
+ slot_id: Slot index.
426
+ default_value: Default value pool index (0 = none).
427
+ default_kind: Default value kind (only if default_value != 0).
428
+ is_const: If True, TRAIT_Const; else TRAIT_Slot.
429
+
430
+ Returns:
431
+ TraitInfo ready to attach to an instance or class.
432
+ """
433
+ kind = TRAIT_Const if is_const else TRAIT_Slot
434
+ data = bytearray()
435
+ data += write_u30(name)
436
+ data += bytes([kind])
437
+ data += write_u30(slot_id)
438
+ data += write_u30(type_mn)
439
+ data += write_u30(default_value)
440
+ if default_value:
441
+ data += bytes([default_kind])
442
+ return TraitInfo(name=name, kind=kind, data=bytes(data))
443
+
444
+ @staticmethod
445
+ def trait_method(
446
+ name: int,
447
+ method: int,
448
+ disp_id: int = 0,
449
+ kind: int = TRAIT_Method,
450
+ attrs: int = 0,
451
+ ) -> TraitInfo:
452
+ """Build a method/getter/setter trait.
453
+
454
+ Args:
455
+ name: Multiname index for the method name.
456
+ method: Method index.
457
+ disp_id: Dispatch ID (usually 0).
458
+ kind: TRAIT_Method, TRAIT_Getter, or TRAIT_Setter.
459
+ attrs: Attribute flags (ATTR_Final, ATTR_Override).
460
+
461
+ Returns:
462
+ TraitInfo ready to attach.
463
+ """
464
+ kind_byte = kind | (attrs << 4)
465
+ data = bytearray()
466
+ data += write_u30(name)
467
+ data += bytes([kind_byte])
468
+ data += write_u30(disp_id)
469
+ data += write_u30(method)
470
+ return TraitInfo(name=name, kind=kind, data=bytes(data))
471
+
472
+ @staticmethod
473
+ def trait_class(name: int, class_index: int, slot_id: int = 0) -> TraitInfo:
474
+ """Build a class trait (for script-level class definitions).
475
+
476
+ Args:
477
+ name: Multiname index for the class name.
478
+ class_index: Index into the class array.
479
+ slot_id: Slot index.
480
+
481
+ Returns:
482
+ TraitInfo ready to attach to a script.
483
+ """
484
+ data = bytearray()
485
+ data += write_u30(name)
486
+ data += bytes([TRAIT_Class])
487
+ data += write_u30(slot_id)
488
+ data += write_u30(class_index)
489
+ return TraitInfo(name=name, kind=TRAIT_Class, data=bytes(data))
490
+
491
+ # ── Classes ────────────────────────────────────────────────────────
492
+
493
+ def define_class(
494
+ self,
495
+ name: int,
496
+ super_name: int = 0,
497
+ constructor: int | None = None,
498
+ static_init: int | None = None,
499
+ flags: int = INSTANCE_Sealed,
500
+ interfaces: list[int] | None = None,
501
+ protected_ns: int = 0,
502
+ instance_traits: list[TraitInfo] | None = None,
503
+ static_traits: list[TraitInfo] | None = None,
504
+ ) -> int:
505
+ """Define a class (instance + static side).
506
+
507
+ If constructor or static_init are not provided, empty methods
508
+ are created automatically.
509
+
510
+ Args:
511
+ name: Multiname index for the class name.
512
+ super_name: Multiname index for the superclass (0 = Object).
513
+ constructor: Method index for the constructor. Auto-created if None.
514
+ static_init: Method index for the static initializer. Auto-created if None.
515
+ flags: Instance flags bitmask.
516
+ interfaces: List of multiname indices for interfaces.
517
+ protected_ns: Protected namespace index (set flag automatically).
518
+ instance_traits: Instance-side traits (fields, methods).
519
+ static_traits: Static-side traits.
520
+
521
+ Returns:
522
+ Class index (same index in both instances and classes arrays).
523
+ """
524
+ # Auto-create constructor if not provided
525
+ if constructor is None:
526
+ constructor = self.method()
527
+ self.method_body(constructor, code=bytes([
528
+ OP_getlocal_0, OP_pushscope,
529
+ OP_getlocal_0, OP_constructsuper, 0x00, # 0 args
530
+ OP_returnvoid,
531
+ ]), max_stack=1, local_count=1,
532
+ init_scope_depth=0, max_scope_depth=1)
533
+
534
+ # Auto-create static init if not provided
535
+ if static_init is None:
536
+ static_init = self.method()
537
+ self.method_body(static_init, code=bytes([OP_returnvoid]),
538
+ max_stack=0, local_count=1,
539
+ init_scope_depth=0, max_scope_depth=1)
540
+
541
+ inst_flags = flags
542
+ if protected_ns:
543
+ inst_flags |= INSTANCE_ProtectedNs
544
+
545
+ inst = InstanceInfo(
546
+ name=name,
547
+ super_name=super_name,
548
+ flags=inst_flags,
549
+ protectedNs=protected_ns,
550
+ interfaces=interfaces or [],
551
+ iinit=constructor,
552
+ traits=instance_traits or [],
553
+ )
554
+
555
+ cls = AbcClassInfo(
556
+ cinit=static_init,
557
+ traits=static_traits or [],
558
+ )
559
+
560
+ idx = len(self._instances)
561
+ self._instances.append(inst)
562
+ self._classes.append(cls)
563
+ return idx
564
+
565
+ # ── Scripts ────────────────────────────────────────────────────────
566
+
567
+ def script(
568
+ self,
569
+ init: int | None = None,
570
+ traits: list[TraitInfo] | None = None,
571
+ ) -> int:
572
+ """Add a script entry point.
573
+
574
+ Args:
575
+ init: Method index for the script initializer. Auto-created if None.
576
+ traits: Script-level traits (class definitions, etc.).
577
+
578
+ Returns:
579
+ Script index.
580
+ """
581
+ if init is None:
582
+ init = self.method()
583
+ self.method_body(init, code=bytes([OP_returnvoid]),
584
+ max_stack=0, local_count=1)
585
+
586
+ si = ScriptInfo(init=init, traits=traits or [])
587
+ idx = len(self._scripts)
588
+ self._scripts.append(si)
589
+ return idx
590
+
591
+ # ── Bytecode assembly helpers ──────────────────────────────────────
592
+
593
+ @staticmethod
594
+ def asm(*parts: bytes) -> bytes:
595
+ """Concatenate bytecode fragments into a single code block.
596
+
597
+ Args:
598
+ *parts: Bytecode fragments from op_*() methods.
599
+
600
+ Returns:
601
+ Combined bytecode bytes.
602
+ """
603
+ return b"".join(parts)
604
+
605
+ # Simple opcodes (no operands)
606
+ @staticmethod
607
+ def op_nop() -> bytes: return bytes([OP_nop])
608
+ @staticmethod
609
+ def op_label() -> bytes: return bytes([OP_label])
610
+ @staticmethod
611
+ def op_throw() -> bytes: return bytes([OP_throw])
612
+ @staticmethod
613
+ def op_getlocal_0() -> bytes: return bytes([OP_getlocal_0])
614
+ @staticmethod
615
+ def op_getlocal_1() -> bytes: return bytes([OP_getlocal_1])
616
+ @staticmethod
617
+ def op_getlocal_2() -> bytes: return bytes([OP_getlocal_2])
618
+ @staticmethod
619
+ def op_getlocal_3() -> bytes: return bytes([OP_getlocal_3])
620
+ @staticmethod
621
+ def op_setlocal_0() -> bytes: return bytes([OP_setlocal_0])
622
+ @staticmethod
623
+ def op_setlocal_1() -> bytes: return bytes([OP_setlocal_1])
624
+ @staticmethod
625
+ def op_setlocal_2() -> bytes: return bytes([OP_setlocal_2])
626
+ @staticmethod
627
+ def op_setlocal_3() -> bytes: return bytes([OP_setlocal_3])
628
+ @staticmethod
629
+ def op_pushscope() -> bytes: return bytes([OP_pushscope])
630
+ @staticmethod
631
+ def op_returnvoid() -> bytes: return bytes([OP_returnvoid])
632
+ @staticmethod
633
+ def op_returnvalue() -> bytes: return bytes([OP_returnvalue])
634
+ @staticmethod
635
+ def op_pop() -> bytes: return bytes([OP_pop])
636
+ @staticmethod
637
+ def op_dup() -> bytes: return bytes([OP_dup])
638
+ @staticmethod
639
+ def op_swap() -> bytes: return bytes([OP_swap])
640
+ @staticmethod
641
+ def op_pushnull() -> bytes: return bytes([OP_pushnull])
642
+ @staticmethod
643
+ def op_pushundefined() -> bytes: return bytes([OP_pushundefined])
644
+ @staticmethod
645
+ def op_pushtrue() -> bytes: return bytes([OP_pushtrue])
646
+ @staticmethod
647
+ def op_pushfalse() -> bytes: return bytes([OP_pushfalse])
648
+ @staticmethod
649
+ def op_convert_i() -> bytes: return bytes([OP_convert_i])
650
+ @staticmethod
651
+ def op_convert_s() -> bytes: return bytes([OP_convert_s])
652
+ @staticmethod
653
+ def op_convert_d() -> bytes: return bytes([OP_convert_d])
654
+ @staticmethod
655
+ def op_coerce_a() -> bytes: return bytes([OP_coerce_a])
656
+ @staticmethod
657
+ def op_coerce_s() -> bytes: return bytes([OP_coerce_s])
658
+ @staticmethod
659
+ def op_add() -> bytes: return bytes([OP_add])
660
+ @staticmethod
661
+ def op_subtract() -> bytes: return bytes([OP_subtract])
662
+ @staticmethod
663
+ def op_multiply() -> bytes: return bytes([OP_multiply])
664
+ @staticmethod
665
+ def op_divide() -> bytes: return bytes([OP_divide])
666
+ @staticmethod
667
+ def op_equals() -> bytes: return bytes([OP_equals])
668
+ @staticmethod
669
+ def op_strictequals() -> bytes: return bytes([OP_strictequals])
670
+ @staticmethod
671
+ def op_lessthan() -> bytes: return bytes([OP_lessthan])
672
+ @staticmethod
673
+ def op_greaterequals() -> bytes: return bytes([OP_greaterequals])
674
+ @staticmethod
675
+ def op_not() -> bytes: return bytes([OP_not])
676
+
677
+ # Opcodes with u30 operand
678
+ @staticmethod
679
+ def op_getlocal(reg: int) -> bytes:
680
+ return bytes([OP_getlocal]) + write_u30(reg)
681
+ @staticmethod
682
+ def op_setlocal(reg: int) -> bytes:
683
+ return bytes([OP_setlocal]) + write_u30(reg)
684
+ @staticmethod
685
+ def op_pushbyte(val: int) -> bytes:
686
+ return bytes([OP_pushbyte, val & 0xFF])
687
+ @staticmethod
688
+ def op_pushshort(val: int) -> bytes:
689
+ return bytes([OP_pushshort]) + write_u30(val)
690
+ @staticmethod
691
+ def op_pushstring(index: int) -> bytes:
692
+ return bytes([OP_pushstring]) + write_u30(index)
693
+ @staticmethod
694
+ def op_pushint(index: int) -> bytes:
695
+ return bytes([OP_pushint]) + write_u30(index)
696
+ @staticmethod
697
+ def op_pushuint(index: int) -> bytes:
698
+ return bytes([OP_pushuint]) + write_u30(index)
699
+ @staticmethod
700
+ def op_pushdouble(index: int) -> bytes:
701
+ return bytes([OP_pushdouble]) + write_u30(index)
702
+ @staticmethod
703
+ def op_getproperty(index: int) -> bytes:
704
+ return bytes([OP_getproperty]) + write_u30(index)
705
+ @staticmethod
706
+ def op_setproperty(index: int) -> bytes:
707
+ return bytes([OP_setproperty]) + write_u30(index)
708
+ @staticmethod
709
+ def op_initproperty(index: int) -> bytes:
710
+ return bytes([OP_initproperty]) + write_u30(index)
711
+ @staticmethod
712
+ def op_getlex(index: int) -> bytes:
713
+ return bytes([OP_getlex]) + write_u30(index)
714
+ @staticmethod
715
+ def op_findpropstrict(index: int) -> bytes:
716
+ return bytes([OP_findpropstrict]) + write_u30(index)
717
+ @staticmethod
718
+ def op_coerce(index: int) -> bytes:
719
+ return bytes([OP_coerce]) + write_u30(index)
720
+ @staticmethod
721
+ def op_constructsuper(arg_count: int) -> bytes:
722
+ return bytes([OP_constructsuper]) + write_u30(arg_count)
723
+ @staticmethod
724
+ def op_newarray(arg_count: int) -> bytes:
725
+ return bytes([OP_newarray]) + write_u30(arg_count)
726
+ @staticmethod
727
+ def op_newclass(class_index: int) -> bytes:
728
+ return bytes([OP_newclass]) + write_u30(class_index)
729
+ @staticmethod
730
+ def op_newfunction(method_index: int) -> bytes:
731
+ return bytes([OP_newfunction]) + write_u30(method_index)
732
+ @staticmethod
733
+ def op_call(arg_count: int) -> bytes:
734
+ return bytes([OP_call]) + write_u30(arg_count)
735
+ @staticmethod
736
+ def op_construct(arg_count: int) -> bytes:
737
+ return bytes([OP_construct]) + write_u30(arg_count)
738
+ @staticmethod
739
+ def op_debugfile(index: int) -> bytes:
740
+ return bytes([OP_debugfile]) + write_u30(index)
741
+ @staticmethod
742
+ def op_debugline(line: int) -> bytes:
743
+ return bytes([OP_debugline]) + write_u30(line)
744
+
745
+ # Opcodes with u30 u30 operands
746
+ @staticmethod
747
+ def op_callproperty(index: int, arg_count: int) -> bytes:
748
+ return bytes([OP_callproperty]) + write_u30(index) + write_u30(arg_count)
749
+ @staticmethod
750
+ def op_callpropvoid(index: int, arg_count: int) -> bytes:
751
+ return bytes([OP_callpropvoid]) + write_u30(index) + write_u30(arg_count)
752
+ @staticmethod
753
+ def op_constructprop(index: int, arg_count: int) -> bytes:
754
+ return bytes([OP_constructprop]) + write_u30(index) + write_u30(arg_count)
755
+
756
+ # Branch opcodes (s24 operand)
757
+ @staticmethod
758
+ def op_jump(offset: int) -> bytes:
759
+ return bytes([OP_jump]) + _encode_s24(offset)
760
+ @staticmethod
761
+ def op_iftrue(offset: int) -> bytes:
762
+ return bytes([OP_iftrue]) + _encode_s24(offset)
763
+ @staticmethod
764
+ def op_iffalse(offset: int) -> bytes:
765
+ return bytes([OP_iffalse]) + _encode_s24(offset)
766
+
767
+ # ── Convenience ────────────────────────────────────────────────────
768
+
769
+ def simple_class(
770
+ self,
771
+ name: str,
772
+ package: str = "",
773
+ super_name: str | None = "Object",
774
+ fields: list[tuple[str, str]] | None = None,
775
+ is_interface: bool = False,
776
+ ) -> int:
777
+ """Define a class with minimal boilerplate.
778
+
779
+ Creates namespaces, multinames, and traits automatically from
780
+ simple string arguments. For full control, use ``define_class()``.
781
+
782
+ Args:
783
+ name: Class name string.
784
+ package: Package name string (empty for default package).
785
+ super_name: Superclass name string, or None for no super.
786
+ fields: List of (field_name, type_name) tuples for instance fields.
787
+ is_interface: Whether this is an interface definition.
788
+
789
+ Returns:
790
+ Class index.
791
+ """
792
+ ns = self.package_namespace(package)
793
+ pub = self.package_namespace("")
794
+ priv = self.private_namespace()
795
+ cls_mn = self.qname(ns, name)
796
+
797
+ super_mn = 0
798
+ if super_name:
799
+ super_mn = self.qname(pub, super_name)
800
+
801
+ instance_traits = []
802
+ if fields:
803
+ for i, (fname, ftype) in enumerate(fields):
804
+ type_mn = self.qname(pub, ftype) if ftype else 0
805
+ field_mn = self.qname(priv, fname)
806
+ instance_traits.append(
807
+ self.trait_slot(field_mn, type_mn=type_mn,
808
+ slot_id=i + 1))
809
+
810
+ flags = INSTANCE_Interface if is_interface else INSTANCE_Sealed
811
+
812
+ return self.define_class(
813
+ name=cls_mn, super_name=super_mn, flags=flags,
814
+ instance_traits=instance_traits,
815
+ )
816
+
817
+ # ── Build ──────────────────────────────────────────────────────────
818
+
819
+ def build(self) -> AbcFile:
820
+ """Build the final AbcFile from all added components.
821
+
822
+ If no scripts have been added, a default empty script is created.
823
+
824
+ Returns:
825
+ Complete AbcFile ready for serialization.
826
+ """
827
+ # Ensure at least one script exists
828
+ if not self._scripts:
829
+ self.script()
830
+
831
+ abc = AbcFile()
832
+ abc.int_pool = list(self._int_pool)
833
+ abc.uint_pool = list(self._uint_pool)
834
+ abc._int_pool_raw = [b""] * len(self._int_pool)
835
+ abc._uint_pool_raw = [b""] * len(self._uint_pool)
836
+ abc.double_pool = list(self._double_pool)
837
+ abc.string_pool = list(self._string_pool)
838
+ abc.namespace_pool = list(self._namespace_pool)
839
+ abc.ns_set_pool = list(self._ns_set_pool)
840
+ abc.multiname_pool = list(self._multiname_pool)
841
+ abc.methods = list(self._methods)
842
+ abc.metadata = list(self._metadata)
843
+ abc.instances = list(self._instances)
844
+ abc.classes = list(self._classes)
845
+ abc.scripts = list(self._scripts)
846
+ abc.method_bodies = list(self._method_bodies)
847
+ return abc