starlight-cli 1.1.9 → 1.1.10
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/README.md +1 -1
- package/dist/index.js +198 -90
- package/package.json +1 -1
- package/src/evaluator.js +135 -66
- package/src/parser.js +62 -23
- package/src/starlight.js +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Starlight is a lightweight, developer-oriented programming language designed for **server-side scripting, automation, and general-purpose programming**. It combines a clean, readable syntax inspired by JavaScript and Python with powerful runtime features such as async/await, modules, and interactive I/O.
|
|
5
5
|
|
|
6
6
|
**Official Reference:**
|
|
7
|
-
https://
|
|
7
|
+
https://dominexmacedon.github.io/starlight-programming-language/learn.html
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
package/dist/index.js
CHANGED
|
@@ -10230,7 +10230,13 @@ class RuntimeError extends Error {
|
|
|
10230
10230
|
const lines = source.split('\n');
|
|
10231
10231
|
const srcLine = lines[node.line - 1] || '';
|
|
10232
10232
|
output += ` ${srcLine}\n`;
|
|
10233
|
-
|
|
10233
|
+
const caretPos =
|
|
10234
|
+
typeof column === 'number' && column > 0
|
|
10235
|
+
? column - 1
|
|
10236
|
+
: 0;
|
|
10237
|
+
|
|
10238
|
+
output += ` ${' '.repeat(caretPos)}^\n`;
|
|
10239
|
+
;
|
|
10234
10240
|
}
|
|
10235
10241
|
|
|
10236
10242
|
super(output);
|
|
@@ -10292,6 +10298,34 @@ class Evaluator {
|
|
|
10292
10298
|
this.global = new Environment();
|
|
10293
10299
|
this.setupBuiltins();
|
|
10294
10300
|
}
|
|
10301
|
+
async callFunction(fn, args, env, node = null) {
|
|
10302
|
+
if (typeof fn === 'function') {
|
|
10303
|
+
const val = await fn(...args);
|
|
10304
|
+
return val === undefined ? null : val; // <<< enforce null
|
|
10305
|
+
}
|
|
10306
|
+
|
|
10307
|
+
if (fn && typeof fn === 'object' && fn.body && fn.params) {
|
|
10308
|
+
const callEnv = new Environment(fn.env);
|
|
10309
|
+
|
|
10310
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
10311
|
+
const param = fn.params[i];
|
|
10312
|
+
const name = typeof param === 'string' ? param : param.name;
|
|
10313
|
+
callEnv.define(name, args[i]);
|
|
10314
|
+
}
|
|
10315
|
+
|
|
10316
|
+
try {
|
|
10317
|
+
const val = await this.evaluate(fn.body, callEnv);
|
|
10318
|
+
return val === undefined ? null : val; // <<< enforce null
|
|
10319
|
+
} catch (e) {
|
|
10320
|
+
if (e instanceof ReturnValue) return e.value === undefined ? null : e.value; // <<< enforce null
|
|
10321
|
+
throw e;
|
|
10322
|
+
}
|
|
10323
|
+
}
|
|
10324
|
+
|
|
10325
|
+
throw new RuntimeError('Value is not callable', node, this.source);
|
|
10326
|
+
}
|
|
10327
|
+
|
|
10328
|
+
|
|
10295
10329
|
suggest(name, env) {
|
|
10296
10330
|
const names = new Set();
|
|
10297
10331
|
let current = env;
|
|
@@ -10365,6 +10399,7 @@ formatValue(value, seen = new Set()) {
|
|
|
10365
10399
|
|
|
10366
10400
|
|
|
10367
10401
|
setupBuiltins() {
|
|
10402
|
+
const evaluator = this;
|
|
10368
10403
|
this.global.define('len', arg => {
|
|
10369
10404
|
if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
|
|
10370
10405
|
if (arg && typeof arg === 'object') return Object.keys(arg).length;
|
|
@@ -10376,63 +10411,76 @@ formatValue(value, seen = new Set()) {
|
|
|
10376
10411
|
if (Array.isArray(arg)) return 'array';
|
|
10377
10412
|
return typeof arg;
|
|
10378
10413
|
});
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
if (typeof fn !== 'function') {
|
|
10384
|
-
throw new RuntimeError('map() expects a function', null, this.source);
|
|
10385
|
-
}
|
|
10414
|
+
this.global.define('map', async (array, fn) => {
|
|
10415
|
+
if (!Array.isArray(array)) {
|
|
10416
|
+
throw new RuntimeError('map() expects an array', null, evaluator.source);
|
|
10417
|
+
}
|
|
10386
10418
|
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
}
|
|
10419
|
+
const result = [];
|
|
10420
|
+
for (let i = 0; i < array.length; i++) {
|
|
10421
|
+
result.push(
|
|
10422
|
+
await evaluator.callFunction(
|
|
10423
|
+
fn,
|
|
10424
|
+
[array[i], i, array],
|
|
10425
|
+
evaluator.global
|
|
10426
|
+
)
|
|
10427
|
+
);
|
|
10428
|
+
}
|
|
10429
|
+
return result;
|
|
10430
|
+
});
|
|
10400
10431
|
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
result.push(array[i]);
|
|
10432
|
+
this.global.define('filter', async (array, fn) => {
|
|
10433
|
+
if (!Array.isArray(array)) {
|
|
10434
|
+
throw new RuntimeError('filter() expects an array', null, evaluator.source);
|
|
10405
10435
|
}
|
|
10406
|
-
}
|
|
10407
|
-
return result;
|
|
10408
|
-
});
|
|
10409
|
-
this.global.define('reduce', async (array, fn, initial) => {
|
|
10410
|
-
if (!Array.isArray(array)) {
|
|
10411
|
-
throw new RuntimeError('reduce() expects an array', null, this.source);
|
|
10412
|
-
}
|
|
10413
|
-
if (typeof fn !== 'function') {
|
|
10414
|
-
throw new RuntimeError('reduce() expects a function', null, this.source);
|
|
10415
|
-
}
|
|
10416
10436
|
|
|
10417
|
-
|
|
10418
|
-
|
|
10437
|
+
const result = [];
|
|
10438
|
+
for (let i = 0; i < array.length; i++) {
|
|
10439
|
+
if (
|
|
10440
|
+
await evaluator.callFunction(
|
|
10441
|
+
fn,
|
|
10442
|
+
[array[i], i, array],
|
|
10443
|
+
evaluator.global
|
|
10444
|
+
)
|
|
10445
|
+
) {
|
|
10446
|
+
result.push(array[i]);
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
return result;
|
|
10450
|
+
});
|
|
10419
10451
|
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
if (array.length === 0) {
|
|
10424
|
-
throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
|
|
10452
|
+
this.global.define('reduce', async (array, fn, initial) => {
|
|
10453
|
+
if (!Array.isArray(array)) {
|
|
10454
|
+
throw new RuntimeError('reduce() expects an array', null, evaluator.source);
|
|
10425
10455
|
}
|
|
10426
|
-
acc = array[0];
|
|
10427
|
-
startIndex = 1;
|
|
10428
|
-
}
|
|
10429
10456
|
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
}
|
|
10457
|
+
let acc;
|
|
10458
|
+
let i = 0;
|
|
10433
10459
|
|
|
10434
|
-
|
|
10435
|
-
|
|
10460
|
+
if (initial !== undefined) {
|
|
10461
|
+
acc = initial;
|
|
10462
|
+
} else {
|
|
10463
|
+
if (array.length === 0) {
|
|
10464
|
+
throw new RuntimeError(
|
|
10465
|
+
'reduce() of empty array with no initial value',
|
|
10466
|
+
null,
|
|
10467
|
+
evaluator.source
|
|
10468
|
+
);
|
|
10469
|
+
}
|
|
10470
|
+
acc = array[0];
|
|
10471
|
+
i = 1;
|
|
10472
|
+
}
|
|
10473
|
+
|
|
10474
|
+
for (; i < array.length; i++) {
|
|
10475
|
+
acc = await evaluator.callFunction(
|
|
10476
|
+
fn,
|
|
10477
|
+
[acc, array[i], i, array],
|
|
10478
|
+
evaluator.global
|
|
10479
|
+
);
|
|
10480
|
+
}
|
|
10481
|
+
|
|
10482
|
+
return acc;
|
|
10483
|
+
});
|
|
10436
10484
|
|
|
10437
10485
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
10438
10486
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
@@ -10586,21 +10634,30 @@ case 'RaceClause':
|
|
|
10586
10634
|
const callee = await this.evaluate(node.callee, env);
|
|
10587
10635
|
|
|
10588
10636
|
if (typeof callee === 'object' && callee.body) {
|
|
10589
|
-
const evaluator = this;
|
|
10590
|
-
|
|
10637
|
+
const evaluator = this;
|
|
10638
|
+
|
|
10639
|
+
const Constructor = function (...args) {
|
|
10591
10640
|
const newEnv = new Environment(callee.env);
|
|
10592
10641
|
newEnv.define('this', this);
|
|
10642
|
+
|
|
10593
10643
|
for (let i = 0; i < callee.params.length; i++) {
|
|
10594
|
-
|
|
10644
|
+
const param = callee.params[i];
|
|
10645
|
+
const paramName = typeof param === 'string' ? param : param.name;
|
|
10646
|
+
newEnv.define(paramName, args[i]);
|
|
10595
10647
|
}
|
|
10596
|
-
|
|
10648
|
+
|
|
10649
|
+
return (async () => {
|
|
10650
|
+
await evaluator.evaluate(callee.body, newEnv);
|
|
10651
|
+
return this;
|
|
10652
|
+
})();
|
|
10597
10653
|
};
|
|
10598
10654
|
|
|
10599
10655
|
const args = [];
|
|
10600
10656
|
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
10601
|
-
return new Constructor(...args);
|
|
10657
|
+
return await new Constructor(...args);
|
|
10602
10658
|
}
|
|
10603
10659
|
|
|
10660
|
+
// native JS constructor fallback
|
|
10604
10661
|
if (typeof callee !== 'function') {
|
|
10605
10662
|
throw new RuntimeError('NewExpression callee is not a function', node, this.source);
|
|
10606
10663
|
}
|
|
@@ -10822,27 +10879,31 @@ evalArrowFunction(node, env) {
|
|
|
10822
10879
|
return async function (...args) {
|
|
10823
10880
|
const localEnv = new Environment(env);
|
|
10824
10881
|
|
|
10882
|
+
// Bind parameters safely
|
|
10825
10883
|
node.params.forEach((p, i) => {
|
|
10826
|
-
|
|
10884
|
+
const paramName = typeof p === 'string' ? p : p.name;
|
|
10885
|
+
localEnv.define(paramName, args[i]);
|
|
10827
10886
|
});
|
|
10828
10887
|
|
|
10829
10888
|
try {
|
|
10830
10889
|
if (node.isBlock) {
|
|
10890
|
+
// Block body
|
|
10831
10891
|
const result = await evaluator.evaluate(node.body, localEnv);
|
|
10832
|
-
return result
|
|
10892
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
10833
10893
|
} else {
|
|
10834
|
-
|
|
10894
|
+
// Expression body
|
|
10895
|
+
const result = await evaluator.evaluate(node.body, localEnv);
|
|
10896
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
10835
10897
|
}
|
|
10836
10898
|
} catch (err) {
|
|
10837
|
-
if (err instanceof ReturnValue)
|
|
10838
|
-
return err.value;
|
|
10839
|
-
}
|
|
10899
|
+
if (err instanceof ReturnValue) return err.value === undefined ? null : err.value;
|
|
10840
10900
|
throw err;
|
|
10841
10901
|
}
|
|
10842
10902
|
};
|
|
10843
10903
|
}
|
|
10844
10904
|
|
|
10845
10905
|
|
|
10906
|
+
|
|
10846
10907
|
async evalAssignment(node, env) {
|
|
10847
10908
|
const rightVal = await this.evaluate(node.right, env);
|
|
10848
10909
|
const left = node.left;
|
|
@@ -11183,18 +11244,26 @@ async evalIndex(node, env) {
|
|
|
11183
11244
|
async evalObject(node, env) {
|
|
11184
11245
|
const out = {};
|
|
11185
11246
|
for (const p of node.props) {
|
|
11186
|
-
if (!p.key
|
|
11187
|
-
throw new RuntimeError('
|
|
11247
|
+
if (!p.key) {
|
|
11248
|
+
throw new RuntimeError('Object property must have a key', node, this.source);
|
|
11188
11249
|
}
|
|
11250
|
+
|
|
11189
11251
|
const key = await this.evaluate(p.key, env);
|
|
11190
|
-
|
|
11191
|
-
|
|
11252
|
+
let value = null;
|
|
11253
|
+
|
|
11254
|
+
if (p.value) {
|
|
11255
|
+
value = await this.evaluate(p.value, env);
|
|
11256
|
+
if (value === undefined) value = null; // <- force null instead of undefined
|
|
11257
|
+
}
|
|
11258
|
+
|
|
11259
|
+
out[key] = value;
|
|
11192
11260
|
}
|
|
11193
11261
|
return out;
|
|
11194
11262
|
}
|
|
11195
11263
|
|
|
11196
11264
|
|
|
11197
11265
|
|
|
11266
|
+
|
|
11198
11267
|
async evalMember(node, env) {
|
|
11199
11268
|
const obj = await this.evaluate(node.object, env);
|
|
11200
11269
|
|
|
@@ -11234,7 +11303,7 @@ async evalUpdate(node, env) {
|
|
|
11234
11303
|
|
|
11235
11304
|
}
|
|
11236
11305
|
|
|
11237
|
-
module.exports = Evaluator;
|
|
11306
|
+
module.exports = Evaluator;
|
|
11238
11307
|
|
|
11239
11308
|
/***/ }),
|
|
11240
11309
|
|
|
@@ -11729,14 +11798,18 @@ ifStatement() {
|
|
|
11729
11798
|
test = this.expression();
|
|
11730
11799
|
}
|
|
11731
11800
|
|
|
11732
|
-
const consequent = this.
|
|
11801
|
+
const consequent = this.statementOrBlock();
|
|
11733
11802
|
|
|
11734
11803
|
let alternate = null;
|
|
11735
11804
|
if (this.current.type === 'ELSE') {
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11805
|
+
this.eat('ELSE');
|
|
11806
|
+
if (this.current.type === 'IF') {
|
|
11807
|
+
alternate = this.ifStatement();
|
|
11808
|
+
} else {
|
|
11809
|
+
alternate = this.statementOrBlock();
|
|
11739
11810
|
}
|
|
11811
|
+
}
|
|
11812
|
+
|
|
11740
11813
|
|
|
11741
11814
|
return {
|
|
11742
11815
|
type: 'IfStatement',
|
|
@@ -11970,6 +12043,12 @@ returnStatement() {
|
|
|
11970
12043
|
|
|
11971
12044
|
return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
|
|
11972
12045
|
}
|
|
12046
|
+
statementOrBlock() {
|
|
12047
|
+
if (this.current.type === 'LBRACE') {
|
|
12048
|
+
return this.block();
|
|
12049
|
+
}
|
|
12050
|
+
return this.statement();
|
|
12051
|
+
}
|
|
11973
12052
|
|
|
11974
12053
|
block() {
|
|
11975
12054
|
const t = this.current; // LBRACE token
|
|
@@ -12179,13 +12258,7 @@ postfix() {
|
|
|
12179
12258
|
continue;
|
|
12180
12259
|
}
|
|
12181
12260
|
|
|
12182
|
-
|
|
12183
|
-
const argNode = { type: 'Identifier', name: t.value, line: t.line, column: t.column };
|
|
12184
|
-
this.eat('IDENTIFIER');
|
|
12185
|
-
node = { type: 'CallExpression', callee: node, arguments: [argNode], line: node.line, column: node.column };
|
|
12186
|
-
continue;
|
|
12187
|
-
}
|
|
12188
|
-
|
|
12261
|
+
|
|
12189
12262
|
break;
|
|
12190
12263
|
}
|
|
12191
12264
|
return node;
|
|
@@ -12339,21 +12412,56 @@ arrowFunction(params) {
|
|
|
12339
12412
|
}
|
|
12340
12413
|
|
|
12341
12414
|
if (t.type === 'LBRACE') {
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12415
|
+
const startLine = t.line;
|
|
12416
|
+
const startCol = t.column;
|
|
12417
|
+
this.eat('LBRACE');
|
|
12418
|
+
|
|
12419
|
+
const props = [];
|
|
12420
|
+
|
|
12421
|
+
while (this.current.type !== 'RBRACE') {
|
|
12422
|
+
let key;
|
|
12423
|
+
|
|
12424
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
12425
|
+
const k = this.current;
|
|
12426
|
+
this.eat('IDENTIFIER');
|
|
12427
|
+
key = {
|
|
12428
|
+
type: 'Literal',
|
|
12429
|
+
value: k.value,
|
|
12430
|
+
line: k.line,
|
|
12431
|
+
column: k.column
|
|
12432
|
+
};
|
|
12352
12433
|
}
|
|
12353
|
-
this.
|
|
12354
|
-
|
|
12434
|
+
else if (this.current.type === 'STRING') {
|
|
12435
|
+
const k = this.current;
|
|
12436
|
+
this.eat('STRING');
|
|
12437
|
+
key = {
|
|
12438
|
+
type: 'Literal',
|
|
12439
|
+
value: k.value,
|
|
12440
|
+
line: k.line,
|
|
12441
|
+
column: k.column
|
|
12442
|
+
};
|
|
12443
|
+
}
|
|
12444
|
+
else {
|
|
12445
|
+
throw new ParseError(
|
|
12446
|
+
'Invalid object key',
|
|
12447
|
+
this.current,
|
|
12448
|
+
this.source
|
|
12449
|
+
);
|
|
12450
|
+
}
|
|
12451
|
+
|
|
12452
|
+
this.eat('COLON');
|
|
12453
|
+
const value = this.expression();
|
|
12454
|
+
|
|
12455
|
+
props.push({ key, value });
|
|
12456
|
+
|
|
12457
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
12355
12458
|
}
|
|
12356
12459
|
|
|
12460
|
+
this.eat('RBRACE');
|
|
12461
|
+
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|
|
12462
|
+
}
|
|
12463
|
+
|
|
12464
|
+
|
|
12357
12465
|
throw new ParseError(
|
|
12358
12466
|
`Unexpected token '${t.type}'`,
|
|
12359
12467
|
t,
|
|
@@ -12544,7 +12652,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
12544
12652
|
const Parser = __nccwpck_require__(222);
|
|
12545
12653
|
const Evaluator = __nccwpck_require__(112);
|
|
12546
12654
|
|
|
12547
|
-
const VERSION = '1.1.
|
|
12655
|
+
const VERSION = '1.1.10';
|
|
12548
12656
|
|
|
12549
12657
|
const COLOR = {
|
|
12550
12658
|
reset: '\x1b[0m',
|
package/package.json
CHANGED
package/src/evaluator.js
CHANGED
|
@@ -20,7 +20,13 @@ class RuntimeError extends Error {
|
|
|
20
20
|
const lines = source.split('\n');
|
|
21
21
|
const srcLine = lines[node.line - 1] || '';
|
|
22
22
|
output += ` ${srcLine}\n`;
|
|
23
|
-
|
|
23
|
+
const caretPos =
|
|
24
|
+
typeof column === 'number' && column > 0
|
|
25
|
+
? column - 1
|
|
26
|
+
: 0;
|
|
27
|
+
|
|
28
|
+
output += ` ${' '.repeat(caretPos)}^\n`;
|
|
29
|
+
;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
super(output);
|
|
@@ -82,6 +88,34 @@ class Evaluator {
|
|
|
82
88
|
this.global = new Environment();
|
|
83
89
|
this.setupBuiltins();
|
|
84
90
|
}
|
|
91
|
+
async callFunction(fn, args, env, node = null) {
|
|
92
|
+
if (typeof fn === 'function') {
|
|
93
|
+
const val = await fn(...args);
|
|
94
|
+
return val === undefined ? null : val; // <<< enforce null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (fn && typeof fn === 'object' && fn.body && fn.params) {
|
|
98
|
+
const callEnv = new Environment(fn.env);
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
101
|
+
const param = fn.params[i];
|
|
102
|
+
const name = typeof param === 'string' ? param : param.name;
|
|
103
|
+
callEnv.define(name, args[i]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const val = await this.evaluate(fn.body, callEnv);
|
|
108
|
+
return val === undefined ? null : val; // <<< enforce null
|
|
109
|
+
} catch (e) {
|
|
110
|
+
if (e instanceof ReturnValue) return e.value === undefined ? null : e.value; // <<< enforce null
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new RuntimeError('Value is not callable', node, this.source);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
85
119
|
suggest(name, env) {
|
|
86
120
|
const names = new Set();
|
|
87
121
|
let current = env;
|
|
@@ -155,6 +189,7 @@ formatValue(value, seen = new Set()) {
|
|
|
155
189
|
|
|
156
190
|
|
|
157
191
|
setupBuiltins() {
|
|
192
|
+
const evaluator = this;
|
|
158
193
|
this.global.define('len', arg => {
|
|
159
194
|
if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
|
|
160
195
|
if (arg && typeof arg === 'object') return Object.keys(arg).length;
|
|
@@ -166,63 +201,76 @@ formatValue(value, seen = new Set()) {
|
|
|
166
201
|
if (Array.isArray(arg)) return 'array';
|
|
167
202
|
return typeof arg;
|
|
168
203
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (typeof fn !== 'function') {
|
|
174
|
-
throw new RuntimeError('map() expects a function', null, this.source);
|
|
175
|
-
}
|
|
204
|
+
this.global.define('map', async (array, fn) => {
|
|
205
|
+
if (!Array.isArray(array)) {
|
|
206
|
+
throw new RuntimeError('map() expects an array', null, evaluator.source);
|
|
207
|
+
}
|
|
176
208
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
209
|
+
const result = [];
|
|
210
|
+
for (let i = 0; i < array.length; i++) {
|
|
211
|
+
result.push(
|
|
212
|
+
await evaluator.callFunction(
|
|
213
|
+
fn,
|
|
214
|
+
[array[i], i, array],
|
|
215
|
+
evaluator.global
|
|
216
|
+
)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
});
|
|
190
221
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
result.push(array[i]);
|
|
222
|
+
this.global.define('filter', async (array, fn) => {
|
|
223
|
+
if (!Array.isArray(array)) {
|
|
224
|
+
throw new RuntimeError('filter() expects an array', null, evaluator.source);
|
|
195
225
|
}
|
|
196
|
-
}
|
|
197
|
-
return result;
|
|
198
|
-
});
|
|
199
|
-
this.global.define('reduce', async (array, fn, initial) => {
|
|
200
|
-
if (!Array.isArray(array)) {
|
|
201
|
-
throw new RuntimeError('reduce() expects an array', null, this.source);
|
|
202
|
-
}
|
|
203
|
-
if (typeof fn !== 'function') {
|
|
204
|
-
throw new RuntimeError('reduce() expects a function', null, this.source);
|
|
205
|
-
}
|
|
206
226
|
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
const result = [];
|
|
228
|
+
for (let i = 0; i < array.length; i++) {
|
|
229
|
+
if (
|
|
230
|
+
await evaluator.callFunction(
|
|
231
|
+
fn,
|
|
232
|
+
[array[i], i, array],
|
|
233
|
+
evaluator.global
|
|
234
|
+
)
|
|
235
|
+
) {
|
|
236
|
+
result.push(array[i]);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
});
|
|
209
241
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (array.length === 0) {
|
|
214
|
-
throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
|
|
242
|
+
this.global.define('reduce', async (array, fn, initial) => {
|
|
243
|
+
if (!Array.isArray(array)) {
|
|
244
|
+
throw new RuntimeError('reduce() expects an array', null, evaluator.source);
|
|
215
245
|
}
|
|
216
|
-
acc = array[0];
|
|
217
|
-
startIndex = 1;
|
|
218
|
-
}
|
|
219
246
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
247
|
+
let acc;
|
|
248
|
+
let i = 0;
|
|
223
249
|
|
|
224
|
-
|
|
225
|
-
|
|
250
|
+
if (initial !== undefined) {
|
|
251
|
+
acc = initial;
|
|
252
|
+
} else {
|
|
253
|
+
if (array.length === 0) {
|
|
254
|
+
throw new RuntimeError(
|
|
255
|
+
'reduce() of empty array with no initial value',
|
|
256
|
+
null,
|
|
257
|
+
evaluator.source
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
acc = array[0];
|
|
261
|
+
i = 1;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (; i < array.length; i++) {
|
|
265
|
+
acc = await evaluator.callFunction(
|
|
266
|
+
fn,
|
|
267
|
+
[acc, array[i], i, array],
|
|
268
|
+
evaluator.global
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return acc;
|
|
273
|
+
});
|
|
226
274
|
|
|
227
275
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
228
276
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
@@ -376,21 +424,30 @@ case 'RaceClause':
|
|
|
376
424
|
const callee = await this.evaluate(node.callee, env);
|
|
377
425
|
|
|
378
426
|
if (typeof callee === 'object' && callee.body) {
|
|
379
|
-
const evaluator = this;
|
|
380
|
-
|
|
427
|
+
const evaluator = this;
|
|
428
|
+
|
|
429
|
+
const Constructor = function (...args) {
|
|
381
430
|
const newEnv = new Environment(callee.env);
|
|
382
431
|
newEnv.define('this', this);
|
|
432
|
+
|
|
383
433
|
for (let i = 0; i < callee.params.length; i++) {
|
|
384
|
-
|
|
434
|
+
const param = callee.params[i];
|
|
435
|
+
const paramName = typeof param === 'string' ? param : param.name;
|
|
436
|
+
newEnv.define(paramName, args[i]);
|
|
385
437
|
}
|
|
386
|
-
|
|
438
|
+
|
|
439
|
+
return (async () => {
|
|
440
|
+
await evaluator.evaluate(callee.body, newEnv);
|
|
441
|
+
return this;
|
|
442
|
+
})();
|
|
387
443
|
};
|
|
388
444
|
|
|
389
445
|
const args = [];
|
|
390
446
|
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
391
|
-
return new Constructor(...args);
|
|
447
|
+
return await new Constructor(...args);
|
|
392
448
|
}
|
|
393
449
|
|
|
450
|
+
// native JS constructor fallback
|
|
394
451
|
if (typeof callee !== 'function') {
|
|
395
452
|
throw new RuntimeError('NewExpression callee is not a function', node, this.source);
|
|
396
453
|
}
|
|
@@ -612,27 +669,31 @@ evalArrowFunction(node, env) {
|
|
|
612
669
|
return async function (...args) {
|
|
613
670
|
const localEnv = new Environment(env);
|
|
614
671
|
|
|
672
|
+
// Bind parameters safely
|
|
615
673
|
node.params.forEach((p, i) => {
|
|
616
|
-
|
|
674
|
+
const paramName = typeof p === 'string' ? p : p.name;
|
|
675
|
+
localEnv.define(paramName, args[i]);
|
|
617
676
|
});
|
|
618
677
|
|
|
619
678
|
try {
|
|
620
679
|
if (node.isBlock) {
|
|
680
|
+
// Block body
|
|
621
681
|
const result = await evaluator.evaluate(node.body, localEnv);
|
|
622
|
-
return result
|
|
682
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
623
683
|
} else {
|
|
624
|
-
|
|
684
|
+
// Expression body
|
|
685
|
+
const result = await evaluator.evaluate(node.body, localEnv);
|
|
686
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
625
687
|
}
|
|
626
688
|
} catch (err) {
|
|
627
|
-
if (err instanceof ReturnValue)
|
|
628
|
-
return err.value;
|
|
629
|
-
}
|
|
689
|
+
if (err instanceof ReturnValue) return err.value === undefined ? null : err.value;
|
|
630
690
|
throw err;
|
|
631
691
|
}
|
|
632
692
|
};
|
|
633
693
|
}
|
|
634
694
|
|
|
635
695
|
|
|
696
|
+
|
|
636
697
|
async evalAssignment(node, env) {
|
|
637
698
|
const rightVal = await this.evaluate(node.right, env);
|
|
638
699
|
const left = node.left;
|
|
@@ -973,18 +1034,26 @@ async evalIndex(node, env) {
|
|
|
973
1034
|
async evalObject(node, env) {
|
|
974
1035
|
const out = {};
|
|
975
1036
|
for (const p of node.props) {
|
|
976
|
-
if (!p.key
|
|
977
|
-
throw new RuntimeError('
|
|
1037
|
+
if (!p.key) {
|
|
1038
|
+
throw new RuntimeError('Object property must have a key', node, this.source);
|
|
978
1039
|
}
|
|
1040
|
+
|
|
979
1041
|
const key = await this.evaluate(p.key, env);
|
|
980
|
-
|
|
981
|
-
|
|
1042
|
+
let value = null;
|
|
1043
|
+
|
|
1044
|
+
if (p.value) {
|
|
1045
|
+
value = await this.evaluate(p.value, env);
|
|
1046
|
+
if (value === undefined) value = null; // <- force null instead of undefined
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
out[key] = value;
|
|
982
1050
|
}
|
|
983
1051
|
return out;
|
|
984
1052
|
}
|
|
985
1053
|
|
|
986
1054
|
|
|
987
1055
|
|
|
1056
|
+
|
|
988
1057
|
async evalMember(node, env) {
|
|
989
1058
|
const obj = await this.evaluate(node.object, env);
|
|
990
1059
|
|
|
@@ -1024,4 +1093,4 @@ async evalUpdate(node, env) {
|
|
|
1024
1093
|
|
|
1025
1094
|
}
|
|
1026
1095
|
|
|
1027
|
-
module.exports = Evaluator;
|
|
1096
|
+
module.exports = Evaluator;
|
package/src/parser.js
CHANGED
|
@@ -256,14 +256,18 @@ ifStatement() {
|
|
|
256
256
|
test = this.expression();
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
const consequent = this.
|
|
259
|
+
const consequent = this.statementOrBlock();
|
|
260
260
|
|
|
261
261
|
let alternate = null;
|
|
262
262
|
if (this.current.type === 'ELSE') {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
this.eat('ELSE');
|
|
264
|
+
if (this.current.type === 'IF') {
|
|
265
|
+
alternate = this.ifStatement();
|
|
266
|
+
} else {
|
|
267
|
+
alternate = this.statementOrBlock();
|
|
266
268
|
}
|
|
269
|
+
}
|
|
270
|
+
|
|
267
271
|
|
|
268
272
|
return {
|
|
269
273
|
type: 'IfStatement',
|
|
@@ -497,6 +501,12 @@ returnStatement() {
|
|
|
497
501
|
|
|
498
502
|
return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
|
|
499
503
|
}
|
|
504
|
+
statementOrBlock() {
|
|
505
|
+
if (this.current.type === 'LBRACE') {
|
|
506
|
+
return this.block();
|
|
507
|
+
}
|
|
508
|
+
return this.statement();
|
|
509
|
+
}
|
|
500
510
|
|
|
501
511
|
block() {
|
|
502
512
|
const t = this.current; // LBRACE token
|
|
@@ -706,13 +716,7 @@ postfix() {
|
|
|
706
716
|
continue;
|
|
707
717
|
}
|
|
708
718
|
|
|
709
|
-
|
|
710
|
-
const argNode = { type: 'Identifier', name: t.value, line: t.line, column: t.column };
|
|
711
|
-
this.eat('IDENTIFIER');
|
|
712
|
-
node = { type: 'CallExpression', callee: node, arguments: [argNode], line: node.line, column: node.column };
|
|
713
|
-
continue;
|
|
714
|
-
}
|
|
715
|
-
|
|
719
|
+
|
|
716
720
|
break;
|
|
717
721
|
}
|
|
718
722
|
return node;
|
|
@@ -866,21 +870,56 @@ arrowFunction(params) {
|
|
|
866
870
|
}
|
|
867
871
|
|
|
868
872
|
if (t.type === 'LBRACE') {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
873
|
+
const startLine = t.line;
|
|
874
|
+
const startCol = t.column;
|
|
875
|
+
this.eat('LBRACE');
|
|
876
|
+
|
|
877
|
+
const props = [];
|
|
878
|
+
|
|
879
|
+
while (this.current.type !== 'RBRACE') {
|
|
880
|
+
let key;
|
|
881
|
+
|
|
882
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
883
|
+
const k = this.current;
|
|
884
|
+
this.eat('IDENTIFIER');
|
|
885
|
+
key = {
|
|
886
|
+
type: 'Literal',
|
|
887
|
+
value: k.value,
|
|
888
|
+
line: k.line,
|
|
889
|
+
column: k.column
|
|
890
|
+
};
|
|
879
891
|
}
|
|
880
|
-
this.
|
|
881
|
-
|
|
892
|
+
else if (this.current.type === 'STRING') {
|
|
893
|
+
const k = this.current;
|
|
894
|
+
this.eat('STRING');
|
|
895
|
+
key = {
|
|
896
|
+
type: 'Literal',
|
|
897
|
+
value: k.value,
|
|
898
|
+
line: k.line,
|
|
899
|
+
column: k.column
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
throw new ParseError(
|
|
904
|
+
'Invalid object key',
|
|
905
|
+
this.current,
|
|
906
|
+
this.source
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
this.eat('COLON');
|
|
911
|
+
const value = this.expression();
|
|
912
|
+
|
|
913
|
+
props.push({ key, value });
|
|
914
|
+
|
|
915
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
882
916
|
}
|
|
883
917
|
|
|
918
|
+
this.eat('RBRACE');
|
|
919
|
+
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
|
|
884
923
|
throw new ParseError(
|
|
885
924
|
`Unexpected token '${t.type}'`,
|
|
886
925
|
t,
|