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,226 @@
1
+ const AST = require('../ast/ast_node');
2
+ const { flushCurrentCaseBody } = require('./misc_other');
3
+
4
+ /**
5
+ * Pattern matching opcodes for match/case statements (Python 3.10+)
6
+ */
7
+
8
+ function ensureMatchInitialized(subjectCandidate, options = {}) {
9
+ const skipStackLookup = options.skipStackLookup || false;
10
+ if (this.currentMatch) {
11
+ return;
12
+ }
13
+
14
+ // Prefer subject from stack (TOS-1), fallback to provided candidate or saved potential
15
+ const stackLen = this.dataStack ? this.dataStack.length : 0;
16
+ let tosMinusOne = (!skipStackLookup && stackLen >= 2) ? this.dataStack[stackLen - 2] : null;
17
+ this.matchSubject = subjectCandidate || tosMinusOne;
18
+
19
+ if (!this.matchSubject && !skipStackLookup && this.dataStack && this.dataStack.length > 0) {
20
+ this.matchSubject = this.dataStack.top();
21
+ }
22
+
23
+ if (!this.matchSubject && this.potentialMatchSubject) {
24
+ this.matchSubject = this.potentialMatchSubject;
25
+ }
26
+
27
+ if (!this.matchSubject) {
28
+ if (global.g_cliArgs?.debug) {
29
+ console.log(`[MATCH] Unable to initialize match at offset ${this.code.Current.Offset}: no subject available (stackLen=${this.dataStack?.length || 0})`);
30
+ }
31
+ return;
32
+ }
33
+
34
+ this.currentMatch = new AST.ASTMatch(this.matchSubject);
35
+ this.currentMatch.line = this.code.Current.LineNo;
36
+ this.matchParentBlock = this.curBlock;
37
+ this.inMatchPattern = true;
38
+ this.patternOps = [];
39
+ if (!this.matchPreNodesStart && this.matchParentBlock) {
40
+ this.matchPreNodesStart = this.matchParentBlock.nodes.length;
41
+ }
42
+
43
+ if (global.g_cliArgs?.debug) {
44
+ console.log(`[MATCH] Initialized new ASTMatch at offset ${this.code.Current.Offset} subject=${this.matchSubject?.constructor?.name}`);
45
+ }
46
+ }
47
+
48
+ function handleMatchSequence() {
49
+ // MATCH_SEQUENCE opcode (Python 3.10+)
50
+ // Tests if TOS is a sequence (tuple, list, etc.) but not str/bytes/bytearray
51
+ // Stack: TOS = value (copy from COPY 1), TOS1 = subject
52
+ // IMPORTANT: TOS is NOT popped! Result is pushed on top.
53
+
54
+ // If we have an open case and starting a new pattern, flush the previous case first
55
+ if (this.currentMatch && this.currentCase && !this.inMatchPattern) {
56
+ if (global.g_cliArgs?.debug) {
57
+ console.log(`[MATCH_SEQUENCE] Flushing previous case before new pattern at offset ${this.code.Current.Offset}`);
58
+ }
59
+ flushCurrentCaseBody.call(this);
60
+ }
61
+
62
+ if (!this.inMatchPattern && this.currentMatch) {
63
+ this.inMatchPattern = true;
64
+ this.patternOps = [];
65
+ }
66
+
67
+ let value = this.dataStack.top(); // Peek at the copy (don't pop!)
68
+
69
+ if (global.g_cliArgs?.debug) {
70
+ console.log(`[MATCH_SEQUENCE] at offset ${this.code.Current.Offset}`);
71
+ console.log(` currentMatch=${this.currentMatch ? 'active' : 'null'}`);
72
+ console.log(` Stack depth=${this.dataStack.length}`);
73
+ }
74
+
75
+ const subjectCandidate = this.dataStack && this.dataStack.length >= 2 ? this.dataStack[this.dataStack.length - 2] : null;
76
+ ensureMatchInitialized.call(this, subjectCandidate);
77
+
78
+ // Mark that we're in pattern checking phase
79
+ this.inMatchPattern = true;
80
+ this.patternOps = []; // Clear pattern operations for new case
81
+
82
+ // Record pattern operation
83
+ this.patternOps.push({type: 'MATCH_SEQUENCE'});
84
+
85
+ // Push True to indicate sequence check passed
86
+ // (In reality, should check type, but for decompilation we assume it passes)
87
+ let node = new AST.ASTObject(new (require('../PythonObject').PythonObject)("Py_Bool", true));
88
+ node.line = this.code.Current.LineNo;
89
+ this.dataStack.push(node);
90
+ }
91
+
92
+ function handleGetLen() {
93
+ // GET_LEN opcode (Python 3.10+)
94
+ // Pushes len(TOS) onto stack (TOS is not popped)
95
+ // Used in sequence pattern matching to check length
96
+
97
+ let value = this.dataStack.top();
98
+
99
+ // Record pattern operation if in pattern matching
100
+ if (this.inMatchPattern) {
101
+ this.patternOps.push({type: 'GET_LEN'});
102
+ }
103
+
104
+ // Create len(value) call node
105
+ // This will be compared with expected length in subsequent COMPARE_OP
106
+ let lenNode = new AST.ASTCall(
107
+ new AST.ASTName('len'),
108
+ [value],
109
+ []
110
+ );
111
+ lenNode.line = this.code.Current.LineNo;
112
+ this.dataStack.push(lenNode);
113
+
114
+ if (global.g_cliArgs?.debug) {
115
+ console.log(`[GET_LEN] at offset ${this.code.Current.Offset}, value=${value?.constructor.name}`);
116
+ }
117
+ }
118
+
119
+ function handleMatchMapping() {
120
+ // MATCH_MAPPING opcode (Python 3.10+)
121
+ // Similar to MATCH_SEQUENCE but for mappings (dicts)
122
+
123
+ ensureMatchInitialized.call(this, null, {skipStackLookup: true});
124
+
125
+ let value = this.dataStack.pop();
126
+ if (this.inMatchPattern) {
127
+ this.patternOps.push({type: 'MATCH_MAPPING'});
128
+ }
129
+ let node = new AST.ASTObject(new (require('../PythonObject').PythonObject)("Py_Bool", true));
130
+ node.line = this.code.Current.LineNo;
131
+ this.dataStack.push(node);
132
+
133
+ if (global.g_cliArgs?.debug) {
134
+ console.log(`[MATCH_MAPPING] Stub implementation at offset ${this.code.Current.Offset}`);
135
+ }
136
+ }
137
+
138
+ function handleMatchClassA() {
139
+ // MATCH_CLASS_A opcode (Python 3.10+)
140
+ // Matches object against a class pattern
141
+
142
+ let count = this.code.Current.Argument;
143
+
144
+ ensureMatchInitialized.call(this, null, {skipStackLookup: true});
145
+
146
+ if (!this.inMatchPattern && this.currentMatch) {
147
+ this.inMatchPattern = true;
148
+ this.patternOps = [];
149
+ }
150
+
151
+ let attrNames = [];
152
+ const prevInstr = this.code.Prev;
153
+ if (prevInstr?.ConstantObject?.ClassName == "Py_Tuple" && prevInstr.ConstantObject.Value) {
154
+ attrNames = prevInstr.ConstantObject.Value.map(obj => obj?.toString());
155
+ }
156
+ let classExpr = this.dataStack?.[this.dataStack.length - 2] || null;
157
+
158
+ // Pop class object and subject
159
+ for (let i = 0; i < count + 1; i++) {
160
+ this.dataStack.pop();
161
+ }
162
+
163
+ if (this.inMatchPattern) {
164
+ this.patternOps.push({type: 'MATCH_CLASS', count, attrNames, classExpr});
165
+ }
166
+
167
+ // Push success placeholder
168
+ let node = new AST.ASTObject(new (require('../PythonObject').PythonObject)("Py_Bool", true));
169
+ node.line = this.code.Current.LineNo;
170
+ this.dataStack.push(node);
171
+
172
+ if (global.g_cliArgs?.debug) {
173
+ console.log(`[MATCH_CLASS] Stub implementation at offset ${this.code.Current.Offset}, count=${count}, inMatch=${this.inMatchPattern}`);
174
+ }
175
+ }
176
+
177
+ function handleMatchKeys() {
178
+ // MATCH_KEYS opcode (Python 3.10+)
179
+ // Used in mapping patterns
180
+
181
+ ensureMatchInitialized.call(this, null, {skipStackLookup: true});
182
+
183
+ const keysNode = this.dataStack.pop(); // keys tuple
184
+ let subject = this.dataStack.top();
185
+
186
+ const keys = extractMatchKeys(keysNode);
187
+ if (this.inMatchPattern) {
188
+ this.patternOps.push({type: 'MATCH_KEYS', keys});
189
+ }
190
+
191
+ // Push (values_tuple, True) or None
192
+ let node = new AST.ASTTuple([]);
193
+ node.line = this.code.Current.LineNo;
194
+ this.dataStack.push(node);
195
+
196
+ if (global.g_cliArgs?.debug) {
197
+ console.log(`[MATCH_KEYS] Stub implementation at offset ${this.code.Current.Offset}`);
198
+ if (keys?.length) {
199
+ console.log(` keys=${keys.map(k => k.codeFragment().toString()).join(', ')}`);
200
+ }
201
+ }
202
+ }
203
+
204
+ function extractMatchKeys(keysNode) {
205
+ if (!keysNode) {
206
+ return [];
207
+ }
208
+ if (keysNode instanceof AST.ASTTuple || keysNode instanceof AST.ASTList) {
209
+ return keysNode.values || [];
210
+ }
211
+ if (keysNode instanceof AST.ASTObject) {
212
+ const obj = keysNode.object;
213
+ if (["Py_Tuple", "Py_SmallTuple", "Py_List"].includes(obj?.ClassName)) {
214
+ return (obj.Value || []).map(value => new AST.ASTObject(value));
215
+ }
216
+ }
217
+ return [];
218
+ }
219
+
220
+ module.exports = {
221
+ handleMatchSequence,
222
+ handleGetLen,
223
+ handleMatchMapping,
224
+ handleMatchClassA,
225
+ handleMatchKeys
226
+ };
@@ -0,0 +1,280 @@
1
+ const AST = require('../ast/ast_node');
2
+
3
+ function handleDupTop() {
4
+ // Track COPY pattern for match detection
5
+ let prevOpCode = this.code.Prev?.OpCodeID;
6
+ let prevOffset = this.code.Prev?.Offset;
7
+
8
+ // Check if this is COPY after LOAD (potential match start)
9
+ if (prevOpCode == this.OpCodes.LOAD_FAST_A ||
10
+ prevOpCode == this.OpCodes.LOAD_NAME_A ||
11
+ prevOpCode == this.OpCodes.LOAD_GLOBAL_A) {
12
+
13
+ // Save subject for later use
14
+ this.potentialMatchSubject = this.dataStack.top();
15
+
16
+ // First COPY after LOAD → potential match candidate
17
+ if (this.matchCandidateStart === -1) {
18
+ this.matchCandidateStart = this.code.Current.Offset;
19
+ this.lastLoadOffset = prevOffset;
20
+ this.matchParentBlock = this.curBlock;
21
+ if (!this.matchPreNodesStart) {
22
+ this.matchPreNodesStart = this.matchParentBlock.nodes.length;
23
+ }
24
+
25
+ if (global.g_cliArgs?.debug) {
26
+ console.log(`[DUP_TOP] Match candidate at offset ${this.matchCandidateStart}`);
27
+ }
28
+
29
+ // Look ahead to confirm match pattern immediately
30
+ if (!this.currentMatch && this.lookAheadForMatchPattern()) {
31
+ this.currentMatch = new AST.ASTMatch(this.potentialMatchSubject);
32
+ this.currentMatch.line = this.code.Current.LineNo;
33
+ this.matchParentBlock = this.curBlock;
34
+ this.inMatchPattern = true; // Start tracking pattern operations
35
+ this.patternOps = []; // Clear for first case
36
+ }
37
+ }
38
+ }
39
+ // Second COPY without intermediate LOAD → confirm match or start new case!
40
+ else if (this.matchCandidateStart > 0 &&
41
+ prevOffset > this.lastLoadOffset &&
42
+ prevOpCode == this.OpCodes.POP_TOP) {
43
+
44
+ // This COPY has no LOAD before it → reusing same subject
45
+ // Match patterns only exist in Python 3.10+
46
+ if (!this.currentMatch && this.potentialMatchSubject &&
47
+ this.object.Reader.versionCompare(3, 10) >= 0) {
48
+ // First time - create match (fallback if look-ahead didn't work)
49
+ this.currentMatch = new AST.ASTMatch(this.potentialMatchSubject);
50
+ this.currentMatch.line = this.code.Current.LineNo;
51
+ this.matchParentBlock = this.curBlock;
52
+ this.inMatchPattern = true;
53
+ this.patternOps = [];
54
+ if (!this.matchPreNodesStart) {
55
+ this.matchPreNodesStart = this.matchParentBlock.nodes.length;
56
+ }
57
+
58
+ this.debug(`[DUP_TOP] MATCH CONFIRMED at offset ${this.code.Current.Offset}! Subject: ${this.potentialMatchSubject?.constructor.name}`);
59
+ } else if (this.currentMatch) {
60
+ // Subsequent case in same match - reset pattern tracking
61
+ this.debug(`[DUP_TOP] Starting new case at offset ${this.code.Current.Offset}`);
62
+ this.inMatchPattern = true;
63
+ this.patternOps = [];
64
+ }
65
+ }
66
+
67
+ if (this.dataStack.top() == null) {
68
+ this.dataStack.push(null);
69
+ } else if (this.code.Next?.OpCodeID == this.OpCodes.ROT_THREE) {
70
+ // double compare case
71
+ this.skipNextJump = true;
72
+ this.code.GoNext();
73
+ } else if (this.dataStack.top() instanceof AST.ASTChainStore) {
74
+ let chainstore = this.dataStack.pop();
75
+ this.dataStack.push(this.dataStack.top());
76
+ this.dataStack.push(chainstore);
77
+ } else if (this.code.Next?.OpCodeID == this.OpCodes.LOAD_ATTR_A) {
78
+ // Augmented assign on attribute (a.value += 1)
79
+ // Don't create ChainStore - just duplicate the value
80
+ this.dataStack.push(this.dataStack.top());
81
+ } else {
82
+ // Check if this is a walrus operator pattern:
83
+ // DUP_TOP → STORE_* → <not another STORE>
84
+ let nextOpCode = this.code.Next?.OpCodeID;
85
+ let isStoreOp = (nextOpCode == this.OpCodes.STORE_NAME_A ||
86
+ nextOpCode == this.OpCodes.STORE_FAST_A ||
87
+ nextOpCode == this.OpCodes.STORE_GLOBAL_A);
88
+
89
+ if (isStoreOp && this.code.Next?.Next) {
90
+ let afterStoreOpCode = this.code.Next.Next.OpCodeID;
91
+ let isAnotherStore = (afterStoreOpCode == this.OpCodes.STORE_NAME_A ||
92
+ afterStoreOpCode == this.OpCodes.STORE_FAST_A ||
93
+ afterStoreOpCode == this.OpCodes.STORE_GLOBAL_A ||
94
+ afterStoreOpCode == this.OpCodes.STORE_ATTR_A ||
95
+ afterStoreOpCode == this.OpCodes.STORE_DEREF_A);
96
+
97
+ if (!isAnotherStore) {
98
+ // This is a walrus operator (named expression)
99
+ // Don't duplicate - just set flag. The value will be wrapped in ASTNamedExpr.
100
+ this.isWalrusOperator = true;
101
+ return;
102
+ }
103
+ }
104
+
105
+ // If next opcode is NOT a store operation, this is just a temporary copy
106
+ // (e.g., for match/case pattern matching) - don't create ChainStore
107
+ if (!isStoreOp) {
108
+ this.dataStack.push(this.dataStack.top());
109
+ return;
110
+ }
111
+
112
+ // Regular chained assignment (a = b = 10)
113
+ this.dataStack.push(this.dataStack.top());
114
+ let node = new AST.ASTChainStore ([], this.dataStack.top());
115
+ this.dataStack.push(node);
116
+ }
117
+ }
118
+
119
+ function handleCopyA() {
120
+ // COPY opcode (Python 3.11+): copies the i-th item to the top of the stack
121
+ // Argument specifies which item: 1 = top, 2 = second, etc.
122
+ let depth = this.code.Current.Argument;
123
+
124
+ // Save potential match subject for COPY 1 (like DUP_TOP)
125
+ if (depth == 1) {
126
+ let prevOpCode = this.code.Prev?.OpCodeID;
127
+ if (prevOpCode == this.OpCodes.LOAD_FAST_A ||
128
+ prevOpCode == this.OpCodes.LOAD_NAME_A ||
129
+ prevOpCode == this.OpCodes.LOAD_GLOBAL_A) {
130
+ this.potentialMatchSubject = this.dataStack.top();
131
+ if (global.g_cliArgs?.debug) {
132
+ console.log(`[COPY] Saved potential match subject: ${this.potentialMatchSubject?.constructor.name}`);
133
+ }
134
+ }
135
+
136
+ // COPY 1 is equivalent to DUP_TOP - same walrus detection logic
137
+ handleDupTop.call(this);
138
+ } else {
139
+ // For other depths, just copy the item
140
+ let index = this.dataStack.length - depth;
141
+ if (index >= 0 && index < this.dataStack.length) {
142
+ this.dataStack.push(this.dataStack[index]);
143
+ }
144
+ }
145
+ }
146
+
147
+ function handleDupTopTwo() {
148
+ let first = this.dataStack.pop();
149
+ let second = this.dataStack.top();
150
+
151
+ this.dataStack.push(first);
152
+ this.dataStack.push(second);
153
+ this.dataStack.push(first);
154
+ }
155
+
156
+ function handleDupTopxA() {
157
+ let first = [];
158
+ let second = [];
159
+
160
+ for (let idx = 0; idx < this.code.Current.Argument; idx++) {
161
+ let node = this.dataStack.pop();
162
+ first.push(node);
163
+ second.push(node);
164
+ }
165
+
166
+ while (first.length) {
167
+ this.dataStack.push(first.pop());
168
+ }
169
+
170
+ while (second.length) {
171
+ this.dataStack.push(second.pop());
172
+ }
173
+ }
174
+
175
+ function handleSwapA() {
176
+ // Python 3.11+ SWAP opcode: swaps i-th item with TOS
177
+ // Argument specifies depth: 2 = swap top 2, 3 = swap TOS with 3rd, etc.
178
+ let depth = this.code.Current.Argument;
179
+
180
+ // Record pattern operation if in pattern matching
181
+ if (this.inMatchPattern) {
182
+ this.patternOps.push({
183
+ type: 'SWAP',
184
+ depth: depth
185
+ });
186
+ }
187
+
188
+ if (depth == 2) {
189
+ // SWAP 2: swap top two items
190
+ handleRotTwo.call(this);
191
+ } else if (depth == 3) {
192
+ // SWAP 3: swap TOS with third item
193
+ let one = this.dataStack.pop();
194
+ let two = this.dataStack.pop();
195
+ let three = this.dataStack.pop();
196
+
197
+ this.dataStack.push(one);
198
+ this.dataStack.push(two);
199
+ this.dataStack.push(three);
200
+ } else {
201
+ // General case: swap TOS with i-th item
202
+ let tos = this.dataStack.pop();
203
+ let index = this.dataStack.length - (depth - 1);
204
+ if (index >= 0) {
205
+ let temp = this.dataStack[index];
206
+ this.dataStack[index] = tos;
207
+ this.dataStack.push(temp);
208
+ }
209
+ }
210
+ }
211
+
212
+ function handleRotTwo() {
213
+ let one = this.dataStack.pop();
214
+ if (this.dataStack.top() instanceof AST.ASTChainStore) {
215
+ this.dataStack.pop();
216
+ }
217
+ let two = this.dataStack.pop();
218
+
219
+ this.dataStack.push(one);
220
+ this.dataStack.push(two);
221
+ }
222
+
223
+ function handleRotThree() {
224
+ let one = this.dataStack.pop();
225
+ let two = this.dataStack.pop();
226
+ if (this.dataStack.top() instanceof AST.ASTChainStore) {
227
+ this.dataStack.pop();
228
+ }
229
+ let three = this.dataStack.pop();
230
+ this.dataStack.push(one);
231
+ this.dataStack.push(three);
232
+ this.dataStack.push(two);
233
+ }
234
+
235
+ function handleRotFour() {
236
+ let one = this.dataStack.pop();
237
+ let two = this.dataStack.pop();
238
+ let three = this.dataStack.pop();
239
+ if (this.dataStack.top() instanceof AST.ASTChainStore) {
240
+ this.dataStack.pop();
241
+ }
242
+ let four = this.dataStack.pop();
243
+ this.dataStack.push(one);
244
+ this.dataStack.push(four);
245
+ this.dataStack.push(three);
246
+ this.dataStack.push(two);
247
+ }
248
+
249
+ function handlePushNull() {
250
+ this.dataStack.push(null);
251
+ }
252
+
253
+ function handleCache() {
254
+ /* These "fake" opcodes are used as placeholders for optimizing
255
+ certain opcodes in Python 3.11+. Since we have no need for
256
+ that during disassembly/decompilation, we can just treat these
257
+ as no-ops. */
258
+ }
259
+
260
+ function handleNop() {}
261
+
262
+ function handleStopCode() {
263
+ // Early Python STOP_CODE opcode: terminate processing of instruction stream.
264
+ this.code.CurrentInstructionIndex = this.code.Instructions.length;
265
+ }
266
+
267
+ module.exports = {
268
+ handleDupTop,
269
+ handleCopyA,
270
+ handleDupTopTwo,
271
+ handleDupTopxA,
272
+ handleSwapA,
273
+ handleRotTwo,
274
+ handleRotThree,
275
+ handleRotFour,
276
+ handlePushNull,
277
+ handleCache,
278
+ handleNop,
279
+ handleStopCode
280
+ };