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
|
@@ -60,8 +60,24 @@ function handleMakeFunctionA() {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function handleMakeCellA() {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
// In 3.11+ the argument is an index into localsplus ([locals | cells | frees]).
|
|
64
|
+
// Prefer the already-computed FreeName when available; otherwise fall back to
|
|
65
|
+
// VarNames + CellVars + FreeVars lookup.
|
|
66
|
+
const arg = this.code.Current.Argument;
|
|
67
|
+
let name = this.code.Current.FreeName;
|
|
68
|
+
if (!name) {
|
|
69
|
+
const varNames = this.object?.VarNames?.Value || [];
|
|
70
|
+
const cellVars = this.object?.CellVars?.Value || [];
|
|
71
|
+
const freeVars = this.object?.FreeVars?.Value || [];
|
|
72
|
+
if (arg < varNames.length) {
|
|
73
|
+
name = varNames[arg]?.toString?.();
|
|
74
|
+
} else if (arg - varNames.length < cellVars.length) {
|
|
75
|
+
name = cellVars[arg - varNames.length]?.toString?.();
|
|
76
|
+
} else if (arg - varNames.length - cellVars.length < freeVars.length) {
|
|
77
|
+
name = freeVars[arg - varNames.length - cellVars.length]?.toString?.();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!name) name = `##cell_${arg}##`;
|
|
65
81
|
this.dataStack.push(new AST.ASTName(name));
|
|
66
82
|
}
|
|
67
83
|
|
|
@@ -76,11 +92,15 @@ function handleSetFunctionAttributeA() {
|
|
|
76
92
|
const pairs = tupleNode?.values || [];
|
|
77
93
|
for (let i = 0; i + 1 < pairs.length; i += 2) {
|
|
78
94
|
const keyNode = pairs[i];
|
|
79
|
-
|
|
95
|
+
let valNode = pairs[i + 1];
|
|
80
96
|
let keyStr = keyNode?.object?.Value || keyNode?.name || keyNode?.codeFragment?.()?.toString?.() || keyNode?.toString?.() || '';
|
|
81
97
|
if (typeof keyStr === 'string') {
|
|
82
98
|
keyStr = keyStr.replace(/^['"]|['"]$/g, "");
|
|
83
99
|
}
|
|
100
|
+
// Strip spurious parens around tuple subscripts (e.g. tuple[(T, U)] → tuple[T, U]).
|
|
101
|
+
if (valNode instanceof AST.ASTSubscr && valNode.key instanceof AST.ASTTuple) {
|
|
102
|
+
valNode.key.m_requireParens = false;
|
|
103
|
+
}
|
|
84
104
|
if (keyStr) {
|
|
85
105
|
annotations[keyStr] = valNode;
|
|
86
106
|
}
|
|
@@ -138,87 +158,162 @@ function processMakeFunction() {
|
|
|
138
158
|
|
|
139
159
|
let decompiler = new PycDecompiler(func_code.object);
|
|
140
160
|
func_code.object.SourceCode = decompiler.decompile();
|
|
161
|
+
if (decompiler.errors.length) this.errors.push(...decompiler.errors);
|
|
141
162
|
|
|
142
|
-
let annotationMap = null;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
let defArgs = [], kwDefArgs = [], annotationMap = null;
|
|
164
|
+
|
|
165
|
+
const stringifyKey = (keyNode) => {
|
|
166
|
+
let keyStr = keyNode?.object?.Value ?? keyNode?.name ?? keyNode?.codeFragment?.()?.toString?.() ?? keyNode?.toString?.() ?? '';
|
|
167
|
+
if (typeof keyStr === 'string') {
|
|
168
|
+
keyStr = keyStr.replace(/^['"]|['"]$/g, "");
|
|
169
|
+
}
|
|
170
|
+
return keyStr;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const buildAnnotationMap = (node) => {
|
|
174
|
+
const map = {};
|
|
175
|
+
// ASTObject wrapping a Py_Tuple constant — unpack
|
|
176
|
+
if (node instanceof AST.ASTObject &&
|
|
177
|
+
(node.object?.ClassName === 'Py_Tuple' || node.object?.ClassName === 'Py_SmallTuple')) {
|
|
178
|
+
const items = (node.object.Value || []).map(v => new AST.ASTObject(v));
|
|
179
|
+
for (let i = 0; i + 1 < items.length; i += 2) {
|
|
180
|
+
const keyStr = stringifyKey(items[i]);
|
|
181
|
+
if (keyStr) map[keyStr] = items[i + 1];
|
|
156
182
|
}
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
return map;
|
|
184
|
+
}
|
|
185
|
+
if (node instanceof AST.ASTTuple) {
|
|
186
|
+
// Python 3.10+ format: (key1, val1, key2, val2, ...)
|
|
187
|
+
const pairs = node.values || [];
|
|
188
|
+
for (let i = 0; i + 1 < pairs.length; i += 2) {
|
|
189
|
+
const keyStr = stringifyKey(pairs[i]);
|
|
190
|
+
if (keyStr) map[keyStr] = pairs[i + 1];
|
|
191
|
+
}
|
|
192
|
+
} else if (node instanceof AST.ASTConstMap) {
|
|
193
|
+
// Python 3.6–3.9 (BUILD_CONST_KEY_MAP): keys = ASTObject(tuple of names),
|
|
194
|
+
// values = array popped from stack in reverse order.
|
|
195
|
+
const keysObj = node.keys;
|
|
196
|
+
let flatKeys = [];
|
|
197
|
+
if (keysObj instanceof AST.ASTObject) {
|
|
198
|
+
flatKeys = keysObj.object?.Value || [];
|
|
199
|
+
} else if (Array.isArray(keysObj)) {
|
|
200
|
+
flatKeys = keysObj;
|
|
201
|
+
}
|
|
202
|
+
const valuesArr = Array.isArray(node.values) ? [...node.values].reverse() : [];
|
|
203
|
+
for (let i = 0; i < Math.min(flatKeys.length, valuesArr.length); i++) {
|
|
204
|
+
let keyStr = flatKeys[i];
|
|
205
|
+
if (keyStr && typeof keyStr === 'object') keyStr = keyStr.Value ?? keyStr.toString?.();
|
|
206
|
+
if (typeof keyStr === 'string') keyStr = keyStr.replace(/^['"]|['"]$/g, "");
|
|
207
|
+
if (keyStr) map[String(keyStr)] = valuesArr[i];
|
|
208
|
+
}
|
|
209
|
+
} else if (node instanceof AST.ASTMap) {
|
|
210
|
+
// BUILD_MAP form: keys/values added in pairs.
|
|
211
|
+
const keys = node.keys || [];
|
|
212
|
+
const values = node.values || [];
|
|
213
|
+
for (let i = 0; i < Math.min(keys.length, values.length); i++) {
|
|
214
|
+
const keyStr = stringifyKey(keys[i]);
|
|
215
|
+
if (keyStr) map[keyStr] = values[i];
|
|
159
216
|
}
|
|
160
217
|
}
|
|
161
|
-
|
|
218
|
+
return map;
|
|
219
|
+
};
|
|
162
220
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
221
|
+
if (this.object.Reader.versionCompare(3, 6) >= 0) {
|
|
222
|
+
// Python 3.6+ MAKE_FUNCTION uses flag-based encoding.
|
|
223
|
+
// Stack from top → bottom: [closure(0x08)?, annotations(0x04)?, kwdefaults(0x02)?, defaults(0x01)?]
|
|
166
224
|
const flags = this.code.Current.Argument;
|
|
167
|
-
|
|
225
|
+
if (flags & 0x08) {
|
|
226
|
+
this.dataStack.pop(); // closure tuple, captured via cellvars
|
|
227
|
+
}
|
|
228
|
+
if (flags & 0x04) {
|
|
229
|
+
const annNode = this.dataStack.pop();
|
|
230
|
+
annotationMap = buildAnnotationMap(annNode);
|
|
231
|
+
}
|
|
232
|
+
if (flags & 0x02) {
|
|
233
|
+
const kwDict = this.dataStack.pop();
|
|
234
|
+
if (kwDict instanceof AST.ASTConstMap || kwDict instanceof AST.ASTMap) {
|
|
235
|
+
const keys = kwDict.keys || kwDict.m_keys || [];
|
|
236
|
+
const values = kwDict.values || kwDict.m_values || [];
|
|
237
|
+
for (let i = 0; i < Math.min(keys.length, values.length); i++) {
|
|
238
|
+
kwDefArgs.push({name: keys[i], value: values[i]});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
168
242
|
if (flags & 0x01) {
|
|
169
243
|
const defaults = this.dataStack.pop();
|
|
170
244
|
if (defaults instanceof AST.ASTTuple) {
|
|
171
245
|
defArgs = defaults.values;
|
|
246
|
+
} else if (defaults instanceof AST.ASTObject &&
|
|
247
|
+
(defaults.object?.ClassName === 'Py_Tuple' || defaults.object?.ClassName === 'Py_SmallTuple')) {
|
|
248
|
+
defArgs = (defaults.object.Value || []).map(v => new AST.ASTObject(v));
|
|
172
249
|
}
|
|
173
250
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
251
|
+
} else if (this.object.Reader.versionCompare(3, 0) >= 0) {
|
|
252
|
+
// Python 3.0–3.5 MAKE_FUNCTION/MAKE_CLOSURE uses count-based encoding.
|
|
253
|
+
// arg = (numAnnotations << 16) | (kwDefCount << 8) | defCount
|
|
254
|
+
// CPython push order (compile.c → ceval.c stack from bottom up):
|
|
255
|
+
// [kwdefault_name1, kwdefault_value1, ..., positional_default1, ...,
|
|
256
|
+
// annotation_value1, ..., annotation_names_tuple,
|
|
257
|
+
// closure_tuple (MAKE_CLOSURE only), code, qualname]
|
|
258
|
+
// After qualname & code are already popped at top of processMakeFunction,
|
|
259
|
+
// remaining stack top → bottom:
|
|
260
|
+
// [closure_tuple?, annotation_names_tuple?, annotation_values...,
|
|
261
|
+
// positional_defaults..., kwdefault pairs (name,value)...]
|
|
262
|
+
// Pop order: closure → annotations → positional defaults → kwdefaults.
|
|
183
263
|
let defCount = this.code.Current.Argument & 0xFF;
|
|
184
264
|
let kwDefCount = (this.code.Current.Argument >> 8) & 0xFF;
|
|
185
|
-
let numAnnotations = (this.code.Current.Argument >> 16) &
|
|
186
|
-
|
|
187
|
-
if (this.object.Reader.versionCompare(3, 0) < 0) {
|
|
188
|
-
for (let idx = 0; idx < defCount; ++idx) {
|
|
189
|
-
defArgs.unshift(this.dataStack.pop());
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (kwDefCount > 0) {
|
|
193
|
-
for (let idx = 0; idx < kwDefCount - defCount; ++idx) {
|
|
194
|
-
kwDefArgs.unshift(this.dataStack.pop());
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
if (numAnnotations > 0) {
|
|
199
|
-
let tuple = this.dataStack.pop();
|
|
200
|
-
while (--numAnnotations > 0) {
|
|
201
|
-
annotations.push({key: tuple[numAnnotations], value: this.dataStack.pop()})
|
|
202
|
-
}
|
|
203
|
-
}
|
|
265
|
+
let numAnnotations = (this.code.Current.Argument >> 16) & 0x7FFF;
|
|
204
266
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
}
|
|
267
|
+
if (this.code.Current.OpCodeID === this.OpCodes.MAKE_CLOSURE_A) {
|
|
268
|
+
this.dataStack.pop(); // closure tuple — captured via cellvars elsewhere
|
|
269
|
+
}
|
|
210
270
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
271
|
+
if (numAnnotations > 0) {
|
|
272
|
+
const namesTuple = this.dataStack.pop();
|
|
273
|
+
// Names tuple is loaded via LOAD_CONST → ASTObject wrapping Py_Tuple,
|
|
274
|
+
// OR (rare) BUILD_TUPLE → ASTTuple.
|
|
275
|
+
let rawNames = [];
|
|
276
|
+
if (namesTuple instanceof AST.ASTObject &&
|
|
277
|
+
(namesTuple.object?.ClassName === 'Py_Tuple' || namesTuple.object?.ClassName === 'Py_SmallTuple')) {
|
|
278
|
+
rawNames = namesTuple.object.Value || [];
|
|
279
|
+
} else if (namesTuple instanceof AST.ASTTuple) {
|
|
280
|
+
rawNames = namesTuple.values || [];
|
|
281
|
+
} else if (namesTuple?.object?.Value) {
|
|
282
|
+
rawNames = namesTuple.object.Value;
|
|
283
|
+
}
|
|
284
|
+
const names = rawNames.map(n => {
|
|
285
|
+
let s = n?.Value ?? n?.object?.Value ?? n?.name ?? n?.toString?.() ?? '';
|
|
286
|
+
if (typeof s === 'string') s = s.replace(/^['"]|['"]$/g, "");
|
|
287
|
+
return s;
|
|
288
|
+
});
|
|
289
|
+
const valueCount = numAnnotations - 1;
|
|
290
|
+
const values = [];
|
|
291
|
+
for (let i = 0; i < valueCount; i++) {
|
|
292
|
+
values.unshift(this.dataStack.pop());
|
|
293
|
+
}
|
|
294
|
+
annotationMap = {};
|
|
295
|
+
for (let i = 0; i < Math.min(names.length, values.length); i++) {
|
|
296
|
+
if (names[i]) annotationMap[names[i]] = values[i];
|
|
217
297
|
}
|
|
218
298
|
}
|
|
299
|
+
// Positional defaults are popped BEFORE kwdefaults (they were pushed later).
|
|
300
|
+
for (let idx = 0; idx < defCount; idx++) {
|
|
301
|
+
defArgs.unshift(this.dataStack.pop());
|
|
302
|
+
}
|
|
303
|
+
for (let idx = 0; idx < kwDefCount; idx++) {
|
|
304
|
+
const value = this.dataStack.pop();
|
|
305
|
+
const name = this.dataStack.pop();
|
|
306
|
+
kwDefArgs.unshift({name, value});
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
// Python 2.x MAKE_FUNCTION: argument is just defCount.
|
|
310
|
+
const defCount = this.code.Current.Argument & 0xFF;
|
|
311
|
+
for (let idx = 0; idx < defCount; idx++) {
|
|
312
|
+
defArgs.unshift(this.dataStack.pop());
|
|
313
|
+
}
|
|
219
314
|
}
|
|
220
315
|
|
|
221
|
-
let node = new AST.ASTFunction
|
|
316
|
+
let node = new AST.ASTFunction(func_code, defArgs, kwDefArgs);
|
|
222
317
|
if (annotationMap) {
|
|
223
318
|
node.annotations = annotationMap;
|
|
224
319
|
}
|
|
@@ -284,7 +379,17 @@ function handleBuildConstKeyMapA() {
|
|
|
284
379
|
function handleBuildStringA() {
|
|
285
380
|
let values = [];
|
|
286
381
|
for (let idx = 0; idx < this.code.Current.Argument; idx++) {
|
|
287
|
-
|
|
382
|
+
let v = this.dataStack.pop();
|
|
383
|
+
// Unwrap premature single-FormattedValue JoinedStr wrappers. These arise
|
|
384
|
+
// when FORMAT_SIMPLE's lookahead (hasUpcomingStringBuild) bailed early
|
|
385
|
+
// on a CALL_A that actually belongs to a later interpolation in this
|
|
386
|
+
// same BUILD_STRING — rendering the wrapper would otherwise produce
|
|
387
|
+
// `f"f"{expr}""` nested inside the outer f-string.
|
|
388
|
+
if (v instanceof AST.ASTJoinedStr && v.values && v.values.length === 1 &&
|
|
389
|
+
v.values[0] instanceof AST.ASTFormattedValue) {
|
|
390
|
+
v = v.values[0];
|
|
391
|
+
}
|
|
392
|
+
values.push(v);
|
|
288
393
|
}
|
|
289
394
|
|
|
290
395
|
let stringNode = new AST.ASTJoinedStr(values);
|
|
@@ -23,6 +23,73 @@ function handleYieldValue() {
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Generator expression: YIELD_VALUE inside a For block tagged as comprehension
|
|
27
|
+
// produces the genexpr's yielded expression. Mirror LIST_APPEND/SET_ADD pattern:
|
|
28
|
+
// build an ASTComprehension on the stack so the enclosing JUMP_ABSOLUTE can
|
|
29
|
+
// attach generators via addGenerator. Skip the trailing POP_TOP that balances
|
|
30
|
+
// YIELD_VALUE's received-value push in the runtime model.
|
|
31
|
+
// The yield may fire inside nested If blocks (genexpr with filter condition),
|
|
32
|
+
// so walk up through If blocks to find the enclosing For+comprehension.
|
|
33
|
+
let forBlock = null;
|
|
34
|
+
let ifBlocks = [];
|
|
35
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.For && this.curBlock.comprehension) {
|
|
36
|
+
forBlock = this.curBlock;
|
|
37
|
+
} else {
|
|
38
|
+
for (let i = this.blocks.length - 1; i >= 0; i--) {
|
|
39
|
+
const blk = this.blocks[i];
|
|
40
|
+
if (blk.blockType == AST.ASTBlock.BlockType.For && blk.comprehension) {
|
|
41
|
+
forBlock = blk;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (blk.blockType == AST.ASTBlock.BlockType.If) {
|
|
45
|
+
ifBlocks.push(blk);
|
|
46
|
+
} else {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (forBlock) {
|
|
52
|
+
for (const ifBlk of ifBlocks) {
|
|
53
|
+
let cond = ifBlk.condition;
|
|
54
|
+
if (!cond) continue;
|
|
55
|
+
if (ifBlk.negative) {
|
|
56
|
+
cond = new AST.ASTUnary(cond, AST.ASTUnary.UnaryOp.Not);
|
|
57
|
+
cond.line = this.code.Current.LineNo;
|
|
58
|
+
}
|
|
59
|
+
if (forBlock.condition) {
|
|
60
|
+
const andCond = new AST.ASTBinary(forBlock.condition, cond, AST.ASTBinary.BinOp.LogicalAnd);
|
|
61
|
+
andCond.line = this.code.Current.LineNo;
|
|
62
|
+
forBlock.condition = andCond;
|
|
63
|
+
} else {
|
|
64
|
+
forBlock.condition = cond;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
while (this.curBlock !== forBlock && this.blocks.length > 1) {
|
|
68
|
+
this.blocks.pop();
|
|
69
|
+
this.curBlock = this.blocks.top();
|
|
70
|
+
}
|
|
71
|
+
let node = new AST.ASTComprehension(value);
|
|
72
|
+
node.line = this.code.Current.LineNo;
|
|
73
|
+
node.kind = AST.ASTComprehension.GENERATOR;
|
|
74
|
+
this.dataStack.push(node);
|
|
75
|
+
if (this.code.Next?.OpCodeID == this.OpCodes.POP_TOP) {
|
|
76
|
+
this.code.GoNext();
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Async genexpr inner code: GET_ANEXT iteration uses SETUP_EXCEPT/FINALLY
|
|
82
|
+
// rather than FOR_ITER, so no For+comp block exists when YIELD_VALUE fires
|
|
83
|
+
// inside the comprehension body. Save the yielded value so the CALL-site
|
|
84
|
+
// reconstruction in function_calls.js can use it as the comprehension's
|
|
85
|
+
// yield expression. We still append ASTReturn below for regular async
|
|
86
|
+
// generators; the save is harmless because the CALL-site only reads
|
|
87
|
+
// _asyncCompYieldExpr when reconstructing a recognized comp code object.
|
|
88
|
+
const objName = this.object?.Name?.toString?.() || '';
|
|
89
|
+
if (objName.includes('genexpr')) {
|
|
90
|
+
this.object._asyncCompYieldExpr = value;
|
|
91
|
+
}
|
|
92
|
+
|
|
26
93
|
// Python 3.11+: YIELD_VALUE appears in async for loops as implementation detail
|
|
27
94
|
// Skip if we're inside AsyncFor block and value looks like generator machinery
|
|
28
95
|
if (this.object.Reader.versionCompare(3, 11) >= 0) {
|