depyo 1.0.2 → 1.0.3
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.
- package/depyo.js +43 -2
- package/lib/OpCodes.js +22 -5
- package/lib/PycDecompiler.js +402 -39
- package/lib/PycDisassembler.js +1 -1
- package/lib/PycReader.js +22 -3
- package/lib/PythonObject.js +40 -6
- package/lib/ast/ast_node.js +292 -71
- package/lib/bytecode/python_3_0.js +1 -1
- package/lib/bytecode/python_3_12.js +1 -1
- package/lib/bytecode/python_3_13.js +13 -13
- package/lib/bytecode/python_3_14.js +13 -13
- package/lib/bytecode/python_3_15.js +183 -0
- package/lib/code_reader.js +107 -146
- package/lib/handlers/collections_update.js +50 -1
- package/lib/handlers/context_managers.js +202 -13
- package/lib/handlers/control_flow_jumps.js +516 -23
- package/lib/handlers/exceptions_blocks.js +85 -22
- package/lib/handlers/formatting.js +60 -17
- package/lib/handlers/function_calls.js +454 -57
- package/lib/handlers/function_class_build.js +159 -64
- package/lib/handlers/generators_async.js +67 -0
- package/lib/handlers/load_store_names.js +190 -57
- package/lib/handlers/loop_iterator.js +162 -6
- package/lib/handlers/misc_other.js +216 -43
- package/lib/handlers/stack_ops.js +81 -19
- package/lib/handlers/subscript_slice.js +103 -1
- package/lib/handlers/unpack.js +18 -16
- package/package.json +1 -1
|
@@ -33,7 +33,10 @@ function extractWithPattern() {
|
|
|
33
33
|
|
|
34
34
|
if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.STORE_FAST_A) {
|
|
35
35
|
// Has "as variable"
|
|
36
|
-
|
|
36
|
+
if (thirdInstr.Name == null) {
|
|
37
|
+
throw new Error(`SETUP_WITH at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): STORE_FAST has null Name (expected "as" variable)`);
|
|
38
|
+
}
|
|
39
|
+
asVariable = new AST.ASTName(thirdInstr.Name.toString());
|
|
37
40
|
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.POP_TOP) {
|
|
38
41
|
// No "as" clause
|
|
39
42
|
asVariable = null;
|
|
@@ -62,11 +65,14 @@ function extractWithPattern() {
|
|
|
62
65
|
instr.OpCodeID === this.OpCodes.LOAD_GLOBAL_A ||
|
|
63
66
|
instr.OpCodeID === this.OpCodes.LOAD_NAME_A ||
|
|
64
67
|
instr.OpCodeID === this.OpCodes.LOAD_DEREF_A) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
if (instr.Name == null) {
|
|
69
|
+
throw new Error(`SETUP_WITH at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): ${instr.InstructionName} at offset ${instr.Offset} has null Name`);
|
|
70
|
+
}
|
|
71
|
+
ctxMgrExpr = new AST.ASTName(instr.Name.toString());
|
|
72
|
+
} else {
|
|
73
|
+
// CALL_FUNCTION/LOAD_ATTR/BINARY_SUBSCR: reconstructing the
|
|
74
|
+
// expression without replaying the stack isn't supported yet.
|
|
75
|
+
throw new Error(`SETUP_WITH at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): unsupported context manager opcode ${instr.InstructionName} at offset ${instr.Offset} — needs stack replay`);
|
|
70
76
|
}
|
|
71
77
|
break;
|
|
72
78
|
}
|
|
@@ -117,6 +123,102 @@ function handleWithCleanupStart() {
|
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
function handleWithCleanup() {
|
|
126
|
+
// Async-with closure runs first because the 3.8+ pattern uses BEGIN_FINALLY
|
|
127
|
+
// (no-op handler) instead of LOAD_CONST None — so the data stack may be
|
|
128
|
+
// empty when WITH_CLEANUP_START fires. Don't gate on the None pop here.
|
|
129
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.AsyncWith
|
|
130
|
+
&& this.curBlock.end == this.code.Current.Offset) {
|
|
131
|
+
let asyncWithBlock = this.curBlock;
|
|
132
|
+
|
|
133
|
+
// Skip the standard 4-op cleanup epilogue:
|
|
134
|
+
// GET_AWAITABLE, LOAD_CONST None, YIELD_FROM, WITH_CLEANUP_FINISH
|
|
135
|
+
let standardEpilogue = [
|
|
136
|
+
this.OpCodes.GET_AWAITABLE,
|
|
137
|
+
this.OpCodes.LOAD_CONST_A,
|
|
138
|
+
this.OpCodes.YIELD_FROM,
|
|
139
|
+
this.OpCodes.WITH_CLEANUP_FINISH,
|
|
140
|
+
];
|
|
141
|
+
for (let expectedOp of standardEpilogue) {
|
|
142
|
+
if (this.code.Next?.OpCodeID === expectedOp) {
|
|
143
|
+
this.code.GoNext();
|
|
144
|
+
} else {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// After the standard epilogue, two patterns are possible (3.8+):
|
|
150
|
+
// Normal exit: END_FINALLY
|
|
151
|
+
// Early return path: POP_FINALLY 0 / LOAD_CONST X / RETURN_VALUE
|
|
152
|
+
// / WITH_CLEANUP_START / GET_AWAITABLE / LOAD_CONST None
|
|
153
|
+
// / YIELD_FROM / WITH_CLEANUP_FINISH / END_FINALLY
|
|
154
|
+
// [/ LOAD_CONST None / RETURN_VALUE -- dead implicit return]
|
|
155
|
+
// For the early-return case we synthesize the explicit return into the body
|
|
156
|
+
// and skip the trailing exception handler + dead implicit return.
|
|
157
|
+
let after = this.code.Next;
|
|
158
|
+
if (after?.OpCodeID === this.OpCodes.POP_FINALLY_A
|
|
159
|
+
|| after?.OpCodeID === this.OpCodes.POP_FINALLY) {
|
|
160
|
+
this.code.GoNext();
|
|
161
|
+
|
|
162
|
+
let loadConst = this.code.Next;
|
|
163
|
+
let returnValue = this.code.PeekNextInstruction(2);
|
|
164
|
+
if (loadConst?.OpCodeID === this.OpCodes.LOAD_CONST_A
|
|
165
|
+
&& returnValue?.OpCodeID === this.OpCodes.RETURN_VALUE) {
|
|
166
|
+
let constObj = new AST.ASTObject(loadConst.ConstantObject);
|
|
167
|
+
constObj.line = loadConst.LineNo;
|
|
168
|
+
let retValue;
|
|
169
|
+
if (constObj.object == null || constObj.object.ClassName == "Py_None") {
|
|
170
|
+
retValue = new AST.ASTNone();
|
|
171
|
+
} else {
|
|
172
|
+
retValue = constObj;
|
|
173
|
+
}
|
|
174
|
+
let retNode = new AST.ASTReturn(retValue);
|
|
175
|
+
retNode.line = loadConst.LineNo;
|
|
176
|
+
asyncWithBlock.append(retNode);
|
|
177
|
+
this.code.GoNext();
|
|
178
|
+
this.code.GoNext();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let exceptionHandler = [
|
|
182
|
+
this.OpCodes.WITH_CLEANUP_START,
|
|
183
|
+
this.OpCodes.GET_AWAITABLE,
|
|
184
|
+
this.OpCodes.LOAD_CONST_A,
|
|
185
|
+
this.OpCodes.YIELD_FROM,
|
|
186
|
+
this.OpCodes.WITH_CLEANUP_FINISH,
|
|
187
|
+
this.OpCodes.END_FINALLY,
|
|
188
|
+
];
|
|
189
|
+
for (let expectedOp of exceptionHandler) {
|
|
190
|
+
if (this.code.Next?.OpCodeID === expectedOp) {
|
|
191
|
+
this.code.GoNext();
|
|
192
|
+
} else {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Skip the dead implicit LOAD_CONST None / RETURN_VALUE if it's
|
|
198
|
+
// the very tail of the function (no more instructions after).
|
|
199
|
+
let load = this.code.Next;
|
|
200
|
+
let ret = this.code.PeekNextInstruction(2);
|
|
201
|
+
let beyond = this.code.PeekNextInstruction(3);
|
|
202
|
+
if (load?.OpCodeID === this.OpCodes.LOAD_CONST_A
|
|
203
|
+
&& ret?.OpCodeID === this.OpCodes.RETURN_VALUE
|
|
204
|
+
&& beyond == null) {
|
|
205
|
+
this.code.GoNext();
|
|
206
|
+
this.code.GoNext();
|
|
207
|
+
}
|
|
208
|
+
} else if (after?.OpCodeID === this.OpCodes.END_FINALLY) {
|
|
209
|
+
this.code.GoNext();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.blocks.pop();
|
|
213
|
+
this.curBlock = this.blocks.top();
|
|
214
|
+
this.curBlock.append(asyncWithBlock);
|
|
215
|
+
|
|
216
|
+
if (global.g_cliArgs?.debug) {
|
|
217
|
+
console.log(`ASYNC WITH block closed, nodes count=${asyncWithBlock.nodes.length}`);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
120
222
|
// Stack top should be a None. Ignore it.
|
|
121
223
|
let none = this.dataStack.pop();
|
|
122
224
|
|
|
@@ -226,19 +328,106 @@ function handleBeforeWith() {
|
|
|
226
328
|
}
|
|
227
329
|
}
|
|
228
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Handler for BEFORE_ASYNC_WITH (Python 3.5-3.10).
|
|
333
|
+
*
|
|
334
|
+
* Bytecode pattern for `async with EXPR [as VAR]:`:
|
|
335
|
+
* LOAD_* EXPR (context manager)
|
|
336
|
+
* BEFORE_ASYNC_WITH <-- we are here
|
|
337
|
+
* GET_AWAITABLE
|
|
338
|
+
* LOAD_CONST None
|
|
339
|
+
* YIELD_FROM
|
|
340
|
+
* SETUP_ASYNC_WITH +N (N targets WITH_CLEANUP_START — end of body)
|
|
341
|
+
* POP_TOP | STORE_FAST VAR
|
|
342
|
+
* ... body ...
|
|
343
|
+
* POP_BLOCK
|
|
344
|
+
* LOAD_CONST None
|
|
345
|
+
* WITH_CLEANUP_START (closes block, see handleWithCleanup)
|
|
346
|
+
* GET_AWAITABLE
|
|
347
|
+
* LOAD_CONST None
|
|
348
|
+
* YIELD_FROM
|
|
349
|
+
* WITH_CLEANUP_FINISH
|
|
350
|
+
* END_FINALLY
|
|
351
|
+
*
|
|
352
|
+
* We pop the context manager off the data stack, then use GoNext to consume
|
|
353
|
+
* the entire setup epilogue. This bypasses the GET_AWAITABLE / YIELD_FROM
|
|
354
|
+
* handlers (which would otherwise emit `await await(EXPR.__aenter__)` noise)
|
|
355
|
+
* and creates a clean ASTAsyncWithBlock for the body.
|
|
356
|
+
*/
|
|
229
357
|
function handleBeforeAsyncWith() {
|
|
230
|
-
let
|
|
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
|
-
}
|
|
358
|
+
let ctxMgr = this.dataStack.pop();
|
|
235
359
|
|
|
236
|
-
|
|
237
|
-
let
|
|
360
|
+
let getAwaitable = this.code.PeekNextInstruction(1);
|
|
361
|
+
let loadNone = this.code.PeekNextInstruction(2);
|
|
362
|
+
let yieldFrom = this.code.PeekNextInstruction(3);
|
|
363
|
+
let setupAsyncWith = this.code.PeekNextInstruction(4);
|
|
364
|
+
|
|
365
|
+
if (!getAwaitable || getAwaitable.OpCodeID !== this.OpCodes.GET_AWAITABLE
|
|
366
|
+
|| !loadNone || loadNone.OpCodeID !== this.OpCodes.LOAD_CONST_A
|
|
367
|
+
|| !yieldFrom || yieldFrom.OpCodeID !== this.OpCodes.YIELD_FROM
|
|
368
|
+
|| !setupAsyncWith || setupAsyncWith.OpCodeID !== this.OpCodes.SETUP_ASYNC_WITH_A) {
|
|
369
|
+
throw new Error(`BEFORE_ASYNC_WITH at offset ${this.code.Current.Offset}: expected GET_AWAITABLE/LOAD_CONST/YIELD_FROM/SETUP_ASYNC_WITH sequence, got ${getAwaitable?.InstructionName}/${loadNone?.InstructionName}/${yieldFrom?.InstructionName}/${setupAsyncWith?.InstructionName}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let asClause = this.code.PeekNextInstruction(5);
|
|
373
|
+
let asVar = null;
|
|
374
|
+
let extraSkip = 0;
|
|
375
|
+
if (asClause?.OpCodeID === this.OpCodes.STORE_FAST_A) {
|
|
376
|
+
if (asClause.Name == null) {
|
|
377
|
+
throw new Error(`BEFORE_ASYNC_WITH at offset ${this.code.Current.Offset}: STORE_FAST has null Name (expected "as" variable)`);
|
|
378
|
+
}
|
|
379
|
+
asVar = new AST.ASTName(asClause.Name.toString());
|
|
380
|
+
extraSkip = 1;
|
|
381
|
+
} else if (asClause?.OpCodeID === this.OpCodes.POP_TOP) {
|
|
382
|
+
extraSkip = 1;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Walk forward from the body start to find the matching WITH_CLEANUP_START
|
|
386
|
+
// for THIS async-with (not a nested one). Track depth so nested
|
|
387
|
+
// SETUP_WITH/SETUP_ASYNC_WITH are skipped over correctly.
|
|
388
|
+
//
|
|
389
|
+
// Note: SETUP_ASYNC_WITH.JumpTarget is unreliable here — for the early-return
|
|
390
|
+
// pattern it points to the exception-path cleanup, not the normal exit.
|
|
391
|
+
let blockEnd = setupAsyncWith.JumpTarget;
|
|
392
|
+
let depth = 0;
|
|
393
|
+
let searchIdx = this.code.CurrentInstructionIndex + 5 + extraSkip;
|
|
394
|
+
for (let i = searchIdx; i < this.code.Instructions.length; i++) {
|
|
395
|
+
let instr = this.code.Instructions[i];
|
|
396
|
+
if (!instr) break;
|
|
397
|
+
if (instr.OpCodeID === this.OpCodes.SETUP_ASYNC_WITH_A
|
|
398
|
+
|| instr.OpCodeID === this.OpCodes.SETUP_WITH_A) {
|
|
399
|
+
depth++;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (instr.OpCodeID === this.OpCodes.WITH_CLEANUP_START
|
|
403
|
+
|| instr.OpCodeID === this.OpCodes.WITH_CLEANUP) {
|
|
404
|
+
if (depth === 0) {
|
|
405
|
+
blockEnd = instr.Offset;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
depth--;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Skip GET_AWAITABLE, LOAD_CONST None, YIELD_FROM, SETUP_ASYNC_WITH, [POP_TOP|STORE_FAST]
|
|
413
|
+
this.code.GoNext(4 + extraSkip);
|
|
414
|
+
|
|
415
|
+
let asyncWithBlock = new AST.ASTAsyncWithBlock(this.code.Current.Offset, blockEnd);
|
|
416
|
+
asyncWithBlock.expr = ctxMgr;
|
|
417
|
+
asyncWithBlock.var = asVar;
|
|
418
|
+
asyncWithBlock.init();
|
|
238
419
|
this.blocks.push(asyncWithBlock);
|
|
239
420
|
this.curBlock = this.blocks.top();
|
|
240
421
|
}
|
|
241
422
|
|
|
423
|
+
function handleSetupAsyncWithA() {
|
|
424
|
+
// Normally consumed by handleBeforeAsyncWith via GoNext. If we land here,
|
|
425
|
+
// the lookahead failed — bail silently rather than pushing a malformed block.
|
|
426
|
+
if (global.g_cliArgs?.debug) {
|
|
427
|
+
console.error(`SETUP_ASYNC_WITH at offset ${this.code.Current.Offset}: reached without preceding BEFORE_ASYNC_WITH consumption — ignoring`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
242
431
|
module.exports = {
|
|
243
432
|
handleBeforeWith,
|
|
244
433
|
handleBeforeAsyncWith,
|