starlight-cli 1.0.47 → 1.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +418 -167
- package/package.json +1 -1
- package/src/evaluator.js +153 -68
- package/src/parser.js +248 -85
- package/src/starlight.js +17 -14
package/package.json
CHANGED
package/src/evaluator.js
CHANGED
|
@@ -10,14 +10,29 @@ class ReturnValue {
|
|
|
10
10
|
class BreakSignal {}
|
|
11
11
|
class ContinueSignal {}
|
|
12
12
|
class RuntimeError extends Error {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
constructor(message, node, source) {
|
|
14
|
+
const line = node?.line ?? '?';
|
|
15
|
+
const column = node?.column ?? '?';
|
|
16
|
+
|
|
17
|
+
let output = ` ${message} at line ${line}, column ${column}\n`;
|
|
18
|
+
|
|
19
|
+
if (source && node?.line != null) {
|
|
20
|
+
const lines = source.split('\n');
|
|
21
|
+
const srcLine = lines[node.line - 1] || '';
|
|
22
|
+
output += ` ${srcLine}\n`;
|
|
23
|
+
output += ` ${' '.repeat(column - 1)}^\n`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
super(output);
|
|
27
|
+
|
|
28
|
+
this.name = 'RuntimeError';
|
|
29
|
+
this.line = line;
|
|
30
|
+
this.column = column;
|
|
31
|
+
}
|
|
19
32
|
}
|
|
20
33
|
|
|
34
|
+
|
|
35
|
+
|
|
21
36
|
class Environment {
|
|
22
37
|
constructor(parent = null) {
|
|
23
38
|
this.store = Object.create(null);
|
|
@@ -30,11 +45,11 @@ class Environment {
|
|
|
30
45
|
return false;
|
|
31
46
|
}
|
|
32
47
|
|
|
33
|
-
get(name) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
get(name, node) {
|
|
49
|
+
if (name in this.store) return this.store[name];
|
|
50
|
+
if (this.parent) return this.parent.get(name, node);
|
|
51
|
+
throw new RuntimeError(`Undefined variable: ${name}`, node);
|
|
52
|
+
}
|
|
38
53
|
|
|
39
54
|
set(name, value) {
|
|
40
55
|
if (name in this.store) { this.store[name] = value; return value; }
|
|
@@ -50,7 +65,8 @@ class Environment {
|
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
class Evaluator {
|
|
53
|
-
constructor() {
|
|
68
|
+
constructor(source = '') {
|
|
69
|
+
this.source = source;
|
|
54
70
|
this.global = new Environment();
|
|
55
71
|
this.setupBuiltins();
|
|
56
72
|
}
|
|
@@ -137,11 +153,11 @@ this.global.define('range', (...args) => {
|
|
|
137
153
|
end = Number(args[1]);
|
|
138
154
|
step = Number(args[2]);
|
|
139
155
|
} else {
|
|
140
|
-
throw new RuntimeError('range() expects 1 to 3 arguments');
|
|
156
|
+
throw new RuntimeError('range() expects 1 to 3 arguments', null, this.source);
|
|
141
157
|
}
|
|
142
158
|
|
|
143
159
|
if (step === 0) {
|
|
144
|
-
throw new RuntimeError('range() step cannot be 0');
|
|
160
|
+
throw new RuntimeError('range() step cannot be 0', null, this.source);
|
|
145
161
|
}
|
|
146
162
|
|
|
147
163
|
const result = [];
|
|
@@ -165,7 +181,7 @@ this.global.define('range', (...args) => {
|
|
|
165
181
|
this.global.define('num', arg => {
|
|
166
182
|
const n = Number(arg);
|
|
167
183
|
if (Number.isNaN(n)) {
|
|
168
|
-
throw new RuntimeError('Cannot convert value to number');
|
|
184
|
+
throw new RuntimeError('Cannot convert value to number', null, this.source);
|
|
169
185
|
}
|
|
170
186
|
return n;
|
|
171
187
|
});
|
|
@@ -218,7 +234,7 @@ async evaluate(node, env = this.global) {
|
|
|
218
234
|
case 'LogicalExpression': return await this.evalLogical(node, env);
|
|
219
235
|
case 'UnaryExpression': return await this.evalUnary(node, env);
|
|
220
236
|
case 'Literal': return node.value;
|
|
221
|
-
case 'Identifier': return env.get(node.name);
|
|
237
|
+
case 'Identifier': return env.get(node.name, node);
|
|
222
238
|
case 'IfStatement': return await this.evalIf(node, env);
|
|
223
239
|
case 'WhileStatement': return await this.evalWhile(node, env);
|
|
224
240
|
case 'ForStatement':
|
|
@@ -241,14 +257,8 @@ case 'DoTrackStatement':
|
|
|
241
257
|
case 'ArrayExpression':
|
|
242
258
|
return await Promise.all(node.elements.map(el => this.evaluate(el, env)));
|
|
243
259
|
case 'IndexExpression': return await this.evalIndex(node, env);
|
|
244
|
-
case 'ObjectExpression':
|
|
245
|
-
|
|
246
|
-
for (const p of node.props) {
|
|
247
|
-
const key = await this.evaluate(p.key, env);
|
|
248
|
-
out[key] = await this.evaluate(p.value, env);
|
|
249
|
-
}
|
|
250
|
-
return out;
|
|
251
|
-
}
|
|
260
|
+
case 'ObjectExpression': return await this.evalObject(node, env);
|
|
261
|
+
|
|
252
262
|
|
|
253
263
|
case 'MemberExpression': return await this.evalMember(node, env);
|
|
254
264
|
case 'UpdateExpression': return await this.evalUpdate(node, env);
|
|
@@ -276,7 +286,7 @@ case 'DoTrackStatement':
|
|
|
276
286
|
}
|
|
277
287
|
|
|
278
288
|
if (typeof callee !== 'function') {
|
|
279
|
-
throw new RuntimeError('NewExpression callee is not a function', node);
|
|
289
|
+
throw new RuntimeError('NewExpression callee is not a function', node, this.source);
|
|
280
290
|
}
|
|
281
291
|
|
|
282
292
|
const args = [];
|
|
@@ -285,7 +295,7 @@ case 'DoTrackStatement':
|
|
|
285
295
|
}
|
|
286
296
|
|
|
287
297
|
default:
|
|
288
|
-
throw new RuntimeError(`Unknown node type in evaluator: ${node.type}`, node);
|
|
298
|
+
throw new RuntimeError(`Unknown node type in evaluator: ${node.type}`, node, this.source);
|
|
289
299
|
|
|
290
300
|
}
|
|
291
301
|
}
|
|
@@ -293,11 +303,19 @@ case 'DoTrackStatement':
|
|
|
293
303
|
async evalProgram(node, env) {
|
|
294
304
|
let result = null;
|
|
295
305
|
for (const stmt of node.body) {
|
|
296
|
-
|
|
306
|
+
try {
|
|
307
|
+
result = await this.evaluate(stmt, env);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
if (e instanceof RuntimeError || e instanceof BreakSignal || e instanceof ContinueSignal || e instanceof ReturnValue) {
|
|
310
|
+
throw e;
|
|
311
|
+
}
|
|
312
|
+
throw new RuntimeError(e.message || 'Error in program', stmt, this.source);
|
|
313
|
+
}
|
|
297
314
|
}
|
|
298
315
|
return result;
|
|
299
316
|
}
|
|
300
317
|
|
|
318
|
+
|
|
301
319
|
async evalDoTrack(node, env) {
|
|
302
320
|
try {
|
|
303
321
|
return await this.evaluate(node.body, env);
|
|
@@ -305,7 +323,7 @@ async evalDoTrack(node, env) {
|
|
|
305
323
|
if (!node.handler) {
|
|
306
324
|
// Wrap any raw error into RuntimeError with line info
|
|
307
325
|
if (err instanceof RuntimeError) throw err;
|
|
308
|
-
throw new RuntimeError(err.message || 'Error in doTrack body', node.body);
|
|
326
|
+
throw new RuntimeError(err.message || 'Error in doTrack body', node.body, this.source);
|
|
309
327
|
}
|
|
310
328
|
|
|
311
329
|
const trackEnv = new Environment(env);
|
|
@@ -316,7 +334,7 @@ async evalDoTrack(node, env) {
|
|
|
316
334
|
} catch (handlerErr) {
|
|
317
335
|
// Wrap handler errors as well
|
|
318
336
|
if (handlerErr instanceof RuntimeError) throw handlerErr;
|
|
319
|
-
throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler);
|
|
337
|
+
throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler, this.source);
|
|
320
338
|
}
|
|
321
339
|
}
|
|
322
340
|
}
|
|
@@ -337,7 +355,7 @@ async evalImport(node, env) {
|
|
|
337
355
|
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
338
356
|
|
|
339
357
|
if (!fs.existsSync(fullPath)) {
|
|
340
|
-
throw new RuntimeError(`Import not found: ${spec}`, node);
|
|
358
|
+
throw new RuntimeError(`Import not found: ${spec}`, node, this.source);
|
|
341
359
|
}
|
|
342
360
|
|
|
343
361
|
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
@@ -364,7 +382,7 @@ async evalImport(node, env) {
|
|
|
364
382
|
}
|
|
365
383
|
if (imp.type === 'NamedImport') {
|
|
366
384
|
if (!(imp.imported in lib)) {
|
|
367
|
-
throw new RuntimeError(`Module '${spec}' has no export '${imp.imported}'`, node);
|
|
385
|
+
throw new RuntimeError(`Module '${spec}' has no export '${imp.imported}'`, node, this.source);
|
|
368
386
|
}
|
|
369
387
|
env.define(imp.local, lib[imp.imported]);
|
|
370
388
|
}
|
|
@@ -381,7 +399,7 @@ async evalBlock(node, env) {
|
|
|
381
399
|
} catch (e) {
|
|
382
400
|
if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
383
401
|
// Wrap any other error in RuntimeError with the current block node
|
|
384
|
-
throw new RuntimeError(e.message || 'Error in block', stmt);
|
|
402
|
+
throw new RuntimeError(e.message || 'Error in block', stmt, this.source);
|
|
385
403
|
}
|
|
386
404
|
}
|
|
387
405
|
return result;
|
|
@@ -389,11 +407,23 @@ async evalBlock(node, env) {
|
|
|
389
407
|
|
|
390
408
|
|
|
391
409
|
async evalVarDeclaration(node, env) {
|
|
410
|
+
if (!node.expr) {
|
|
411
|
+
throw new RuntimeError('Variable declaration requires an initializer', node, this.source);
|
|
412
|
+
}
|
|
413
|
+
|
|
392
414
|
const val = await this.evaluate(node.expr, env);
|
|
393
415
|
return env.define(node.id, val);
|
|
394
416
|
}
|
|
395
417
|
|
|
418
|
+
|
|
396
419
|
evalArrowFunction(node, env) {
|
|
420
|
+
if (!node.body) {
|
|
421
|
+
throw new RuntimeError('Arrow function missing body', node, this.source);
|
|
422
|
+
}
|
|
423
|
+
if (!Array.isArray(node.params)) {
|
|
424
|
+
throw new RuntimeError('Invalid arrow function parameters', node, this.source);
|
|
425
|
+
}
|
|
426
|
+
|
|
397
427
|
return {
|
|
398
428
|
params: node.params,
|
|
399
429
|
body: node.body,
|
|
@@ -403,24 +433,28 @@ evalArrowFunction(node, env) {
|
|
|
403
433
|
};
|
|
404
434
|
}
|
|
405
435
|
|
|
436
|
+
|
|
406
437
|
async evalAssignment(node, env) {
|
|
407
438
|
const rightVal = await this.evaluate(node.right, env);
|
|
408
439
|
const left = node.left;
|
|
409
440
|
|
|
410
441
|
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
411
442
|
if (left.type === 'MemberExpression') {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
443
|
+
const obj = await this.evaluate(left.object, env);
|
|
444
|
+
if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
|
|
445
|
+
obj[left.property] = rightVal;
|
|
446
|
+
return rightVal;
|
|
447
|
+
}
|
|
448
|
+
if (left.type === 'IndexExpression') {
|
|
449
|
+
const obj = await this.evaluate(left.object, env);
|
|
450
|
+
const idx = await this.evaluate(left.indexer, env);
|
|
451
|
+
if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
|
|
452
|
+
obj[idx] = rightVal;
|
|
453
|
+
return rightVal;
|
|
454
|
+
}
|
|
455
|
+
|
|
422
456
|
|
|
423
|
-
throw new RuntimeError('Invalid assignment target', node);
|
|
457
|
+
throw new RuntimeError('Invalid assignment target', node, this.source);
|
|
424
458
|
|
|
425
459
|
}
|
|
426
460
|
|
|
@@ -431,7 +465,7 @@ async evalCompoundAssignment(node, env) {
|
|
|
431
465
|
if (left.type === 'Identifier') current = env.get(left.name);
|
|
432
466
|
else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
|
|
433
467
|
else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
|
|
434
|
-
else throw new RuntimeError('Invalid compound assignment target', node);
|
|
468
|
+
else throw new RuntimeError('Invalid compound assignment target', node, this.source);
|
|
435
469
|
|
|
436
470
|
|
|
437
471
|
const rhs = await this.evaluate(node.right, env);
|
|
@@ -442,7 +476,7 @@ async evalCompoundAssignment(node, env) {
|
|
|
442
476
|
case 'STAREQ': computed = current * rhs; break;
|
|
443
477
|
case 'SLASHEQ': computed = current / rhs; break;
|
|
444
478
|
case 'MODEQ': computed = current % rhs; break;
|
|
445
|
-
default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node);
|
|
479
|
+
default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node, this.source);
|
|
446
480
|
|
|
447
481
|
}
|
|
448
482
|
|
|
@@ -462,21 +496,33 @@ async evalSldeploy(node, env) {
|
|
|
462
496
|
|
|
463
497
|
async evalAsk(node, env) {
|
|
464
498
|
const prompt = await this.evaluate(node.prompt, env);
|
|
499
|
+
|
|
500
|
+
if (typeof prompt !== 'string') {
|
|
501
|
+
throw new RuntimeError('ask() prompt must be a string', node, this.source);
|
|
502
|
+
}
|
|
503
|
+
|
|
465
504
|
const input = readlineSync.question(prompt + ' ');
|
|
466
505
|
return input;
|
|
467
506
|
}
|
|
468
507
|
|
|
508
|
+
|
|
469
509
|
async evalDefine(node, env) {
|
|
510
|
+
if (!node.id || typeof node.id !== 'string') {
|
|
511
|
+
throw new RuntimeError('Invalid identifier in define statement', node, this.source);
|
|
512
|
+
}
|
|
513
|
+
|
|
470
514
|
const val = node.expr ? await this.evaluate(node.expr, env) : null;
|
|
471
|
-
return
|
|
515
|
+
return env.define(node.id, val);
|
|
516
|
+
|
|
472
517
|
}
|
|
473
518
|
|
|
519
|
+
|
|
474
520
|
async evalBinary(node, env) {
|
|
475
521
|
const l = await this.evaluate(node.left, env);
|
|
476
522
|
const r = await this.evaluate(node.right, env);
|
|
477
523
|
|
|
478
524
|
if (node.operator === 'SLASH' && r === 0) {
|
|
479
|
-
throw new RuntimeError('Division by zero', node);
|
|
525
|
+
throw new RuntimeError('Division by zero', node, this.source);
|
|
480
526
|
}
|
|
481
527
|
|
|
482
528
|
switch (node.operator) {
|
|
@@ -491,7 +537,7 @@ async evalBinary(node, env) {
|
|
|
491
537
|
case 'LTE': return l <= r;
|
|
492
538
|
case 'GT': return l > r;
|
|
493
539
|
case 'GTE': return l >= r;
|
|
494
|
-
default: throw new RuntimeError(`Unknown binary operator: ${node.operator}`, node);
|
|
540
|
+
default: throw new RuntimeError(`Unknown binary operator: ${node.operator}`, node, this.source);
|
|
495
541
|
|
|
496
542
|
}
|
|
497
543
|
}
|
|
@@ -500,7 +546,7 @@ async evalLogical(node, env) {
|
|
|
500
546
|
const l = await this.evaluate(node.left, env);
|
|
501
547
|
if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
|
|
502
548
|
if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
|
|
503
|
-
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node);
|
|
549
|
+
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
504
550
|
|
|
505
551
|
}
|
|
506
552
|
|
|
@@ -510,22 +556,43 @@ async evalUnary(node, env) {
|
|
|
510
556
|
case 'NOT': return !val;
|
|
511
557
|
case 'MINUS': return -val;
|
|
512
558
|
case 'PLUS': return +val;
|
|
513
|
-
default: throw new RuntimeError(`Unknown unary operator: ${node.operator}`, node);
|
|
559
|
+
default: throw new RuntimeError(`Unknown unary operator: ${node.operator}`, node, this.source);
|
|
514
560
|
|
|
515
561
|
}
|
|
516
562
|
}
|
|
517
563
|
|
|
518
564
|
async evalIf(node, env) {
|
|
519
565
|
const test = await this.evaluate(node.test, env);
|
|
520
|
-
|
|
521
|
-
if (
|
|
566
|
+
|
|
567
|
+
if (typeof test !== 'boolean') {
|
|
568
|
+
throw new RuntimeError('If condition must evaluate to a boolean', node.test, this.source);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (test) {
|
|
572
|
+
return await this.evaluate(node.consequent, env);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (node.alternate) {
|
|
576
|
+
return await this.evaluate(node.alternate, env);
|
|
577
|
+
}
|
|
578
|
+
|
|
522
579
|
return null;
|
|
523
580
|
}
|
|
524
581
|
|
|
582
|
+
|
|
525
583
|
async evalWhile(node, env) {
|
|
526
|
-
while (
|
|
527
|
-
|
|
528
|
-
|
|
584
|
+
while (true) {
|
|
585
|
+
const test = await this.evaluate(node.test, env);
|
|
586
|
+
|
|
587
|
+
if (typeof test !== 'boolean') {
|
|
588
|
+
throw new RuntimeError('While condition must evaluate to a boolean', node.test, this.source);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (!test) break;
|
|
592
|
+
|
|
593
|
+
try {
|
|
594
|
+
await this.evaluate(node.body, env);
|
|
595
|
+
} catch (e) {
|
|
529
596
|
if (e instanceof BreakSignal) break;
|
|
530
597
|
if (e instanceof ContinueSignal) continue;
|
|
531
598
|
throw e;
|
|
@@ -533,6 +600,7 @@ async evalWhile(node, env) {
|
|
|
533
600
|
}
|
|
534
601
|
return null;
|
|
535
602
|
}
|
|
603
|
+
|
|
536
604
|
async evalFor(node, env) {
|
|
537
605
|
// -------------------------------
|
|
538
606
|
// Python-style: for x in iterable (with optional 'let')
|
|
@@ -541,7 +609,7 @@ async evalFor(node, env) {
|
|
|
541
609
|
const iterable = await this.evaluate(node.iterable, env);
|
|
542
610
|
|
|
543
611
|
if (iterable == null || typeof iterable !== 'object') {
|
|
544
|
-
throw new RuntimeError('Cannot iterate over non-iterable', node);
|
|
612
|
+
throw new RuntimeError('Cannot iterate over non-iterable', node, this.source);
|
|
545
613
|
}
|
|
546
614
|
|
|
547
615
|
const loopVar = node.variable; // STRING from parser
|
|
@@ -606,9 +674,26 @@ async evalFor(node, env) {
|
|
|
606
674
|
|
|
607
675
|
return null;
|
|
608
676
|
}
|
|
609
|
-
|
|
610
677
|
evalFunctionDeclaration(node, env) {
|
|
611
|
-
|
|
678
|
+
if (!node.name || typeof node.name !== 'string') {
|
|
679
|
+
throw new RuntimeError('Function declaration requires a valid name', node, this.source);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!Array.isArray(node.params)) {
|
|
683
|
+
throw new RuntimeError(`Invalid parameter list in function '${node.name}'`, node, this.source);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (!node.body) {
|
|
687
|
+
throw new RuntimeError(`Function '${node.name}' has no body`, node, this.source);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const fn = {
|
|
691
|
+
params: node.params,
|
|
692
|
+
body: node.body,
|
|
693
|
+
env,
|
|
694
|
+
async: node.async || false
|
|
695
|
+
};
|
|
696
|
+
|
|
612
697
|
env.define(node.name, fn);
|
|
613
698
|
return null;
|
|
614
699
|
}
|
|
@@ -618,10 +703,10 @@ async evalCall(node, env) {
|
|
|
618
703
|
if (typeof calleeEvaluated === 'function') {
|
|
619
704
|
const args = [];
|
|
620
705
|
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
621
|
-
return calleeEvaluated(...args);
|
|
706
|
+
return await calleeEvaluated(...args);
|
|
622
707
|
}
|
|
623
708
|
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
|
|
624
|
-
throw new RuntimeError('Call to non-function', node);
|
|
709
|
+
throw new RuntimeError('Call to non-function', node, this.source);
|
|
625
710
|
}
|
|
626
711
|
|
|
627
712
|
const fn = calleeEvaluated;
|
|
@@ -634,7 +719,7 @@ async evalCall(node, env) {
|
|
|
634
719
|
|
|
635
720
|
try {
|
|
636
721
|
const result = await this.evaluate(fn.body, callEnv);
|
|
637
|
-
return
|
|
722
|
+
return result;
|
|
638
723
|
} catch (e) {
|
|
639
724
|
if (e instanceof ReturnValue) return e.value;
|
|
640
725
|
throw e;
|
|
@@ -645,13 +730,13 @@ async evalIndex(node, env) {
|
|
|
645
730
|
const obj = await this.evaluate(node.object, env);
|
|
646
731
|
const idx = await this.evaluate(node.indexer, env);
|
|
647
732
|
|
|
648
|
-
if (obj == null) throw new RuntimeError('Indexing null or undefined', node);
|
|
733
|
+
if (obj == null) throw new RuntimeError('Indexing null or undefined', node, this.source);
|
|
649
734
|
|
|
650
735
|
if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
|
|
651
|
-
throw new RuntimeError('Array index out of bounds', node);
|
|
736
|
+
throw new RuntimeError('Array index out of bounds', node, this.source);
|
|
652
737
|
}
|
|
653
738
|
if (typeof obj === 'object' && !(idx in obj)) {
|
|
654
|
-
throw new RuntimeError(`Property '${idx}' does not exist`, node);
|
|
739
|
+
throw new RuntimeError(`Property '${idx}' does not exist`, node, this.source);
|
|
655
740
|
}
|
|
656
741
|
|
|
657
742
|
return obj[idx];
|
|
@@ -661,7 +746,7 @@ async evalObject(node, env) {
|
|
|
661
746
|
const out = {};
|
|
662
747
|
for (const p of node.props) {
|
|
663
748
|
if (!p.key || !p.value) {
|
|
664
|
-
throw new RuntimeError('Invalid object property', node);
|
|
749
|
+
throw new RuntimeError('Invalid object property', node, this.source);
|
|
665
750
|
}
|
|
666
751
|
const key = await this.evaluate(p.key, env);
|
|
667
752
|
const value = await this.evaluate(p.value, env);
|
|
@@ -673,8 +758,8 @@ async evalObject(node, env) {
|
|
|
673
758
|
|
|
674
759
|
async evalMember(node, env) {
|
|
675
760
|
const obj = await this.evaluate(node.object, env);
|
|
676
|
-
if (obj == null) throw new RuntimeError('Member access of null or undefined', node);
|
|
677
|
-
if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node);
|
|
761
|
+
if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
|
|
762
|
+
if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node, this.source);
|
|
678
763
|
return obj[node.property];
|
|
679
764
|
}
|
|
680
765
|
|
|
@@ -684,7 +769,7 @@ async evalUpdate(node, env) {
|
|
|
684
769
|
if (arg.type === 'Identifier') return env.get(arg.name);
|
|
685
770
|
if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
|
|
686
771
|
if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
|
|
687
|
-
throw new RuntimeError('Invalid update target', node);
|
|
772
|
+
throw new RuntimeError('Invalid update target', node, this.source);
|
|
688
773
|
|
|
689
774
|
};
|
|
690
775
|
const setValue = async (v) => {
|