starlight-cli 1.1.15 → 1.1.17
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 +608 -16
- package/package.json +1 -1
- package/src/evaluator.js +465 -3
- package/src/parser.js +142 -12
- package/src/starlight.js +1 -1
package/package.json
CHANGED
package/src/evaluator.js
CHANGED
|
@@ -3,7 +3,6 @@ const fs = require('fs');
|
|
|
3
3
|
const Lexer = require('./lexer');
|
|
4
4
|
const Parser = require('./parser');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
|
|
7
6
|
class ReturnValue {
|
|
8
7
|
constructor(value) { this.value = value; }
|
|
9
8
|
}
|
|
@@ -204,7 +203,51 @@ formatValue(value, seen = new Set()) {
|
|
|
204
203
|
if (arg && typeof arg === 'object') return Object.keys(arg).length;
|
|
205
204
|
return 0;
|
|
206
205
|
});
|
|
206
|
+
this.global.define('lower', arg => {
|
|
207
|
+
if (typeof arg !== 'string') return String(arg).toLowerCase();
|
|
208
|
+
return arg.toLowerCase();
|
|
209
|
+
});
|
|
210
|
+
this.global.define('toStr', arg => String(arg));
|
|
211
|
+
|
|
212
|
+
this.global.define('upper', arg => {
|
|
213
|
+
if (typeof arg !== 'string') return String(arg).toUpperCase();
|
|
214
|
+
return arg.toUpperCase();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
this.global.define('trim', arg => {
|
|
218
|
+
if (typeof arg !== 'string') return String(arg).trim();
|
|
219
|
+
return arg.trim();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
this.global.define('startsWith', (str, prefix) => {
|
|
223
|
+
if (typeof str !== 'string') str = String(str);
|
|
224
|
+
if (typeof prefix !== 'string') prefix = String(prefix);
|
|
225
|
+
return str.startsWith(prefix);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this.global.define('endsWith', (str, suffix) => {
|
|
229
|
+
if (typeof str !== 'string') str = String(str);
|
|
230
|
+
if (typeof suffix !== 'string') suffix = String(suffix);
|
|
231
|
+
return str.endsWith(suffix);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
this.global.define('includes', (str, substr) => {
|
|
235
|
+
if (typeof str !== 'string') str = String(str);
|
|
236
|
+
if (typeof substr !== 'string') substr = String(substr);
|
|
237
|
+
return str.includes(substr);
|
|
238
|
+
});
|
|
207
239
|
|
|
240
|
+
this.global.define('repeat', (str, times) => {
|
|
241
|
+
if (typeof str !== 'string') str = String(str);
|
|
242
|
+
return str.repeat(Number(times) || 0);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
this.global.define('replace', (str, search, replacement) => {
|
|
246
|
+
if (typeof str !== 'string') str = String(str);
|
|
247
|
+
if (typeof search !== 'string') search = String(search);
|
|
248
|
+
if (typeof replacement !== 'string') replacement = String(replacement);
|
|
249
|
+
return str.split(search).join(replacement);
|
|
250
|
+
});
|
|
208
251
|
this.global.define('print', arg => { console.log(arg); return null; });
|
|
209
252
|
this.global.define('type', arg => {
|
|
210
253
|
if (Array.isArray(arg)) return 'array';
|
|
@@ -353,6 +396,72 @@ this.global.define('range', (...args) => {
|
|
|
353
396
|
|
|
354
397
|
return result;
|
|
355
398
|
});
|
|
399
|
+
this.global.define('floor', arg => Math.floor(Number(arg)));
|
|
400
|
+
this.global.define('ceil', arg => Math.ceil(Number(arg)));
|
|
401
|
+
this.global.define('round', arg => Math.round(Number(arg)));
|
|
402
|
+
this.global.define('abs', arg => Math.abs(Number(arg)));
|
|
403
|
+
this.global.define('pow', (base, exp) => Math.pow(Number(base), Number(exp)));
|
|
404
|
+
this.global.define('sqrt', arg => Math.sqrt(Number(arg)));
|
|
405
|
+
this.global.define('min', (...args) => Math.min(...args.map(Number)));
|
|
406
|
+
this.global.define('max', (...args) => Math.max(...args.map(Number)));
|
|
407
|
+
this.global.define('randomFloat', (min, max) => {
|
|
408
|
+
min = Number(min); max = Number(max);
|
|
409
|
+
if (isNaN(min) || isNaN(max)) return 0;
|
|
410
|
+
return Math.random() * (max - min) + min;
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
this.global.define('push', (arr, val) => {
|
|
414
|
+
if (!Array.isArray(arr)) throw new RuntimeError('push() expects an array', null, this.source);
|
|
415
|
+
arr.push(val); return arr.length;
|
|
416
|
+
});
|
|
417
|
+
this.global.define('pop', arr => {
|
|
418
|
+
if (!Array.isArray(arr)) throw new RuntimeError('pop() expects an array', null, this.source);
|
|
419
|
+
return arr.pop();
|
|
420
|
+
});
|
|
421
|
+
this.global.define('shift', arr => {
|
|
422
|
+
if (!Array.isArray(arr)) throw new RuntimeError('shift() expects an array', null, this.source);
|
|
423
|
+
return arr.shift();
|
|
424
|
+
});
|
|
425
|
+
this.global.define('unshift', (arr, val) => {
|
|
426
|
+
if (!Array.isArray(arr)) throw new RuntimeError('unshift() expects an array', null, this.source);
|
|
427
|
+
arr.unshift(val); return arr.length;
|
|
428
|
+
});
|
|
429
|
+
this.global.define('sort', (arr, fn) => {
|
|
430
|
+
if (!Array.isArray(arr)) throw new RuntimeError('sort() expects an array', null, this.source);
|
|
431
|
+
if (fn && typeof fn === 'function') return arr.sort(fn);
|
|
432
|
+
return arr.sort();
|
|
433
|
+
});
|
|
434
|
+
this.global.define('reverse', arr => {
|
|
435
|
+
if (!Array.isArray(arr)) throw new RuntimeError('reverse() expects an array', null, this.source);
|
|
436
|
+
return arr.reverse();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
this.global.define('has', (obj, key) => obj && typeof obj === 'object' ? obj.hasOwnProperty(key) : false);
|
|
440
|
+
this.global.define('merge', (obj1, obj2) => {
|
|
441
|
+
if (!obj1 || typeof obj1 !== 'object') obj1 = {};
|
|
442
|
+
if (!obj2 || typeof obj2 !== 'object') obj2 = {};
|
|
443
|
+
return { ...obj1, ...obj2 };
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
this.global.define('uuid', () => {
|
|
447
|
+
// simple random UUID v4
|
|
448
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
449
|
+
const r = Math.random() * 16 | 0;
|
|
450
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
451
|
+
return v.toString(16);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
this.global.define('timestamp', () => Date.now());
|
|
455
|
+
this.global.define('clone', arg => {
|
|
456
|
+
if (Array.isArray(arg)) return [...arg];
|
|
457
|
+
if (arg && typeof arg === 'object') return { ...arg };
|
|
458
|
+
return arg; // primitive types
|
|
459
|
+
});
|
|
460
|
+
this.global.define('typeOf', arg => {
|
|
461
|
+
if (arg === null) return 'null';
|
|
462
|
+
if (Array.isArray(arg)) return 'array';
|
|
463
|
+
return typeof arg;
|
|
464
|
+
});
|
|
356
465
|
|
|
357
466
|
this.global.define('ask', prompt => {
|
|
358
467
|
const readlineSync = require('readline-sync');
|
|
@@ -409,6 +518,271 @@ this.global.define('sleep', async (ms) => {
|
|
|
409
518
|
this.global.define('str', arg => {
|
|
410
519
|
return String(arg);
|
|
411
520
|
});
|
|
521
|
+
this.global.define('now', () => Date.now());
|
|
522
|
+
this.global.define('formatDate', (timestamp, locale = 'en-US', options = {}) => {
|
|
523
|
+
if (timestamp === null || timestamp === undefined) timestamp = Date.now();
|
|
524
|
+
const t = typeof timestamp === 'number' ? timestamp : Number(timestamp);
|
|
525
|
+
if (isNaN(t)) t = Date.now();
|
|
526
|
+
return new Date(t).toLocaleString(locale, options);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
this.global.define('readFile', path => {
|
|
530
|
+
if (typeof path !== 'string') path = String(path);
|
|
531
|
+
try {
|
|
532
|
+
return fs.readFileSync(path, 'utf-8');
|
|
533
|
+
} catch (e) {
|
|
534
|
+
throw new RuntimeError('readFile error: ' + e.message, null, this.source);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
this.global.define('writeFile', (path, content) => {
|
|
538
|
+
if (typeof path !== 'string') path = String(path);
|
|
539
|
+
if (typeof content !== 'string') content = String(content);
|
|
540
|
+
try {
|
|
541
|
+
fs.writeFileSync(path, content, 'utf-8');
|
|
542
|
+
return true;
|
|
543
|
+
} catch (e) {
|
|
544
|
+
throw new RuntimeError('writeFile error: ' + e.message, null, this.source);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
this.global.define('split', (str, separator) => {
|
|
549
|
+
if (typeof str !== 'string') str = String(str);
|
|
550
|
+
if (separator === undefined) separator = '';
|
|
551
|
+
return str.split(separator);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
this.global.define('join', (arr, separator = '') => {
|
|
555
|
+
if (!Array.isArray(arr)) throw new RuntimeError('join() expects an array', null, this.source);
|
|
556
|
+
return arr.join(separator);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
this.global.define('substring', (str, start, end) => {
|
|
560
|
+
if (typeof str !== 'string') str = String(str);
|
|
561
|
+
start = Number(start) || 0;
|
|
562
|
+
end = end !== undefined ? Number(end) : undefined;
|
|
563
|
+
return str.substring(start, end);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
this.global.define('padStart', (str, targetLength, padString = ' ') => {
|
|
567
|
+
if (typeof str !== 'string') str = String(str);
|
|
568
|
+
targetLength = Number(targetLength) || 0;
|
|
569
|
+
return str.padStart(targetLength, padString);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
this.global.define('padEnd', (str, targetLength, padString = ' ') => {
|
|
573
|
+
if (typeof str !== 'string') str = String(str);
|
|
574
|
+
targetLength = Number(targetLength) || 0;
|
|
575
|
+
return str.padEnd(targetLength, padString);
|
|
576
|
+
});
|
|
577
|
+
this.global.define('unique', arr => {
|
|
578
|
+
if (!Array.isArray(arr)) throw new RuntimeError('unique() expects an array', null, this.source);
|
|
579
|
+
return [...new Set(arr)];
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
this.global.define('indexOf', (arr, val) => {
|
|
583
|
+
if (!Array.isArray(arr)) throw new RuntimeError('indexOf() expects an array', null, this.source);
|
|
584
|
+
return arr.indexOf(val);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
this.global.define('includesArr', (arr, val) => {
|
|
588
|
+
if (!Array.isArray(arr)) throw new RuntimeError('includesArr() expects an array', null, this.source);
|
|
589
|
+
return arr.includes(val);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
this.global.define('flatten', arr => {
|
|
593
|
+
if (!Array.isArray(arr)) throw new RuntimeError('flatten() expects an array', null, this.source);
|
|
594
|
+
return arr.flat(Infinity);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
this.global.define('randomChoice', arr => {
|
|
598
|
+
if (!Array.isArray(arr)) throw new RuntimeError('randomChoice() expects an array', null, this.source);
|
|
599
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
this.global.define('entries', obj => {
|
|
603
|
+
if (!obj || typeof obj !== 'object') throw new RuntimeError('entries() expects an object', null, this.source);
|
|
604
|
+
return Object.entries(obj);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
this.global.define('invert', obj => {
|
|
608
|
+
if (!obj || typeof obj !== 'object') throw new RuntimeError('invert() expects an object', null, this.source);
|
|
609
|
+
const result = {};
|
|
610
|
+
for (const key in obj) result[obj[key]] = key;
|
|
611
|
+
return result;
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
this.global.define('isEmpty', arg => {
|
|
615
|
+
if (arg == null) return true;
|
|
616
|
+
if (Array.isArray(arg) || typeof arg === 'string') return arg.length === 0;
|
|
617
|
+
if (typeof arg === 'object') return Object.keys(arg).length === 0;
|
|
618
|
+
return false;
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
this.global.define('deepClone', arg => {
|
|
622
|
+
return JSON.parse(JSON.stringify(arg));
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
this.global.define('capitalize', str => {
|
|
626
|
+
if (typeof str !== 'string') str = String(str);
|
|
627
|
+
if (str.length === 0) return '';
|
|
628
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
this.global.define('reverseStr', str => {
|
|
632
|
+
if (typeof str !== 'string') str = String(str);
|
|
633
|
+
return str.split('').reverse().join('');
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
this.global.define('trimStart', str => {
|
|
637
|
+
if (typeof str !== 'string') str = String(str);
|
|
638
|
+
return str.trimStart();
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
this.global.define('trimEnd', str => {
|
|
642
|
+
if (typeof str !== 'string') str = String(str);
|
|
643
|
+
return str.trimEnd();
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
this.global.define('clamp', (value, min, max) => {
|
|
647
|
+
value = Number(value); min = Number(min); max = Number(max);
|
|
648
|
+
return Math.min(Math.max(value, min), max);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
this.global.define('sign', value => {
|
|
652
|
+
value = Number(value);
|
|
653
|
+
return Math.sign(value);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
this.global.define('appendFile', (path, content) => {
|
|
657
|
+
if (typeof path !== 'string') path = String(path);
|
|
658
|
+
if (typeof content !== 'string') content = String(content);
|
|
659
|
+
try {
|
|
660
|
+
fs.appendFileSync(path, content, 'utf-8');
|
|
661
|
+
return true;
|
|
662
|
+
} catch (e) {
|
|
663
|
+
throw new RuntimeError('appendFile error: ' + e.message, null, this.source);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
this.global.define('exists', path => {
|
|
668
|
+
if (typeof path !== 'string') path = String(path);
|
|
669
|
+
return fs.existsSync(path);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
this.global.define('mkdir', path => {
|
|
673
|
+
if (typeof path !== 'string') path = String(path);
|
|
674
|
+
if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true });
|
|
675
|
+
return true;
|
|
676
|
+
});
|
|
677
|
+
this.global.define('count', (arr, value) => {
|
|
678
|
+
if (!Array.isArray(arr)) throw new RuntimeError('count() expects an array', null, this.source);
|
|
679
|
+
return arr.filter(x => x === value).length;
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
this.global.define('uniqueBy', (arr, fn) => {
|
|
683
|
+
if (!Array.isArray(arr)) throw new RuntimeError('uniqueBy() expects an array', null, this.source);
|
|
684
|
+
const seen = new Set();
|
|
685
|
+
const result = [];
|
|
686
|
+
for (const item of arr) {
|
|
687
|
+
const key = fn ? fn(item) : item;
|
|
688
|
+
if (!seen.has(key)) {
|
|
689
|
+
seen.add(key);
|
|
690
|
+
result.push(item);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return result;
|
|
694
|
+
});
|
|
695
|
+
this.global.define('getProp', (obj, key, defaultValue = null) => {
|
|
696
|
+
if (!obj || typeof obj !== 'object') return defaultValue;
|
|
697
|
+
const keys = key.split('.');
|
|
698
|
+
let current = obj;
|
|
699
|
+
for (const k of keys) {
|
|
700
|
+
if (current[k] === undefined) return defaultValue;
|
|
701
|
+
current = current[k];
|
|
702
|
+
}
|
|
703
|
+
return current;
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
this.global.define('setProp', (obj, key, value) => {
|
|
707
|
+
if (!obj || typeof obj !== 'object') throw new RuntimeError('setProp() expects an object', null, this.source);
|
|
708
|
+
const keys = key.split('.');
|
|
709
|
+
let current = obj;
|
|
710
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
711
|
+
if (!current[keys[i]] || typeof current[keys[i]] !== 'object') current[keys[i]] = {};
|
|
712
|
+
current = current[keys[i]];
|
|
713
|
+
}
|
|
714
|
+
current[keys[keys.length - 1]] = value;
|
|
715
|
+
return obj;
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
this.global.define('mergeDeep', (obj1, obj2) => {
|
|
719
|
+
const isObject = val => val && typeof val === 'object';
|
|
720
|
+
const result = {...obj1};
|
|
721
|
+
for (const key in obj2) {
|
|
722
|
+
if (isObject(obj2[key]) && isObject(result[key])) {
|
|
723
|
+
result[key] = this.global.get('mergeDeep')(result[key], obj2[key]);
|
|
724
|
+
} else {
|
|
725
|
+
result[key] = obj2[key];
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return result;
|
|
729
|
+
});
|
|
730
|
+
this.global.define('camelCase', str => {
|
|
731
|
+
if (typeof str !== 'string') str = String(str);
|
|
732
|
+
return str
|
|
733
|
+
.replace(/[-_ ]+./g, s => s.charAt(s.length - 1).toUpperCase())
|
|
734
|
+
.replace(/^[A-Z]/, s => s.toLowerCase());
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
this.global.define('kebabCase', str => {
|
|
738
|
+
if (typeof str !== 'string') str = String(str);
|
|
739
|
+
return str
|
|
740
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
741
|
+
.replace(/\s+/g, '-')
|
|
742
|
+
.replace(/_+/g, '-')
|
|
743
|
+
.toLowerCase();
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
this.global.define('repeatStr', (str, times) => {
|
|
747
|
+
if (typeof str !== 'string') str = String(str);
|
|
748
|
+
return str.repeat(Number(times) || 0);
|
|
749
|
+
});
|
|
750
|
+
this.global.define('randomInt', (min, max) => {
|
|
751
|
+
min = Number(min); max = Number(max);
|
|
752
|
+
if (isNaN(min) || isNaN(max)) return 0;
|
|
753
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
this.global.define('lerp', (a, b, t) => {
|
|
757
|
+
a = Number(a); b = Number(b); t = Number(t);
|
|
758
|
+
return a + (b - a) * t;
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
this.global.define('degToRad', deg => Number(deg) * (Math.PI / 180));
|
|
762
|
+
this.global.define('radToDeg', rad => Number(rad) * (180 / Math.PI));
|
|
763
|
+
this.global.define('readJSON', path => {
|
|
764
|
+
const content = this.global.get('readFile')(path);
|
|
765
|
+
return JSON.parse(content);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
this.global.define('writeJSON', (path, obj) => {
|
|
769
|
+
const content = JSON.stringify(obj, null, 2);
|
|
770
|
+
return this.global.get('writeFile')(path, content);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
this.global.define('deleteFile', path => {
|
|
774
|
+
if (typeof path !== 'string') path = String(path);
|
|
775
|
+
if (!fs.existsSync(path)) return false;
|
|
776
|
+
fs.unlinkSync(path);
|
|
777
|
+
return true;
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
this.global.define('rmdir', path => {
|
|
781
|
+
if (typeof path !== 'string') path = String(path);
|
|
782
|
+
if (!fs.existsSync(path)) return false;
|
|
783
|
+
fs.rmSync(path, { recursive: true, force: true });
|
|
784
|
+
return true;
|
|
785
|
+
});
|
|
412
786
|
|
|
413
787
|
}
|
|
414
788
|
|
|
@@ -422,10 +796,17 @@ async evaluate(node, env = this.global) {
|
|
|
422
796
|
case 'SldeployStatement': return await this.evalSldeploy(node, env);
|
|
423
797
|
case 'AskStatement': return await this.evalAsk(node, env);
|
|
424
798
|
case 'DefineStatement': return await this.evalDefine(node, env);
|
|
799
|
+
case 'FunctionExpression':
|
|
800
|
+
return this.evalFunctionExpression(node, env);
|
|
801
|
+
case 'SliceExpression':
|
|
802
|
+
return await this.evalSlice(node, env);
|
|
803
|
+
|
|
425
804
|
case 'ExpressionStatement': return await this.evaluate(node.expression, env);
|
|
426
805
|
case 'BinaryExpression': return await this.evalBinary(node, env);
|
|
427
806
|
case 'LogicalExpression': return await this.evalLogical(node, env);
|
|
428
807
|
case 'UnaryExpression': return await this.evalUnary(node, env);
|
|
808
|
+
case 'ConditionalExpression':
|
|
809
|
+
return await this.evalConditional(node, env);
|
|
429
810
|
case 'Literal': return node.value;
|
|
430
811
|
case 'Identifier': return env.get(node.name, node, this.source);
|
|
431
812
|
case 'IfStatement': return await this.evalIf(node, env);
|
|
@@ -528,6 +909,36 @@ async evalProgram(node, env) {
|
|
|
528
909
|
}
|
|
529
910
|
return result;
|
|
530
911
|
}
|
|
912
|
+
async evalSlice(node, env) {
|
|
913
|
+
try {
|
|
914
|
+
const arr = await this.evaluate(node.object, env);
|
|
915
|
+
const start = node.start ? await this.evaluate(node.start, env) : 0;
|
|
916
|
+
const end = node.end ? await this.evaluate(node.end, env) : arr.length;
|
|
917
|
+
|
|
918
|
+
if (!Array.isArray(arr)) {
|
|
919
|
+
throw new RuntimeError(
|
|
920
|
+
'Slice target must be an array',
|
|
921
|
+
node,
|
|
922
|
+
this.source,
|
|
923
|
+
env
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const s = start < 0 ? Math.max(arr.length + start, 0) : Math.min(start, arr.length);
|
|
928
|
+
const e = end < 0 ? Math.max(arr.length + end, 0) : Math.min(end, arr.length);
|
|
929
|
+
|
|
930
|
+
return arr.slice(s, e);
|
|
931
|
+
|
|
932
|
+
} catch (err) {
|
|
933
|
+
if (err instanceof RuntimeError) throw err;
|
|
934
|
+
throw new RuntimeError(
|
|
935
|
+
err.message || 'Error evaluating slice expression',
|
|
936
|
+
node,
|
|
937
|
+
this.source,
|
|
938
|
+
env
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
531
942
|
|
|
532
943
|
async evalStartStatement(node, env) {
|
|
533
944
|
try {
|
|
@@ -576,6 +987,24 @@ async evalStartStatement(node, env) {
|
|
|
576
987
|
);
|
|
577
988
|
}
|
|
578
989
|
}
|
|
990
|
+
async evalConditional(node, env) {
|
|
991
|
+
try {
|
|
992
|
+
const test = await this.evaluate(node.test, env);
|
|
993
|
+
if (test) {
|
|
994
|
+
return await this.evaluate(node.consequent, env);
|
|
995
|
+
} else {
|
|
996
|
+
return await this.evaluate(node.alternate, env);
|
|
997
|
+
}
|
|
998
|
+
} catch (e) {
|
|
999
|
+
if (e instanceof RuntimeError) throw e;
|
|
1000
|
+
throw new RuntimeError(
|
|
1001
|
+
e.message || 'Error evaluating conditional expression',
|
|
1002
|
+
node,
|
|
1003
|
+
this.source,
|
|
1004
|
+
env
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
579
1008
|
|
|
580
1009
|
async evalRaceClause(node, env) {
|
|
581
1010
|
try {
|
|
@@ -1186,6 +1615,41 @@ async evalFor(node, env) {
|
|
|
1186
1615
|
);
|
|
1187
1616
|
}
|
|
1188
1617
|
}
|
|
1618
|
+
evalFunctionExpression(node, env) {
|
|
1619
|
+
if (!node.body || !Array.isArray(node.params)) {
|
|
1620
|
+
throw new RuntimeError(
|
|
1621
|
+
'Invalid function expression',
|
|
1622
|
+
node,
|
|
1623
|
+
this.source,
|
|
1624
|
+
env
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
const evaluator = this;
|
|
1629
|
+
|
|
1630
|
+
const fn = async function (...args) {
|
|
1631
|
+
const localEnv = new Environment(env);
|
|
1632
|
+
|
|
1633
|
+
for (let i = 0; i < node.params.length; i++) {
|
|
1634
|
+
const param = node.params[i];
|
|
1635
|
+
const paramName = typeof param === 'string' ? param : param.name;
|
|
1636
|
+
localEnv.define(paramName, args[i]);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
try {
|
|
1640
|
+
const result = await evaluator.evaluate(node.body, localEnv);
|
|
1641
|
+
return result === undefined ? null : result;
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
if (e instanceof ReturnValue) return e.value === undefined ? null : e.value;
|
|
1644
|
+
throw e;
|
|
1645
|
+
}
|
|
1646
|
+
};
|
|
1647
|
+
fn.params = node.params;
|
|
1648
|
+
fn.body = node.body;
|
|
1649
|
+
fn.env = env;
|
|
1650
|
+
|
|
1651
|
+
return fn;
|
|
1652
|
+
}
|
|
1189
1653
|
|
|
1190
1654
|
evalFunctionDeclaration(node, env) {
|
|
1191
1655
|
try {
|
|
@@ -1273,7 +1737,6 @@ async evalCall(node, env) {
|
|
|
1273
1737
|
);
|
|
1274
1738
|
}
|
|
1275
1739
|
}
|
|
1276
|
-
|
|
1277
1740
|
async evalIndex(node, env) {
|
|
1278
1741
|
try {
|
|
1279
1742
|
const obj = await this.evaluate(node.object, env);
|
|
@@ -1306,7 +1769,6 @@ async evalIndex(node, env) {
|
|
|
1306
1769
|
);
|
|
1307
1770
|
}
|
|
1308
1771
|
}
|
|
1309
|
-
|
|
1310
1772
|
async evalObject(node, env) {
|
|
1311
1773
|
try {
|
|
1312
1774
|
const out = {};
|