jslike 1.3.0 → 1.4.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.
@@ -1,1802 +0,0 @@
1
- import { Environment, ReturnValue, BreakSignal, ContinueSignal, ThrowSignal } from '../runtime/environment.js';
2
- import { parse as acornParse } from '../parser.js';
3
- import { createMethodNotFoundError } from '../errors/enhanced-error.js';
4
-
5
- export class Interpreter {
6
- constructor(globalEnv, options = {}) {
7
- this.globalEnv = globalEnv;
8
- this.moduleResolver = options.moduleResolver;
9
- this.moduleCache = new Map(); // Cache loaded modules
10
- this.moduleExports = {}; // Track exports in current module
11
- this.abortSignal = options.abortSignal;
12
- }
13
-
14
- // Check if execution should be aborted
15
- checkAbortSignal() {
16
- if (this.abortSignal && this.abortSignal.aborted) {
17
- const error = new Error('The operation was aborted');
18
- error.name = 'AbortError';
19
- throw error;
20
- }
21
- }
22
-
23
- // Async evaluation for async functions - handles await expressions
24
- async evaluateAsync(node, env) {
25
- if (!node) return undefined;
26
-
27
- // Check for abort signal before evaluating
28
- this.checkAbortSignal();
29
-
30
- // Handle await expressions by actually awaiting the promise
31
- if (node.type === 'AwaitExpression') {
32
- const promise = await this.evaluateAsync(node.argument, env);
33
- return await promise;
34
- }
35
-
36
- // For block statements, evaluate each statement async
37
- if (node.type === 'BlockStatement') {
38
- let result = undefined;
39
- for (const statement of node.body) {
40
- result = await this.evaluateAsync(statement, env);
41
- if (result instanceof ReturnValue || result instanceof ThrowSignal ||
42
- result instanceof BreakSignal || result instanceof ContinueSignal) {
43
- return result;
44
- }
45
- }
46
- return result;
47
- }
48
-
49
- // For expression statements, evaluate the expression async
50
- if (node.type === 'ExpressionStatement') {
51
- return await this.evaluateAsync(node.expression, env);
52
- }
53
-
54
- // For variable declarations with await in init
55
- if (node.type === 'VariableDeclaration') {
56
- for (const declarator of node.declarations) {
57
- const value = declarator.init
58
- ? await this.evaluateAsync(declarator.init, env)
59
- : undefined;
60
-
61
- const isConst = node.kind === 'const';
62
- if (declarator.id.type === 'Identifier') {
63
- env.define(declarator.id.name, value, isConst);
64
- } else if (declarator.id.type === 'ObjectPattern') {
65
- this.bindObjectPattern(declarator.id, value, env, isConst);
66
- } else if (declarator.id.type === 'ArrayPattern') {
67
- this.bindArrayPattern(declarator.id, value, env, isConst);
68
- }
69
- }
70
- return undefined;
71
- }
72
-
73
- // For Program nodes (evaluate all statements async)
74
- if (node.type === 'Program') {
75
- let result = undefined;
76
- for (const statement of node.body) {
77
- result = await this.evaluateAsync(statement, env);
78
- // Handle top-level return and throw
79
- if (result instanceof ReturnValue || result instanceof ThrowSignal) {
80
- return result;
81
- }
82
- }
83
- return result;
84
- }
85
-
86
- // For import declarations (always async)
87
- if (node.type === 'ImportDeclaration') {
88
- return await this.evaluateImportDeclaration(node, env);
89
- }
90
-
91
- // For export declarations
92
- if (node.type === 'ExportNamedDeclaration') {
93
- return this.evaluateExportNamedDeclaration(node, env);
94
- }
95
-
96
- if (node.type === 'ExportDefaultDeclaration') {
97
- return this.evaluateExportDefaultDeclaration(node, env);
98
- }
99
-
100
- // For return statements with await
101
- if (node.type === 'ReturnStatement') {
102
- const value = node.argument ? await this.evaluateAsync(node.argument, env) : undefined;
103
- return new ReturnValue(value);
104
- }
105
-
106
- // For binary/unary expressions that might contain awaits
107
- if (node.type === 'BinaryExpression') {
108
- const left = await this.evaluateAsync(node.left, env);
109
- const right = await this.evaluateAsync(node.right, env);
110
- return this.evaluateBinaryExpressionValues(node.operator, left, right);
111
- }
112
-
113
- // For call expressions (might be calling async functions)
114
- if (node.type === 'CallExpression') {
115
- let thisContext = undefined;
116
- let callee;
117
- let objectName = null;
118
- let methodName = null;
119
-
120
- if (node.callee.type === 'MemberExpression') {
121
- thisContext = await this.evaluateAsync(node.callee.object, env);
122
- const prop = node.callee.computed
123
- ? await this.evaluateAsync(node.callee.property, env)
124
- : node.callee.property.name;
125
- callee = thisContext[prop];
126
-
127
- // Capture names for enhanced error messages
128
- methodName = prop;
129
- objectName = this.getExpressionName(node.callee.object);
130
- } else {
131
- callee = await this.evaluateAsync(node.callee, env);
132
- }
133
-
134
- // Handle optional call - if optional and callee is null/undefined, return undefined
135
- if (node.optional && (callee === null || callee === undefined)) {
136
- return undefined;
137
- }
138
-
139
- const args = [];
140
- for (const arg of node.arguments) {
141
- args.push(await this.evaluateAsync(arg, env));
142
- }
143
-
144
- if (typeof callee === 'function') {
145
- if (thisContext !== undefined) {
146
- return await callee.call(thisContext, ...args);
147
- }
148
- return await callee(...args);
149
- } else if (callee && callee.__isFunction) {
150
- return await this.callUserFunction(callee, args, env, thisContext);
151
- }
152
-
153
- // Throw enhanced error for member expression calls
154
- if (objectName && methodName) {
155
- throw createMethodNotFoundError(objectName, methodName, thisContext);
156
- }
157
-
158
- throw new TypeError(`${node.callee.name || 'Expression'} is not a function`);
159
- }
160
-
161
- // For chain expressions (optional chaining)
162
- if (node.type === 'ChainExpression') {
163
- return await this.evaluateAsync(node.expression, env);
164
- }
165
-
166
- // For member expressions in async context
167
- if (node.type === 'MemberExpression') {
168
- const obj = await this.evaluateAsync(node.object, env);
169
-
170
- // Handle optional chaining
171
- if (node.optional && (obj === null || obj === undefined)) {
172
- return undefined;
173
- }
174
-
175
- if (obj === null || obj === undefined) {
176
- throw new TypeError(`Cannot read property of ${obj}`);
177
- }
178
-
179
- const prop = node.computed
180
- ? await this.evaluateAsync(node.property, env)
181
- : node.property.name;
182
-
183
- return obj[prop];
184
- }
185
-
186
- // For template literals with await expressions
187
- if (node.type === 'TemplateLiteral') {
188
- let result = '';
189
- for (let i = 0; i < node.quasis.length; i++) {
190
- result += node.quasis[i].value.cooked || node.quasis[i].value.raw;
191
- if (i < node.expressions.length) {
192
- const exprValue = await this.evaluateAsync(node.expressions[i], env);
193
- result += String(exprValue);
194
- }
195
- }
196
- return result;
197
- }
198
-
199
- // For logical expressions with async operands (await support)
200
- if (node.type === 'LogicalExpression') {
201
- const left = await this.evaluateAsync(node.left, env);
202
-
203
- if (node.operator === '&&') {
204
- return left ? await this.evaluateAsync(node.right, env) : left;
205
- } else if (node.operator === '||') {
206
- return left ? left : await this.evaluateAsync(node.right, env);
207
- } else if (node.operator === '??') {
208
- return left !== null && left !== undefined ? left : await this.evaluateAsync(node.right, env);
209
- }
210
-
211
- throw new Error(`Unknown logical operator: ${node.operator}`);
212
- }
213
-
214
- // For try-catch-finally with async operations
215
- if (node.type === 'TryStatement') {
216
- let result;
217
-
218
- try {
219
- result = await this.evaluateAsync(node.block, env);
220
-
221
- if (result instanceof ThrowSignal) {
222
- throw result.value;
223
- }
224
- } catch (error) {
225
- if (node.handler) {
226
- const catchEnv = new Environment(env);
227
- if (node.handler.param) {
228
- catchEnv.define(node.handler.param.name, error);
229
- }
230
- result = await this.evaluateAsync(node.handler.body, catchEnv);
231
- } else {
232
- throw error;
233
- }
234
- } finally {
235
- if (node.finalizer) {
236
- const finalResult = await this.evaluateAsync(node.finalizer, env);
237
- // If finally block throws or returns, it overrides the try/catch result
238
- if (finalResult instanceof ThrowSignal || finalResult instanceof ReturnValue) {
239
- return finalResult;
240
- }
241
- }
242
- }
243
-
244
- return result;
245
- }
246
-
247
- // For new expressions (async constructors)
248
- if (node.type === 'NewExpression') {
249
- const result = this.evaluateNewExpression(node, env);
250
- // If it's a promise, await it
251
- if (result && typeof result.then === 'function') {
252
- return await result;
253
- }
254
- return result;
255
- }
256
-
257
- // For everything else, delegate to sync evaluate
258
- return this.evaluate(node, env);
259
- }
260
-
261
- evaluate(node, env) {
262
- if (!node) return undefined;
263
-
264
- // Check for abort signal before evaluating
265
- this.checkAbortSignal();
266
-
267
- switch (node.type) {
268
- case 'Program':
269
- return this.evaluateProgram(node, env);
270
-
271
- case 'Literal':
272
- // Handle regex literals
273
- if (node.regex) {
274
- return new RegExp(node.regex.pattern, node.regex.flags);
275
- }
276
- return node.value;
277
-
278
- case 'Identifier':
279
- return env.get(node.name);
280
-
281
- case 'BinaryExpression':
282
- return this.evaluateBinaryExpression(node, env);
283
-
284
- case 'UnaryExpression':
285
- return this.evaluateUnaryExpression(node, env);
286
-
287
- case 'UpdateExpression':
288
- return this.evaluateUpdateExpression(node, env);
289
-
290
- case 'AwaitExpression':
291
- return this.evaluateAwaitExpression(node, env);
292
-
293
- case 'AssignmentExpression':
294
- return this.evaluateAssignmentExpression(node, env);
295
-
296
- case 'LogicalExpression':
297
- return this.evaluateLogicalExpression(node, env);
298
-
299
- case 'ConditionalExpression':
300
- return this.evaluateConditionalExpression(node, env);
301
-
302
- case 'CallExpression':
303
- return this.evaluateCallExpression(node, env);
304
-
305
- case 'MemberExpression':
306
- return this.evaluateMemberExpression(node, env);
307
-
308
- case 'ChainExpression':
309
- return this.evaluateChainExpression(node, env);
310
-
311
- case 'ArrayExpression':
312
- return this.evaluateArrayExpression(node, env);
313
-
314
- case 'ObjectExpression':
315
- return this.evaluateObjectExpression(node, env);
316
-
317
- case 'FunctionExpression':
318
- case 'ArrowFunctionExpression':
319
- return this.evaluateFunctionExpression(node, env);
320
-
321
- case 'NewExpression':
322
- return this.evaluateNewExpression(node, env);
323
-
324
- case 'ThisExpression':
325
- return this.evaluateThisExpression(node, env);
326
-
327
- case 'Super':
328
- return this.evaluateSuperExpression(node, env);
329
-
330
- case 'SequenceExpression':
331
- return this.evaluateSequenceExpression(node, env);
332
-
333
- case 'VariableDeclaration':
334
- return this.evaluateVariableDeclaration(node, env);
335
-
336
- case 'FunctionDeclaration':
337
- return this.evaluateFunctionDeclaration(node, env);
338
-
339
- case 'ImportDeclaration':
340
- return this.evaluateImportDeclaration(node, env);
341
-
342
- case 'ExportNamedDeclaration':
343
- return this.evaluateExportNamedDeclaration(node, env);
344
-
345
- case 'ExportDefaultDeclaration':
346
- return this.evaluateExportDefaultDeclaration(node, env);
347
-
348
- case 'BlockStatement':
349
- return this.evaluateBlockStatement(node, env);
350
-
351
- case 'ExpressionStatement':
352
- return this.evaluate(node.expression, env);
353
-
354
- case 'ReturnStatement':
355
- return new ReturnValue(node.argument ? this.evaluate(node.argument, env) : undefined);
356
-
357
- case 'IfStatement':
358
- return this.evaluateIfStatement(node, env);
359
-
360
- case 'WhileStatement':
361
- return this.evaluateWhileStatement(node, env);
362
-
363
- case 'DoWhileStatement':
364
- return this.evaluateDoWhileStatement(node, env);
365
-
366
- case 'ForStatement':
367
- return this.evaluateForStatement(node, env);
368
-
369
- case 'ForInStatement':
370
- return this.evaluateForInStatement(node, env);
371
-
372
- case 'ForOfStatement':
373
- return this.evaluateForOfStatement(node, env);
374
-
375
- case 'BreakStatement':
376
- return new BreakSignal();
377
-
378
- case 'ContinueStatement':
379
- return new ContinueSignal();
380
-
381
- case 'ThrowStatement':
382
- return new ThrowSignal(this.evaluate(node.argument, env));
383
-
384
- case 'TryStatement':
385
- return this.evaluateTryStatement(node, env);
386
-
387
- case 'SwitchStatement':
388
- return this.evaluateSwitchStatement(node, env);
389
-
390
- case 'EmptyStatement':
391
- return undefined;
392
-
393
- // ES6+ Features
394
- case 'TemplateLiteral':
395
- return this.evaluateTemplateLiteral(node, env);
396
-
397
- case 'ClassDeclaration':
398
- return this.evaluateClassDeclaration(node, env);
399
-
400
- case 'ClassExpression':
401
- return this.evaluateClassExpression(node, env);
402
-
403
- case 'MethodDefinition':
404
- return this.evaluateMethodDefinition(node, env);
405
-
406
- case 'SpreadElement':
407
- return this.evaluateSpreadElement(node, env);
408
-
409
- case 'RestElement':
410
- return this.evaluateRestElement(node, env);
411
-
412
- case 'ObjectPattern':
413
- return this.evaluateObjectPattern(node, env);
414
-
415
- case 'ArrayPattern':
416
- return this.evaluateArrayPattern(node, env);
417
-
418
- case 'AssignmentPattern':
419
- return this.evaluateAssignmentPattern(node, env);
420
-
421
- case 'Property':
422
- return this.evaluateProperty(node, env);
423
-
424
- default:
425
- throw new Error(`Unknown node type: ${node.type}`);
426
- }
427
- }
428
-
429
- evaluateProgram(node, env) {
430
- let result = undefined;
431
- for (let i = 0; i < node.body.length; i++) {
432
- const statement = node.body[i];
433
- const isLast = i === node.body.length - 1;
434
-
435
- // Special case: Last statement is a BlockStatement that looks like object literal
436
- // Handle both shorthand { x, y } and full syntax { key: value, key2: value2 }
437
- if (isLast && statement.type === 'BlockStatement') {
438
- const objLiteral = this.tryConvertBlockToObjectLiteral(statement, env);
439
- if (objLiteral !== null) {
440
- return objLiteral;
441
- }
442
- }
443
-
444
- const statementResult = this.evaluate(statement, env);
445
- if (statementResult instanceof ReturnValue || statementResult instanceof ThrowSignal) {
446
- return statementResult;
447
- }
448
- result = statementResult;
449
- }
450
- return result;
451
- }
452
-
453
- // Try to convert a BlockStatement to an object literal
454
- // Returns null if the block doesn't look like an object literal
455
- tryConvertBlockToObjectLiteral(block, env) {
456
- if (block.body.length === 0) return null;
457
-
458
- // Check if it's shorthand syntax: { x, y }
459
- if (block.body.length === 1 && block.body[0].type === 'ExpressionStatement') {
460
- const expr = block.body[0].expression;
461
-
462
- // SequenceExpression of Identifiers: { x, y, z }
463
- if (expr.type === 'SequenceExpression' &&
464
- expr.expressions.every(e => e.type === 'Identifier')) {
465
- const obj = {};
466
- for (const identifier of expr.expressions) {
467
- obj[identifier.name] = env.get(identifier.name);
468
- }
469
- return obj;
470
- }
471
-
472
- // Single Identifier: { x }
473
- if (expr.type === 'Identifier') {
474
- const obj = {};
475
- obj[expr.name] = env.get(expr.name);
476
- return obj;
477
- }
478
- }
479
-
480
- // Check if it's labeled statements that look like object properties
481
- // Example: { first: first(arr), last: last(arr) }
482
- // This gets parsed as LabeledStatements in script mode
483
- const allLabeled = block.body.every(stmt => stmt.type === 'LabeledStatement');
484
- if (!allLabeled) return null;
485
-
486
- // Convert labeled statements to object properties
487
- const obj = {};
488
- for (const stmt of block.body) {
489
- const label = stmt.label.name;
490
-
491
- // The body of LabeledStatement should be ExpressionStatement
492
- if (stmt.body.type !== 'ExpressionStatement') {
493
- return null; // Not an object literal pattern
494
- }
495
-
496
- const value = this.evaluate(stmt.body.expression, env);
497
- obj[label] = value;
498
- }
499
-
500
- return obj;
501
- }
502
-
503
- evaluateBinaryExpression(node, env) {
504
- const left = this.evaluate(node.left, env);
505
- const right = this.evaluate(node.right, env);
506
- return this.evaluateBinaryExpressionValues(node.operator, left, right);
507
- }
508
-
509
- evaluateBinaryExpressionValues(operator, left, right) {
510
- switch (operator) {
511
- case '+': return left + right;
512
- case '-': return left - right;
513
- case '*': return left * right;
514
- case '/': return left / right;
515
- case '%': return left % right;
516
- case '**': return left ** right;
517
- case '<': return left < right;
518
- case '>': return left > right;
519
- case '<=': return left <= right;
520
- case '>=': return left >= right;
521
- case '==': return left == right;
522
- case '!=': return left != right;
523
- case '===': return left === right;
524
- case '!==': return left !== right;
525
- case '&': return left & right;
526
- case '|': return left | right;
527
- case '^': return left ^ right;
528
- case '<<': return left << right;
529
- case '>>': return left >> right;
530
- case '>>>': return left >>> right;
531
- case 'in': {
532
- // Check right operand is not null/undefined
533
- if (right === null || right === undefined) {
534
- throw new TypeError(
535
- 'Cannot use "in" operator to search for property in null or undefined'
536
- );
537
- }
538
- // Coerce left operand to string/symbol for property key
539
- const key = String(left);
540
- return key in Object(right);
541
- }
542
- case 'instanceof': {
543
- // Check right operand is a constructor function or JSLike function
544
- if (typeof right !== 'function' && !(right && right.__isFunction)) {
545
- throw new TypeError(
546
- 'Right-hand side of instanceof is not a constructor'
547
- );
548
- }
549
- // Primitives (null/undefined) always return false
550
- if (left === null || left === undefined) {
551
- return false;
552
- }
553
- // Special case: check if left is a JSLike function and right is Function constructor
554
- if (right === Function && left && left.__isFunction) {
555
- return true;
556
- }
557
- // Use JavaScript's instanceof for native objects
558
- if (typeof right === 'function') {
559
- return left instanceof right;
560
- }
561
- // For JSLike functions, check prototype chain
562
- return false;
563
- }
564
- default:
565
- throw new Error(`Unknown binary operator: ${operator}`);
566
- }
567
- }
568
-
569
- evaluateUnaryExpression(node, env) {
570
- const argument = this.evaluate(node.argument, env);
571
-
572
- switch (node.operator) {
573
- case '+': return +argument;
574
- case '-': return -argument;
575
- case '!': return !argument;
576
- case '~': return ~argument;
577
- case 'typeof':
578
- // JSLike functions should report as 'function'
579
- if (argument && argument.__isFunction) {
580
- return 'function';
581
- }
582
- return typeof argument;
583
- case 'void': return undefined;
584
- case 'delete':
585
- if (node.argument.type === 'MemberExpression') {
586
- const obj = this.evaluate(node.argument.object, env);
587
- const prop = node.argument.computed
588
- ? this.evaluate(node.argument.property, env)
589
- : node.argument.property.name;
590
- return delete obj[prop];
591
- }
592
- return true;
593
- default:
594
- throw new Error(`Unknown unary operator: ${node.operator}`);
595
- }
596
- }
597
-
598
- evaluateUpdateExpression(node, env) {
599
- if (node.argument.type === 'Identifier') {
600
- const name = node.argument.name;
601
- const current = env.get(name);
602
- // Wang feature: treat null/undefined as 0 for increment/decrement
603
- const numericCurrent = (current === null || current === undefined) ? 0 : Number(current);
604
- const newValue = node.operator === '++' ? numericCurrent + 1 : numericCurrent - 1;
605
- env.set(name, newValue);
606
- return node.prefix ? newValue : numericCurrent;
607
- } else if (node.argument.type === 'MemberExpression') {
608
- const obj = this.evaluate(node.argument.object, env);
609
-
610
- // Check for null/undefined object
611
- if (obj === null || obj === undefined) {
612
- throw new TypeError(
613
- `Cannot read properties of ${obj} (reading '${
614
- node.argument.computed
615
- ? this.evaluate(node.argument.property, env)
616
- : node.argument.property.name
617
- }')`
618
- );
619
- }
620
-
621
- const prop = node.argument.computed
622
- ? this.evaluate(node.argument.property, env)
623
- : node.argument.property.name;
624
-
625
- // Get current value and convert to number
626
- let current = obj[prop];
627
- // Wang feature: treat null/undefined as 0 for increment/decrement
628
- const numericCurrent = (current === null || current === undefined) ? 0 : Number(current);
629
- const newValue = node.operator === '++' ? numericCurrent + 1 : numericCurrent - 1;
630
- obj[prop] = newValue;
631
-
632
- return node.prefix ? newValue : numericCurrent;
633
- }
634
- throw new Error('Invalid update expression target');
635
- }
636
-
637
- evaluateAwaitExpression(node, env) {
638
- // Evaluate the argument (should be a Promise)
639
- const promise = this.evaluate(node.argument, env);
640
-
641
- // Return the promise - the caller must handle it
642
- // This is a simplified implementation that relies on the runtime being async
643
- return promise;
644
- }
645
-
646
- evaluateAssignmentExpression(node, env) {
647
- const value = this.evaluate(node.right, env);
648
-
649
- if (node.left.type === 'Identifier') {
650
- const name = node.left.name;
651
-
652
- if (node.operator === '=') {
653
- if (env.has(name)) {
654
- env.set(name, value);
655
- } else {
656
- env.define(name, value);
657
- }
658
- return value;
659
- } else {
660
- const current = env.get(name);
661
- const newValue = this.applyCompoundAssignment(node.operator, current, value);
662
- env.set(name, newValue);
663
- return newValue;
664
- }
665
- } else if (node.left.type === 'MemberExpression') {
666
- const obj = this.evaluate(node.left.object, env);
667
- const prop = node.left.computed
668
- ? this.evaluate(node.left.property, env)
669
- : node.left.property.name;
670
-
671
- if (node.operator === '=') {
672
- obj[prop] = value;
673
- return value;
674
- } else {
675
- const newValue = this.applyCompoundAssignment(node.operator, obj[prop], value);
676
- obj[prop] = newValue;
677
- return newValue;
678
- }
679
- }
680
-
681
- throw new Error('Invalid assignment target');
682
- }
683
-
684
- applyCompoundAssignment(operator, left, right) {
685
- // For numeric operators, coerce undefined to 0 (like JavaScript does for += with numbers)
686
- // But keep undefined for string concatenation
687
- const isNumericOp = operator !== '+=';
688
- const leftVal = (isNumericOp && left === undefined) ? 0 : left;
689
-
690
- switch (operator) {
691
- case '+=':
692
- // Special case: undefined + number should coerce undefined to 0
693
- if (left === undefined && typeof right === 'number') {
694
- return 0 + right;
695
- }
696
- return left + right;
697
- case '-=': return leftVal - right;
698
- case '*=': return leftVal * right;
699
- case '/=': return leftVal / right;
700
- case '%=': return leftVal % right;
701
- default: throw new Error(`Unknown assignment operator: ${operator}`);
702
- }
703
- }
704
-
705
- evaluateLogicalExpression(node, env) {
706
- const left = this.evaluate(node.left, env);
707
-
708
- if (node.operator === '&&') {
709
- return left ? this.evaluate(node.right, env) : left;
710
- } else if (node.operator === '||') {
711
- return left ? left : this.evaluate(node.right, env);
712
- } else if (node.operator === '??') {
713
- return left !== null && left !== undefined ? left : this.evaluate(node.right, env);
714
- }
715
-
716
- throw new Error(`Unknown logical operator: ${node.operator}`);
717
- }
718
-
719
- evaluateConditionalExpression(node, env) {
720
- const test = this.evaluate(node.test, env);
721
- return test
722
- ? this.evaluate(node.consequent, env)
723
- : this.evaluate(node.alternate, env);
724
- }
725
-
726
- evaluateCallExpression(node, env) {
727
- // Determine thisContext for method calls
728
- let thisContext = undefined;
729
- let callee;
730
- let objectName = null;
731
- let methodName = null;
732
-
733
- if (node.callee.type === 'MemberExpression') {
734
- // For method calls like obj.method(), set this to obj
735
- thisContext = this.evaluate(node.callee.object, env);
736
- const prop = node.callee.computed
737
- ? this.evaluate(node.callee.property, env)
738
- : node.callee.property.name;
739
- callee = thisContext[prop];
740
-
741
- // Capture names for enhanced error messages
742
- methodName = prop;
743
- objectName = this.getExpressionName(node.callee.object);
744
- } else {
745
- callee = this.evaluate(node.callee, env);
746
- }
747
-
748
- // Handle optional call - if optional and callee is null/undefined, return undefined
749
- if (node.optional && (callee === null || callee === undefined)) {
750
- return undefined;
751
- }
752
-
753
- const args = node.arguments.map(arg => this.evaluate(arg, env));
754
-
755
- if (typeof callee === 'function') {
756
- // Native JavaScript function or class method
757
- if (thisContext !== undefined) {
758
- return callee.call(thisContext, ...args);
759
- }
760
- return callee(...args);
761
- } else if (callee && callee.__isFunction) {
762
- // User-defined function - pass thisContext
763
- return this.callUserFunction(callee, args, env, thisContext);
764
- }
765
-
766
- // Throw enhanced error for member expression calls
767
- if (objectName && methodName) {
768
- throw createMethodNotFoundError(objectName, methodName, thisContext);
769
- }
770
-
771
- throw new TypeError(`${node.callee.name || 'Expression'} is not a function`);
772
- }
773
-
774
- // Helper to get a readable name for an expression (for error messages)
775
- getExpressionName(node) {
776
- if (!node) return 'object';
777
-
778
- switch (node.type) {
779
- case 'Identifier':
780
- return node.name;
781
- case 'ThisExpression':
782
- return 'this';
783
- case 'MemberExpression':
784
- const objName = this.getExpressionName(node.object);
785
- const propName = node.computed ? '[...]' : node.property.name;
786
- return `${objName}.${propName}`;
787
- default:
788
- return 'object';
789
- }
790
- }
791
-
792
- callUserFunction(func, args, callingEnv, thisContext = undefined) {
793
- // Extract metadata if function is wrapped
794
- const metadata = func.__metadata || func;
795
- const funcEnv = new Environment(metadata.closure);
796
-
797
- // Bind 'this' if provided (for method calls)
798
- if (thisContext !== undefined) {
799
- funcEnv.define('this', thisContext);
800
- }
801
-
802
- // Bind parameters
803
- for (let i = 0; i < metadata.params.length; i++) {
804
- const param = metadata.params[i];
805
-
806
- if (param.type === 'Identifier') {
807
- // Simple parameter: function(x)
808
- funcEnv.define(param.name, args[i]);
809
- } else if (param.type === 'AssignmentPattern') {
810
- // Default parameter: function(x = defaultValue)
811
- const value = args[i] !== undefined ? args[i] : this.evaluate(param.right, funcEnv);
812
- funcEnv.define(param.left.name, value);
813
- } else if (param.type === 'RestElement') {
814
- // Rest parameter: function(...rest)
815
- funcEnv.define(param.argument.name, args.slice(i));
816
- break; // Rest element must be last
817
- } else if (param.type === 'ObjectPattern') {
818
- // Destructuring parameter: function({a, b})
819
- this.bindObjectPattern(param, args[i], funcEnv);
820
- } else if (param.type === 'ArrayPattern') {
821
- // Array destructuring parameter: function([a, b])
822
- this.bindArrayPattern(param, args[i], funcEnv);
823
- } else {
824
- // Fallback for simple parameter names
825
- funcEnv.define(param.name, args[i]);
826
- }
827
- }
828
-
829
- // Execute function body
830
- // If async, use async evaluation and return a promise
831
- if (metadata.async) {
832
- return (async () => {
833
- if (metadata.expression) {
834
- // Arrow function with expression body
835
- const result = await this.evaluateAsync(metadata.body, funcEnv);
836
- // If the result is a ThrowSignal, throw the error
837
- if (result instanceof ThrowSignal) {
838
- throw result.value;
839
- }
840
- return result;
841
- } else {
842
- // Block statement body
843
- const result = await this.evaluateAsync(metadata.body, funcEnv);
844
- if (result instanceof ReturnValue) {
845
- return result.value;
846
- }
847
- // If the result is a ThrowSignal, throw the error
848
- if (result instanceof ThrowSignal) {
849
- throw result.value;
850
- }
851
- return undefined;
852
- }
853
- })();
854
- } else {
855
- // Synchronous evaluation for non-async functions
856
- if (metadata.expression) {
857
- const result = this.evaluate(metadata.body, funcEnv);
858
- // If the result is a ThrowSignal, throw the error
859
- if (result instanceof ThrowSignal) {
860
- throw result.value;
861
- }
862
- return result;
863
- } else {
864
- const result = this.evaluate(metadata.body, funcEnv);
865
- if (result instanceof ReturnValue) {
866
- return result.value;
867
- }
868
- // If the result is a ThrowSignal, throw the error
869
- if (result instanceof ThrowSignal) {
870
- throw result.value;
871
- }
872
- return undefined;
873
- }
874
- }
875
- }
876
-
877
- evaluateMemberExpression(node, env) {
878
- const obj = this.evaluate(node.object, env);
879
-
880
- // Handle optional chaining - if optional and obj is null/undefined, return undefined
881
- if (node.optional && (obj === null || obj === undefined)) {
882
- return undefined;
883
- }
884
-
885
- if (obj === null || obj === undefined) {
886
- throw new TypeError(`Cannot read property of ${obj}`);
887
- }
888
-
889
- const prop = node.computed
890
- ? this.evaluate(node.property, env)
891
- : node.property.name;
892
-
893
- return obj[prop];
894
- }
895
-
896
- evaluateChainExpression(node, env) {
897
- // ChainExpression is a wrapper for optional chaining expressions
898
- // It contains the actual expression (MemberExpression or CallExpression with optional: true)
899
- // We just evaluate the inner expression, which will handle the optional logic
900
- return this.evaluate(node.expression, env);
901
- }
902
-
903
- evaluateArrayExpression(node, env) {
904
- const result = [];
905
- for (const elem of node.elements) {
906
- if (!elem) {
907
- // Hole in array [1, , 3]
908
- result.push(undefined);
909
- } else if (elem.type === 'SpreadElement') {
910
- // Spread syntax [...arr]
911
- const spreadValue = this.evaluate(elem.argument, env);
912
- if (Array.isArray(spreadValue)) {
913
- result.push(...spreadValue);
914
- } else if (typeof spreadValue[Symbol.iterator] === 'function') {
915
- result.push(...spreadValue);
916
- } else {
917
- throw new TypeError('Spread syntax requires an iterable');
918
- }
919
- } else {
920
- result.push(this.evaluate(elem, env));
921
- }
922
- }
923
- return result;
924
- }
925
-
926
- evaluateObjectExpression(node, env) {
927
- const obj = {};
928
- for (const prop of node.properties) {
929
- if (prop.type === 'SpreadElement') {
930
- // Object spread {...other}
931
- const spreadValue = this.evaluate(prop.argument, env);
932
- if (typeof spreadValue === 'object' && spreadValue !== null) {
933
- Object.assign(obj, spreadValue);
934
- }
935
- } else {
936
- // Regular property or shorthand
937
- const key = prop.key.type === 'Identifier' && !prop.computed
938
- ? prop.key.name
939
- : this.evaluate(prop.key, env);
940
-
941
- // Handle shorthand properties {x} => {x: x}
942
- const value = prop.value ? this.evaluate(prop.value, env) : env.get(key);
943
-
944
- // Handle method shorthand: method() {}
945
- if (prop.method && prop.value.type === 'FunctionExpression') {
946
- obj[key] = (...args) => {
947
- const funcValue = this.evaluate(prop.value, env);
948
- return this.callUserFunction(funcValue, args, env);
949
- };
950
- } else {
951
- obj[key] = value;
952
- }
953
- }
954
- }
955
- return obj;
956
- }
957
-
958
- evaluateFunctionExpression(node, env) {
959
- const funcMetadata = {
960
- __isFunction: true,
961
- params: node.params,
962
- body: node.body,
963
- closure: env,
964
- expression: node.type === 'ArrowFunctionExpression' && node.expression,
965
- async: node.async || false
966
- };
967
-
968
- // Wrap in actual JavaScript function so it can be called by native code
969
- const interpreter = this;
970
-
971
- // Create async or sync wrapper based on function type
972
- // Note: using regular function (not arrow) to capture 'this' for method calls
973
- const wrappedFunc = funcMetadata.async
974
- ? async function(...args) {
975
- return await interpreter.callUserFunction(funcMetadata, args, funcMetadata.closure, this);
976
- }
977
- : function(...args) {
978
- return interpreter.callUserFunction(funcMetadata, args, funcMetadata.closure, this);
979
- };
980
-
981
- // Preserve metadata for JSLike's internal use
982
- wrappedFunc.__isFunction = true;
983
- wrappedFunc.__metadata = funcMetadata;
984
-
985
- return wrappedFunc;
986
- }
987
-
988
- evaluateNewExpression(node, env) {
989
- const constructor = this.evaluate(node.callee, env);
990
- const args = node.arguments.map(arg => this.evaluate(arg, env));
991
-
992
- // Handle user-defined functions (including async and arrow functions)
993
- if (constructor && constructor.__isFunction) {
994
- const result = this.callUserFunction(constructor, args, env);
995
- // If result is a promise (async function), return it directly
996
- // The async context will handle it
997
- if (result && typeof result.then === 'function') {
998
- return result.then(res => {
999
- if (res && typeof res === 'object') {
1000
- return res;
1001
- }
1002
- return {};
1003
- });
1004
- }
1005
- // If the function returns an object, use it; otherwise create a new object
1006
- if (result && typeof result === 'object') {
1007
- return result;
1008
- }
1009
- // For arrow/async functions that don't return an object, create one
1010
- return {};
1011
- }
1012
-
1013
- if (typeof constructor === 'function') {
1014
- // For native functions and classes, try to construct
1015
- // Arrow functions and async functions can't be constructed with 'new' in JavaScript,
1016
- // but if they return an object, we can use that
1017
- try {
1018
- return new constructor(...args);
1019
- } catch (err) {
1020
- // If construction fails (e.g., arrow function, async function),
1021
- // try calling it normally and see if it returns an object
1022
- if (err.message && err.message.includes('not a constructor')) {
1023
- const result = constructor(...args);
1024
- // If result is a promise, handle it
1025
- if (result && typeof result.then === 'function') {
1026
- return result.then(res => {
1027
- if (res && typeof res === 'object') {
1028
- return res;
1029
- }
1030
- throw new TypeError(`Type mismatch in new expression: ${node.callee.name || 'Expression'} is not a constructor`);
1031
- });
1032
- }
1033
- if (result && typeof result === 'object') {
1034
- return result;
1035
- }
1036
- throw new TypeError(`Type mismatch in new expression: ${node.callee.name || 'Expression'} is not a constructor`);
1037
- }
1038
- throw err;
1039
- }
1040
- }
1041
-
1042
- throw new TypeError(`Type mismatch in new expression: ${node.callee.name || 'Expression'} is not a constructor`);
1043
- }
1044
-
1045
- evaluateThisExpression(node, env) {
1046
- try {
1047
- return env.get('this');
1048
- } catch (e) {
1049
- // 'this' not defined in current scope
1050
- return undefined;
1051
- }
1052
- }
1053
-
1054
- evaluateSuperExpression(node, env) {
1055
- // Super is used in class methods to access parent class
1056
- try {
1057
- return env.get('super');
1058
- } catch (e) {
1059
- throw new ReferenceError("'super' keyword is unexpected here");
1060
- }
1061
- }
1062
-
1063
- evaluateSequenceExpression(node, env) {
1064
- let result;
1065
- for (const expr of node.expressions) {
1066
- result = this.evaluate(expr, env);
1067
- }
1068
- return result;
1069
- }
1070
-
1071
- evaluateVariableDeclaration(node, env) {
1072
- const isConst = node.kind === 'const';
1073
-
1074
- for (const declarator of node.declarations) {
1075
- const value = declarator.init
1076
- ? this.evaluate(declarator.init, env)
1077
- : undefined;
1078
-
1079
- // Handle destructuring patterns
1080
- if (declarator.id.type === 'ObjectPattern') {
1081
- this.bindObjectPattern(declarator.id, value, env, isConst);
1082
- } else if (declarator.id.type === 'ArrayPattern') {
1083
- this.bindArrayPattern(declarator.id, value, env, isConst);
1084
- } else {
1085
- env.define(declarator.id.name, value, isConst);
1086
- }
1087
- }
1088
- return undefined;
1089
- }
1090
-
1091
- bindObjectPattern(pattern, value, env, isConst = false) {
1092
- if (value === null || value === undefined) {
1093
- throw new TypeError('Cannot destructure undefined or null');
1094
- }
1095
-
1096
- for (const prop of pattern.properties) {
1097
- if (prop.type === 'RestElement') {
1098
- // Handle rest properties {...rest}
1099
- const assignedKeys = pattern.properties
1100
- .filter(p => p.type !== 'RestElement')
1101
- .map(p => p.key.name || p.key.value);
1102
- const restObj = {};
1103
- for (const key in value) {
1104
- if (!assignedKeys.includes(key)) {
1105
- restObj[key] = value[key];
1106
- }
1107
- }
1108
- env.define(prop.argument.name, restObj, isConst);
1109
- } else {
1110
- const key = prop.key.name || prop.key.value;
1111
- const propValue = value[key];
1112
-
1113
- if (prop.value.type === 'Identifier') {
1114
- env.define(prop.value.name, propValue, isConst);
1115
- } else if (prop.value.type === 'AssignmentPattern') {
1116
- // Handle default values
1117
- const finalValue = propValue !== undefined
1118
- ? propValue
1119
- : this.evaluate(prop.value.right, env);
1120
- env.define(prop.value.left.name, finalValue, isConst);
1121
- } else if (prop.value.type === 'ObjectPattern') {
1122
- this.bindObjectPattern(prop.value, propValue, env, isConst);
1123
- } else if (prop.value.type === 'ArrayPattern') {
1124
- this.bindArrayPattern(prop.value, propValue, env, isConst);
1125
- }
1126
- }
1127
- }
1128
- }
1129
-
1130
- bindArrayPattern(pattern, value, env, isConst = false) {
1131
- if (!Array.isArray(value)) {
1132
- throw new TypeError('Cannot destructure non-iterable');
1133
- }
1134
-
1135
- for (let i = 0; i < pattern.elements.length; i++) {
1136
- const element = pattern.elements[i];
1137
- if (!element) continue; // Hole in pattern [a, , c]
1138
-
1139
- if (element.type === 'RestElement') {
1140
- // Handle rest elements [...rest]
1141
- const restValues = value.slice(i);
1142
- env.define(element.argument.name, restValues, isConst);
1143
- break;
1144
- } else if (element.type === 'Identifier') {
1145
- env.define(element.name, value[i], isConst);
1146
- } else if (element.type === 'AssignmentPattern') {
1147
- // Handle default values
1148
- const finalValue = value[i] !== undefined
1149
- ? value[i]
1150
- : this.evaluate(element.right, env);
1151
- env.define(element.left.name, finalValue, isConst);
1152
- } else if (element.type === 'ObjectPattern') {
1153
- this.bindObjectPattern(element, value[i], env, isConst);
1154
- } else if (element.type === 'ArrayPattern') {
1155
- this.bindArrayPattern(element, value[i], env, isConst);
1156
- }
1157
- }
1158
- }
1159
-
1160
- evaluateFunctionDeclaration(node, env) {
1161
- const funcMetadata = {
1162
- __isFunction: true,
1163
- params: node.params,
1164
- body: node.body,
1165
- closure: env,
1166
- expression: false,
1167
- async: node.async || false
1168
- };
1169
-
1170
- // Wrap in actual JavaScript function so it can be called by native code
1171
- const interpreter = this;
1172
-
1173
- // Create async or sync wrapper based on function type
1174
- // Note: using regular function (not arrow) to capture 'this' for method calls
1175
- const wrappedFunc = funcMetadata.async
1176
- ? async function(...args) {
1177
- return await interpreter.callUserFunction(funcMetadata, args, funcMetadata.closure, this);
1178
- }
1179
- : function(...args) {
1180
- return interpreter.callUserFunction(funcMetadata, args, funcMetadata.closure, this);
1181
- };
1182
-
1183
- // Preserve metadata for JSLike's internal use
1184
- wrappedFunc.__isFunction = true;
1185
- wrappedFunc.__metadata = funcMetadata;
1186
-
1187
- env.define(node.id.name, wrappedFunc);
1188
- return undefined;
1189
- }
1190
-
1191
- async evaluateImportDeclaration(node, env) {
1192
- // Get module path from import source
1193
- const modulePath = node.source.value;
1194
-
1195
- // Check if module resolver is configured
1196
- if (!this.moduleResolver) {
1197
- throw new Error('Module resolver not configured - cannot import modules');
1198
- }
1199
-
1200
- // Check if module is already cached
1201
- let moduleExports;
1202
- if (this.moduleCache.has(modulePath)) {
1203
- moduleExports = this.moduleCache.get(modulePath);
1204
- } else {
1205
- // Resolve and load module code
1206
- const resolution = await this.moduleResolver.resolve(modulePath);
1207
- if (!resolution) {
1208
- throw new Error(`Cannot find module '${modulePath}'`);
1209
- }
1210
-
1211
- // Handle both old (string) and new (ModuleResolution) formats
1212
- const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
1213
-
1214
- // Parse and execute module in its own environment
1215
- const moduleAst = acornParse(moduleCode, {
1216
- ecmaVersion: 2020,
1217
- sourceType: 'module',
1218
- locations: false
1219
- });
1220
- const moduleEnv = new Environment(this.globalEnv);
1221
-
1222
- // Create a new interpreter for the module with shared module cache
1223
- const moduleInterpreter = new Interpreter(this.globalEnv, {
1224
- moduleResolver: this.moduleResolver
1225
- });
1226
- moduleInterpreter.moduleCache = this.moduleCache; // Share cache
1227
-
1228
- // Execute module and collect exports
1229
- await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
1230
-
1231
- // Cache the module exports
1232
- moduleExports = moduleInterpreter.moduleExports;
1233
- this.moduleCache.set(modulePath, moduleExports);
1234
- }
1235
-
1236
- // Import specified bindings into current environment
1237
- for (const specifier of node.specifiers) {
1238
- if (specifier.type === 'ImportSpecifier') {
1239
- // Named import: import { foo, bar } from "module"
1240
- const importedName = specifier.imported.name;
1241
- const localName = specifier.local.name;
1242
-
1243
- if (!(importedName in moduleExports)) {
1244
- throw new Error(`Module '${modulePath}' has no export '${importedName}'`);
1245
- }
1246
-
1247
- env.define(localName, moduleExports[importedName]);
1248
- } else if (specifier.type === 'ImportDefaultSpecifier') {
1249
- // Default import: import foo from "module"
1250
- const localName = specifier.local.name;
1251
-
1252
- if (!('default' in moduleExports)) {
1253
- throw new Error(`Module '${modulePath}' has no default export`);
1254
- }
1255
-
1256
- env.define(localName, moduleExports.default);
1257
- } else if (specifier.type === 'ImportNamespaceSpecifier') {
1258
- // Namespace import: import * as foo from "module"
1259
- const localName = specifier.local.name;
1260
- env.define(localName, moduleExports);
1261
- }
1262
- }
1263
-
1264
- return undefined;
1265
- }
1266
-
1267
- evaluateExportNamedDeclaration(node, env) {
1268
- // Handle export with declaration: export function foo() {} or export const x = 42
1269
- if (node.declaration) {
1270
- const result = this.evaluate(node.declaration, env);
1271
-
1272
- // Register exported names
1273
- if (node.declaration.type === 'FunctionDeclaration') {
1274
- // export function foo() {}
1275
- const name = node.declaration.id.name;
1276
- this.moduleExports[name] = env.get(name);
1277
- } else if (node.declaration.type === 'VariableDeclaration') {
1278
- // export const x = 42, y = 10
1279
- for (const declarator of node.declaration.declarations) {
1280
- const name = declarator.id.name;
1281
- this.moduleExports[name] = env.get(name);
1282
- }
1283
- } else if (node.declaration.type === 'ClassDeclaration') {
1284
- // export class Foo {}
1285
- const name = node.declaration.id.name;
1286
- this.moduleExports[name] = env.get(name);
1287
- }
1288
-
1289
- return result;
1290
- }
1291
-
1292
- // Handle export list: export { foo, bar }
1293
- if (node.specifiers && node.specifiers.length > 0) {
1294
- for (const specifier of node.specifiers) {
1295
- const exportedName = specifier.exported.name;
1296
- const localName = specifier.local.name;
1297
- this.moduleExports[exportedName] = env.get(localName);
1298
- }
1299
- }
1300
-
1301
- return undefined;
1302
- }
1303
-
1304
- evaluateExportDefaultDeclaration(node, env) {
1305
- // Evaluate the default export expression/declaration
1306
- let value;
1307
-
1308
- if (node.declaration.type === 'FunctionDeclaration' || node.declaration.type === 'ClassDeclaration') {
1309
- // export default function foo() {} or export default class Foo {}
1310
- value = this.evaluate(node.declaration, env);
1311
- // If it has a name, it's also defined in the environment
1312
- if (node.declaration.id) {
1313
- value = env.get(node.declaration.id.name);
1314
- }
1315
- } else {
1316
- // export default expression
1317
- value = this.evaluate(node.declaration, env);
1318
- }
1319
-
1320
- // Register as default export
1321
- this.moduleExports.default = value;
1322
-
1323
- return undefined;
1324
- }
1325
-
1326
- evaluateBlockStatement(node, env) {
1327
- const blockEnv = new Environment(env);
1328
- let result;
1329
-
1330
- for (const statement of node.body) {
1331
- result = this.evaluate(statement, blockEnv);
1332
- if (result instanceof ReturnValue ||
1333
- result instanceof BreakSignal ||
1334
- result instanceof ContinueSignal ||
1335
- result instanceof ThrowSignal) {
1336
- return result;
1337
- }
1338
- }
1339
-
1340
- return result;
1341
- }
1342
-
1343
- evaluateIfStatement(node, env) {
1344
- const test = this.evaluate(node.test, env);
1345
-
1346
- if (test) {
1347
- return this.evaluate(node.consequent, env);
1348
- } else if (node.alternate) {
1349
- return this.evaluate(node.alternate, env);
1350
- }
1351
-
1352
- return undefined;
1353
- }
1354
-
1355
- evaluateWhileStatement(node, env) {
1356
- let result;
1357
-
1358
- while (this.evaluate(node.test, env)) {
1359
- result = this.evaluate(node.body, env);
1360
-
1361
- if (result instanceof BreakSignal) {
1362
- break;
1363
- }
1364
- if (result instanceof ContinueSignal) {
1365
- continue;
1366
- }
1367
- if (result instanceof ReturnValue || result instanceof ThrowSignal) {
1368
- return result;
1369
- }
1370
- }
1371
-
1372
- return undefined;
1373
- }
1374
-
1375
- evaluateDoWhileStatement(node, env) {
1376
- let result;
1377
-
1378
- do {
1379
- result = this.evaluate(node.body, env);
1380
-
1381
- if (result instanceof BreakSignal) {
1382
- break;
1383
- }
1384
- if (result instanceof ContinueSignal) {
1385
- continue;
1386
- }
1387
- if (result instanceof ReturnValue || result instanceof ThrowSignal) {
1388
- return result;
1389
- }
1390
- } while (this.evaluate(node.test, env));
1391
-
1392
- return undefined;
1393
- }
1394
-
1395
- evaluateForStatement(node, env) {
1396
- const forEnv = new Environment(env);
1397
- let result;
1398
-
1399
- if (node.init) {
1400
- this.evaluate(node.init, forEnv);
1401
- }
1402
-
1403
- while (!node.test || this.evaluate(node.test, forEnv)) {
1404
- result = this.evaluate(node.body, forEnv);
1405
-
1406
- if (result instanceof BreakSignal) {
1407
- break;
1408
- }
1409
- if (result instanceof ContinueSignal) {
1410
- if (node.update) {
1411
- this.evaluate(node.update, forEnv);
1412
- }
1413
- continue;
1414
- }
1415
- if (result instanceof ReturnValue || result instanceof ThrowSignal) {
1416
- return result;
1417
- }
1418
-
1419
- if (node.update) {
1420
- this.evaluate(node.update, forEnv);
1421
- }
1422
- }
1423
-
1424
- return undefined;
1425
- }
1426
-
1427
- evaluateForInStatement(node, env) {
1428
- const forEnv = new Environment(env);
1429
- const obj = this.evaluate(node.right, forEnv);
1430
- let result;
1431
-
1432
- // Check for null or undefined - JavaScript throws TypeError
1433
- if (obj === null || obj === undefined) {
1434
- throw new TypeError(`Cannot use 'in' operator to iterate over ${obj}`);
1435
- }
1436
-
1437
- // Get the variable name from the declaration
1438
- const varName = node.left.declarations[0].id.name;
1439
-
1440
- // Define the variable once before the loop
1441
- forEnv.define(varName, undefined);
1442
-
1443
- for (const key in obj) {
1444
- // Update the variable value for each iteration
1445
- forEnv.set(varName, key);
1446
- result = this.evaluate(node.body, forEnv);
1447
-
1448
- if (result instanceof BreakSignal) {
1449
- break;
1450
- }
1451
- if (result instanceof ContinueSignal) {
1452
- continue;
1453
- }
1454
- if (result instanceof ReturnValue || result instanceof ThrowSignal) {
1455
- return result;
1456
- }
1457
- }
1458
-
1459
- return undefined;
1460
- }
1461
-
1462
- evaluateForOfStatement(node, env) {
1463
- const forEnv = new Environment(env);
1464
- const iterable = this.evaluate(node.right, forEnv);
1465
- let result;
1466
-
1467
- const declarator = node.left.declarations[0];
1468
- const isConst = node.left.kind === 'const';
1469
-
1470
- for (const value of iterable) {
1471
- // Create a new child environment for each iteration to handle const properly
1472
- const iterEnv = forEnv.extend();
1473
-
1474
- // Bind the value using the appropriate pattern
1475
- if (declarator.id.type === 'Identifier') {
1476
- iterEnv.define(declarator.id.name, value, isConst);
1477
- } else if (declarator.id.type === 'ArrayPattern') {
1478
- this.bindArrayPattern(declarator.id, value, iterEnv, isConst);
1479
- } else if (declarator.id.type === 'ObjectPattern') {
1480
- this.bindObjectPattern(declarator.id, value, iterEnv, isConst);
1481
- }
1482
-
1483
- result = this.evaluate(node.body, iterEnv);
1484
-
1485
- if (result instanceof BreakSignal) {
1486
- break;
1487
- }
1488
- if (result instanceof ContinueSignal) {
1489
- continue;
1490
- }
1491
- if (result instanceof ReturnValue || result instanceof ThrowSignal) {
1492
- return result;
1493
- }
1494
- }
1495
-
1496
- return undefined;
1497
- }
1498
-
1499
- evaluateTryStatement(node, env) {
1500
- let result;
1501
-
1502
- try {
1503
- result = this.evaluate(node.block, env);
1504
-
1505
- if (result instanceof ThrowSignal) {
1506
- throw result.value;
1507
- }
1508
- } catch (error) {
1509
- if (node.handler) {
1510
- const catchEnv = new Environment(env);
1511
- if (node.handler.param) {
1512
- catchEnv.define(node.handler.param.name, error);
1513
- }
1514
- result = this.evaluate(node.handler.body, catchEnv);
1515
- } else {
1516
- throw error;
1517
- }
1518
- } finally {
1519
- if (node.finalizer) {
1520
- const finalResult = this.evaluate(node.finalizer, env);
1521
- // If finally block throws or returns, it overrides the try/catch result
1522
- if (finalResult instanceof ThrowSignal || finalResult instanceof ReturnValue) {
1523
- return finalResult;
1524
- }
1525
- }
1526
- }
1527
-
1528
- return result;
1529
- }
1530
-
1531
- evaluateSwitchStatement(node, env) {
1532
- const discriminant = this.evaluate(node.discriminant, env);
1533
- let matched = false;
1534
- let result;
1535
-
1536
- for (const switchCase of node.cases) {
1537
- // Check if this case matches (or if we're in fall-through mode)
1538
- if (!matched && switchCase.test) {
1539
- const testValue = this.evaluate(switchCase.test, env);
1540
- if (testValue === discriminant) {
1541
- matched = true;
1542
- }
1543
- } else if (!switchCase.test) {
1544
- // Default case
1545
- matched = true;
1546
- }
1547
-
1548
- // Execute consequent if matched
1549
- if (matched) {
1550
- for (const statement of switchCase.consequent) {
1551
- result = this.evaluate(statement, env);
1552
-
1553
- if (result instanceof BreakSignal) {
1554
- return undefined;
1555
- }
1556
- if (result instanceof ReturnValue || result instanceof ThrowSignal) {
1557
- return result;
1558
- }
1559
- }
1560
- }
1561
- }
1562
-
1563
- return undefined;
1564
- }
1565
-
1566
- // ===== ES6+ Feature Implementations =====
1567
-
1568
- evaluateTemplateLiteral(node, env) {
1569
- let result = '';
1570
- for (let i = 0; i < node.quasis.length; i++) {
1571
- result += node.quasis[i].value.cooked || node.quasis[i].value.raw;
1572
- if (i < node.expressions.length) {
1573
- const exprValue = this.evaluate(node.expressions[i], env);
1574
- result += String(exprValue);
1575
- }
1576
- }
1577
- return result;
1578
- }
1579
-
1580
- evaluateClassDeclaration(node, env) {
1581
- const className = node.id.name;
1582
- const classFunc = this.createClass(node, env);
1583
- env.define(className, classFunc);
1584
- return undefined;
1585
- }
1586
-
1587
- evaluateClassExpression(node, env) {
1588
- return this.createClass(node, env);
1589
- }
1590
-
1591
- createClass(node, env) {
1592
- const className = node.id ? node.id.name : 'AnonymousClass';
1593
- const superClass = node.superClass ? this.evaluate(node.superClass, env) : null;
1594
- const interpreter = this; // Capture interpreter reference
1595
-
1596
- // Find constructor
1597
- let constructor = null;
1598
- const methods = {};
1599
- const staticMethods = {};
1600
-
1601
- for (const member of node.body.body) {
1602
- if (member.type === 'MethodDefinition') {
1603
- const methodName = member.key.name || member.key.value;
1604
- const methodFunc = this.createMethodFunction(member.value, env, className);
1605
-
1606
- if (member.kind === 'constructor') {
1607
- constructor = methodFunc;
1608
- } else if (member.static) {
1609
- staticMethods[methodName] = methodFunc;
1610
- } else {
1611
- methods[methodName] = methodFunc;
1612
- }
1613
- }
1614
- }
1615
-
1616
- // Create class constructor function
1617
- const classConstructor = function(...args) {
1618
- // Create instance
1619
- const instance = Object.create(classConstructor.prototype);
1620
-
1621
- // Call constructor - super() must be called explicitly inside constructor
1622
- if (constructor) {
1623
- const result = interpreter.callMethodFunction(constructor, instance, args, env, superClass);
1624
- // Only use the returned object if it's an explicit return of an object (not the instance)
1625
- if (result && result.__explicitReturn && result.value && typeof result.value === 'object' && result.value !== instance) {
1626
- return result.value;
1627
- }
1628
- } else if (superClass) {
1629
- // If no constructor defined but has superClass, implicitly call super()
1630
- // Call the superClass constructor properly - it's a classConstructor function
1631
- superClass.call(instance, ...args);
1632
- }
1633
-
1634
- return instance;
1635
- };
1636
-
1637
- // Store the constructor method on the classConstructor for super() to access
1638
- if (constructor) {
1639
- classConstructor.__constructor = constructor;
1640
- }
1641
-
1642
- // Set up prototype chain
1643
- if (superClass) {
1644
- classConstructor.prototype = Object.create(superClass.prototype);
1645
- classConstructor.prototype.constructor = classConstructor;
1646
- }
1647
-
1648
- // Add methods to prototype
1649
- for (const [name, method] of Object.entries(methods)) {
1650
- classConstructor.prototype[name] = function(...args) {
1651
- const result = interpreter.callMethodFunction(method, this, args, env);
1652
- // Unwrap explicit return marker
1653
- if (result && result.__explicitReturn) {
1654
- return result.value;
1655
- }
1656
- return result;
1657
- };
1658
- }
1659
-
1660
- // Add static methods
1661
- for (const [name, method] of Object.entries(staticMethods)) {
1662
- classConstructor[name] = function(...args) {
1663
- const result = interpreter.callMethodFunction(method, classConstructor, args, env);
1664
- // Unwrap explicit return marker
1665
- if (result && result.__explicitReturn) {
1666
- return result.value;
1667
- }
1668
- return result;
1669
- };
1670
- }
1671
-
1672
- classConstructor.__className = className;
1673
- return classConstructor;
1674
- }
1675
-
1676
- createMethodFunction(funcNode, env, className) {
1677
- const func = {
1678
- __isFunction: true,
1679
- __params: funcNode.params,
1680
- __body: funcNode.body,
1681
- __env: env,
1682
- __className: className
1683
- };
1684
- return func;
1685
- }
1686
-
1687
- callMethodFunction(methodFunc, thisContext, args, env, superClass = null) {
1688
- const funcEnv = new Environment(methodFunc.__env || env);
1689
-
1690
- // Bind 'this'
1691
- funcEnv.define('this', thisContext);
1692
-
1693
- // Bind 'super' if superClass exists
1694
- if (superClass) {
1695
- // Create a super function that calls the parent constructor
1696
- const superFunc = (...superArgs) => {
1697
- // Call the parent constructor method if it exists
1698
- if (superClass.__constructor) {
1699
- this.callMethodFunction(superClass.__constructor, thisContext, superArgs, env, null);
1700
- }
1701
- // Otherwise, call superClass as a regular constructor (for native/external classes)
1702
- else {
1703
- // For native constructors like Error, we need to use Reflect.construct
1704
- // to properly initialize the instance properties
1705
- const tempInstance = Reflect.construct(superClass, superArgs, thisContext.constructor);
1706
- // Copy properties from the temp instance to our thisContext
1707
- Object.getOwnPropertyNames(tempInstance).forEach(name => {
1708
- thisContext[name] = tempInstance[name];
1709
- });
1710
- }
1711
- return undefined;
1712
- };
1713
- // Store both the function and mark it as super
1714
- superFunc.__isSuperConstructor = true;
1715
- superFunc.__superClass = superClass;
1716
- funcEnv.define('super', superFunc);
1717
- }
1718
-
1719
- // Bind parameters
1720
- for (let i = 0; i < methodFunc.__params.length; i++) {
1721
- const param = methodFunc.__params[i];
1722
-
1723
- if (param.type === 'Identifier') {
1724
- // Simple parameter: function(x)
1725
- funcEnv.define(param.name, args[i]);
1726
- } else if (param.type === 'AssignmentPattern') {
1727
- // Default parameter: function(x = defaultValue)
1728
- const value = args[i] !== undefined ? args[i] : this.evaluate(param.right, funcEnv);
1729
- funcEnv.define(param.left.name, value);
1730
- } else if (param.type === 'RestElement') {
1731
- // Rest parameter: function(...rest)
1732
- funcEnv.define(param.argument.name, args.slice(i));
1733
- break; // Rest element must be last
1734
- } else if (param.type === 'ObjectPattern') {
1735
- // Destructuring parameter: function({a, b})
1736
- this.bindObjectPattern(param, args[i], funcEnv);
1737
- } else if (param.type === 'ArrayPattern') {
1738
- // Array destructuring parameter: function([a, b])
1739
- this.bindArrayPattern(param, args[i], funcEnv);
1740
- } else {
1741
- // Fallback for simple parameter names
1742
- funcEnv.define(param.name, args[i]);
1743
- }
1744
- }
1745
-
1746
- const result = this.evaluate(methodFunc.__body, funcEnv);
1747
-
1748
- if (result instanceof ReturnValue) {
1749
- // Mark that this was an explicit return for constructor handling
1750
- return { __explicitReturn: true, value: result.value };
1751
- }
1752
-
1753
- // If the result is a ThrowSignal, throw the error
1754
- if (result instanceof ThrowSignal) {
1755
- throw result.value;
1756
- }
1757
-
1758
- // Return implicit result (for arrow function expressions)
1759
- return result;
1760
- }
1761
-
1762
- evaluateMethodDefinition(node, env) {
1763
- // This is handled by class creation
1764
- return undefined;
1765
- }
1766
-
1767
- evaluateSpreadElement(node, env) {
1768
- const arg = this.evaluate(node.argument, env);
1769
- if (Array.isArray(arg)) {
1770
- return { __spread: true, __values: arg };
1771
- }
1772
- if (typeof arg === 'object' && arg !== null) {
1773
- return { __spread: true, __values: Object.entries(arg) };
1774
- }
1775
- throw new TypeError('Spread syntax requires an iterable');
1776
- }
1777
-
1778
- evaluateRestElement(node, env) {
1779
- // Handled during parameter binding
1780
- return undefined;
1781
- }
1782
-
1783
- evaluateObjectPattern(node, env) {
1784
- // Handled during destructuring
1785
- return undefined;
1786
- }
1787
-
1788
- evaluateArrayPattern(node, env) {
1789
- // Handled during destructuring
1790
- return undefined;
1791
- }
1792
-
1793
- evaluateAssignmentPattern(node, env) {
1794
- // Handled during parameter binding with defaults
1795
- return undefined;
1796
- }
1797
-
1798
- evaluateProperty(node, env) {
1799
- // Already handled in evaluateObjectExpression
1800
- return undefined;
1801
- }
1802
- }