depyo 1.0.2 → 1.1.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.
- package/depyo.js +43 -2
- package/lib/OpCodes.js +22 -5
- package/lib/PycDecompiler.js +1050 -40
- package/lib/PycDisassembler.js +1 -1
- package/lib/PycReader.js +22 -3
- package/lib/PythonObject.js +42 -6
- package/lib/ast/ast_node.js +381 -88
- 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/comparisons.js +3 -10
- package/lib/handlers/context_managers.js +202 -13
- package/lib/handlers/control_flow_jumps.js +516 -23
- package/lib/handlers/exceptions_blocks.js +92 -24
- package/lib/handlers/formatting.js +60 -17
- package/lib/handlers/function_calls.js +474 -58
- package/lib/handlers/function_class_build.js +170 -65
- 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 +253 -44
- 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 +2 -2
|
@@ -132,17 +132,18 @@ function extractWithPatternForFinally() {
|
|
|
132
132
|
|
|
133
133
|
if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.STORE_FAST_A) {
|
|
134
134
|
// Has "as variable"
|
|
135
|
-
|
|
135
|
+
if (thirdInstr.Name == null) {
|
|
136
|
+
throw new Error(`SETUP_FINALLY (Py2.7 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): STORE_FAST has null Name`);
|
|
137
|
+
}
|
|
138
|
+
asVariable = new AST.ASTName(thirdInstr.Name.toString());
|
|
136
139
|
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.POP_TOP) {
|
|
137
140
|
// No "as" clause
|
|
138
141
|
asVariable = null;
|
|
139
142
|
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.UNPACK_SEQUENCE_A) {
|
|
140
|
-
// Tuple unpacking: with expr as (x, y)
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
// Skip LOAD_FAST + DELETE_FAST + UNPACK_SEQUENCE + n STORE_FAST instructions
|
|
145
|
-
skipCount = 3 + (thirdInstr.Argument || 2);
|
|
143
|
+
// Tuple unpacking: `with expr as (x, y):` — reconstructing the
|
|
144
|
+
// tuple of names from UNPACK_SEQUENCE + STORE_FASTs requires
|
|
145
|
+
// forward instruction collection that this path doesn't yet do.
|
|
146
|
+
throw new Error(`SETUP_FINALLY (Py2.7 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): tuple-unpack 'as (...)' clauses not yet supported (UNPACK_SEQUENCE_A at offset ${thirdInstr.Offset})`);
|
|
146
147
|
} else {
|
|
147
148
|
if (global.g_cliArgs?.debug) {
|
|
148
149
|
console.log(`WITH pattern (Python 2.7) check failed: third=${thirdInstr?.InstructionName}`);
|
|
@@ -228,7 +229,10 @@ function extractWithPatternForFinally() {
|
|
|
228
229
|
// Check if this is variable assignment (WITH ... as var:)
|
|
229
230
|
if (instr.OpCodeID === this.OpCodes.STORE_FAST_A && i < 5) {
|
|
230
231
|
// Found variable assignment early in sequence
|
|
231
|
-
|
|
232
|
+
if (instr.Name == null) {
|
|
233
|
+
throw new Error(`SETUP_FINALLY (Py3 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): STORE_FAST at offset ${instr.Offset} has null Name`);
|
|
234
|
+
}
|
|
235
|
+
asVariable = new AST.ASTName(instr.Name.toString());
|
|
232
236
|
skipCount = i + 1;
|
|
233
237
|
foundBodyStart = true;
|
|
234
238
|
if (global.g_cliArgs?.debug) {
|
|
@@ -323,10 +327,14 @@ function extractContextManager() {
|
|
|
323
327
|
instr.OpCodeID === this.OpCodes.LOAD_GLOBAL_A ||
|
|
324
328
|
instr.OpCodeID === this.OpCodes.LOAD_NAME_A ||
|
|
325
329
|
instr.OpCodeID === this.OpCodes.LOAD_DEREF_A) {
|
|
326
|
-
|
|
330
|
+
if (instr.Name == null) {
|
|
331
|
+
throw new Error(`extractContextManager: ${instr.InstructionName} at offset ${instr.Offset} has null Name`);
|
|
332
|
+
}
|
|
333
|
+
ctxMgrExpr = new AST.ASTName(instr.Name.toString());
|
|
327
334
|
} else {
|
|
328
|
-
//
|
|
329
|
-
|
|
335
|
+
// CALL_FUNCTION/LOAD_ATTR/BINARY_SUBSCR: full expression
|
|
336
|
+
// reconstruction needs stack replay (not implemented here).
|
|
337
|
+
throw new Error(`extractContextManager: unsupported context manager opcode ${instr.InstructionName} at offset ${instr.Offset} — needs stack replay`);
|
|
330
338
|
}
|
|
331
339
|
break;
|
|
332
340
|
}
|
|
@@ -353,10 +361,25 @@ function handlePopBlock() {
|
|
|
353
361
|
return;
|
|
354
362
|
}
|
|
355
363
|
|
|
356
|
-
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
|
|
364
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
|
|
365
|
+
|| this.curBlock.blockType == AST.ASTBlock.BlockType.AsyncWith) {
|
|
357
366
|
// This should only be popped by a WITH_CLEANUP
|
|
358
367
|
if (global.g_cliArgs?.debug) {
|
|
359
|
-
console.log(`POP_BLOCK on
|
|
368
|
+
console.log(`POP_BLOCK on ${this.curBlock.type_str.toUpperCase()} block ignored at offset ${this.code.Current.Offset}`);
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Py2.x nested genexpr/comprehension: inner loop emits SETUP_LOOP/POP_BLOCK
|
|
374
|
+
// around FOR_ITER/JUMP_ABSOLUTE. FOR_ITER consumes the SETUP_LOOP's While and
|
|
375
|
+
// JUMP_ABSOLUTE pops the resulting For, leaving this POP_BLOCK phantom. If it
|
|
376
|
+
// runs here it pops the enclosing For+comprehension prematurely and orphans
|
|
377
|
+
// the ASTComprehension sitting on the data stack.
|
|
378
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.For
|
|
379
|
+
&& this.curBlock.comprehension
|
|
380
|
+
&& this.dataStack.top() instanceof AST.ASTComprehension) {
|
|
381
|
+
if (global.g_cliArgs?.debug) {
|
|
382
|
+
console.log(`POP_BLOCK ignored on For+comprehension at offset ${this.code.Current.Offset} (ASTComprehension on stack)`);
|
|
360
383
|
}
|
|
361
384
|
return;
|
|
362
385
|
}
|
|
@@ -413,7 +436,11 @@ function handlePopBlock() {
|
|
|
413
436
|
(tmp.blockType == AST.ASTBlock.BlockType.Try && !this.curBlock.hasExcept)
|
|
414
437
|
) {
|
|
415
438
|
|
|
416
|
-
|
|
439
|
+
// The Finally body actually starts at container.finally (the SETUP_FINALLY
|
|
440
|
+
// jump target), not at tmp.start which inherits the popped Else/Try offsets.
|
|
441
|
+
// End is left as 0 so closeEndedBlocks doesn't pop it before END_FINALLY runs.
|
|
442
|
+
let finalStart = this.curBlock.finally || tmp.start;
|
|
443
|
+
let final = new AST.ASTBlock(AST.ASTBlock.BlockType.Finally, finalStart, 0, true);
|
|
417
444
|
this.blocks.push(final);
|
|
418
445
|
this.curBlock = this.blocks.top();
|
|
419
446
|
}
|
|
@@ -450,7 +477,8 @@ function handlePopExcept() {
|
|
|
450
477
|
this.curBlock = this.defBlock || exceptBlock;
|
|
451
478
|
}
|
|
452
479
|
|
|
453
|
-
|
|
480
|
+
// exceptBlock.nodes returns the block's m_nodes array directly (getter with no setter).
|
|
481
|
+
let bodyNodes = exceptBlock.nodes;
|
|
454
482
|
|
|
455
483
|
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Try) {
|
|
456
484
|
const pendingEgType = this.egMatchTypeStack?.shift?.() || this.pendingEgMatchType;
|
|
@@ -475,7 +503,11 @@ function handlePopExcept() {
|
|
|
475
503
|
}
|
|
476
504
|
let catchBlock = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, exceptBlock.start, exceptBlock.end, matchType, false);
|
|
477
505
|
catchBlock.line = exceptBlock.line;
|
|
478
|
-
|
|
506
|
+
// Copy the handler body from the popped Except. `nodes` is getter-only,
|
|
507
|
+
// so mutate m_nodes directly — plain assignment here silently no-ops.
|
|
508
|
+
if (Array.isArray(bodyNodes) && bodyNodes.length) {
|
|
509
|
+
catchBlock.m_nodes.push(...bodyNodes);
|
|
510
|
+
}
|
|
479
511
|
catchBlock.isExceptStar = isExceptStar;
|
|
480
512
|
if (catchBlock.condition && typeof catchBlock.condition === 'object' && 'src' in catchBlock.condition) {
|
|
481
513
|
const storeSrc = catchBlock.condition.src;
|
|
@@ -563,6 +595,36 @@ function handleEndFinally() {
|
|
|
563
595
|
if (!this.curBlock.inited) {
|
|
564
596
|
console.error("Error when decompiling 'async for'.\n");
|
|
565
597
|
}
|
|
598
|
+
} else if (asyncForBlock.blockType == AST.ASTBlock.BlockType.AsyncFor && asyncForBlock.inited) {
|
|
599
|
+
// Initialized AsyncFor: STORE_FAST already set the index.
|
|
600
|
+
// Extract the loop body from the Container's try block,
|
|
601
|
+
// close the AsyncFor, and mark remaining handler as unreachable.
|
|
602
|
+
let tryBlock = container.nodes.find(n => n.blockType == AST.ASTBlock.BlockType.Try);
|
|
603
|
+
if (tryBlock && !tryBlock.nodes.empty()) {
|
|
604
|
+
let store = tryBlock.nodes.find(n => n instanceof AST.ASTStore);
|
|
605
|
+
let startIdx = store ? tryBlock.nodes.indexOf(store) + 1 : 0;
|
|
606
|
+
for (let i = startIdx; i < tryBlock.nodes.length; i++) {
|
|
607
|
+
let node = tryBlock.nodes[i];
|
|
608
|
+
if (node && node.constructor.name !== 'ASTReturn' &&
|
|
609
|
+
!(node.constructor.name === 'ASTCall' && node.func?.name === 'await')) {
|
|
610
|
+
asyncForBlock.nodes.push(node);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (asyncForBlock.nodes.length === 0) {
|
|
614
|
+
let passNode = new AST.ASTKeyword(AST.ASTKeyword.Word.Pass);
|
|
615
|
+
passNode.line = tryBlock.line;
|
|
616
|
+
asyncForBlock.nodes.push(passNode);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
let asyncForEnd = asyncForBlock.end;
|
|
620
|
+
this.blocks.pop();
|
|
621
|
+
this.blocks.top().append(asyncForBlock);
|
|
622
|
+
this.curBlock = this.blocks.top();
|
|
623
|
+
this.unreachableUntil = asyncForEnd;
|
|
624
|
+
if (global.g_cliArgs?.debug) {
|
|
625
|
+
console.log(`[handleEndFinally-Except] Closed initialized AsyncFor, unreachable until ${asyncForEnd}`);
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
566
628
|
} else {
|
|
567
629
|
this.blocks.push(container);
|
|
568
630
|
}
|
|
@@ -782,7 +844,10 @@ function handleRaiseVarargsA() {
|
|
|
782
844
|
for (let idx = 0; idx < this.code.Current.Argument; idx++) {
|
|
783
845
|
paramList.unshift(this.dataStack.pop());
|
|
784
846
|
}
|
|
785
|
-
|
|
847
|
+
const fromClause =
|
|
848
|
+
this.code.Current.Argument === 2 &&
|
|
849
|
+
this.object.Reader.versionCompare(3, 0) >= 0;
|
|
850
|
+
let node = new AST.ASTRaise(paramList, fromClause);
|
|
786
851
|
node.line = this.code.Current.LineNo;
|
|
787
852
|
this.curBlock.append(node);
|
|
788
853
|
|
|
@@ -919,13 +984,16 @@ function handleCheckEgMatch() {
|
|
|
919
984
|
}
|
|
920
985
|
|
|
921
986
|
function handlePrepReraiseStar() {
|
|
922
|
-
//
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
987
|
+
// CPython 3.11: pops orig + excs list, replaces with None-or-new-EG.
|
|
988
|
+
// The following POP_JUMP_IF_NOT_NONE/SWAP/POP_EXCEPT/RERAISE sequence is
|
|
989
|
+
// compiler-generated plumbing for `except*`, not user code. Mirror the
|
|
990
|
+
// 3.12 CALL_INTRINSIC_2(arg=1) handler: consume stack, suppress the
|
|
991
|
+
// next conditional, mark exception-group context.
|
|
992
|
+
this.dataStack.pop();
|
|
993
|
+
this.dataStack.pop();
|
|
994
|
+
this.ignoreNextConditional = true;
|
|
995
|
+
this.cleanupStackDepth = this.dataStack.length + 1;
|
|
996
|
+
this.dataStack.push(new AST.ASTNone());
|
|
929
997
|
this.inExceptionGroup = true;
|
|
930
998
|
}
|
|
931
999
|
|
|
@@ -1,26 +1,69 @@
|
|
|
1
1
|
const AST = require('../ast/ast_node');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
// PEP 750 t-strings (Python 3.14+):
|
|
4
|
+
// strings_tuple ; (value ; str_repr ; [format_spec])+ ; BUILD_INTERPOLATION oparg
|
|
5
|
+
// ... ; BUILD_TUPLE N ; BUILD_TEMPLATE -> Template
|
|
6
|
+
// BUILD_INTERPOLATION oparg layout:
|
|
7
|
+
// oparg & 3 = pop count (2 = no format spec, 3 = has format spec)
|
|
8
|
+
// (oparg >> 2) & 3 = conversion (0=none, 1=!s, 2=!r, 3=!a)
|
|
9
|
+
function handleBuildInterpolationA() {
|
|
10
|
+
const oparg = this.code.Current.Argument || 0;
|
|
11
|
+
const popCount = oparg & 3;
|
|
12
|
+
const convCode = (oparg >> 2) & 3;
|
|
13
|
+
|
|
14
|
+
let formatSpec = null;
|
|
15
|
+
if (popCount === 3) {
|
|
16
|
+
formatSpec = this.dataStack.pop();
|
|
9
17
|
}
|
|
10
|
-
//
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
this.dataStack.pop(); // discard str representation (used at runtime)
|
|
19
|
+
const value = this.dataStack.pop();
|
|
20
|
+
|
|
21
|
+
const conversion = convCode === 0 ? AST.ASTFormattedValue.ConversionFlag.None
|
|
22
|
+
: convCode === 1 ? AST.ASTFormattedValue.ConversionFlag.Str
|
|
23
|
+
: convCode === 2 ? AST.ASTFormattedValue.ConversionFlag.Repr
|
|
24
|
+
: AST.ASTFormattedValue.ConversionFlag.ASCII;
|
|
25
|
+
|
|
26
|
+
const node = new AST.ASTFormattedValue(value, conversion, formatSpec);
|
|
27
|
+
node.line = this.code.Current.LineNo;
|
|
28
|
+
this.dataStack.push(node);
|
|
14
29
|
}
|
|
15
30
|
|
|
16
|
-
function
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
function handleBuildTemplate() {
|
|
32
|
+
// Pops (strings_tuple, interpolations_tuple) and produces a Template node.
|
|
33
|
+
const interpolations = this.dataStack.pop();
|
|
34
|
+
const strings = this.dataStack.pop();
|
|
35
|
+
|
|
36
|
+
let interpVals = [];
|
|
37
|
+
if (interpolations instanceof AST.ASTTuple) {
|
|
38
|
+
interpVals = interpolations.values || [];
|
|
39
|
+
} else if (interpolations?.object?.Value) {
|
|
40
|
+
interpVals = interpolations.object.Value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// strings is loaded via LOAD_CONST as a Py_Tuple of Py_String objects
|
|
44
|
+
let stringVals = [];
|
|
45
|
+
if (strings instanceof AST.ASTTuple) {
|
|
46
|
+
stringVals = strings.values || [];
|
|
47
|
+
} else if (strings?.object?.Value) {
|
|
48
|
+
// Wrap each Py_String in an ASTObject so ASTJoinedStr.codeFragment can render it.
|
|
49
|
+
stringVals = strings.object.Value.map(pyStr => new AST.ASTObject(pyStr));
|
|
22
50
|
}
|
|
23
|
-
|
|
51
|
+
|
|
52
|
+
// Interleave: str[0], interp[0], str[1], interp[1], ..., str[N]
|
|
53
|
+
const pieces = [];
|
|
54
|
+
for (let i = 0; i < stringVals.length; i++) {
|
|
55
|
+
if (stringVals[i]) {
|
|
56
|
+
pieces.push(stringVals[i]);
|
|
57
|
+
}
|
|
58
|
+
if (i < interpVals.length) {
|
|
59
|
+
pieces.push(interpVals[i]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ASTJoinedStr's codeFragment reverses (BUILD_STRING pops in reverse). Compensate.
|
|
64
|
+
pieces.reverse();
|
|
65
|
+
const joined = new AST.ASTJoinedStr(pieces);
|
|
66
|
+
joined.isTemplateString = true;
|
|
24
67
|
joined.line = this.code.Current.LineNo;
|
|
25
68
|
this.dataStack.push(joined);
|
|
26
69
|
}
|