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,250 @@
1
+ const AST = require('../ast/ast_node');
2
+
3
+ /**
4
+ * Helper function to extract WITH statement pattern from bytecode
5
+ * Pattern (Python 2.6-3.1 with SETUP_FINALLY, Python 3.2+ with SETUP_WITH):
6
+ *
7
+ * LOAD_* (context manager)
8
+ * DUP_TOP
9
+ * LOAD_ATTR (__exit__)
10
+ * STORE_FAST (temp var)
11
+ * LOAD_ATTR (__enter__)
12
+ * CALL_FUNCTION 0
13
+ * STORE_FAST (temp var 2)
14
+ * SETUP_FINALLY/SETUP_WITH <- we are here
15
+ * LOAD_FAST (temp var 2)
16
+ * DELETE_FAST (temp var 2)
17
+ * [STORE_FAST (variable) | POP_TOP] <- variable name or no "as" clause
18
+ */
19
+ function extractWithPattern() {
20
+ // Look ahead to confirm this is a WITH pattern
21
+ let nextInstr = this.code.Next;
22
+ if (!nextInstr) return null;
23
+
24
+ // After SETUP_FINALLY/SETUP_WITH, should have LOAD_FAST + DELETE_FAST
25
+ if (nextInstr.OpCodeID !== this.OpCodes.LOAD_FAST_A) return null;
26
+
27
+ let secondInstr = this.code.PeekInstructionAtOffset(nextInstr.Offset + 3);
28
+ if (!secondInstr || secondInstr.OpCodeID !== this.OpCodes.DELETE_FAST_A) return null;
29
+
30
+ // Third instruction determines if there's an "as" clause
31
+ let thirdInstr = this.code.PeekInstructionAtOffset(secondInstr.Offset + 3);
32
+ let asVariable = null;
33
+
34
+ if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.STORE_FAST_A) {
35
+ // Has "as variable"
36
+ asVariable = new AST.ASTName(thirdInstr.Name?.toString() || "###FIXME###");
37
+ } else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.POP_TOP) {
38
+ // No "as" clause
39
+ asVariable = null;
40
+ } else {
41
+ return null; // Not a WITH pattern
42
+ }
43
+
44
+ // Look backward for the context manager expression
45
+ let ctxMgrExpr = null;
46
+
47
+ // Walk back to find LOAD_* instruction (context manager)
48
+ // Scan by instruction index (not offset - instruction sizes vary!)
49
+ for (let idx = this.code.CurrentInstructionIndex - 1; idx >= 0; idx--) {
50
+ let instr = this.code.Instructions[idx];
51
+ if (!instr) continue;
52
+
53
+ if ([this.OpCodes.LOAD_FAST_A, this.OpCodes.LOAD_GLOBAL_A, this.OpCodes.LOAD_NAME_A,
54
+ this.OpCodes.LOAD_DEREF_A, this.OpCodes.CALL_FUNCTION, this.OpCodes.CALL_FUNCTION_A,
55
+ this.OpCodes.BINARY_SUBSCR, this.OpCodes.LOAD_ATTR_A].includes(instr.OpCodeID)) {
56
+
57
+ // Check if next instruction (by index) is DUP_TOP
58
+ let afterLoad = this.code.Instructions[idx + 1];
59
+ if (afterLoad && afterLoad.OpCodeID === this.OpCodes.DUP_TOP) {
60
+ // Found it! Create AST node for the context manager
61
+ if (instr.OpCodeID === this.OpCodes.LOAD_FAST_A ||
62
+ instr.OpCodeID === this.OpCodes.LOAD_GLOBAL_A ||
63
+ instr.OpCodeID === this.OpCodes.LOAD_NAME_A ||
64
+ instr.OpCodeID === this.OpCodes.LOAD_DEREF_A) {
65
+ ctxMgrExpr = new AST.ASTName(instr.Name?.toString() || "###FIXME###");
66
+ }else {
67
+ // For CALL_FUNCTION, LOAD_ATTR, etc., we can't easily reconstruct
68
+ // the expression without replaying the stack. Use placeholder for now.
69
+ ctxMgrExpr = new AST.ASTName("###FIXME###");
70
+ }
71
+ break;
72
+ }
73
+ }
74
+ }
75
+
76
+ return {
77
+ contextManager: ctxMgrExpr,
78
+ asVariable: asVariable
79
+ };
80
+ }
81
+
82
+ function handleSetupWithA() {
83
+ let pattern = extractWithPattern.call(this);
84
+
85
+ // Find the WITH_CLEANUP instruction to determine the actual end of the WITH block
86
+ let withEnd = this.code.Current.JumpTarget;
87
+ let searchInstr = this.code.PeekInstructionAtOffset(withEnd);
88
+ for (let i = 0; i < 10 && searchInstr; i++) {
89
+ if (searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP ||
90
+ searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_START ||
91
+ searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_FINISH) {
92
+ withEnd = searchInstr.Offset;
93
+ break;
94
+ }
95
+ searchInstr = this.code.PeekInstructionAtOffset(searchInstr.Offset + 3);
96
+ }
97
+
98
+ let withBlock = new AST.ASTWithBlock(this.code.Current.Offset, withEnd);
99
+
100
+ if (pattern) {
101
+ withBlock.expr = pattern.contextManager;
102
+ withBlock.var = pattern.asVariable;
103
+
104
+ // Skip the pattern instructions (LOAD_FAST, DELETE_FAST, STORE_FAST/POP_TOP)
105
+ // These were already extracted into withBlock.expr and withBlock.var
106
+ this.code.GoNext(); // Skip LOAD_FAST
107
+ this.code.GoNext(); // Skip DELETE_FAST
108
+ this.code.GoNext(); // Skip STORE_FAST or POP_TOP
109
+ }
110
+
111
+ this.blocks.push(withBlock);
112
+ this.curBlock = this.blocks.top();
113
+ }
114
+
115
+ function handleWithCleanupStart() {
116
+ handleWithCleanup.call(this);
117
+ }
118
+
119
+ function handleWithCleanup() {
120
+ // Stack top should be a None. Ignore it.
121
+ let none = this.dataStack.pop();
122
+
123
+ if (global.g_cliArgs?.debug) {
124
+ console.log(`WITH_CLEANUP at offset ${this.code.Current.Offset}, curBlock=${this.curBlock.type_str}, end=${this.curBlock.end}, stackTop=${none?.constructor.name}`);
125
+ }
126
+
127
+ if (!(none instanceof AST.ASTNone)) {
128
+ if (global.g_cliArgs?.debug) {
129
+ console.error("Something TERRIBLE happened!\n");
130
+ }
131
+ return;
132
+ }
133
+
134
+ if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
135
+ && this.curBlock.end == this.code.Current.Offset) {
136
+ let withBlock = this.curBlock;
137
+ this.blocks.pop(); // Remove WITH block from stack
138
+ this.curBlock = this.blocks.top(); // Get parent block
139
+ this.curBlock.append(withBlock);
140
+
141
+ if (global.g_cliArgs?.debug) {
142
+ console.log(`WITH block closed successfully, nodes count=${withBlock.nodes.length}, expr=${withBlock.expr?.name}, var=${withBlock.var?.name}`);
143
+ console.log(` Added to ${this.curBlock.type_str} block`);
144
+ }
145
+ }
146
+ else {
147
+ if (global.g_cliArgs?.debug) {
148
+ console.error(`WITH_CLEANUP mismatch: curBlock.type=${this.curBlock.type_str}, curBlock.end=${this.curBlock.end}, currentOffset=${this.code.Current.Offset}`);
149
+ console.error(`Something TERRIBLE happened! No matching with block found for WITH_CLEANUP at ${this.code.Current.Offset}\n`);
150
+ }
151
+ }
152
+ }
153
+
154
+ function handleWithCleanupFinish() {
155
+ /* Ignore this */
156
+ }
157
+
158
+ /**
159
+ * Handler for BEFORE_WITH opcode (Python 3.11+)
160
+ *
161
+ * In Python 3.11+, context managers use BEFORE_WITH instead of SETUP_WITH.
162
+ * BEFORE_WITH:
163
+ * - Pops the context manager from the stack
164
+ * - Calls __enter__() on it
165
+ * - Pushes the result (which the next STORE_* will capture as the 'as' variable)
166
+ *
167
+ * The with block boundaries come from the exception table, not from jump targets.
168
+ */
169
+ function handleBeforeWith() {
170
+ // Pop the context manager from the stack (result of CALL opcode)
171
+ let ctxMgr = this.dataStack.pop();
172
+
173
+ if (global.g_cliArgs?.debug) {
174
+ console.log(`[BEFORE_WITH] at offset ${this.code.Current.Offset}, ctxMgr=${ctxMgr?.constructor.name}`);
175
+ }
176
+
177
+ // Find the with block end from exception table
178
+ // In Python 3.11+, the exception table entry for with statements has depth=1
179
+ // and covers the range from STORE_* (after BEFORE_WITH) to the end of the body
180
+ let withEnd = this.code.LastOffset;
181
+ const exceptionTable = this.object.ExceptionTable || [];
182
+ const currentOffset = this.code.Current.Offset;
183
+
184
+ // Find the exception table entry for this with statement
185
+ // It should start at the STORE_* instruction that follows BEFORE_WITH
186
+ let witchExcTableEntry = null;
187
+ for (const entry of exceptionTable) {
188
+ // Look for entry that starts right after BEFORE_WITH
189
+ // The entry.start is typically the STORE_* instruction offset
190
+ if (entry.start > currentOffset && entry.start <= currentOffset + 10) {
191
+ // Found the exception table entry for this with block
192
+ // The 'end' points to the first instruction AFTER the protected body
193
+ // We use this directly since the pre-handler WITH close logic will
194
+ // close the block when we REACH this offset (before processing)
195
+ withEnd = entry.end || withEnd;
196
+ witchExcTableEntry = entry;
197
+ if (global.g_cliArgs?.debug) {
198
+ console.log(`[BEFORE_WITH] Found exception table entry: start=${entry.start}, end=${entry.end}, withEnd=${withEnd}, target=${entry.target}`);
199
+ }
200
+ break;
201
+ }
202
+ }
203
+
204
+ // Mark this exception table entry as belonging to a with statement
205
+ // This prevents it from being treated as a regular exception handler
206
+ if (witchExcTableEntry) {
207
+ witchExcTableEntry._isWithStatement = true;
208
+ }
209
+
210
+ // Create the WITH block (uninitialized - processStore will set expr and var)
211
+ let withBlock = new AST.ASTWithBlock(currentOffset, withEnd);
212
+ // Don't set expr here - let processStore do it when it handles the STORE_* instruction
213
+ // This is important because processStore checks for uninitialized WITH blocks
214
+
215
+ // Push the __enter__ result to the stack
216
+ // This will be consumed by the subsequent STORE_* instruction (for 'as' variable)
217
+ // The processStore function will pop this and set withBlock.expr and withBlock.var
218
+ this.dataStack.push(ctxMgr);
219
+
220
+ // Push the with block onto the block stack
221
+ this.blocks.push(withBlock);
222
+ this.curBlock = this.blocks.top();
223
+
224
+ if (global.g_cliArgs?.debug) {
225
+ console.log(`[BEFORE_WITH] Created WITH block (uninitialized), end=${withEnd}, ctxMgr=${ctxMgr?.codeFragment?.()}`);
226
+ }
227
+ }
228
+
229
+ function handleBeforeAsyncWith() {
230
+ let ctxmgr = this.dataStack.top();
231
+ let callNode = new AST.ASTCall(new AST.ASTName('await'), [new AST.ASTBinary(ctxmgr, new AST.ASTName('__aenter__'), AST.ASTBinary.BinOp.Attr)], []);
232
+ callNode.line = this.code.Current.LineNo;
233
+ this.dataStack.push(callNode);
234
+ }
235
+
236
+ function handleSetupAsyncWithA() {
237
+ let asyncWithBlock = new AST.ASTBlock(AST.ASTBlock.BlockType.AsyncWith, this.code.Current.Offset, this.code.Current.JumpTarget);
238
+ this.blocks.push(asyncWithBlock);
239
+ this.curBlock = this.blocks.top();
240
+ }
241
+
242
+ module.exports = {
243
+ handleBeforeWith,
244
+ handleBeforeAsyncWith,
245
+ handleSetupWithA,
246
+ handleWithCleanupStart,
247
+ handleWithCleanup,
248
+ handleWithCleanupFinish,
249
+ handleSetupAsyncWithA
250
+ };