depyo 1.0.0

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/depyo.js +213 -0
  4. package/lib/BinaryReader.js +153 -0
  5. package/lib/OpCode.js +90 -0
  6. package/lib/OpCodes.js +940 -0
  7. package/lib/PycDecompiler.js +2031 -0
  8. package/lib/PycDisassembler.js +55 -0
  9. package/lib/PycReader.js +905 -0
  10. package/lib/PycResult.js +82 -0
  11. package/lib/PythonObject.js +242 -0
  12. package/lib/Unpickle.js +173 -0
  13. package/lib/ast/ast_node.js +3442 -0
  14. package/lib/bytecode/python_1_0.js +116 -0
  15. package/lib/bytecode/python_1_1.js +116 -0
  16. package/lib/bytecode/python_1_3.js +119 -0
  17. package/lib/bytecode/python_1_4.js +121 -0
  18. package/lib/bytecode/python_1_5.js +120 -0
  19. package/lib/bytecode/python_1_6.js +124 -0
  20. package/lib/bytecode/python_2_0.js +137 -0
  21. package/lib/bytecode/python_2_1.js +142 -0
  22. package/lib/bytecode/python_2_2.js +147 -0
  23. package/lib/bytecode/python_2_3.js +145 -0
  24. package/lib/bytecode/python_2_4.js +147 -0
  25. package/lib/bytecode/python_2_5.js +147 -0
  26. package/lib/bytecode/python_2_6.js +147 -0
  27. package/lib/bytecode/python_2_7.js +151 -0
  28. package/lib/bytecode/python_3_0.js +132 -0
  29. package/lib/bytecode/python_3_1.js +135 -0
  30. package/lib/bytecode/python_3_10.js +312 -0
  31. package/lib/bytecode/python_3_11.js +284 -0
  32. package/lib/bytecode/python_3_12.js +327 -0
  33. package/lib/bytecode/python_3_13.js +173 -0
  34. package/lib/bytecode/python_3_14.js +177 -0
  35. package/lib/bytecode/python_3_2.js +136 -0
  36. package/lib/bytecode/python_3_3.js +136 -0
  37. package/lib/bytecode/python_3_4.js +137 -0
  38. package/lib/bytecode/python_3_5.js +149 -0
  39. package/lib/bytecode/python_3_6.js +153 -0
  40. package/lib/bytecode/python_3_7.js +292 -0
  41. package/lib/bytecode/python_3_8.js +294 -0
  42. package/lib/bytecode/python_3_9.js +296 -0
  43. package/lib/code_reader.js +146 -0
  44. package/lib/handlers/binary_ops.js +174 -0
  45. package/lib/handlers/collections_update.js +239 -0
  46. package/lib/handlers/comparisons.js +95 -0
  47. package/lib/handlers/context_managers.js +250 -0
  48. package/lib/handlers/control_flow_jumps.js +954 -0
  49. package/lib/handlers/exceptions_blocks.js +952 -0
  50. package/lib/handlers/formatting.js +31 -0
  51. package/lib/handlers/function_calls.js +496 -0
  52. package/lib/handlers/function_class_build.js +330 -0
  53. package/lib/handlers/generators_async.js +172 -0
  54. package/lib/handlers/imports.js +53 -0
  55. package/lib/handlers/load_store_names.js +711 -0
  56. package/lib/handlers/loop_iterator.js +318 -0
  57. package/lib/handlers/misc_other.js +1201 -0
  58. package/lib/handlers/pattern_matching.js +226 -0
  59. package/lib/handlers/stack_ops.js +280 -0
  60. package/lib/handlers/subscript_slice.js +394 -0
  61. package/lib/handlers/unary_ops.js +91 -0
  62. package/lib/handlers/unpack.js +141 -0
  63. package/lib/stack_history.js +63 -0
  64. package/lib/zip_reader.js +217 -0
  65. package/package.json +35 -0
