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