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