@@ -0,0 +1,711 @@
1
+ const PycDecompiler = require('../PycDecompiler');
2
+ const AST = require('../ast/ast_node');
3
+ const { beginMatchCaseFromPattern } = require('./misc_other');
4
+
5
+ function handleDeleteAttrA() {
6
+ let name = this.dataStack.pop();
7
+ let node = new AST.ASTDelete(new AST.ASTBinary(name, new AST.ASTName(this.code.Current.Name), AST.ASTBinary.BinOp.Attr));
8
+ node.line = this.code.Current.LineNo;
9
+ this.curBlock.nodes.push(node);
10
+ }
11
+
12
+ function handleDeleteGlobalA() {
13
+ this.object.Globals.add(this.code.Current.Name);
14
+ handleDeleteNameA.call(this);
15
+ }
16
+
17
+ function handleDeleteNameA() {
18
+ let varname = this.code.Current.Name || "";
19
+
20
+ if (!varname.length) {
21
+ return;
22
+ }
23
+
24
+ if (varname.length >= 2 && varname.startsWith('_[')) {
25
+ /* Don't show deletes that are a result of list comps. */
26
+ return;
27
+ }
28
+
29
+ let node = new AST.ASTDelete(new AST.ASTName(varname));
30
+ node.line = this.code.Current.LineNo;
31
+ this.curBlock.nodes.push(node);
32
+ }
33
+
34
+ function handleDeleteFastA() {
35
+ let nameVal = this.code.Current.Name || "";
36
+ if (!nameVal.length) {
37
+ return;
38
+ }
39
+ let nameNode = new AST.ASTName(nameVal);
40
+
41
+ if (nameNode.name.startsWith('_[')) {
42
+ /* Don't show deletes that are a result of list comps. */
43
+ return;
44
+ }
45
+
46
+ let node = new AST.ASTDelete(nameNode);
47
+ node.line = this.code.Current.LineNo;
48
+ this.curBlock.nodes.push(node);
49
+ }
50
+
51
+ function handleDeleteDerefA() {
52
+ const freeName = this.code.Current.FreeName || "";
53
+ if (!freeName) {
54
+ return;
55
+ }
56
+ let node = new AST.ASTDelete(new AST.ASTName(freeName));
57
+ node.line = this.code.Current.LineNo;
58
+ this.curBlock.nodes.push(node);
59
+ }
60
+
61
+ function handleLoadAttrA() {
62
+ let name = this.dataStack.top();
63
+ if (!(name instanceof AST.ASTImport)) {
64
+ this.dataStack.pop();
65
+
66
+ if (this.object.Reader.versionCompare(3, 12) >= 0) {
67
+ if (this.code.Current.Argument & 1) {
68
+ /* Changed in version 3.12:
69
+ If the low bit of namei is set, then a null or self is pushed to the stack
70
+ before the attribute or unbound method respectively. */
71
+ this.dataStack.push(null);
72
+ }
73
+ this.code.Current.Argument >>= 1;
74
+ }
75
+
76
+ let node = new AST.ASTBinary(name, new AST.ASTName(this.code.Current.Name), AST.ASTBinary.BinOp.Attr);
77
+ node.line = this.code.Current.LineNo;
78
+ this.dataStack.push(node);
79
+ }
80
+ }
81
+
82
+ function handleLoadConstA() {
83
+ let constantObject = new AST.ASTObject(this.code.Current.ConstantObject);
84
+ constantObject.line = this.code.Current.LineNo;
85
+
86
+ if (global.g_cliArgs?.debug) {
87
+ let value = constantObject.object?.ClassName == 'Py_String' ? constantObject.object.Value : constantObject.object?.ClassName;
88
+ console.log(`[LOAD_CONST] offset=${this.code.Current.Offset}, arg=${this.code.Current.Argument}, value=${value}`);
89
+ }
90
+
91
+ if (constantObject.object == null || constantObject.object.ClassName == "Py_None") {
92
+ this.dataStack.push(new AST.ASTNone());
93
+ } else if ((constantObject.object.ClassName == "Py_Tuple" ||
94
+ constantObject.object.ClassName == "Py_SmallTuple") &&
95
+ constantObject.object.Value.empty()) {
96
+ let node = new AST.ASTTuple ([]);
97
+ node.line = this.code.Current.LineNo;
98
+ this.dataStack.push(node);
99
+ } else {
100
+ this.dataStack.push(constantObject);
101
+ }
102
+ }
103
+
104
+ function handleLoadDerefA() {
105
+ processLoadDeref.call(this);
106
+ }
107
+
108
+ function handleLoadClassderefA() {
109
+ processLoadDeref.call(this);
110
+ }
111
+
112
+ function processLoadDeref() {
113
+ let varName = this.code.Current.FreeName || "";
114
+ if (varName.length >= 2 && varName.startsWith('_[')) {
115
+ return;
116
+ }
117
+ let node = new AST.ASTName (varName);
118
+ node.line = this.code.Current.LineNo;
119
+ this.dataStack.push(node);
120
+ }
121
+
122
+ function handleLoadFastA() {
123
+ let varName = this.code.Current.Name || "";
124
+ if (varName.length >= 2 && varName.startsWith('_[')) {
125
+ return;
126
+ }
127
+
128
+ let node = new AST.ASTName (varName);
129
+ node.line = this.code.Current.LineNo;
130
+ this.dataStack.push(node);
131
+ }
132
+
133
+ function handleLoadFastCheckA() {
134
+ // Python 3.12+: same semantics as LOAD_FAST for reconstruction.
135
+ handleLoadFastA.call(this);
136
+ }
137
+
138
+ function handleLoadGlobalA() {
139
+ let varName = this.code.Current.Name || "";
140
+ if (varName.length >= 2 && varName.startsWith('_[')) {
141
+ return;
142
+ }
143
+
144
+ if (this.object.Reader.versionCompare(3, 11) >= 0) {
145
+ // Loads the global named co_names[namei>>1] onto the this.dataStack.
146
+ if (this.code.Current.Argument & 1) {
147
+ /* Changed in version 3.11:
148
+ If the low bit of "NAMEI" (this.code.Current.Argument) is set,
149
+ then a null is pushed to the stack before the global variable. */
150
+ this.dataStack.push(null);
151
+ }
152
+ this.code.Current.Argument >>= 1;
153
+ }
154
+ let node = new AST.ASTName (varName);
155
+ node.line = this.code.Current.LineNo;
156
+ this.dataStack.push(node);
157
+ }
158
+
159
+ function handleLoadFromDictOrDerefA() {
160
+ // Python 3.12+: load from locals dict or closure; approximate as a normal name load.
161
+ const varName = this.code.Current.Name || "";
162
+ if (!varName.length) {
163
+ return;
164
+ }
165
+ let node = new AST.ASTName(varName);
166
+ node.line = this.code.Current.LineNo;
167
+ this.dataStack.push(node);
168
+ }
169
+
170
+ function handleLoadLocals() {
171
+ let node = new AST.ASTLocals ();
172
+ node.line = this.code.Current.LineNo;
173
+ this.dataStack.push(node);
174
+ }
175
+
176
+ function handleStoreLocals() {
177
+ this.dataStack.pop();
178
+ }
179
+
180
+ function handleLoadMethodA() {
181
+ // Behave like LOAD_ATTR
182
+ let name = this.dataStack.pop();
183
+ let node = new AST.ASTBinary (name, new AST.ASTName(this.code.Current.Name), AST.ASTBinary.BinOp.Attr);
184
+ node.line = this.code.Current.LineNo;
185
+ this.dataStack.push(node);
186
+ }
187
+
188
+ function handleLoadNameA() {
189
+ let varName = this.code.Current.Name || "";
190
+ if (varName.length >= 2 && varName.startsWith('_[')) {
191
+ return;
192
+ }
193
+ let node = new AST.ASTName (varName);
194
+ node.line = this.code.Current.LineNo;
195
+ this.dataStack.push(node);
196
+ }
197
+
198
+ // Python 3.14 LOAD_SPECIAL oparg-to-method mapping (from pycore_ceval.h)
199
+ const SPECIAL_METHOD_NAMES = {
200
+ 0: "__enter__",
201
+ 1: "__exit__",
202
+ 2: "__aenter__",
203
+ 3: "__aexit__"
204
+ };
205
+
206
+ function handleLoadSpecialA() {
207
+ // Python 3.14 LOAD_SPECIAL: oparg indexes into special method table.
208
+ // Per CPython docs: pushes (method, self) pair onto stack.
209
+ // - For methods: [type(obj).__xxx__, obj]
210
+ // - For non-methods: [obj.__xxx__, NULL]
211
+ let obj = this.dataStack.pop();
212
+ const oparg = this.code.Current.Argument || 0;
213
+ const attrName = SPECIAL_METHOD_NAMES[oparg] || `__special_${oparg}__`;
214
+ if (!obj) {
215
+ obj = new AST.ASTName("##MISSING##");
216
+ }
217
+
218
+ // For __exit__ (oparg 1): save the context manager expression for with-block
219
+ // This is called BEFORE __enter__ in 3.14 bytecode pattern
220
+ if (oparg === 1) {
221
+ this._py314WithContextMgr = obj;
222
+ }
223
+
224
+ // For __enter__ (oparg 0): create with-block using saved context manager
225
+ if (oparg === 0) {
226
+ // Get the saved context manager from when __exit__ was loaded
227
+ const ctxMgr = this._py314WithContextMgr || obj;
228
+
229
+ // Find with block end from exception table
230
+ let withEnd = this.code.LastOffset;
231
+ const exceptionTable = this.object.ExceptionTable || [];
232
+ const currentOffset = this.code.Current.Offset;
233
+
234
+ for (const entry of exceptionTable) {
235
+ if (entry.start > currentOffset && entry.start <= currentOffset + 20) {
236
+ withEnd = entry.end || withEnd;
237
+ entry._isWithStatement = true;
238
+ break;
239
+ }
240
+ }
241
+
242
+ // Create the WITH block (don't set expr - let STORE do it via _py314WithCtxMgrForStore)
243
+ let withBlock = new AST.ASTWithBlock(currentOffset, withEnd);
244
+
245
+ // Push the with block onto the block stack
246
+ this.blocks.push(withBlock);
247
+ this.curBlock = this.blocks.top();
248
+
249
+ // Save context manager for CALL to push to stack (so STORE gets it as valueNode)
250
+ this._py314WithCtxMgrForStore = ctxMgr;
251
+
252
+ // Clear the temporary from __exit__
253
+ this._py314WithContextMgr = null;
254
+ }
255
+
256
+ let method = new AST.ASTBinary(obj, new AST.ASTName(attrName), AST.ASTBinary.BinOp.Attr);
257
+ method.line = this.code.Current.LineNo;
258
+ // Push self_or_null and callable to match CALL convention:
259
+ // Stack layout for CALL: [self_or_null, callable, args...]
260
+ // For method from LOAD_SPECIAL: self_or_null=obj, callable=method
261
+ this.dataStack.push(obj); // self_or_null (TOS-1 for CALL)
262
+ this.dataStack.push(method); // callable (TOS for CALL pops this first)
263
+ }
264
+
265
+ function handleLoadSuperAttrA() {
266
+ // Python 3.12+ LOAD_SUPER_ATTR uses flags in argument to encode self/null.
267
+ // Treat as attribute access on super(): super().<name>
268
+ const attr = new AST.ASTName(this.code.Current.Name || "");
269
+ const superCall = new AST.ASTCall(new AST.ASTName("super"), [], []);
270
+ const node = new AST.ASTBinary(superCall, attr, AST.ASTBinary.BinOp.Attr);
271
+ node.line = this.code.Current.LineNo;
272
+ this.dataStack.push(node);
273
+ }
274
+
275
+ function handleLoadSuperMethodA() {
276
+ // Similar to LOAD_SUPER_ATTR; represent as super().<name>
277
+ handleLoadSuperAttrA.call(this);
278
+ }
279
+
280
+ function handleLoadZeroSuperAttrA() {
281
+ // Zero-cost variants; reuse super attr logic.
282
+ handleLoadSuperAttrA.call(this);
283
+ }
284
+
285
+ function handleLoadZeroSuperMethodA() {
286
+ handleLoadSuperAttrA.call(this);
287
+ }
288
+
289
+ function handleStoreAttrA() {
290
+ if (this.unpack) {
291
+ let name = this.dataStack.pop();
292
+ let attrNode = new AST.ASTBinary(name, new AST.ASTName(this.code.Current.Name), AST.ASTBinary.BinOp.Attr);
293
+
294
+ let tup = this.dataStack.top();
295
+ if (tup instanceof AST.ASTTuple) {
296
+ tup.add(attrNode);
297
+ } else {
298
+ if (global.g_cliArgs?.debug) {
299
+ console.error("Something TERRIBLE happened!\n");
300
+ }
301
+ }
302
+
303
+ if (--this.unpack <= 0) {
304
+ this.dataStack.pop();
305
+ let seqNode = this.dataStack.pop();
306
+ if (seqNode instanceof AST.ASTChainStore) {
307
+ seqNode.line = this.code.Current.LineNo;
308
+ this.append_to_chain_store(seqNode, tup);
309
+ } else {
310
+ let node = new AST.ASTStore(seqNode, tup);
311
+ node.line = this.code.Current.LineNo;
312
+ this.curBlock.append(node);
313
+ }
314
+ }
315
+ } else {
316
+ let name = this.dataStack.pop();
317
+ let value = this.dataStack.pop();
318
+ let attrNode = new AST.ASTBinary(name, new AST.ASTName(this.code.Current.Name), AST.ASTBinary.BinOp.Attr);
319
+ if (value instanceof AST.ASTChainStore) {
320
+ this.append_to_chain_store(value, attrNode);
321
+ } else {
322
+ let node = new AST.ASTStore(value, attrNode);
323
+ node.line = this.code.Current.LineNo;
324
+ this.curBlock.append(node);
325
+ }
326
+ }
327
+ }
328
+
329
+ function handleStoreDerefA() {
330
+ if (this.unpack) {
331
+ let nameNode = new AST.ASTName(this.code.Current.FreeName);
332
+ let tupleNode = this.dataStack.top();
333
+ if (tupleNode instanceof AST.ASTTuple)
334
+ tupleNode.add(nameNode);
335
+ else if (global.g_cliArgs?.debug)
336
+ console.error("Something TERRIBLE happened!\n");
337
+
338
+ if (--this.unpack <= 0) {
339
+ this.dataStack.pop();
340
+ let seqNode = this.dataStack.pop();
341
+
342
+ if (seqNode instanceof AST.ASTChainStore) {
343
+ seqNode.line = this.code.Current.LineNo;
344
+ this.append_to_chain_store(seqNode, tupleNode);
345
+ } else {
346
+ let node = new AST.ASTStore(seqNode, tupleNode);
347
+ node.line = this.code.Current.LineNo;
348
+ this.curBlock.append(node);
349
+ }
350
+ }
351
+ } else {
352
+ let valueNode = this.dataStack.pop();
353
+ let nameNode = new AST.ASTName(this.code.Current.FreeName);
354
+
355
+ if (valueNode instanceof AST.ASTChainStore) {
356
+ this.append_to_chain_store(valueNode, nameNode);
357
+ } else {
358
+ let node = new AST.ASTStore(valueNode, nameNode);
359
+ node.line = this.code.Current.LineNo;
360
+ this.curBlock.append(node);
361
+ }
362
+ }
363
+ }
364
+
365
+ function handleStoreFastA() {
366
+ processStore.call(this);
367
+ }
368
+
369
+ function handleStoreGlobalA() {
370
+ processStore.call(this);
371
+ }
372
+
373
+ function handleStoreNameA() {
374
+ processStore.call(this);
375
+ }
376
+
377
+ function processStore() {
378
+ // Pattern matching: first capture expected bindings, then start case body
379
+ if (this.inMatchPattern) {
380
+ if (shouldCaptureStoreInPattern(this.patternOps)) {
381
+ this.patternOps.push({
382
+ type: 'STORE_FAST',
383
+ name: this.code.Current.Name
384
+ });
385
+ return;
386
+ }
387
+
388
+ if (beginMatchCaseFromPattern.call(this, {reason: 'store'})) {
389
+ this.unpack = 0;
390
+ this.starPos = -1;
391
+ // Fall through to normal store handling now that case body started
392
+ }
393
+ }
394
+
395
+ if (global.g_cliArgs?.debug && this.code.Current.Name && (this.code.Current.Name === 'b' || this.code.Current.Name === 'i')) {
396
+ console.log(`[processStore] varName=${this.code.Current.Name}, curBlock=${this.curBlock.type_str}, inited=${this.curBlock.inited}, unpack=${this.unpack}`);
397
+ console.log(` Block stack: ${this.blocks.map((b,i) => `[${i}]${b.type_str}(inited=${b.inited})`).join(' → ')}`);
398
+ }
399
+
400
+ // Check if we're inside an uninitialized For/AsyncFor block (could be nested inside try/container)
401
+ let parentForBlock = null;
402
+ for (let i = this.blocks.length - 1; i >= 0; i--) {
403
+ if ((this.blocks[i].blockType == AST.ASTBlock.BlockType.For ||
404
+ this.blocks[i].blockType == AST.ASTBlock.BlockType.AsyncFor) &&
405
+ !this.blocks[i].inited) {
406
+ parentForBlock = this.blocks[i];
407
+ break;
408
+ }
409
+ }
410
+
411
+ if (this.unpack) {
412
+ let nameNode = new AST.ASTName(this.code.Current.Name);
413
+
414
+ let tupleNode = this.dataStack.top();
415
+ if (tupleNode instanceof AST.ASTTuple) {
416
+ if (this.starPos-- == 0) {
417
+ nameNode.name = '*' + nameNode.name;
418
+ }
419
+ tupleNode.add(nameNode);
420
+ } else {
421
+ if (global.g_cliArgs?.debug) {
422
+ console.error("Something TERRIBLE happened!\n");
423
+ }
424
+ }
425
+
426
+ if (--this.unpack <= 0) {
427
+ this.dataStack.pop();
428
+ let seqNode = this.dataStack.pop();
429
+
430
+ if (parentForBlock) {
431
+ let tuple = tupleNode;
432
+ if (tuple != null) {
433
+ tuple.requireParens = false;
434
+ }
435
+ parentForBlock.index = tupleNode;
436
+ parentForBlock.init();
437
+ } else if (seqNode instanceof AST.ASTChainStore) {
438
+ seqNode.line = this.code.Current.LineNo;
439
+ this.append_to_chain_store(seqNode, tupleNode);
440
+ } else {
441
+ let node = new AST.ASTStore(seqNode, tupleNode);
442
+ node.line = this.code.Current.LineNo;
443
+ this.curBlock.append(node);
444
+ }
445
+ }
446
+ } else {
447
+ let varName = this.code.Current.Name || "";
448
+ if (varName.length >= 2 && varName.startsWith('_[')) {
449
+ /* Don't show stores of list comp append objects. */
450
+ return;
451
+ }
452
+
453
+ // Return private names back to their original name
454
+ let class_prefix = "_" + varName;
455
+ if (varName.startsWith(class_prefix + "__")) {
456
+ varName.value = varName.substring(class_prefix.length);
457
+ }
458
+
459
+ let nameNode = new AST.ASTName(varName);
460
+ nameNode.line = this.code.Current.LineNo;
461
+
462
+ // Handle walrus operator (named expression): (n := 10)
463
+ if (this.isWalrusOperator) {
464
+ let valueNode = this.dataStack.pop();
465
+ let namedExpr = new AST.ASTNamedExpr(nameNode, valueNode);
466
+ namedExpr.line = this.code.Current.LineNo;
467
+ this.dataStack.push(namedExpr);
468
+ this.isWalrusOperator = false;
469
+ return;
470
+ }
471
+
472
+ if (parentForBlock) {
473
+ if (global.g_cliArgs?.debug && parentForBlock.blockType == AST.ASTBlock.BlockType.AsyncFor) {
474
+ console.log(`[processStore] Setting AsyncFor index to: ${varName}`);
475
+ }
476
+ parentForBlock.index = nameNode;
477
+ parentForBlock.init();
478
+ } else {
479
+ let valueNode = this.dataStack.pop();
480
+ let importNode = this.dataStack.top();
481
+ if (importNode instanceof AST.ASTImport) {
482
+ let storeNode = new AST.ASTStore(valueNode, nameNode);
483
+ storeNode.line = this.code.Current.LineNo;
484
+ importNode.add_store?.(storeNode);
485
+ return;
486
+ } else if (importNode instanceof AST.ASTChainStore) {
487
+ // fall through after reusing valueNode
488
+ this.dataStack.pop(); // remove chain store from stack
489
+ this.append_to_chain_store(importNode, nameNode);
490
+ if (this.code.Prev?.OpCodeID != this.OpCodes.DUP_TOP) {
491
+ this.curBlock.append(importNode);
492
+ }
493
+ return;
494
+ } else if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
495
+ && !this.curBlock.inited) {
496
+ this.curBlock.expr = valueNode;
497
+ this.curBlock.var = nameNode;
498
+ this.curBlock.init();
499
+ return;
500
+ }
501
+
502
+ if (!valueNode) {
503
+ valueNode = new AST.ASTNone();
504
+ }
505
+ if (valueNode instanceof AST.ASTFunction && !valueNode.code.object.SourceCode) {
506
+ let decompiler = new PycDecompiler(valueNode.code.object);
507
+ valueNode.code.object.SourceCode = decompiler.decompile();
508
+ }
509
+ let lastBlockNode = this.curBlock.nodes.top();
510
+ if (
511
+ lastBlockNode instanceof AST.ASTCondBlock &&
512
+ lastBlockNode.nodes.length == 0 &&
513
+ this.code.Current.LineNo == lastBlockNode.line &&
514
+ lastBlockNode.line == valueNode.line
515
+ ) {
516
+ valueNode = new AST.ASTBinary(lastBlockNode.condition, valueNode, lastBlockNode.negative ? AST.ASTBinary.BinOp.LogicalOr : AST.ASTBinary.BinOp.LogicalAnd);
517
+ this.curBlock.nodes.pop();
518
+ }
519
+ let node = new AST.ASTStore(valueNode, nameNode);
520
+ node.line = this.code.Current.LineNo;
521
+ this.curBlock.append(node);
522
+ }
523
+
524
+ if (this.code.Current.OpCodeID == this.OpCodes.STORE_GLOBAL_A) {
525
+ this.object.Globals.add(nameNode.name);
526
+ }
527
+ }
528
+ }
529
+
530
+ function shouldCaptureStoreInPattern(patternOps) {
531
+ patternOps = patternOps || [];
532
+ const expected = getExpectedPatternStoreCount(patternOps);
533
+ if (expected == null) {
534
+ return true;
535
+ }
536
+ const recorded = patternOps.filter(op => op.type === 'STORE_FAST').length;
537
+ return recorded < expected;
538
+ }
539
+
540
+ function getExpectedPatternStoreCount(patternOps) {
541
+ const matchClassOp = patternOps.find(op => op.type === 'MATCH_CLASS');
542
+ if (matchClassOp) {
543
+ if (matchClassOp.attrNames && matchClassOp.attrNames.length) {
544
+ return matchClassOp.attrNames.length;
545
+ }
546
+ if (matchClassOp.count) {
547
+ return matchClassOp.count;
548
+ }
549
+ return 0;
550
+ }
551
+
552
+ const unpackOp = patternOps.find(op => op.type === 'UNPACK_SEQUENCE');
553
+ if (unpackOp) {
554
+ return unpackOp.count;
555
+ }
556
+
557
+ return null;
558
+ }
559
+
560
+ function handleReserveFastA() {
561
+ let list = [];
562
+ this.code.Current.ConstantObject.Value.map(el => list[el.value] = el.key);
563
+ this.dataStack.push(list);
564
+ }
565
+
566
+ function handleLoadFastAndClearA() {
567
+ // Python 3.11+ LOAD_FAST_AND_CLEAR - loads local and clears it
568
+ // Used in comprehensions to save/restore outer scope variables
569
+ // For decompilation: just load the variable (clearing is implementation detail)
570
+ handleLoadFastA.call(this);
571
+ }
572
+
573
+ function handleLoadFastBorrowA() {
574
+ // Python 3.14+ LOAD_FAST_BORROW - optimized LOAD_FAST
575
+ handleLoadFastA.call(this);
576
+ }
577
+
578
+ function handleLoadCommonConstantA() {
579
+ // Python 3.14+ common constants table
580
+ const {PythonObject} = require('../PythonObject');
581
+ const mapping = {
582
+ 0: () => new AST.ASTNone(),
583
+ 1: () => new AST.ASTObject(new PythonObject("Py_Bool", false)),
584
+ 2: () => new AST.ASTObject(new PythonObject("Py_Bool", true)),
585
+ 3: () => new AST.ASTObject(new PythonObject("Py_Int", -1)),
586
+ 4: () => new AST.ASTObject(new PythonObject("Py_Int", 0)),
587
+ 5: () => new AST.ASTObject(new PythonObject("Py_Int", 1)),
588
+ 6: () => new AST.ASTObject(new PythonObject("Py_Int", 2)),
589
+ 7: () => new AST.ASTObject(new PythonObject("Py_Int", 3)),
590
+ 8: () => new AST.ASTObject(new PythonObject("Py_Int", 4)),
591
+ 9: () => new AST.ASTObject(new PythonObject("Py_Int", 5))
592
+ };
593
+ const maker = mapping[this.code.Current.Argument];
594
+ let node = maker ? maker() : new AST.ASTName(`__common_const_${this.code.Current.Argument}__`);
595
+ node.line = this.code.Current.LineNo;
596
+ this.dataStack.push(node);
597
+ }
598
+
599
+ function handleLoadFastLoadFastA() {
600
+ // Combined load of two fast locals: arg packs (hi<<8)|lo
601
+ const packed = this.code.Current.Argument;
602
+ const isPackedNibbles = this.object?.Reader?.versionCompare(3, 13) >= 0;
603
+ const idx1 = isPackedNibbles ? ((packed) & 0x0F) : (packed & 0xFF);
604
+ const idx2 = isPackedNibbles ? ((packed >> 4) & 0x0F) : ((packed >> 8) & 0xFF);
605
+ const varNames = this.object?.VarNames?.Value || this.object?.code?.object?.VarNames?.Value || [];
606
+ const name1 = varNames[idx1]?.toString?.() || `##var_${idx1}##`;
607
+ const name2 = varNames[idx2]?.toString?.() || `##var_${idx2}##`;
608
+ // Preserve operand order expected by STORE_ATTR sequences: lower nibble first
609
+ this.dataStack.push(new AST.ASTName(name1));
610
+ this.dataStack.push(new AST.ASTName(name2));
611
+ }
612
+
613
+ function handleStoreFastLoadFastA() {
614
+ // Combined store then load: store to first index, load second
615
+ const packed = this.code.Current.Argument;
616
+ const isPackedNibbles = this.object?.Reader?.versionCompare(3, 13) >= 0;
617
+ const idx1 = isPackedNibbles ? (packed & 0x0F) : (packed & 0xFF);
618
+ const idx2 = isPackedNibbles ? ((packed >> 4) & 0x0F) : ((packed >> 8) & 0xFF);
619
+ const varNames = this.object?.VarNames?.Value || this.object?.code?.object?.VarNames?.Value || [];
620
+ const name1 = varNames[idx1]?.toString?.() || `##var_${idx1}##`;
621
+ const name2 = varNames[idx2]?.toString?.() || `##var_${idx2}##`;
622
+
623
+ if (this.inMatchPattern && shouldCaptureStoreInPattern(this.patternOps)) {
624
+ // Capture binding and surface loaded local for guards
625
+ this.dataStack.pop();
626
+ this.patternOps.push({type: 'STORE_FAST', name: name1});
627
+ this.dataStack.push(new AST.ASTName(name2));
628
+ return;
629
+ }
630
+
631
+ const valueNode = this.dataStack.pop() || new AST.ASTNone();
632
+ const destNode = new AST.ASTName(name1);
633
+ this.curBlock.append(new AST.ASTStore(valueNode, destNode));
634
+
635
+ // Push second local load as result
636
+ this.dataStack.push(new AST.ASTName(name2));
637
+ }
638
+
639
+ function handleStoreFastStoreFastA() {
640
+ // Combined store to two locals: arg packs (hi<<8)|lo
641
+ const packed = this.code.Current.Argument;
642
+ const isPackedNibbles = this.object?.Reader?.versionCompare(3, 13) >= 0;
643
+ const idx1 = isPackedNibbles ? (packed & 0x0F) : (packed & 0xFF);
644
+ const idx2 = isPackedNibbles ? ((packed >> 4) & 0x0F) : ((packed >> 8) & 0xFF);
645
+ const varNames = this.object?.VarNames?.Value || this.object?.code?.object?.VarNames?.Value || [];
646
+ const name1 = varNames[idx1]?.toString?.() || `##var_${idx1}##`;
647
+ const name2 = varNames[idx2]?.toString?.() || `##var_${idx2}##`;
648
+
649
+ if (this.inMatchPattern && shouldCaptureStoreInPattern(this.patternOps)) {
650
+ // Record bindings in pattern order (high nibble corresponds to first element)
651
+ this.dataStack.pop(); // second element
652
+ this.dataStack.pop(); // first element
653
+ this.patternOps.push({type: 'STORE_FAST', name: name2});
654
+ this.patternOps.push({type: 'STORE_FAST', name: name1});
655
+ return;
656
+ }
657
+
658
+ const value2 = this.dataStack.pop() || new AST.ASTNone();
659
+ const value1 = this.dataStack.pop() || new AST.ASTNone();
660
+
661
+ // First store lower nibble value (top of stack), then higher nibble
662
+ this.curBlock.append(new AST.ASTStore(value2, new AST.ASTName(name1)));
663
+ this.curBlock.append(new AST.ASTStore(value1, new AST.ASTName(name2)));
664
+ }
665
+
666
+ function handleLoadSmallIntA() {
667
+ // Python 3.14+ LOAD_SMALL_INT - loads small integer constant
668
+ const PythonObject = require('../PythonObject').PythonObject;
669
+ let value = this.code.Current.Argument;
670
+ let node = new AST.ASTObject(new PythonObject("Py_Int", value));
671
+ node.line = this.code.Current.LineNo;
672
+ this.dataStack.push(node);
673
+ }
674
+
675
+ module.exports = {
676
+ handleDeleteAttrA,
677
+ handleDeleteGlobalA,
678
+ handleDeleteNameA,
679
+ handleDeleteFastA,
680
+ handleDeleteDerefA,
681
+ handleLoadAttrA,
682
+ handleLoadConstA,
683
+ handleLoadDerefA,
684
+ handleLoadClassderefA,
685
+ handleLoadFastA,
686
+ handleLoadFastCheckA,
687
+ handleLoadFastAndClearA,
688
+ handleLoadFastBorrowA,
689
+ handleLoadFastLoadFastA,
690
+ handleLoadGlobalA,
691
+ handleLoadCommonConstantA,
692
+ handleLoadLocals,
693
+ handleLoadSmallIntA,
694
+ handleStoreLocals,
695
+ handleLoadMethodA,
696
+ handleLoadNameA,
697
+ handleLoadSpecialA,
698
+ handleLoadSuperAttrA,
699
+ handleLoadSuperMethodA,
700
+ handleLoadZeroSuperAttrA,
701
+ handleLoadZeroSuperMethodA,
702
+ handleLoadFromDictOrDerefA,
703
+ handleStoreAttrA,
704
+ handleStoreDerefA,
705
+ handleStoreFastA,
706
+ handleStoreFastLoadFastA,
707
+ handleStoreFastStoreFastA,
708
+ handleStoreGlobalA,
709
+ handleStoreNameA,
710
+ handleReserveFastA
711
+ };