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,146 @@
1
+ class CodeReader {
2
+ constructor(code, endian) {
3
+ this.endian = endian || 'B';
4
+ this._reader = Buffer.from(code);
5
+ this._pc = 0;
6
+ }
7
+
8
+ get pc() {
9
+ return this._pc;
10
+ }
11
+
12
+ set pc(pos) {
13
+ if (pos > this.length || pos < 0) {
14
+ pos = this.length;
15
+ }
16
+ this._pc = pos;
17
+ }
18
+
19
+ get length() {
20
+ return this._reader.length
21
+ }
22
+
23
+ get EOF() {
24
+ return this._pc >= this._reader.length;
25
+ }
26
+
27
+ readByte() {
28
+ try {
29
+ let value = this._reader.readUInt8(this.pc, true);
30
+ this.pc++;
31
+ return value;
32
+ } catch {
33
+ debugger;
34
+ }
35
+ }
36
+
37
+ readUShort() {
38
+ try {
39
+ let value = this._reader[`readUInt16${this.endian}E`](this.pc, true);
40
+ this.pc += 2;
41
+ return value;
42
+ } catch {
43
+ debugger;
44
+ }
45
+ }
46
+
47
+ readShort() {
48
+ try {
49
+ let value = this._reader[`readInt16${this.endian}E`](this.pc, true);
50
+ this.pc += 2;
51
+ return value;
52
+ } catch {
53
+ debugger;
54
+ }
55
+ }
56
+
57
+ readInt() {
58
+ try {
59
+ let value = this._reader[`readInt32${this.endian}E`](this.pc, true);
60
+ this.pc += 4;
61
+ return value;
62
+ } catch {
63
+ debugger;
64
+ }
65
+ }
66
+
67
+ readUInt() {
68
+ try {
69
+ let value = this._reader[`readUInt32${this.endian}E`](this.pc, true);
70
+ this.pc += 4;
71
+ return value;
72
+ } catch (ex) {
73
+ debugger;
74
+ }
75
+ }
76
+
77
+ readLong() {
78
+ try {
79
+ let high = this.readInt()
80
+ let low = this.readInt()
81
+ return {low, high};
82
+ } catch {
83
+ debugger;
84
+ }
85
+ }
86
+
87
+ readULong() {
88
+ try {
89
+ let high = this.readUInt()
90
+ let low = this.readUInt()
91
+ return {low, high};
92
+ } catch {
93
+ debugger;
94
+ }
95
+ }
96
+
97
+ readFloat() {
98
+ try {
99
+ let value = this._reader[`readFloat${this.endian}E`](this.pc, true);
100
+ this.pc += 4;
101
+ return value;
102
+ } catch {
103
+ debugger;
104
+ }
105
+ }
106
+
107
+ readDouble() {
108
+ try {
109
+ let value = this._reader[`readDouble${this.endian}E`](this.pc, true);
110
+ this.pc += 8;
111
+ return value;
112
+ } catch {
113
+ debugger;
114
+ }
115
+ }
116
+
117
+ readBytes(length) {
118
+ try {
119
+ let value = this._reader.slice(this.pc, this.pc + length);
120
+ this.pc += length;
121
+ return value;
122
+ } catch {
123
+ debugger;
124
+ }
125
+ }
126
+
127
+ readString(length) {
128
+ try {
129
+ let value = this._reader.slice(this.pc, this.pc + length).toString('utf8');
130
+ this.pc += length;
131
+ return value;
132
+ } catch {
133
+ debugger;
134
+ }
135
+ }
136
+
137
+ peekByte() {
138
+ try {
139
+ return this._reader.readUInt8(this.pc, true);
140
+ } catch {
141
+ debugger;
142
+ }
143
+ }
144
+ }
145
+
146
+ module.exports = CodeReader;
@@ -0,0 +1,174 @@
1
+ const AST = require('../ast/ast_node');
2
+
3
+ function handleBinaryOpA()
4
+ {
5
+ let rVal = this.dataStack.pop();
6
+ let lVal = this.dataStack.pop();
7
+ let op = AST.ASTBinary.from_binary_op(this.code.Current.Argument);
8
+ if (op == AST.ASTBinary.BinOp.InvalidOp) {
9
+ // TODO: Throw and handle proper exeception.
10
+ throw new SyntaxError("Invalid op");
11
+ }
12
+ let node = new AST.ASTBinary(lVal, rVal,op);
13
+ node.line = this.code.Current.LineNo;
14
+ this.dataStack.push(node);
15
+ }
16
+
17
+ function handleBinaryAdd() {
18
+ processBinaryOp.call(this);
19
+ }
20
+
21
+ function handleBinaryAnd() {
22
+ processBinaryOp.call(this);
23
+ }
24
+
25
+ function handleBinaryDivide(){
26
+ processBinaryOp.call(this);
27
+ }
28
+
29
+ function handleBinaryFloorDivide() {
30
+ processBinaryOp.call(this);
31
+ }
32
+
33
+ function handleBinaryLshift(){
34
+ processBinaryOp.call(this);
35
+ }
36
+
37
+ function handleBinaryModulo() {
38
+ processBinaryOp.call(this);
39
+ }
40
+
41
+ function handleBinaryMultiply() {
42
+ processBinaryOp.call(this);
43
+ }
44
+
45
+ function handleBinaryOr() {
46
+ processBinaryOp.call(this);
47
+ }
48
+
49
+ function handleBinaryPower() {
50
+ processBinaryOp.call(this);
51
+ }
52
+
53
+ function handleBinaryRshift() {
54
+ processBinaryOp.call(this);
55
+ }
56
+
57
+ function handleBinarySubtract() {
58
+ processBinaryOp.call(this);
59
+ }
60
+
61
+ function handleBinaryTrueDivide() {
62
+ processBinaryOp.call(this);
63
+ }
64
+
65
+ function handleBinaryXor() {
66
+ processBinaryOp.call(this);
67
+ }
68
+
69
+ function handleBinaryMatrixMultiply() {
70
+ processBinaryOp.call(this);
71
+ }
72
+
73
+ function handleInplaceAdd() {
74
+ processBinaryOp.call(this);
75
+ }
76
+
77
+ function handleInplaceAnd() {
78
+ processBinaryOp.call(this);
79
+ }
80
+
81
+ function handleInplaceDivide() {
82
+ processBinaryOp.call(this);
83
+ }
84
+
85
+ function handleInplaceFloorDivide() {
86
+ processBinaryOp.call(this);
87
+ }
88
+
89
+ function handleInplaceLShift() {
90
+ processBinaryOp.call(this);
91
+ }
92
+
93
+ function handleInplaceModulo() {
94
+ processBinaryOp.call(this);
95
+ }
96
+
97
+ function handleInplaceMultiply() {
98
+ processBinaryOp.call(this);
99
+ }
100
+
101
+ function handleInplaceOr() {
102
+ processBinaryOp.call(this);
103
+ }
104
+
105
+ function handleInplacePower() {
106
+ processBinaryOp.call(this);
107
+ }
108
+
109
+ function handleInplaceRshift() {
110
+ processBinaryOp.call(this);
111
+ }
112
+
113
+ function handleInplaceSubtract() {
114
+ processBinaryOp.call(this);
115
+ }
116
+
117
+ function handleInplaceTrueDivide() {
118
+ processBinaryOp.call(this);
119
+ }
120
+
121
+ function handleInplaceXor() {
122
+ processBinaryOp.call(this);
123
+ }
124
+
125
+ function handleInplaceMatrixMultiply() {
126
+ processBinaryOp.call(this);
127
+ }
128
+
129
+ function processBinaryOp()
130
+ {
131
+ let rVal = this.dataStack.pop();
132
+ let lVal = this.dataStack.pop();
133
+ let op = AST.ASTBinary.from_opcode(this.code.Current.OpCodeID);
134
+ if (op == AST.ASTBinary.BinOp.InvalidOp) {
135
+ // TODO: Throw and handle proper exeception.
136
+ throw new SyntaxError("Invalid op");
137
+ }
138
+ let node = new AST.ASTBinary(lVal, rVal,op);
139
+ node.line = this.code.Current.LineNo;
140
+ this.dataStack.push(node);
141
+ }
142
+
143
+ module.exports = {
144
+ handleBinaryOr,
145
+ handleBinaryAdd,
146
+ handleBinaryAnd,
147
+ handleBinaryDivide,
148
+ handleBinaryFloorDivide,
149
+ handleBinaryLshift,
150
+ handleBinaryModulo,
151
+ handleBinaryMultiply,
152
+ handleBinaryOr,
153
+ handleBinaryPower,
154
+ handleBinaryRshift,
155
+ handleBinarySubtract,
156
+ handleBinaryTrueDivide,
157
+ handleBinaryXor,
158
+ handleBinaryMatrixMultiply,
159
+ handleInplaceAdd,
160
+ handleInplaceAnd,
161
+ handleInplaceDivide,
162
+ handleInplaceFloorDivide,
163
+ handleInplaceLShift,
164
+ handleInplaceModulo,
165
+ handleInplaceMultiply,
166
+ handleInplaceOr,
167
+ handleInplacePower,
168
+ handleInplaceRshift,
169
+ handleInplaceSubtract,
170
+ handleInplaceTrueDivide,
171
+ handleInplaceXor,
172
+ handleInplaceMatrixMultiply,
173
+ handleBinaryOpA
174
+ };
@@ -0,0 +1,239 @@
1
+ const AST = require('../ast/ast_node');
2
+
3
+ function handleSetAdd() {
4
+ handleSetAddA.call(this);
5
+ }
6
+
7
+ function handleSetAddA() {
8
+ let value = this.dataStack.pop();
9
+
10
+ // Check if this is a set comprehension first
11
+ if (this.curBlock.blockType == AST.ASTBlock.BlockType.For && this.curBlock.comprehension) {
12
+ this.dataStack.pop(); // Pop the empty set
13
+ let node = new AST.ASTComprehension(value);
14
+ node.line = this.code.Current.LineNo;
15
+ node.kind = AST.ASTComprehension.SET;
16
+ this.dataStack.push(node);
17
+ return;
18
+ }
19
+
20
+ // Normal set add
21
+ let setOffset = this.code.Current.OpCodeID == this.OpCodes.SET_ADD_A ? this.code.Current.Argument - 1 : 0;
22
+ let setNode = this.dataStack.top(setOffset);
23
+ if (!setNode || typeof setNode.add !== 'function') {
24
+ if (global.g_cliArgs?.debug) {
25
+ console.error(`SET_ADD target missing or invalid at offset ${this.code.Current.Offset}`);
26
+ }
27
+ return;
28
+ }
29
+ setNode.add(value);
30
+ }
31
+
32
+ function handleListAppend() {
33
+ processListAppend.call(this);
34
+ }
35
+
36
+ function handleListAppendA() {
37
+ processListAppend.call(this);
38
+ }
39
+
40
+ function processListAppend() {
41
+ let value = this.dataStack.pop();
42
+ let list = this.dataStack.top();
43
+
44
+ if (this.curBlock.blockType == AST.ASTBlock.BlockType.For && this.curBlock.comprehension) {
45
+ this.dataStack.pop();
46
+ let node = new AST.ASTComprehension (value);
47
+ node.line = this.code.Current.LineNo;
48
+ this.dataStack.push(node);
49
+ } else {
50
+ let node = new AST.ASTSubscr (list, value);
51
+ node.line = this.code.Current.LineNo;
52
+ this.dataStack.push(node);
53
+ }
54
+ }
55
+
56
+ function handleSetUpdateA() {
57
+ const rhs = this.dataStack.pop();
58
+ const lhs = this.dataStack.pop();
59
+
60
+ const targetValues = lhs instanceof AST.ASTSet ? [...(lhs.values || [])] : [];
61
+ const rhsValues = collectIterableValues(rhs);
62
+
63
+ if (!rhsValues) {
64
+ if (global.g_cliArgs?.debug) {
65
+ console.error("Unsupported argument found for SET_UPDATE");
66
+ }
67
+ this.dataStack.push(lhs);
68
+ return;
69
+ }
70
+
71
+ let node = new AST.ASTSet([...targetValues, ...rhsValues]);
72
+ node.line = this.code.Current.LineNo;
73
+ this.dataStack.push(node);
74
+ }
75
+
76
+ function handleListExtendA() {
77
+ const rhs = this.dataStack.pop();
78
+ const lhs = this.dataStack.pop();
79
+
80
+ if (!(lhs instanceof AST.ASTList)) {
81
+ if (global.g_cliArgs?.debug) {
82
+ console.error("LIST_EXTEND target is not a list literal");
83
+ }
84
+ this.dataStack.push(lhs);
85
+ return;
86
+ }
87
+
88
+ const appendValues = collectIterableValues(rhs);
89
+
90
+ if (!appendValues || appendValues.length === 0) {
91
+ if (global.g_cliArgs?.debug) {
92
+ console.error("Unsupported argument found for LIST_EXTEND");
93
+ }
94
+ this.dataStack.push(lhs);
95
+ return;
96
+ }
97
+
98
+ lhs.values.push(...appendValues);
99
+
100
+ this.dataStack.push(lhs);
101
+ }
102
+
103
+ function handleDictMergeA() {
104
+ const dictToMerge = this.dataStack.pop();
105
+ const targetDict = this.dataStack.top();
106
+ const entries = collectMapEntries(dictToMerge);
107
+
108
+ if (!(targetDict instanceof AST.ASTMap) || !entries) {
109
+ if (global.g_cliArgs?.debug) {
110
+ console.error("Expected ASTMap for DICT_MERGE_A");
111
+ }
112
+ return;
113
+ }
114
+
115
+ for (const entry of entries) {
116
+ targetDict.add(entry.key, entry.value);
117
+ }
118
+ }
119
+
120
+ function handleDictUpdateA() {
121
+ const updateSource = this.dataStack.pop();
122
+ const targetDict = this.dataStack.top();
123
+
124
+ if (!(targetDict instanceof AST.ASTMap)) {
125
+ if (global.g_cliArgs?.debug) {
126
+ console.error("Expected ASTMap for DICT_UPDATE_A target");
127
+ }
128
+ return;
129
+ }
130
+
131
+ if (updateSource instanceof AST.ASTCall && updateSource.func instanceof AST.ASTName && updateSource.func.name == 'zip') {
132
+ // Handle case where updateSource is a zip of keys and values
133
+ if (updateSource.pparams.length === 2) {
134
+ let keys = updateSource.pparams[0];
135
+ let values = updateSource.pparams[1];
136
+ if (keys instanceof AST.ASTList && values instanceof AST.ASTList && keys.values.length === values.values.length) {
137
+ for (let i = 0; i < keys.values.length; i++) {
138
+ targetDict.add(keys.values[i], values.values[i]);
139
+ }
140
+ return;
141
+ } else if (keys instanceof AST.ASTTuple && values instanceof AST.ASTTuple && keys.values.length === values.values.length) {
142
+ for (let i = 0; i < keys.values.length; i++) {
143
+ targetDict.add(keys.values[i], values.values[i]);
144
+ }
145
+ return;
146
+ } else if (global.g_cliArgs?.debug) {
147
+ console.error("Expected lists or tuples of equal length for zip in DICT_UPDATE_A");
148
+ }
149
+ }
150
+ }
151
+
152
+ const entries = collectMapEntries(updateSource);
153
+ if (!entries) {
154
+ if (global.g_cliArgs?.debug) {
155
+ console.error("Expected ASTMap or iterable for DICT_UPDATE_A");
156
+ }
157
+ return;
158
+ }
159
+
160
+ for (const entry of entries) {
161
+ targetDict.add(entry.key, entry.value);
162
+ }
163
+ }
164
+
165
+ function collectIterableValues(node) {
166
+ if (node instanceof AST.ASTList || node instanceof AST.ASTTuple || node instanceof AST.ASTSet) {
167
+ return node.values || [];
168
+ }
169
+
170
+ if (node instanceof AST.ASTObject) {
171
+ const obj = node.object;
172
+ if (["Py_List", "Py_Tuple", "Py_SmallTuple", "Py_Set", "Py_FrozenSet"].includes(obj?.ClassName)) {
173
+ return (obj.Value || []).map(value => new AST.ASTObject(value));
174
+ }
175
+ }
176
+
177
+ return null;
178
+ }
179
+
180
+ function collectMapEntries(node) {
181
+ if (node instanceof AST.ASTMap || node instanceof AST.ASTKwNamesMap) {
182
+ return node.values || [];
183
+ }
184
+
185
+ if (node instanceof AST.ASTConstMap) {
186
+ let keysArray = [];
187
+ if (node.keys instanceof AST.ASTObject) {
188
+ const obj = node.keys.object;
189
+ if (obj?.ClassName === 'Py_Tuple' || obj?.ClassName === 'Py_SmallTuple') {
190
+ keysArray = (obj.Value || []).map(v => new AST.ASTObject(v));
191
+ }
192
+ } else if (Array.isArray(node.keys)) {
193
+ keysArray = node.keys;
194
+ }
195
+
196
+ const valuesArray = Array.isArray(node.values) ? [...node.values].reverse() : [];
197
+ const count = Math.min(keysArray.length, valuesArray.length);
198
+ const entries = [];
199
+ for (let i = 0; i < count; i++) {
200
+ entries.push({key: keysArray[i], value: valuesArray[i]});
201
+ }
202
+ return entries.length ? entries : null;
203
+ }
204
+
205
+ if (node instanceof AST.ASTList || node instanceof AST.ASTTuple) {
206
+ const entries = [];
207
+ for (const pair of node.values || []) {
208
+ if (pair instanceof AST.ASTTuple && pair.values?.length === 2) {
209
+ entries.push({key: pair.values[0], value: pair.values[1]});
210
+ }
211
+ }
212
+ if (entries.length) {
213
+ return entries;
214
+ }
215
+ }
216
+
217
+ if (node instanceof AST.ASTObject) {
218
+ const obj = node.object;
219
+ if (obj?.ClassName === "Py_Dict") {
220
+ return (obj.Value || []).map(({key, value}) => ({
221
+ key: new AST.ASTObject(key),
222
+ value: new AST.ASTObject(value)
223
+ }));
224
+ }
225
+ }
226
+
227
+ return null;
228
+ }
229
+
230
+ module.exports = {
231
+ handleSetAdd,
232
+ handleSetAddA,
233
+ handleListAppend,
234
+ handleListAppendA,
235
+ handleSetUpdateA,
236
+ handleListExtendA,
237
+ handleDictMergeA,
238
+ handleDictUpdateA
239
+ };
@@ -0,0 +1,95 @@
1
+ const AST = require('../ast/ast_node');
2
+
3
+ function handleCompareOpA() {
4
+ // Literal-only match patterns in 3.13+: COPY 1 + LOAD_CONST + COMPARE_OP
5
+ // No MATCH_* opcodes appear, so initialize match tracking here.
6
+ // Match patterns only exist in Python 3.10+
7
+ if (!this.currentMatch && this.potentialMatchSubject && !this.inMatchPattern &&
8
+ this.object.Reader.versionCompare(3, 10) >= 0) {
9
+ this.matchSubject = this.potentialMatchSubject;
10
+ this.currentMatch = new AST.ASTMatch(this.matchSubject);
11
+ this.currentMatch.line = this.code.Current.LineNo;
12
+ this.matchParentBlock = this.curBlock;
13
+ this.patternOps = [];
14
+ if (!this.matchPreNodesStart && this.matchParentBlock) {
15
+ this.matchPreNodesStart = this.matchParentBlock.nodes.length;
16
+ }
17
+ }
18
+ if (this.currentMatch && !this.inMatchPattern) {
19
+ this.inMatchPattern = true;
20
+ this.patternOps = [];
21
+ }
22
+ const jumpOps = [
23
+ this.OpCodes.POP_JUMP_IF_FALSE_A,
24
+ this.OpCodes.POP_JUMP_FORWARD_IF_FALSE_A,
25
+ this.OpCodes.POP_JUMP_IF_TRUE_A,
26
+ this.OpCodes.POP_JUMP_FORWARD_IF_TRUE_A
27
+ ];
28
+ if (this.currentMatch && this.patternOps?.length && jumpOps.includes(this.code.Prev?.OpCodeID)) {
29
+ this.patternOps = [];
30
+ }
31
+
32
+ let right = this.dataStack.pop();
33
+ let left = this.dataStack.pop();
34
+ let arg = this.code.Current.Argument;
35
+ if (this.object.Reader.versionCompare(3, 13) >= 0) {
36
+ // Python 3.13 encodes the comparison in packed form; map observed
37
+ // specializations back to standard compare ops.
38
+ const mapped = {
39
+ 18: AST.ASTCompare.CompareOp.Less, // guards: n < 0
40
+ 72: AST.ASTCompare.CompareOp.Equal, // len == N
41
+ 88: AST.ASTCompare.CompareOp.Equal, // literal == value
42
+ 148: AST.ASTCompare.CompareOp.Greater // guards: n > 0
43
+ };
44
+ arg = mapped[arg] ?? (arg & 0x0F);
45
+ } else if (this.object.Reader.versionCompare(3, 12) >= 0) {
46
+ // 3.12 quickened form stores compare op in high nibble
47
+ arg >>= 4;
48
+ }
49
+
50
+ // Record pattern operation if in pattern matching
51
+ if (this.inMatchPattern) {
52
+ this.patternOps.push({
53
+ type: 'COMPARE',
54
+ op: arg,
55
+ left: left,
56
+ right: right
57
+ });
58
+ }
59
+
60
+ // Debug: exception match (arg=10)
61
+ if (arg == 10 && global.g_cliArgs?.debug) {
62
+ console.log(`[COMPARE_OP] EXCEPTION MATCH at offset ${this.code.Current.Offset}`);
63
+ console.log(` left=${left?.constructor.name} ${left?.codeFragment?.() || left}`);
64
+ console.log(` right=${right?.constructor.name} ${right?.codeFragment?.() || right}`);
65
+ console.log(` Stack depth: ${this.dataStack.length}`);
66
+ }
67
+
68
+ let node = new AST.ASTCompare (left, right, arg);
69
+ node.line = this.code.Current.LineNo;
70
+ this.dataStack.push(node);
71
+ }
72
+
73
+ function handleContainsOpA() {
74
+ let right = this.dataStack.pop();
75
+ let left = this.dataStack.pop();
76
+ // The this.code.Current.Argument will be 0 for 'in' and 1 for 'not in'.
77
+ let node = new AST.ASTCompare (left, right, this.code.Current.Argument ? AST.ASTCompare.CompareOp.NotIn : AST.ASTCompare.CompareOp.In);
78
+ node.line = this.code.Current.LineNo;
79
+ this.dataStack.push(node);
80
+ }
81
+
82
+ function handleIsOpA() {
83
+ let right = this.dataStack.pop();
84
+ let left = this.dataStack.pop();
85
+ // The this.code.Current.Argument will be 0 for 'is' and 1 for 'is not'.
86
+ let node = new AST.ASTCompare (left, right, this.code.Current.Argument ? AST.ASTCompare.CompareOp.IsNot : AST.ASTCompare.CompareOp.Is);
87
+ node.line = this.code.Current.LineNo;
88
+ this.dataStack.push(node);
89
+ }
90
+
91
+ module.exports = {
92
+ handleCompareOpA,
93
+ handleContainsOpA,
94
+ handleIsOpA
95
+ };