@lewin671/python-vm 0.1.0 → 0.1.1
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/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compiler_module/compiler.d.ts.map +1 -0
- package/dist/compiler_module/compiler.js.map +1 -0
- package/dist/compiler_module/index.d.ts.map +1 -0
- package/dist/compiler_module/index.js.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/index.d.ts.map +1 -0
- package/dist/lexer/index.js.map +1 -0
- package/dist/lexer/lexer.d.ts.map +1 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/parser/expressions.d.ts.map +1 -0
- package/dist/parser/expressions.js.map +1 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/statements.d.ts.map +1 -0
- package/dist/parser/statements.js.map +1 -0
- package/dist/parser/targets.d.ts.map +1 -0
- package/dist/parser/targets.js.map +1 -0
- package/dist/types/ast.d.ts.map +1 -0
- package/dist/types/ast.js.map +1 -0
- package/dist/types/bytecode.d.ts.map +1 -0
- package/dist/types/bytecode.js.map +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/token.d.ts.map +1 -0
- package/dist/types/token.js.map +1 -0
- package/dist/vm/builtins.d.ts.map +1 -0
- package/dist/vm/builtins.js.map +1 -0
- package/dist/vm/callable.d.ts.map +1 -0
- package/dist/vm/callable.js.map +1 -0
- package/dist/vm/execution.d.ts.map +1 -0
- package/dist/vm/execution.js.map +1 -0
- package/dist/vm/expression-generator.d.ts.map +1 -0
- package/dist/vm/expression-generator.js.map +1 -0
- package/dist/vm/expressions.d.ts.map +1 -0
- package/dist/vm/expressions.js.map +1 -0
- package/dist/vm/imports.d.ts.map +1 -0
- package/dist/vm/imports.js.map +1 -0
- package/dist/vm/index.d.ts.map +1 -0
- package/dist/vm/index.js.map +1 -0
- package/dist/vm/operations.d.ts.map +1 -0
- package/dist/vm/operations.js.map +1 -0
- package/dist/vm/runtime-types.d.ts.map +1 -0
- package/dist/vm/runtime-types.js.map +1 -0
- package/dist/vm/statements.d.ts.map +1 -0
- package/dist/vm/statements.js.map +1 -0
- package/dist/vm/truthy.d.ts.map +1 -0
- package/dist/vm/truthy.js.map +1 -0
- package/dist/vm/value-utils.d.ts.map +1 -0
- package/dist/vm/value-utils.js.map +1 -0
- package/dist/vm/vm.d.ts.map +1 -0
- package/dist/vm/vm.js.map +1 -0
- package/package.json +7 -1
- package/.claude/settings.local.json +0 -3
- package/.prettierrc +0 -7
- package/Agents.md +0 -66
- package/SETUP.md +0 -171
- package/examples/assert_testing.py +0 -38
- package/examples/big_int_precision.py +0 -2
- package/examples/boolean_logic.py +0 -35
- package/examples/break_continue.py +0 -43
- package/examples/classes_objects.py +0 -43
- package/examples/compiler_killer_async.py +0 -6
- package/examples/compiler_killer_bigint.py +0 -3
- package/examples/compiler_killer_bool_int_dict_key.py +0 -5
- package/examples/compiler_killer_bool_len.py +0 -9
- package/examples/compiler_killer_floor_division.py +0 -4
- package/examples/compiler_killer_is_identity.py +0 -3
- package/examples/compiler_killer_list_sort_return.py +0 -3
- package/examples/compiler_killer_match.py +0 -13
- package/examples/compiler_killer_negative_repeat.py +0 -3
- package/examples/compiler_killer_negative_zero_repr.py +0 -3
- package/examples/compiler_killer_rounding.py +0 -4
- package/examples/compiler_killer_slice_assign.py +0 -3
- package/examples/comprehensions.py +0 -28
- package/examples/conditions.py +0 -13
- package/examples/context_manager.py +0 -35
- package/examples/decorators.py +0 -50
- package/examples/exceptions.py +0 -40
- package/examples/fibonacci.py +0 -10
- package/examples/functions.py +0 -38
- package/examples/generator.py +0 -51
- package/examples/global_nonlocal.py +0 -48
- package/examples/hello.py +0 -3
- package/examples/itertools_example.py +0 -33
- package/examples/lists_dicts.py +0 -29
- package/examples/loops.py +0 -19
- package/examples/math_ops.py +0 -15
- package/examples/nan_set.py +0 -6
- package/examples/numbers_operators.py +0 -51
- package/examples/sets.py +0 -36
- package/examples/slicing.py +0 -29
- package/examples/starred_unpacking.py +0 -3
- package/examples/string_formatting.py +0 -36
- package/examples/strings.py +0 -22
- package/examples/tuples.py +0 -45
- package/examples/type_conversion.py +0 -41
- package/jest.config.js +0 -15
- package/notes/iterations/compiler-runtime/compiler-runtime_2025-09-16.md +0 -25
- package/notes/iterations/compiler-runtime/compiler-runtime_2026-01-16.md +0 -24
- package/notes/iterations/compiler-runtime/compiler-runtime_test_2026-01-16.md +0 -21
- package/notes/iterations/floor-division/floor-division_2026-01-16.md +0 -29
- package/prompts/commit.txt +0 -9
- package/prompts/task.txt +0 -21
- package/prompts/test.txt +0 -23
- package/scripts/codex-loop.js +0 -215
- package/scripts/verify.sh +0 -12
- package/src/compiler.ts +0 -58
- package/src/compiler_module/compiler.ts +0 -19
- package/src/compiler_module/index.ts +0 -1
- package/src/index.ts +0 -39
- package/src/lexer/index.ts +0 -1
- package/src/lexer/lexer.ts +0 -402
- package/src/parser/expressions.ts +0 -462
- package/src/parser/index.ts +0 -1
- package/src/parser/parser.ts +0 -102
- package/src/parser/statements.ts +0 -366
- package/src/parser/targets.ts +0 -71
- package/src/types/ast.ts +0 -64
- package/src/types/bytecode.ts +0 -50
- package/src/types/index.ts +0 -3
- package/src/types/token.ts +0 -44
- package/src/vm/builtins.ts +0 -237
- package/src/vm/callable.ts +0 -154
- package/src/vm/execution.ts +0 -251
- package/src/vm/expression-generator.ts +0 -65
- package/src/vm/expressions.ts +0 -373
- package/src/vm/imports.ts +0 -61
- package/src/vm/index.ts +0 -2
- package/src/vm/operations.ts +0 -414
- package/src/vm/runtime-types.ts +0 -292
- package/src/vm/statements.ts +0 -358
- package/src/vm/truthy.ts +0 -36
- package/src/vm/value-utils.ts +0 -173
- package/src/vm/vm.ts +0 -80
- package/tests/compiler.test.ts +0 -111
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -16
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { VirtualMachine } from './vm';
|
|
2
|
-
import { ASTNodeType } from '../types';
|
|
3
|
-
|
|
4
|
-
export function* evaluateExpressionGenerator(this: VirtualMachine, node: any, scope: any): Generator<any, any, any> {
|
|
5
|
-
switch (node.type) {
|
|
6
|
-
case ASTNodeType.YIELD: {
|
|
7
|
-
const value = node.value ? yield* this.evaluateExpressionGenerator(node.value, scope) : null;
|
|
8
|
-
const sent = yield value;
|
|
9
|
-
return sent;
|
|
10
|
-
}
|
|
11
|
-
case ASTNodeType.BINARY_OPERATION: {
|
|
12
|
-
const left = yield* this.evaluateExpressionGenerator(node.left, scope);
|
|
13
|
-
const right = yield* this.evaluateExpressionGenerator(node.right, scope);
|
|
14
|
-
return this.applyBinary(node.operator, left, right);
|
|
15
|
-
}
|
|
16
|
-
case ASTNodeType.UNARY_OPERATION: {
|
|
17
|
-
const operand = yield* this.evaluateExpressionGenerator(node.operand, scope);
|
|
18
|
-
return this.evaluateExpression({ ...node, operand }, scope);
|
|
19
|
-
}
|
|
20
|
-
case ASTNodeType.COMPARE: {
|
|
21
|
-
const left = yield* this.evaluateExpressionGenerator(node.left, scope);
|
|
22
|
-
const comparators = [];
|
|
23
|
-
for (const comp of node.comparators) {
|
|
24
|
-
comparators.push(yield* this.evaluateExpressionGenerator(comp, scope));
|
|
25
|
-
}
|
|
26
|
-
return this.evaluateExpression({ ...node, left, comparators }, scope);
|
|
27
|
-
}
|
|
28
|
-
case ASTNodeType.CALL: {
|
|
29
|
-
const callee = yield* this.evaluateExpressionGenerator(node.callee, scope);
|
|
30
|
-
const positional: any[] = [];
|
|
31
|
-
const kwargs: Record<string, any> = {};
|
|
32
|
-
for (const arg of node.args) {
|
|
33
|
-
if (arg.type === 'KeywordArg') {
|
|
34
|
-
kwargs[arg.name] = yield* this.evaluateExpressionGenerator(arg.value, scope);
|
|
35
|
-
} else if (arg.type === 'StarArg') {
|
|
36
|
-
const value = yield* this.evaluateExpressionGenerator(arg.value, scope);
|
|
37
|
-
positional.push(...(Array.isArray(value) ? value : Array.from(value)));
|
|
38
|
-
} else if (arg.type === 'KwArg') {
|
|
39
|
-
const value = yield* this.evaluateExpressionGenerator(arg.value, scope);
|
|
40
|
-
Object.assign(kwargs, value);
|
|
41
|
-
} else {
|
|
42
|
-
positional.push(yield* this.evaluateExpressionGenerator(arg, scope));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return this.callFunction(callee, positional, scope, kwargs);
|
|
46
|
-
}
|
|
47
|
-
case ASTNodeType.ATTRIBUTE: {
|
|
48
|
-
const obj = yield* this.evaluateExpressionGenerator(node.object, scope);
|
|
49
|
-
return this.getAttribute(obj, node.name, scope);
|
|
50
|
-
}
|
|
51
|
-
case ASTNodeType.SUBSCRIPT: {
|
|
52
|
-
const obj = yield* this.evaluateExpressionGenerator(node.object, scope);
|
|
53
|
-
const index = yield* this.evaluateExpressionGenerator(node.index, scope);
|
|
54
|
-
return this.getSubscript(obj, index);
|
|
55
|
-
}
|
|
56
|
-
case ASTNodeType.IF_EXPRESSION: {
|
|
57
|
-
const test = yield* this.evaluateExpressionGenerator(node.test, scope);
|
|
58
|
-
return this.isTruthy(test, scope)
|
|
59
|
-
? yield* this.evaluateExpressionGenerator(node.consequent, scope)
|
|
60
|
-
: yield* this.evaluateExpressionGenerator(node.alternate, scope);
|
|
61
|
-
}
|
|
62
|
-
default:
|
|
63
|
-
return this.evaluateExpression(node, scope);
|
|
64
|
-
}
|
|
65
|
-
}
|
package/src/vm/expressions.ts
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import type { VirtualMachine } from './vm';
|
|
2
|
-
import { ASTNodeType } from '../types';
|
|
3
|
-
import { Lexer } from '../lexer';
|
|
4
|
-
import { Parser } from '../parser';
|
|
5
|
-
import { PyClass, PyDict, PyException, PyFunction, PyGenerator, PyInstance, Scope } from './runtime-types';
|
|
6
|
-
import {
|
|
7
|
-
isFloatObject,
|
|
8
|
-
isIntObject,
|
|
9
|
-
isNumericLike,
|
|
10
|
-
numericCompare,
|
|
11
|
-
numericEquals,
|
|
12
|
-
parseStringToken,
|
|
13
|
-
pyStr,
|
|
14
|
-
toNumber,
|
|
15
|
-
} from './value-utils';
|
|
16
|
-
|
|
17
|
-
export function evaluateExpression(this: VirtualMachine, node: any, scope: Scope): any {
|
|
18
|
-
switch (node.type) {
|
|
19
|
-
case ASTNodeType.NUMBER_LITERAL: {
|
|
20
|
-
const raw = node.value;
|
|
21
|
-
if (typeof raw === 'number') return raw;
|
|
22
|
-
if (typeof raw === 'string' && raw.endsWith('j')) {
|
|
23
|
-
const imag = parseFloat(raw.slice(0, -1));
|
|
24
|
-
return { __complex__: true, re: 0, im: imag };
|
|
25
|
-
}
|
|
26
|
-
if (raw.includes('.')) return new Number(parseFloat(raw));
|
|
27
|
-
const big = BigInt(raw);
|
|
28
|
-
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
|
|
29
|
-
const minSafe = BigInt(Number.MIN_SAFE_INTEGER);
|
|
30
|
-
if (big > maxSafe || big < minSafe) return big;
|
|
31
|
-
return Number(raw);
|
|
32
|
-
}
|
|
33
|
-
case ASTNodeType.STRING_LITERAL: {
|
|
34
|
-
const { value, isFString } = parseStringToken(node.value);
|
|
35
|
-
if (isFString) {
|
|
36
|
-
return value.replace(/\{([^}]+)\}/g, (_m, expr) => {
|
|
37
|
-
const { rawExpr, rawSpec } = this.splitFormatSpec(expr);
|
|
38
|
-
const inner = this.evaluateExpressionString(rawExpr.trim(), scope);
|
|
39
|
-
const formatted = this.applyFormatSpec(inner, rawSpec ? rawSpec.trim() : '');
|
|
40
|
-
return formatted;
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
case ASTNodeType.BOOLEAN_LITERAL:
|
|
46
|
-
return node.value;
|
|
47
|
-
case ASTNodeType.NONE_LITERAL:
|
|
48
|
-
return null;
|
|
49
|
-
case ASTNodeType.IDENTIFIER:
|
|
50
|
-
return scope.get(node.name);
|
|
51
|
-
case ASTNodeType.LIST_LITERAL:
|
|
52
|
-
return node.elements.map((el: any) => this.evaluateExpression(el, scope));
|
|
53
|
-
case ASTNodeType.LIST_COMP: {
|
|
54
|
-
const result: any[] = [];
|
|
55
|
-
const compScope = new Scope(scope);
|
|
56
|
-
this.evaluateComprehension(node.comprehension, compScope, () => {
|
|
57
|
-
result.push(this.evaluateExpression(node.expression, compScope));
|
|
58
|
-
});
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
case ASTNodeType.TUPLE_LITERAL: {
|
|
62
|
-
const arr = node.elements.map((el: any) => this.evaluateExpression(el, scope));
|
|
63
|
-
(arr as any).__tuple__ = true;
|
|
64
|
-
return arr;
|
|
65
|
-
}
|
|
66
|
-
case ASTNodeType.SET_COMP: {
|
|
67
|
-
const result = new Set<any>();
|
|
68
|
-
const compScope = new Scope(scope);
|
|
69
|
-
this.evaluateComprehension(node.comprehension, compScope, () => {
|
|
70
|
-
result.add(this.evaluateExpression(node.expression, compScope));
|
|
71
|
-
});
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
case ASTNodeType.SET_LITERAL:
|
|
75
|
-
return new Set(node.elements.map((el: any) => this.evaluateExpression(el, scope)));
|
|
76
|
-
case ASTNodeType.DICT_LITERAL: {
|
|
77
|
-
const map = new PyDict();
|
|
78
|
-
for (const entry of node.entries) {
|
|
79
|
-
map.set(this.evaluateExpression(entry.key, scope), this.evaluateExpression(entry.value, scope));
|
|
80
|
-
}
|
|
81
|
-
return map;
|
|
82
|
-
}
|
|
83
|
-
case ASTNodeType.DICT_COMP: {
|
|
84
|
-
const map = new PyDict();
|
|
85
|
-
const compScope = new Scope(scope);
|
|
86
|
-
this.evaluateComprehension(node.comprehension, compScope, () => {
|
|
87
|
-
map.set(this.evaluateExpression(node.key, compScope), this.evaluateExpression(node.value, compScope));
|
|
88
|
-
});
|
|
89
|
-
return map;
|
|
90
|
-
}
|
|
91
|
-
case ASTNodeType.GENERATOR_EXPR: {
|
|
92
|
-
const self = this;
|
|
93
|
-
const compScope = new Scope(scope);
|
|
94
|
-
const iterator = function* () {
|
|
95
|
-
yield* self.generateComprehension(node.comprehension, compScope, () => self.evaluateExpression(node.expression, compScope));
|
|
96
|
-
};
|
|
97
|
-
return new PyGenerator(iterator());
|
|
98
|
-
}
|
|
99
|
-
case ASTNodeType.BINARY_OPERATION: {
|
|
100
|
-
const left = this.evaluateExpression(node.left, scope);
|
|
101
|
-
const right = this.evaluateExpression(node.right, scope);
|
|
102
|
-
return this.applyBinary(node.operator, left, right);
|
|
103
|
-
}
|
|
104
|
-
case ASTNodeType.UNARY_OPERATION: {
|
|
105
|
-
const operand = this.evaluateExpression(node.operand, scope);
|
|
106
|
-
switch (node.operator) {
|
|
107
|
-
case 'not':
|
|
108
|
-
return !this.isTruthy(operand, scope);
|
|
109
|
-
case '+':
|
|
110
|
-
return typeof operand === 'bigint' ? operand : +operand;
|
|
111
|
-
case '-':
|
|
112
|
-
if (isIntObject(operand)) {
|
|
113
|
-
const boxed = new Number(-operand.valueOf());
|
|
114
|
-
(boxed as any).__int__ = true;
|
|
115
|
-
return boxed;
|
|
116
|
-
}
|
|
117
|
-
if (isFloatObject(operand)) return new Number(-operand.valueOf());
|
|
118
|
-
return -operand;
|
|
119
|
-
case '~':
|
|
120
|
-
return ~operand;
|
|
121
|
-
default:
|
|
122
|
-
throw new PyException('TypeError', `unsupported unary operator ${node.operator}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
case ASTNodeType.BOOL_OPERATION: {
|
|
126
|
-
if (node.operator === 'and') {
|
|
127
|
-
const left = this.evaluateExpression(node.values[0], scope);
|
|
128
|
-
return this.isTruthy(left, scope) ? this.evaluateExpression(node.values[1], scope) : left;
|
|
129
|
-
}
|
|
130
|
-
const left = this.evaluateExpression(node.values[0], scope);
|
|
131
|
-
return this.isTruthy(left, scope) ? left : this.evaluateExpression(node.values[1], scope);
|
|
132
|
-
}
|
|
133
|
-
case ASTNodeType.COMPARE: {
|
|
134
|
-
let left = this.evaluateExpression(node.left, scope);
|
|
135
|
-
for (let i = 0; i < node.ops.length; i++) {
|
|
136
|
-
const op = node.ops[i];
|
|
137
|
-
const right = this.evaluateExpression(node.comparators[i], scope);
|
|
138
|
-
let result = false;
|
|
139
|
-
switch (op) {
|
|
140
|
-
case '==':
|
|
141
|
-
result = numericEquals(left, right);
|
|
142
|
-
break;
|
|
143
|
-
case '!=':
|
|
144
|
-
result = !numericEquals(left, right);
|
|
145
|
-
break;
|
|
146
|
-
case '<': {
|
|
147
|
-
const numeric = numericCompare(left, right);
|
|
148
|
-
if (numeric) {
|
|
149
|
-
if (numeric.kind === 'float') {
|
|
150
|
-
result =
|
|
151
|
-
!Number.isNaN(numeric.left) && !Number.isNaN(numeric.right) && (numeric.left as number) < (numeric.right as number);
|
|
152
|
-
} else {
|
|
153
|
-
result = (numeric.left as bigint) < (numeric.right as bigint);
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
result = left < right;
|
|
157
|
-
}
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
case '>': {
|
|
161
|
-
const numeric = numericCompare(left, right);
|
|
162
|
-
if (numeric) {
|
|
163
|
-
if (numeric.kind === 'float') {
|
|
164
|
-
result =
|
|
165
|
-
!Number.isNaN(numeric.left) && !Number.isNaN(numeric.right) && (numeric.left as number) > (numeric.right as number);
|
|
166
|
-
} else {
|
|
167
|
-
result = (numeric.left as bigint) > (numeric.right as bigint);
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
result = left > right;
|
|
171
|
-
}
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
case '<=': {
|
|
175
|
-
const numeric = numericCompare(left, right);
|
|
176
|
-
if (numeric) {
|
|
177
|
-
if (numeric.kind === 'float') {
|
|
178
|
-
result =
|
|
179
|
-
!Number.isNaN(numeric.left) && !Number.isNaN(numeric.right) && (numeric.left as number) <= (numeric.right as number);
|
|
180
|
-
} else {
|
|
181
|
-
result = (numeric.left as bigint) <= (numeric.right as bigint);
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
result = left <= right;
|
|
185
|
-
}
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
case '>=': {
|
|
189
|
-
const numeric = numericCompare(left, right);
|
|
190
|
-
if (numeric) {
|
|
191
|
-
if (numeric.kind === 'float') {
|
|
192
|
-
result =
|
|
193
|
-
!Number.isNaN(numeric.left) && !Number.isNaN(numeric.right) && (numeric.left as number) >= (numeric.right as number);
|
|
194
|
-
} else {
|
|
195
|
-
result = (numeric.left as bigint) >= (numeric.right as bigint);
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
result = left >= right;
|
|
199
|
-
}
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
case 'in':
|
|
203
|
-
result = this.contains(right, left);
|
|
204
|
-
break;
|
|
205
|
-
case 'not in':
|
|
206
|
-
result = !this.contains(right, left);
|
|
207
|
-
break;
|
|
208
|
-
case 'is':
|
|
209
|
-
result = left === right;
|
|
210
|
-
break;
|
|
211
|
-
case 'is not':
|
|
212
|
-
result = left !== right;
|
|
213
|
-
break;
|
|
214
|
-
default:
|
|
215
|
-
throw new PyException('TypeError', `unsupported comparison ${op}`);
|
|
216
|
-
}
|
|
217
|
-
if (!result) return false;
|
|
218
|
-
left = right;
|
|
219
|
-
}
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
case ASTNodeType.CALL: {
|
|
223
|
-
const callee = this.evaluateExpression(node.callee, scope);
|
|
224
|
-
const positional: any[] = [];
|
|
225
|
-
const kwargs: Record<string, any> = {};
|
|
226
|
-
for (const arg of node.args) {
|
|
227
|
-
if (arg.type === 'KeywordArg') {
|
|
228
|
-
kwargs[arg.name] = this.evaluateExpression(arg.value, scope);
|
|
229
|
-
} else if (arg.type === 'StarArg') {
|
|
230
|
-
const value = this.evaluateExpression(arg.value, scope);
|
|
231
|
-
positional.push(...(Array.isArray(value) ? value : Array.from(value)));
|
|
232
|
-
} else if (arg.type === 'KwArg') {
|
|
233
|
-
const value = this.evaluateExpression(arg.value, scope);
|
|
234
|
-
Object.assign(kwargs, value);
|
|
235
|
-
} else {
|
|
236
|
-
positional.push(this.evaluateExpression(arg, scope));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return this.callFunction(callee, positional, scope, kwargs);
|
|
240
|
-
}
|
|
241
|
-
case ASTNodeType.ATTRIBUTE: {
|
|
242
|
-
const obj = this.evaluateExpression(node.object, scope);
|
|
243
|
-
return this.getAttribute(obj, node.name, scope);
|
|
244
|
-
}
|
|
245
|
-
case ASTNodeType.SUBSCRIPT: {
|
|
246
|
-
const obj = this.evaluateExpression(node.object, scope);
|
|
247
|
-
if (node.index && node.index.type === ASTNodeType.SLICE) {
|
|
248
|
-
const slice = {
|
|
249
|
-
type: ASTNodeType.SLICE,
|
|
250
|
-
start: node.index.start ? this.evaluateExpression(node.index.start, scope) : null,
|
|
251
|
-
end: node.index.end ? this.evaluateExpression(node.index.end, scope) : null,
|
|
252
|
-
step: node.index.step ? this.evaluateExpression(node.index.step, scope) : null,
|
|
253
|
-
};
|
|
254
|
-
return this.getSubscript(obj, slice);
|
|
255
|
-
}
|
|
256
|
-
const index = this.evaluateExpression(node.index, scope);
|
|
257
|
-
return this.getSubscript(obj, index);
|
|
258
|
-
}
|
|
259
|
-
case ASTNodeType.IF_EXPRESSION: {
|
|
260
|
-
const test = this.evaluateExpression(node.test, scope);
|
|
261
|
-
return this.isTruthy(test, scope)
|
|
262
|
-
? this.evaluateExpression(node.consequent, scope)
|
|
263
|
-
: this.evaluateExpression(node.alternate, scope);
|
|
264
|
-
}
|
|
265
|
-
case ASTNodeType.LAMBDA: {
|
|
266
|
-
return new PyFunction(
|
|
267
|
-
'<lambda>',
|
|
268
|
-
node.params.map((p: string) => ({ type: 'Param', name: p })),
|
|
269
|
-
[
|
|
270
|
-
{
|
|
271
|
-
type: ASTNodeType.RETURN_STATEMENT,
|
|
272
|
-
value: node.body,
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
scope,
|
|
276
|
-
false
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
default:
|
|
280
|
-
throw new Error(`Unsupported expression type: ${node.type}`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function evaluateExpressionString(this: VirtualMachine, expr: string, scope: Scope): any {
|
|
285
|
-
const wrapped = `__f = ${expr}\n`;
|
|
286
|
-
const tokens = new Lexer(wrapped).tokenize();
|
|
287
|
-
const ast = new Parser(tokens).parse();
|
|
288
|
-
const assignment = ast.body[0];
|
|
289
|
-
if (!assignment || assignment.type !== ASTNodeType.ASSIGNMENT) {
|
|
290
|
-
return this.executeExpressionInline(expr, scope);
|
|
291
|
-
}
|
|
292
|
-
return this.evaluateExpression(assignment.value, scope);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export function executeExpressionInline(this: VirtualMachine, expr: string, scope: Scope): any {
|
|
296
|
-
const tokens = expr.trim().split(/\s+/);
|
|
297
|
-
if (tokens.length === 1 && scope.values.has(tokens[0])) {
|
|
298
|
-
return scope.get(tokens[0]);
|
|
299
|
-
}
|
|
300
|
-
return expr;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function applyFormatSpec(this: VirtualMachine, value: any, spec: string): string {
|
|
304
|
-
if (!spec) return pyStr(value);
|
|
305
|
-
if (spec.endsWith('%')) {
|
|
306
|
-
const digits = spec.includes('.') ? parseInt(spec.split('.')[1], 10) : 0;
|
|
307
|
-
const num = isNumericLike(value) ? toNumber(value) : parseFloat(value);
|
|
308
|
-
return (num * 100).toFixed(digits) + '%';
|
|
309
|
-
}
|
|
310
|
-
if (spec.includes('.')) {
|
|
311
|
-
const parts = spec.split('.');
|
|
312
|
-
const width = parts[0];
|
|
313
|
-
const precision = parseInt(parts[1].replace(/[^\d]/g, ''), 10);
|
|
314
|
-
const num = isNumericLike(value) ? toNumber(value) : parseFloat(value);
|
|
315
|
-
const formatted = num.toFixed(precision);
|
|
316
|
-
return this.applyWidth(formatted, width);
|
|
317
|
-
}
|
|
318
|
-
if (spec === 'd') return typeof value === 'bigint' ? value.toString() : String(parseInt(value, 10));
|
|
319
|
-
if (spec === 'b') return typeof value === 'bigint' ? value.toString(2) : Number(value).toString(2);
|
|
320
|
-
if (spec === 'x') return typeof value === 'bigint' ? value.toString(16) : Number(value).toString(16);
|
|
321
|
-
if (spec === 'o') return typeof value === 'bigint' ? value.toString(8) : Number(value).toString(8);
|
|
322
|
-
return this.applyWidth(String(value), spec);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function splitFormatSpec(expr: string): { rawExpr: string; rawSpec: string } {
|
|
326
|
-
let depth = 0;
|
|
327
|
-
for (let i = 0; i < expr.length; i++) {
|
|
328
|
-
const ch = expr[i];
|
|
329
|
-
if (ch === '(' || ch === '[' || ch === '{') depth++;
|
|
330
|
-
if (ch === ')' || ch === ']' || ch === '}') depth = Math.max(0, depth - 1);
|
|
331
|
-
if (ch === ':' && depth === 0) {
|
|
332
|
-
return { rawExpr: expr.slice(0, i), rawSpec: expr.slice(i + 1) };
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return { rawExpr: expr, rawSpec: '' };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export function applyWidth(text: string, spec: string): string {
|
|
339
|
-
const match = spec.match(/([<^>])?(\d+)/);
|
|
340
|
-
if (!match) return text;
|
|
341
|
-
const align = match[1] || '>';
|
|
342
|
-
const width = parseInt(match[2], 10);
|
|
343
|
-
if (text.length >= width) return text;
|
|
344
|
-
const padding = width - text.length;
|
|
345
|
-
if (align === '<') return text + ' '.repeat(padding);
|
|
346
|
-
if (align === '^') {
|
|
347
|
-
const left = Math.floor(padding / 2);
|
|
348
|
-
const right = padding - left;
|
|
349
|
-
return ' '.repeat(left) + text + ' '.repeat(right);
|
|
350
|
-
}
|
|
351
|
-
return ' '.repeat(padding) + text;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
export function contains(this: VirtualMachine, container: any, value: any): boolean {
|
|
355
|
-
if (Array.isArray(container)) {
|
|
356
|
-
if (isNumericLike(value)) {
|
|
357
|
-
return container.some((item) => isNumericLike(item) && numericEquals(item, value));
|
|
358
|
-
}
|
|
359
|
-
return container.includes(value);
|
|
360
|
-
}
|
|
361
|
-
if (typeof container === 'string') return container.includes(value);
|
|
362
|
-
if (container instanceof Set) {
|
|
363
|
-
if (container.has(value)) return true;
|
|
364
|
-
if (isNumericLike(value)) {
|
|
365
|
-
for (const item of container.values()) {
|
|
366
|
-
if (isNumericLike(item) && numericEquals(item, value)) return true;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
if (container instanceof PyDict) return container.has(value);
|
|
372
|
-
return false;
|
|
373
|
-
}
|
package/src/vm/imports.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import type { VirtualMachine } from './vm';
|
|
4
|
-
import { Lexer } from '../lexer';
|
|
5
|
-
import { Parser } from '../parser';
|
|
6
|
-
import { PyException, PyFunction, PyGenerator, Scope } from './runtime-types';
|
|
7
|
-
|
|
8
|
-
export function importModule(this: VirtualMachine, name: string, scope: Scope): any {
|
|
9
|
-
if (this.moduleCache.has(name)) {
|
|
10
|
-
return this.moduleCache.get(name);
|
|
11
|
-
}
|
|
12
|
-
let module: any;
|
|
13
|
-
if (name === 'asyncio') {
|
|
14
|
-
module = this.createAsyncioModule(scope);
|
|
15
|
-
} else {
|
|
16
|
-
module = this.loadModuleFromFile(name, scope);
|
|
17
|
-
}
|
|
18
|
-
this.moduleCache.set(name, module);
|
|
19
|
-
return module;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function createAsyncioModule(this: VirtualMachine, scope: Scope): any {
|
|
23
|
-
return {
|
|
24
|
-
__name__: 'asyncio',
|
|
25
|
-
run: (value: any) => {
|
|
26
|
-
if (value instanceof PyFunction) {
|
|
27
|
-
return this.callFunction(value, [], scope);
|
|
28
|
-
}
|
|
29
|
-
if (value instanceof PyGenerator) {
|
|
30
|
-
return value.next();
|
|
31
|
-
}
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function loadModuleFromFile(this: VirtualMachine, name: string, scope: Scope): any {
|
|
38
|
-
const modulePath = this.resolveModulePath(name);
|
|
39
|
-
if (!modulePath) {
|
|
40
|
-
throw new PyException('ImportError', `No module named '${name}'`);
|
|
41
|
-
}
|
|
42
|
-
const code = fs.readFileSync(modulePath, 'utf-8');
|
|
43
|
-
const lexer = new Lexer(code);
|
|
44
|
-
const tokens = lexer.tokenize();
|
|
45
|
-
const parser = new Parser(tokens);
|
|
46
|
-
const ast = parser.parse();
|
|
47
|
-
const moduleScope = new Scope(scope.root());
|
|
48
|
-
moduleScope.set('__name__', name);
|
|
49
|
-
this.executeBlock(ast.body, moduleScope);
|
|
50
|
-
return { __name__: name, __moduleScope__: moduleScope };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function resolveModulePath(this: VirtualMachine, name: string): string | null {
|
|
54
|
-
for (const basePath of this.moduleSearchPaths) {
|
|
55
|
-
const directPath = path.join(basePath, `${name}.py`);
|
|
56
|
-
if (fs.existsSync(directPath)) return directPath;
|
|
57
|
-
const initPath = path.join(basePath, name, '__init__.py');
|
|
58
|
-
if (fs.existsSync(initPath)) return initPath;
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
package/src/vm/index.ts
DELETED