starlight-cli 1.1.9 → 1.1.11
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 +270 -108
- package/package.json +1 -1
- package/src/evaluator.js +168 -81
- package/src/lexer.js +3 -1
- package/src/parser.js +98 -25
- 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,90 @@ formatValue(value, seen = new Set()) {
|
|
|
10376
10411
|
if (Array.isArray(arg)) return 'array';
|
|
10377
10412
|
return typeof arg;
|
|
10378
10413
|
});
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
throw new RuntimeError('map() expects an array', null, this.source);
|
|
10382
|
-
}
|
|
10383
|
-
if (typeof fn !== 'function') {
|
|
10384
|
-
throw new RuntimeError('map() expects a function', null, this.source);
|
|
10385
|
-
}
|
|
10386
|
-
|
|
10387
|
-
const result = [];
|
|
10388
|
-
for (let i = 0; i < array.length; i++) {
|
|
10389
|
-
result.push(await fn(array[i], i, array));
|
|
10390
|
-
}
|
|
10391
|
-
return result;
|
|
10414
|
+
this.global.define('isNaN', arg => {
|
|
10415
|
+
return typeof arg !== 'number' || Number.isNaN(arg);
|
|
10392
10416
|
});
|
|
10393
|
-
this.global.define('
|
|
10394
|
-
if (
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
if (typeof fn !== 'function') {
|
|
10398
|
-
throw new RuntimeError('filter() expects a function', null, this.source);
|
|
10417
|
+
this.global.define('random', (min, max) => {
|
|
10418
|
+
if (max === undefined) {
|
|
10419
|
+
// Only one argument → random between 0 and min
|
|
10420
|
+
return Math.floor(Math.random() * min);
|
|
10399
10421
|
}
|
|
10422
|
+
min = Number(min);
|
|
10423
|
+
max = Number(max);
|
|
10424
|
+
if (isNaN(min) || isNaN(max)) return 0;
|
|
10425
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
10426
|
+
});
|
|
10400
10427
|
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
result.push(array[i]);
|
|
10428
|
+
this.global.define('map', async (array, fn) => {
|
|
10429
|
+
if (!Array.isArray(array)) {
|
|
10430
|
+
throw new RuntimeError('map() expects an array', null, evaluator.source);
|
|
10405
10431
|
}
|
|
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
10432
|
|
|
10417
|
-
|
|
10418
|
-
|
|
10433
|
+
const result = [];
|
|
10434
|
+
for (let i = 0; i < array.length; i++) {
|
|
10435
|
+
result.push(
|
|
10436
|
+
await evaluator.callFunction(
|
|
10437
|
+
fn,
|
|
10438
|
+
[array[i], i, array],
|
|
10439
|
+
evaluator.global
|
|
10440
|
+
)
|
|
10441
|
+
);
|
|
10442
|
+
}
|
|
10443
|
+
return result;
|
|
10444
|
+
});
|
|
10419
10445
|
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
if (array.length === 0) {
|
|
10424
|
-
throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
|
|
10446
|
+
this.global.define('filter', async (array, fn) => {
|
|
10447
|
+
if (!Array.isArray(array)) {
|
|
10448
|
+
throw new RuntimeError('filter() expects an array', null, evaluator.source);
|
|
10425
10449
|
}
|
|
10426
|
-
acc = array[0];
|
|
10427
|
-
startIndex = 1;
|
|
10428
|
-
}
|
|
10429
10450
|
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10451
|
+
const result = [];
|
|
10452
|
+
for (let i = 0; i < array.length; i++) {
|
|
10453
|
+
if (
|
|
10454
|
+
await evaluator.callFunction(
|
|
10455
|
+
fn,
|
|
10456
|
+
[array[i], i, array],
|
|
10457
|
+
evaluator.global
|
|
10458
|
+
)
|
|
10459
|
+
) {
|
|
10460
|
+
result.push(array[i]);
|
|
10461
|
+
}
|
|
10462
|
+
}
|
|
10463
|
+
return result;
|
|
10464
|
+
});
|
|
10433
10465
|
|
|
10434
|
-
|
|
10435
|
-
|
|
10466
|
+
this.global.define('reduce', async (array, fn, initial) => {
|
|
10467
|
+
if (!Array.isArray(array)) {
|
|
10468
|
+
throw new RuntimeError('reduce() expects an array', null, evaluator.source);
|
|
10469
|
+
}
|
|
10470
|
+
|
|
10471
|
+
let acc;
|
|
10472
|
+
let i = 0;
|
|
10473
|
+
|
|
10474
|
+
if (initial !== undefined) {
|
|
10475
|
+
acc = initial;
|
|
10476
|
+
} else {
|
|
10477
|
+
if (array.length === 0) {
|
|
10478
|
+
throw new RuntimeError(
|
|
10479
|
+
'reduce() of empty array with no initial value',
|
|
10480
|
+
null,
|
|
10481
|
+
evaluator.source
|
|
10482
|
+
);
|
|
10483
|
+
}
|
|
10484
|
+
acc = array[0];
|
|
10485
|
+
i = 1;
|
|
10486
|
+
}
|
|
10487
|
+
|
|
10488
|
+
for (; i < array.length; i++) {
|
|
10489
|
+
acc = await evaluator.callFunction(
|
|
10490
|
+
fn,
|
|
10491
|
+
[acc, array[i], i, array],
|
|
10492
|
+
evaluator.global
|
|
10493
|
+
);
|
|
10494
|
+
}
|
|
10495
|
+
|
|
10496
|
+
return acc;
|
|
10497
|
+
});
|
|
10436
10498
|
|
|
10437
10499
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
10438
10500
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
@@ -10586,21 +10648,30 @@ case 'RaceClause':
|
|
|
10586
10648
|
const callee = await this.evaluate(node.callee, env);
|
|
10587
10649
|
|
|
10588
10650
|
if (typeof callee === 'object' && callee.body) {
|
|
10589
|
-
const evaluator = this;
|
|
10590
|
-
|
|
10651
|
+
const evaluator = this;
|
|
10652
|
+
|
|
10653
|
+
const Constructor = function (...args) {
|
|
10591
10654
|
const newEnv = new Environment(callee.env);
|
|
10592
10655
|
newEnv.define('this', this);
|
|
10656
|
+
|
|
10593
10657
|
for (let i = 0; i < callee.params.length; i++) {
|
|
10594
|
-
|
|
10658
|
+
const param = callee.params[i];
|
|
10659
|
+
const paramName = typeof param === 'string' ? param : param.name;
|
|
10660
|
+
newEnv.define(paramName, args[i]);
|
|
10595
10661
|
}
|
|
10596
|
-
|
|
10662
|
+
|
|
10663
|
+
return (async () => {
|
|
10664
|
+
await evaluator.evaluate(callee.body, newEnv);
|
|
10665
|
+
return this;
|
|
10666
|
+
})();
|
|
10597
10667
|
};
|
|
10598
10668
|
|
|
10599
10669
|
const args = [];
|
|
10600
10670
|
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
10601
|
-
return new Constructor(...args);
|
|
10671
|
+
return await new Constructor(...args);
|
|
10602
10672
|
}
|
|
10603
10673
|
|
|
10674
|
+
// native JS constructor fallback
|
|
10604
10675
|
if (typeof callee !== 'function') {
|
|
10605
10676
|
throw new RuntimeError('NewExpression callee is not a function', node, this.source);
|
|
10606
10677
|
}
|
|
@@ -10822,27 +10893,31 @@ evalArrowFunction(node, env) {
|
|
|
10822
10893
|
return async function (...args) {
|
|
10823
10894
|
const localEnv = new Environment(env);
|
|
10824
10895
|
|
|
10896
|
+
// Bind parameters safely
|
|
10825
10897
|
node.params.forEach((p, i) => {
|
|
10826
|
-
|
|
10898
|
+
const paramName = typeof p === 'string' ? p : p.name;
|
|
10899
|
+
localEnv.define(paramName, args[i]);
|
|
10827
10900
|
});
|
|
10828
10901
|
|
|
10829
10902
|
try {
|
|
10830
10903
|
if (node.isBlock) {
|
|
10904
|
+
// Block body
|
|
10831
10905
|
const result = await evaluator.evaluate(node.body, localEnv);
|
|
10832
|
-
return result
|
|
10906
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
10833
10907
|
} else {
|
|
10834
|
-
|
|
10908
|
+
// Expression body
|
|
10909
|
+
const result = await evaluator.evaluate(node.body, localEnv);
|
|
10910
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
10835
10911
|
}
|
|
10836
10912
|
} catch (err) {
|
|
10837
|
-
if (err instanceof ReturnValue)
|
|
10838
|
-
return err.value;
|
|
10839
|
-
}
|
|
10913
|
+
if (err instanceof ReturnValue) return err.value === undefined ? null : err.value;
|
|
10840
10914
|
throw err;
|
|
10841
10915
|
}
|
|
10842
10916
|
};
|
|
10843
10917
|
}
|
|
10844
10918
|
|
|
10845
10919
|
|
|
10920
|
+
|
|
10846
10921
|
async evalAssignment(node, env) {
|
|
10847
10922
|
const rightVal = await this.evaluate(node.right, env);
|
|
10848
10923
|
const left = node.left;
|
|
@@ -11003,13 +11078,10 @@ async evalUnary(node, env) {
|
|
|
11003
11078
|
|
|
11004
11079
|
}
|
|
11005
11080
|
}
|
|
11006
|
-
|
|
11007
11081
|
async evalIf(node, env) {
|
|
11008
|
-
|
|
11082
|
+
let test = await this.evaluate(node.test, env);
|
|
11009
11083
|
|
|
11010
|
-
|
|
11011
|
-
throw new RuntimeError('If condition must evaluate to a boolean', node.test, this.source);
|
|
11012
|
-
}
|
|
11084
|
+
test = !!test;
|
|
11013
11085
|
|
|
11014
11086
|
if (test) {
|
|
11015
11087
|
return await this.evaluate(node.consequent, env);
|
|
@@ -11022,27 +11094,26 @@ async evalIf(node, env) {
|
|
|
11022
11094
|
return null;
|
|
11023
11095
|
}
|
|
11024
11096
|
|
|
11025
|
-
|
|
11026
11097
|
async evalWhile(node, env) {
|
|
11027
11098
|
while (true) {
|
|
11028
|
-
|
|
11099
|
+
let test = await this.evaluate(node.test, env);
|
|
11029
11100
|
|
|
11030
|
-
|
|
11031
|
-
throw new RuntimeError('While condition must evaluate to a boolean', node.test, this.source);
|
|
11032
|
-
}
|
|
11101
|
+
test = !!test;
|
|
11033
11102
|
|
|
11034
11103
|
if (!test) break;
|
|
11035
11104
|
|
|
11036
11105
|
try {
|
|
11037
11106
|
await this.evaluate(node.body, env);
|
|
11038
11107
|
} catch (e) {
|
|
11039
|
-
if (e instanceof BreakSignal) break;
|
|
11040
|
-
if (e instanceof ContinueSignal) continue;
|
|
11041
|
-
throw e;
|
|
11108
|
+
if (e instanceof BreakSignal) break;
|
|
11109
|
+
if (e instanceof ContinueSignal) continue;
|
|
11110
|
+
throw e;
|
|
11042
11111
|
}
|
|
11043
11112
|
}
|
|
11113
|
+
|
|
11044
11114
|
return null;
|
|
11045
11115
|
}
|
|
11116
|
+
|
|
11046
11117
|
async evalFor(node, env) {
|
|
11047
11118
|
if (node.type === 'ForInStatement') {
|
|
11048
11119
|
const iterable = await this.evaluate(node.iterable, env);
|
|
@@ -11183,24 +11254,40 @@ async evalIndex(node, env) {
|
|
|
11183
11254
|
async evalObject(node, env) {
|
|
11184
11255
|
const out = {};
|
|
11185
11256
|
for (const p of node.props) {
|
|
11186
|
-
if (!p.key
|
|
11187
|
-
throw new RuntimeError('
|
|
11257
|
+
if (!p.key) {
|
|
11258
|
+
throw new RuntimeError('Object property must have a key', node, this.source);
|
|
11188
11259
|
}
|
|
11260
|
+
|
|
11189
11261
|
const key = await this.evaluate(p.key, env);
|
|
11190
|
-
|
|
11191
|
-
|
|
11262
|
+
let value = null;
|
|
11263
|
+
|
|
11264
|
+
if (p.value) {
|
|
11265
|
+
value = await this.evaluate(p.value, env);
|
|
11266
|
+
if (value === undefined) value = null; // <- force null instead of undefined
|
|
11267
|
+
}
|
|
11268
|
+
|
|
11269
|
+
out[key] = value;
|
|
11192
11270
|
}
|
|
11193
11271
|
return out;
|
|
11194
11272
|
}
|
|
11195
11273
|
|
|
11196
11274
|
|
|
11197
11275
|
|
|
11276
|
+
|
|
11198
11277
|
async evalMember(node, env) {
|
|
11199
11278
|
const obj = await this.evaluate(node.object, env);
|
|
11200
11279
|
|
|
11201
|
-
if (obj == null)
|
|
11280
|
+
if (obj == null) {
|
|
11281
|
+
throw new RuntimeError('Member access of null or undefined', node, this.source);
|
|
11282
|
+
}
|
|
11283
|
+
|
|
11284
|
+
const prop = obj[node.property];
|
|
11202
11285
|
|
|
11203
|
-
|
|
11286
|
+
if (typeof prop === 'function') {
|
|
11287
|
+
return prop.bind(obj);
|
|
11288
|
+
}
|
|
11289
|
+
|
|
11290
|
+
return prop;
|
|
11204
11291
|
}
|
|
11205
11292
|
|
|
11206
11293
|
|
|
@@ -11234,7 +11321,7 @@ async evalUpdate(node, env) {
|
|
|
11234
11321
|
|
|
11235
11322
|
}
|
|
11236
11323
|
|
|
11237
|
-
module.exports = Evaluator;
|
|
11324
|
+
module.exports = Evaluator;
|
|
11238
11325
|
|
|
11239
11326
|
/***/ }),
|
|
11240
11327
|
|
|
@@ -11271,7 +11358,7 @@ class Lexer {
|
|
|
11271
11358
|
'break', 'continue', 'func', 'return',
|
|
11272
11359
|
'true', 'false', 'null',
|
|
11273
11360
|
'ask', 'define', 'import', 'from', 'as',
|
|
11274
|
-
'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race'
|
|
11361
|
+
'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race', 'not', 'and', 'or'
|
|
11275
11362
|
];
|
|
11276
11363
|
}
|
|
11277
11364
|
|
|
@@ -11354,6 +11441,8 @@ class Lexer {
|
|
|
11354
11441
|
}
|
|
11355
11442
|
|
|
11356
11443
|
if (this.keywords.includes(result)) {
|
|
11444
|
+
if (result === 'and') return { type: 'AND', value: 'and', line: startLine, column: startCol };
|
|
11445
|
+
if (result === 'or') return { type: 'OR', value: 'or', line: startLine, column: startCol };
|
|
11357
11446
|
return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
|
|
11358
11447
|
}
|
|
11359
11448
|
|
|
@@ -11469,7 +11558,7 @@ module.exports = Lexer;
|
|
|
11469
11558
|
/***/ }),
|
|
11470
11559
|
|
|
11471
11560
|
/***/ 222:
|
|
11472
|
-
/***/ ((module) => {
|
|
11561
|
+
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
|
11473
11562
|
|
|
11474
11563
|
class ParseError extends Error {
|
|
11475
11564
|
constructor(message, token, source) {
|
|
@@ -11729,14 +11818,18 @@ ifStatement() {
|
|
|
11729
11818
|
test = this.expression();
|
|
11730
11819
|
}
|
|
11731
11820
|
|
|
11732
|
-
const consequent = this.
|
|
11821
|
+
const consequent = this.statementOrBlock();
|
|
11733
11822
|
|
|
11734
11823
|
let alternate = null;
|
|
11735
11824
|
if (this.current.type === 'ELSE') {
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11825
|
+
this.eat('ELSE');
|
|
11826
|
+
if (this.current.type === 'IF') {
|
|
11827
|
+
alternate = this.ifStatement();
|
|
11828
|
+
} else {
|
|
11829
|
+
alternate = this.statementOrBlock();
|
|
11739
11830
|
}
|
|
11831
|
+
}
|
|
11832
|
+
|
|
11740
11833
|
|
|
11741
11834
|
return {
|
|
11742
11835
|
type: 'IfStatement',
|
|
@@ -11747,18 +11840,51 @@ ifStatement() {
|
|
|
11747
11840
|
column: t.column
|
|
11748
11841
|
};
|
|
11749
11842
|
}
|
|
11843
|
+
parseExpressionOnly() {
|
|
11844
|
+
return this.expression();
|
|
11845
|
+
}
|
|
11750
11846
|
|
|
11751
11847
|
whileStatement() {
|
|
11752
11848
|
const t = this.current;
|
|
11753
11849
|
this.eat('WHILE');
|
|
11754
11850
|
|
|
11755
11851
|
let test;
|
|
11852
|
+
|
|
11756
11853
|
if (this.current.type === 'LPAREN') {
|
|
11757
11854
|
this.eat('LPAREN');
|
|
11758
11855
|
test = this.expression();
|
|
11759
11856
|
this.eat('RPAREN');
|
|
11760
|
-
}
|
|
11761
|
-
|
|
11857
|
+
}
|
|
11858
|
+
else {
|
|
11859
|
+
const startPos = this.pos;
|
|
11860
|
+
const startToken = this.current;
|
|
11861
|
+
|
|
11862
|
+
const exprTokens = [];
|
|
11863
|
+
let braceFound = false;
|
|
11864
|
+
let depth = 0;
|
|
11865
|
+
|
|
11866
|
+
while (this.current.type !== 'EOF') {
|
|
11867
|
+
if (this.current.type === 'LBRACE' && depth === 0) {
|
|
11868
|
+
braceFound = true;
|
|
11869
|
+
break;
|
|
11870
|
+
}
|
|
11871
|
+
if (this.current.type === 'LPAREN') depth++;
|
|
11872
|
+
if (this.current.type === 'RPAREN') depth--;
|
|
11873
|
+
exprTokens.push(this.current);
|
|
11874
|
+
this.advance();
|
|
11875
|
+
}
|
|
11876
|
+
|
|
11877
|
+
if (!braceFound) {
|
|
11878
|
+
throw new ParseError(
|
|
11879
|
+
"Expected '{' after while condition",
|
|
11880
|
+
this.current,
|
|
11881
|
+
this.source
|
|
11882
|
+
);
|
|
11883
|
+
}
|
|
11884
|
+
|
|
11885
|
+
const Parser = __nccwpck_require__(222);
|
|
11886
|
+
const exprParser = new Parser(exprTokens, this.source);
|
|
11887
|
+
test = exprParser.parseExpressionOnly();
|
|
11762
11888
|
}
|
|
11763
11889
|
|
|
11764
11890
|
const body = this.block();
|
|
@@ -11771,6 +11897,7 @@ whileStatement() {
|
|
|
11771
11897
|
};
|
|
11772
11898
|
}
|
|
11773
11899
|
|
|
11900
|
+
|
|
11774
11901
|
importStatement() {
|
|
11775
11902
|
const t = this.current;
|
|
11776
11903
|
this.eat('IMPORT');
|
|
@@ -11970,6 +12097,12 @@ returnStatement() {
|
|
|
11970
12097
|
|
|
11971
12098
|
return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
|
|
11972
12099
|
}
|
|
12100
|
+
statementOrBlock() {
|
|
12101
|
+
if (this.current.type === 'LBRACE') {
|
|
12102
|
+
return this.block();
|
|
12103
|
+
}
|
|
12104
|
+
return this.statement();
|
|
12105
|
+
}
|
|
11973
12106
|
|
|
11974
12107
|
block() {
|
|
11975
12108
|
const t = this.current; // LBRACE token
|
|
@@ -12179,13 +12312,7 @@ postfix() {
|
|
|
12179
12312
|
continue;
|
|
12180
12313
|
}
|
|
12181
12314
|
|
|
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
|
-
|
|
12315
|
+
|
|
12189
12316
|
break;
|
|
12190
12317
|
}
|
|
12191
12318
|
return node;
|
|
@@ -12339,21 +12466,56 @@ arrowFunction(params) {
|
|
|
12339
12466
|
}
|
|
12340
12467
|
|
|
12341
12468
|
if (t.type === 'LBRACE') {
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12469
|
+
const startLine = t.line;
|
|
12470
|
+
const startCol = t.column;
|
|
12471
|
+
this.eat('LBRACE');
|
|
12472
|
+
|
|
12473
|
+
const props = [];
|
|
12474
|
+
|
|
12475
|
+
while (this.current.type !== 'RBRACE') {
|
|
12476
|
+
let key;
|
|
12477
|
+
|
|
12478
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
12479
|
+
const k = this.current;
|
|
12480
|
+
this.eat('IDENTIFIER');
|
|
12481
|
+
key = {
|
|
12482
|
+
type: 'Literal',
|
|
12483
|
+
value: k.value,
|
|
12484
|
+
line: k.line,
|
|
12485
|
+
column: k.column
|
|
12486
|
+
};
|
|
12352
12487
|
}
|
|
12353
|
-
this.
|
|
12354
|
-
|
|
12488
|
+
else if (this.current.type === 'STRING') {
|
|
12489
|
+
const k = this.current;
|
|
12490
|
+
this.eat('STRING');
|
|
12491
|
+
key = {
|
|
12492
|
+
type: 'Literal',
|
|
12493
|
+
value: k.value,
|
|
12494
|
+
line: k.line,
|
|
12495
|
+
column: k.column
|
|
12496
|
+
};
|
|
12497
|
+
}
|
|
12498
|
+
else {
|
|
12499
|
+
throw new ParseError(
|
|
12500
|
+
'Invalid object key',
|
|
12501
|
+
this.current,
|
|
12502
|
+
this.source
|
|
12503
|
+
);
|
|
12504
|
+
}
|
|
12505
|
+
|
|
12506
|
+
this.eat('COLON');
|
|
12507
|
+
const value = this.expression();
|
|
12508
|
+
|
|
12509
|
+
props.push({ key, value });
|
|
12510
|
+
|
|
12511
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
12355
12512
|
}
|
|
12356
12513
|
|
|
12514
|
+
this.eat('RBRACE');
|
|
12515
|
+
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|
|
12516
|
+
}
|
|
12517
|
+
|
|
12518
|
+
|
|
12357
12519
|
throw new ParseError(
|
|
12358
12520
|
`Unexpected token '${t.type}'`,
|
|
12359
12521
|
t,
|
|
@@ -12544,7 +12706,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
12544
12706
|
const Parser = __nccwpck_require__(222);
|
|
12545
12707
|
const Evaluator = __nccwpck_require__(112);
|
|
12546
12708
|
|
|
12547
|
-
const VERSION = '1.1.
|
|
12709
|
+
const VERSION = '1.1.11';
|
|
12548
12710
|
|
|
12549
12711
|
const COLOR = {
|
|
12550
12712
|
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,90 @@ 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 (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return result;
|
|
204
|
+
this.global.define('isNaN', arg => {
|
|
205
|
+
return typeof arg !== 'number' || Number.isNaN(arg);
|
|
206
|
+
});
|
|
207
|
+
this.global.define('random', (min, max) => {
|
|
208
|
+
if (max === undefined) {
|
|
209
|
+
// Only one argument → random between 0 and min
|
|
210
|
+
return Math.floor(Math.random() * min);
|
|
211
|
+
}
|
|
212
|
+
min = Number(min);
|
|
213
|
+
max = Number(max);
|
|
214
|
+
if (isNaN(min) || isNaN(max)) return 0;
|
|
215
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
182
216
|
});
|
|
183
|
-
this.global.define('filter', async (array, fn) => {
|
|
184
|
-
if (!Array.isArray(array)) {
|
|
185
|
-
throw new RuntimeError('filter() expects an array', null, this.source);
|
|
186
|
-
}
|
|
187
|
-
if (typeof fn !== 'function') {
|
|
188
|
-
throw new RuntimeError('filter() expects a function', null, this.source);
|
|
189
|
-
}
|
|
190
217
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
result.push(array[i]);
|
|
218
|
+
this.global.define('map', async (array, fn) => {
|
|
219
|
+
if (!Array.isArray(array)) {
|
|
220
|
+
throw new RuntimeError('map() expects an array', null, evaluator.source);
|
|
195
221
|
}
|
|
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
222
|
|
|
207
|
-
|
|
208
|
-
|
|
223
|
+
const result = [];
|
|
224
|
+
for (let i = 0; i < array.length; i++) {
|
|
225
|
+
result.push(
|
|
226
|
+
await evaluator.callFunction(
|
|
227
|
+
fn,
|
|
228
|
+
[array[i], i, array],
|
|
229
|
+
evaluator.global
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
});
|
|
209
235
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (array.length === 0) {
|
|
214
|
-
throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
|
|
236
|
+
this.global.define('filter', async (array, fn) => {
|
|
237
|
+
if (!Array.isArray(array)) {
|
|
238
|
+
throw new RuntimeError('filter() expects an array', null, evaluator.source);
|
|
215
239
|
}
|
|
216
|
-
acc = array[0];
|
|
217
|
-
startIndex = 1;
|
|
218
|
-
}
|
|
219
240
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
241
|
+
const result = [];
|
|
242
|
+
for (let i = 0; i < array.length; i++) {
|
|
243
|
+
if (
|
|
244
|
+
await evaluator.callFunction(
|
|
245
|
+
fn,
|
|
246
|
+
[array[i], i, array],
|
|
247
|
+
evaluator.global
|
|
248
|
+
)
|
|
249
|
+
) {
|
|
250
|
+
result.push(array[i]);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
});
|
|
223
255
|
|
|
224
|
-
|
|
225
|
-
|
|
256
|
+
this.global.define('reduce', async (array, fn, initial) => {
|
|
257
|
+
if (!Array.isArray(array)) {
|
|
258
|
+
throw new RuntimeError('reduce() expects an array', null, evaluator.source);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let acc;
|
|
262
|
+
let i = 0;
|
|
263
|
+
|
|
264
|
+
if (initial !== undefined) {
|
|
265
|
+
acc = initial;
|
|
266
|
+
} else {
|
|
267
|
+
if (array.length === 0) {
|
|
268
|
+
throw new RuntimeError(
|
|
269
|
+
'reduce() of empty array with no initial value',
|
|
270
|
+
null,
|
|
271
|
+
evaluator.source
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
acc = array[0];
|
|
275
|
+
i = 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (; i < array.length; i++) {
|
|
279
|
+
acc = await evaluator.callFunction(
|
|
280
|
+
fn,
|
|
281
|
+
[acc, array[i], i, array],
|
|
282
|
+
evaluator.global
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return acc;
|
|
287
|
+
});
|
|
226
288
|
|
|
227
289
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
228
290
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
@@ -376,21 +438,30 @@ case 'RaceClause':
|
|
|
376
438
|
const callee = await this.evaluate(node.callee, env);
|
|
377
439
|
|
|
378
440
|
if (typeof callee === 'object' && callee.body) {
|
|
379
|
-
const evaluator = this;
|
|
380
|
-
|
|
441
|
+
const evaluator = this;
|
|
442
|
+
|
|
443
|
+
const Constructor = function (...args) {
|
|
381
444
|
const newEnv = new Environment(callee.env);
|
|
382
445
|
newEnv.define('this', this);
|
|
446
|
+
|
|
383
447
|
for (let i = 0; i < callee.params.length; i++) {
|
|
384
|
-
|
|
448
|
+
const param = callee.params[i];
|
|
449
|
+
const paramName = typeof param === 'string' ? param : param.name;
|
|
450
|
+
newEnv.define(paramName, args[i]);
|
|
385
451
|
}
|
|
386
|
-
|
|
452
|
+
|
|
453
|
+
return (async () => {
|
|
454
|
+
await evaluator.evaluate(callee.body, newEnv);
|
|
455
|
+
return this;
|
|
456
|
+
})();
|
|
387
457
|
};
|
|
388
458
|
|
|
389
459
|
const args = [];
|
|
390
460
|
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
391
|
-
return new Constructor(...args);
|
|
461
|
+
return await new Constructor(...args);
|
|
392
462
|
}
|
|
393
463
|
|
|
464
|
+
// native JS constructor fallback
|
|
394
465
|
if (typeof callee !== 'function') {
|
|
395
466
|
throw new RuntimeError('NewExpression callee is not a function', node, this.source);
|
|
396
467
|
}
|
|
@@ -612,27 +683,31 @@ evalArrowFunction(node, env) {
|
|
|
612
683
|
return async function (...args) {
|
|
613
684
|
const localEnv = new Environment(env);
|
|
614
685
|
|
|
686
|
+
// Bind parameters safely
|
|
615
687
|
node.params.forEach((p, i) => {
|
|
616
|
-
|
|
688
|
+
const paramName = typeof p === 'string' ? p : p.name;
|
|
689
|
+
localEnv.define(paramName, args[i]);
|
|
617
690
|
});
|
|
618
691
|
|
|
619
692
|
try {
|
|
620
693
|
if (node.isBlock) {
|
|
694
|
+
// Block body
|
|
621
695
|
const result = await evaluator.evaluate(node.body, localEnv);
|
|
622
|
-
return result
|
|
696
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
623
697
|
} else {
|
|
624
|
-
|
|
698
|
+
// Expression body
|
|
699
|
+
const result = await evaluator.evaluate(node.body, localEnv);
|
|
700
|
+
return result === undefined ? null : result; // ensure null instead of undefined
|
|
625
701
|
}
|
|
626
702
|
} catch (err) {
|
|
627
|
-
if (err instanceof ReturnValue)
|
|
628
|
-
return err.value;
|
|
629
|
-
}
|
|
703
|
+
if (err instanceof ReturnValue) return err.value === undefined ? null : err.value;
|
|
630
704
|
throw err;
|
|
631
705
|
}
|
|
632
706
|
};
|
|
633
707
|
}
|
|
634
708
|
|
|
635
709
|
|
|
710
|
+
|
|
636
711
|
async evalAssignment(node, env) {
|
|
637
712
|
const rightVal = await this.evaluate(node.right, env);
|
|
638
713
|
const left = node.left;
|
|
@@ -793,13 +868,10 @@ async evalUnary(node, env) {
|
|
|
793
868
|
|
|
794
869
|
}
|
|
795
870
|
}
|
|
796
|
-
|
|
797
871
|
async evalIf(node, env) {
|
|
798
|
-
|
|
872
|
+
let test = await this.evaluate(node.test, env);
|
|
799
873
|
|
|
800
|
-
|
|
801
|
-
throw new RuntimeError('If condition must evaluate to a boolean', node.test, this.source);
|
|
802
|
-
}
|
|
874
|
+
test = !!test;
|
|
803
875
|
|
|
804
876
|
if (test) {
|
|
805
877
|
return await this.evaluate(node.consequent, env);
|
|
@@ -812,27 +884,26 @@ async evalIf(node, env) {
|
|
|
812
884
|
return null;
|
|
813
885
|
}
|
|
814
886
|
|
|
815
|
-
|
|
816
887
|
async evalWhile(node, env) {
|
|
817
888
|
while (true) {
|
|
818
|
-
|
|
889
|
+
let test = await this.evaluate(node.test, env);
|
|
819
890
|
|
|
820
|
-
|
|
821
|
-
throw new RuntimeError('While condition must evaluate to a boolean', node.test, this.source);
|
|
822
|
-
}
|
|
891
|
+
test = !!test;
|
|
823
892
|
|
|
824
893
|
if (!test) break;
|
|
825
894
|
|
|
826
895
|
try {
|
|
827
896
|
await this.evaluate(node.body, env);
|
|
828
897
|
} catch (e) {
|
|
829
|
-
if (e instanceof BreakSignal) break;
|
|
830
|
-
if (e instanceof ContinueSignal) continue;
|
|
831
|
-
throw e;
|
|
898
|
+
if (e instanceof BreakSignal) break;
|
|
899
|
+
if (e instanceof ContinueSignal) continue;
|
|
900
|
+
throw e;
|
|
832
901
|
}
|
|
833
902
|
}
|
|
903
|
+
|
|
834
904
|
return null;
|
|
835
905
|
}
|
|
906
|
+
|
|
836
907
|
async evalFor(node, env) {
|
|
837
908
|
if (node.type === 'ForInStatement') {
|
|
838
909
|
const iterable = await this.evaluate(node.iterable, env);
|
|
@@ -973,24 +1044,40 @@ async evalIndex(node, env) {
|
|
|
973
1044
|
async evalObject(node, env) {
|
|
974
1045
|
const out = {};
|
|
975
1046
|
for (const p of node.props) {
|
|
976
|
-
if (!p.key
|
|
977
|
-
throw new RuntimeError('
|
|
1047
|
+
if (!p.key) {
|
|
1048
|
+
throw new RuntimeError('Object property must have a key', node, this.source);
|
|
978
1049
|
}
|
|
1050
|
+
|
|
979
1051
|
const key = await this.evaluate(p.key, env);
|
|
980
|
-
|
|
981
|
-
|
|
1052
|
+
let value = null;
|
|
1053
|
+
|
|
1054
|
+
if (p.value) {
|
|
1055
|
+
value = await this.evaluate(p.value, env);
|
|
1056
|
+
if (value === undefined) value = null; // <- force null instead of undefined
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
out[key] = value;
|
|
982
1060
|
}
|
|
983
1061
|
return out;
|
|
984
1062
|
}
|
|
985
1063
|
|
|
986
1064
|
|
|
987
1065
|
|
|
1066
|
+
|
|
988
1067
|
async evalMember(node, env) {
|
|
989
1068
|
const obj = await this.evaluate(node.object, env);
|
|
990
1069
|
|
|
991
|
-
if (obj == null)
|
|
1070
|
+
if (obj == null) {
|
|
1071
|
+
throw new RuntimeError('Member access of null or undefined', node, this.source);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const prop = obj[node.property];
|
|
1075
|
+
|
|
1076
|
+
if (typeof prop === 'function') {
|
|
1077
|
+
return prop.bind(obj);
|
|
1078
|
+
}
|
|
992
1079
|
|
|
993
|
-
return
|
|
1080
|
+
return prop;
|
|
994
1081
|
}
|
|
995
1082
|
|
|
996
1083
|
|
|
@@ -1024,4 +1111,4 @@ async evalUpdate(node, env) {
|
|
|
1024
1111
|
|
|
1025
1112
|
}
|
|
1026
1113
|
|
|
1027
|
-
module.exports = Evaluator;
|
|
1114
|
+
module.exports = Evaluator;
|
package/src/lexer.js
CHANGED
|
@@ -28,7 +28,7 @@ class Lexer {
|
|
|
28
28
|
'break', 'continue', 'func', 'return',
|
|
29
29
|
'true', 'false', 'null',
|
|
30
30
|
'ask', 'define', 'import', 'from', 'as',
|
|
31
|
-
'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race'
|
|
31
|
+
'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race', 'not', 'and', 'or'
|
|
32
32
|
];
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -111,6 +111,8 @@ class Lexer {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (this.keywords.includes(result)) {
|
|
114
|
+
if (result === 'and') return { type: 'AND', value: 'and', line: startLine, column: startCol };
|
|
115
|
+
if (result === 'or') return { type: 'OR', value: 'or', line: startLine, column: startCol };
|
|
114
116
|
return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
|
|
115
117
|
}
|
|
116
118
|
|
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',
|
|
@@ -274,18 +278,51 @@ ifStatement() {
|
|
|
274
278
|
column: t.column
|
|
275
279
|
};
|
|
276
280
|
}
|
|
281
|
+
parseExpressionOnly() {
|
|
282
|
+
return this.expression();
|
|
283
|
+
}
|
|
277
284
|
|
|
278
285
|
whileStatement() {
|
|
279
286
|
const t = this.current;
|
|
280
287
|
this.eat('WHILE');
|
|
281
288
|
|
|
282
289
|
let test;
|
|
290
|
+
|
|
283
291
|
if (this.current.type === 'LPAREN') {
|
|
284
292
|
this.eat('LPAREN');
|
|
285
293
|
test = this.expression();
|
|
286
294
|
this.eat('RPAREN');
|
|
287
|
-
}
|
|
288
|
-
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const startPos = this.pos;
|
|
298
|
+
const startToken = this.current;
|
|
299
|
+
|
|
300
|
+
const exprTokens = [];
|
|
301
|
+
let braceFound = false;
|
|
302
|
+
let depth = 0;
|
|
303
|
+
|
|
304
|
+
while (this.current.type !== 'EOF') {
|
|
305
|
+
if (this.current.type === 'LBRACE' && depth === 0) {
|
|
306
|
+
braceFound = true;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
if (this.current.type === 'LPAREN') depth++;
|
|
310
|
+
if (this.current.type === 'RPAREN') depth--;
|
|
311
|
+
exprTokens.push(this.current);
|
|
312
|
+
this.advance();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!braceFound) {
|
|
316
|
+
throw new ParseError(
|
|
317
|
+
"Expected '{' after while condition",
|
|
318
|
+
this.current,
|
|
319
|
+
this.source
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const Parser = require('./parser');
|
|
324
|
+
const exprParser = new Parser(exprTokens, this.source);
|
|
325
|
+
test = exprParser.parseExpressionOnly();
|
|
289
326
|
}
|
|
290
327
|
|
|
291
328
|
const body = this.block();
|
|
@@ -298,6 +335,7 @@ whileStatement() {
|
|
|
298
335
|
};
|
|
299
336
|
}
|
|
300
337
|
|
|
338
|
+
|
|
301
339
|
importStatement() {
|
|
302
340
|
const t = this.current;
|
|
303
341
|
this.eat('IMPORT');
|
|
@@ -497,6 +535,12 @@ returnStatement() {
|
|
|
497
535
|
|
|
498
536
|
return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
|
|
499
537
|
}
|
|
538
|
+
statementOrBlock() {
|
|
539
|
+
if (this.current.type === 'LBRACE') {
|
|
540
|
+
return this.block();
|
|
541
|
+
}
|
|
542
|
+
return this.statement();
|
|
543
|
+
}
|
|
500
544
|
|
|
501
545
|
block() {
|
|
502
546
|
const t = this.current; // LBRACE token
|
|
@@ -706,13 +750,7 @@ postfix() {
|
|
|
706
750
|
continue;
|
|
707
751
|
}
|
|
708
752
|
|
|
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
|
-
|
|
753
|
+
|
|
716
754
|
break;
|
|
717
755
|
}
|
|
718
756
|
return node;
|
|
@@ -866,21 +904,56 @@ arrowFunction(params) {
|
|
|
866
904
|
}
|
|
867
905
|
|
|
868
906
|
if (t.type === 'LBRACE') {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
907
|
+
const startLine = t.line;
|
|
908
|
+
const startCol = t.column;
|
|
909
|
+
this.eat('LBRACE');
|
|
910
|
+
|
|
911
|
+
const props = [];
|
|
912
|
+
|
|
913
|
+
while (this.current.type !== 'RBRACE') {
|
|
914
|
+
let key;
|
|
915
|
+
|
|
916
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
917
|
+
const k = this.current;
|
|
918
|
+
this.eat('IDENTIFIER');
|
|
919
|
+
key = {
|
|
920
|
+
type: 'Literal',
|
|
921
|
+
value: k.value,
|
|
922
|
+
line: k.line,
|
|
923
|
+
column: k.column
|
|
924
|
+
};
|
|
879
925
|
}
|
|
880
|
-
this.
|
|
881
|
-
|
|
926
|
+
else if (this.current.type === 'STRING') {
|
|
927
|
+
const k = this.current;
|
|
928
|
+
this.eat('STRING');
|
|
929
|
+
key = {
|
|
930
|
+
type: 'Literal',
|
|
931
|
+
value: k.value,
|
|
932
|
+
line: k.line,
|
|
933
|
+
column: k.column
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
throw new ParseError(
|
|
938
|
+
'Invalid object key',
|
|
939
|
+
this.current,
|
|
940
|
+
this.source
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
this.eat('COLON');
|
|
945
|
+
const value = this.expression();
|
|
946
|
+
|
|
947
|
+
props.push({ key, value });
|
|
948
|
+
|
|
949
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
882
950
|
}
|
|
883
951
|
|
|
952
|
+
this.eat('RBRACE');
|
|
953
|
+
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
|
|
884
957
|
throw new ParseError(
|
|
885
958
|
`Unexpected token '${t.type}'`,
|
|
886
959
|
t,
|