starlight-cli 1.1.17 → 1.1.19
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 +187 -152
- package/package.json +1 -1
- package/src/evaluator.js +126 -11
- package/src/parser.js +58 -139
- package/src/starlight.js +2 -2
package/dist/index.js
CHANGED
|
@@ -10466,6 +10466,8 @@ this.global.define('toStr', arg => String(arg));
|
|
|
10466
10466
|
this.global.define('isNaN', arg => {
|
|
10467
10467
|
return typeof arg !== 'number' || Number.isNaN(arg);
|
|
10468
10468
|
});
|
|
10469
|
+
|
|
10470
|
+
|
|
10469
10471
|
this.global.define('random', (min, max) => {
|
|
10470
10472
|
if (max === undefined) {
|
|
10471
10473
|
// Only one argument → random between 0 and min
|
|
@@ -10515,7 +10517,9 @@ this.global.define('encodeURLComponent', arg => {
|
|
|
10515
10517
|
if (arg === null || arg === undefined) return '';
|
|
10516
10518
|
return encodeURIComponent(String(arg));
|
|
10517
10519
|
});
|
|
10518
|
-
|
|
10520
|
+
this.global.define("Date", Date);
|
|
10521
|
+
this.global.define("Math", Math);
|
|
10522
|
+
this.global.define("String", String);
|
|
10519
10523
|
this.global.define('filter', async (array, fn) => {
|
|
10520
10524
|
if (!Array.isArray(array)) {
|
|
10521
10525
|
throw new RuntimeError('filter() expects an array', null, evaluator.source);
|
|
@@ -10568,6 +10572,9 @@ this.global.define('encodeURLComponent', arg => {
|
|
|
10568
10572
|
|
|
10569
10573
|
return acc;
|
|
10570
10574
|
});
|
|
10575
|
+
this.global.define('hasOwn', (obj, prop) => {
|
|
10576
|
+
return Object.prototype.hasOwnProperty.call(obj, prop)
|
|
10577
|
+
});
|
|
10571
10578
|
|
|
10572
10579
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
10573
10580
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
@@ -11122,8 +11129,6 @@ async evalProgram(node, env) {
|
|
|
11122
11129
|
async evalSlice(node, env) {
|
|
11123
11130
|
try {
|
|
11124
11131
|
const arr = await this.evaluate(node.object, env);
|
|
11125
|
-
const start = node.start ? await this.evaluate(node.start, env) : 0;
|
|
11126
|
-
const end = node.end ? await this.evaluate(node.end, env) : arr.length;
|
|
11127
11132
|
|
|
11128
11133
|
if (!Array.isArray(arr)) {
|
|
11129
11134
|
throw new RuntimeError(
|
|
@@ -11134,10 +11139,37 @@ async evalSlice(node, env) {
|
|
|
11134
11139
|
);
|
|
11135
11140
|
}
|
|
11136
11141
|
|
|
11137
|
-
|
|
11138
|
-
|
|
11142
|
+
let start = node.start ? await this.evaluate(node.start, env) : 0;
|
|
11143
|
+
let end = node.end ? await this.evaluate(node.end, env) : arr.length;
|
|
11144
|
+
let step = node.step ? await this.evaluate(node.step, env) : 1;
|
|
11139
11145
|
|
|
11140
|
-
|
|
11146
|
+
if (step === 0) {
|
|
11147
|
+
throw new RuntimeError(
|
|
11148
|
+
'Slice step cannot be zero',
|
|
11149
|
+
node,
|
|
11150
|
+
this.source,
|
|
11151
|
+
env
|
|
11152
|
+
);
|
|
11153
|
+
}
|
|
11154
|
+
start = start < 0 ? arr.length + start : start;
|
|
11155
|
+
end = end < 0 ? arr.length + end : end;
|
|
11156
|
+
|
|
11157
|
+
start = Math.min(Math.max(start, 0), arr.length);
|
|
11158
|
+
end = Math.min(Math.max(end, 0), arr.length);
|
|
11159
|
+
|
|
11160
|
+
const result = [];
|
|
11161
|
+
|
|
11162
|
+
if (step > 0) {
|
|
11163
|
+
for (let i = start; i < end; i += step) {
|
|
11164
|
+
result.push(arr[i]);
|
|
11165
|
+
}
|
|
11166
|
+
} else {
|
|
11167
|
+
for (let i = start; i > end; i += step) {
|
|
11168
|
+
result.push(arr[i]);
|
|
11169
|
+
}
|
|
11170
|
+
}
|
|
11171
|
+
|
|
11172
|
+
return result;
|
|
11141
11173
|
|
|
11142
11174
|
} catch (err) {
|
|
11143
11175
|
if (err instanceof RuntimeError) throw err;
|
|
@@ -11150,6 +11182,7 @@ async evalSlice(node, env) {
|
|
|
11150
11182
|
}
|
|
11151
11183
|
}
|
|
11152
11184
|
|
|
11185
|
+
|
|
11153
11186
|
async evalStartStatement(node, env) {
|
|
11154
11187
|
try {
|
|
11155
11188
|
const value = await this.evaluate(node.discriminant, env);
|
|
@@ -11674,12 +11707,49 @@ async evalLogical(node, env) {
|
|
|
11674
11707
|
|
|
11675
11708
|
async evalUnary(node, env) {
|
|
11676
11709
|
try {
|
|
11710
|
+
// Handle prefix update operators
|
|
11711
|
+
if (node.type === 'UpdateExpression' && node.prefix) {
|
|
11712
|
+
if (node.argument.type !== 'Identifier') {
|
|
11713
|
+
throw new RuntimeError(
|
|
11714
|
+
`Invalid operand for update operator: ${node.operator}`,
|
|
11715
|
+
node,
|
|
11716
|
+
this.source,
|
|
11717
|
+
env
|
|
11718
|
+
);
|
|
11719
|
+
}
|
|
11720
|
+
|
|
11721
|
+
const varName = node.argument.name;
|
|
11722
|
+
let currentVal = await env.get(varName); // assuming env.get returns the variable value
|
|
11723
|
+
|
|
11724
|
+
switch (node.operator) {
|
|
11725
|
+
case 'PLUSPLUS':
|
|
11726
|
+
currentVal++;
|
|
11727
|
+
await env.set(varName, currentVal);
|
|
11728
|
+
return currentVal;
|
|
11729
|
+
case 'MINUSMINUS':
|
|
11730
|
+
currentVal--;
|
|
11731
|
+
await env.set(varName, currentVal);
|
|
11732
|
+
return currentVal;
|
|
11733
|
+
default:
|
|
11734
|
+
throw new RuntimeError(
|
|
11735
|
+
`Unknown update operator: ${node.operator}`,
|
|
11736
|
+
node,
|
|
11737
|
+
this.source,
|
|
11738
|
+
env
|
|
11739
|
+
);
|
|
11740
|
+
}
|
|
11741
|
+
}
|
|
11742
|
+
|
|
11743
|
+
// Evaluate the argument first
|
|
11677
11744
|
const val = await this.evaluate(node.argument, env);
|
|
11678
11745
|
|
|
11679
11746
|
switch (node.operator) {
|
|
11680
|
-
case 'NOT':
|
|
11681
|
-
|
|
11682
|
-
case '
|
|
11747
|
+
case 'NOT':
|
|
11748
|
+
return !val;
|
|
11749
|
+
case 'MINUS':
|
|
11750
|
+
return -val;
|
|
11751
|
+
case 'PLUS':
|
|
11752
|
+
return +val;
|
|
11683
11753
|
default:
|
|
11684
11754
|
throw new RuntimeError(
|
|
11685
11755
|
`Unknown unary operator: ${node.operator}`,
|
|
@@ -11688,7 +11758,6 @@ async evalUnary(node, env) {
|
|
|
11688
11758
|
env
|
|
11689
11759
|
);
|
|
11690
11760
|
}
|
|
11691
|
-
|
|
11692
11761
|
} catch (e) {
|
|
11693
11762
|
if (e instanceof RuntimeError) throw e;
|
|
11694
11763
|
throw new RuntimeError(
|
|
@@ -11700,6 +11769,51 @@ async evalUnary(node, env) {
|
|
|
11700
11769
|
}
|
|
11701
11770
|
}
|
|
11702
11771
|
|
|
11772
|
+
// Handle postfix updates (x++ or x--)
|
|
11773
|
+
async evalPostfixUpdate(node, env) {
|
|
11774
|
+
if (node.type !== 'UpdateExpression' || node.prefix) {
|
|
11775
|
+
throw new RuntimeError(
|
|
11776
|
+
`Invalid postfix update node`,
|
|
11777
|
+
node,
|
|
11778
|
+
this.source,
|
|
11779
|
+
env
|
|
11780
|
+
);
|
|
11781
|
+
}
|
|
11782
|
+
|
|
11783
|
+
if (node.argument.type !== 'Identifier') {
|
|
11784
|
+
throw new RuntimeError(
|
|
11785
|
+
`Invalid operand for postfix update operator: ${node.operator}`,
|
|
11786
|
+
node,
|
|
11787
|
+
this.source,
|
|
11788
|
+
env
|
|
11789
|
+
);
|
|
11790
|
+
}
|
|
11791
|
+
|
|
11792
|
+
const varName = node.argument.name;
|
|
11793
|
+
let currentVal = await env.get(varName);
|
|
11794
|
+
|
|
11795
|
+
let returnVal = currentVal;
|
|
11796
|
+
|
|
11797
|
+
switch (node.operator) {
|
|
11798
|
+
case 'PLUSPLUS':
|
|
11799
|
+
currentVal++;
|
|
11800
|
+
await env.set(varName, currentVal);
|
|
11801
|
+
return returnVal; // return original value
|
|
11802
|
+
case 'MINUSMINUS':
|
|
11803
|
+
currentVal--;
|
|
11804
|
+
await env.set(varName, currentVal);
|
|
11805
|
+
return returnVal; // return original value
|
|
11806
|
+
default:
|
|
11807
|
+
throw new RuntimeError(
|
|
11808
|
+
`Unknown postfix operator: ${node.operator}`,
|
|
11809
|
+
node,
|
|
11810
|
+
this.source,
|
|
11811
|
+
env
|
|
11812
|
+
);
|
|
11813
|
+
}
|
|
11814
|
+
}
|
|
11815
|
+
|
|
11816
|
+
|
|
11703
11817
|
async evalIf(node, env) {
|
|
11704
11818
|
let test = await this.evaluate(node.test, env);
|
|
11705
11819
|
test = !!test; // coerce to boolean
|
|
@@ -11978,7 +12092,8 @@ async evalIndex(node, env) {
|
|
|
11978
12092
|
env
|
|
11979
12093
|
);
|
|
11980
12094
|
}
|
|
11981
|
-
}
|
|
12095
|
+
}
|
|
12096
|
+
|
|
11982
12097
|
async evalObject(node, env) {
|
|
11983
12098
|
try {
|
|
11984
12099
|
const out = {};
|
|
@@ -13617,192 +13732,111 @@ unary() {
|
|
|
13617
13732
|
|
|
13618
13733
|
return this.postfix();
|
|
13619
13734
|
}
|
|
13735
|
+
|
|
13620
13736
|
postfix() {
|
|
13621
13737
|
let node = this.primary();
|
|
13622
13738
|
|
|
13623
13739
|
while (true) {
|
|
13624
13740
|
const t = this.current;
|
|
13625
13741
|
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
let start = null;
|
|
13632
|
-
let end = null;
|
|
13633
|
-
let step = null;
|
|
13634
|
-
|
|
13635
|
-
if (this.current.type === 'COLON') {
|
|
13636
|
-
this.eat('COLON');
|
|
13637
|
-
|
|
13638
|
-
if (this.current.type !== 'RBRACKET' && this.current.type !== 'COLON') {
|
|
13639
|
-
end = this.expression();
|
|
13640
|
-
}
|
|
13641
|
-
|
|
13642
|
-
if (this.current.type === 'COLON') {
|
|
13643
|
-
this.eat('COLON');
|
|
13644
|
-
if (this.current.type !== 'RBRACKET') {
|
|
13645
|
-
step = this.expression();
|
|
13646
|
-
}
|
|
13647
|
-
}
|
|
13648
|
-
|
|
13649
|
-
if (this.current.type !== 'RBRACKET') {
|
|
13650
|
-
throw new ParseError(
|
|
13651
|
-
"Expected ']' after slice",
|
|
13652
|
-
this.current,
|
|
13653
|
-
this.source
|
|
13654
|
-
);
|
|
13655
|
-
}
|
|
13656
|
-
|
|
13657
|
-
this.eat('RBRACKET');
|
|
13658
|
-
|
|
13659
|
-
node = {
|
|
13660
|
-
type: 'SliceExpression',
|
|
13661
|
-
object: node,
|
|
13662
|
-
start,
|
|
13663
|
-
end,
|
|
13664
|
-
step,
|
|
13665
|
-
line: startLine,
|
|
13666
|
-
column: startCol
|
|
13667
|
-
};
|
|
13668
|
-
|
|
13669
|
-
} else {
|
|
13670
|
-
start = this.expression();
|
|
13671
|
-
|
|
13672
|
-
if (this.current.type === 'COLON') {
|
|
13673
|
-
this.eat('COLON');
|
|
13742
|
+
// Handle indexing / slicing
|
|
13743
|
+
if (t.type === 'LBRACKET') {
|
|
13744
|
+
const startLine = t.line;
|
|
13745
|
+
const startCol = t.column;
|
|
13746
|
+
this.eat('LBRACKET');
|
|
13674
13747
|
|
|
13675
|
-
|
|
13676
|
-
|
|
13677
|
-
|
|
13748
|
+
let start = null;
|
|
13749
|
+
let end = null;
|
|
13750
|
+
let step = null;
|
|
13678
13751
|
|
|
13752
|
+
// Slice: [:end:step] or [start:end:step]
|
|
13679
13753
|
if (this.current.type === 'COLON') {
|
|
13680
13754
|
this.eat('COLON');
|
|
13755
|
+
if (this.current.type !== 'RBRACKET' && this.current.type !== 'COLON') {
|
|
13756
|
+
end = this.expression();
|
|
13757
|
+
}
|
|
13758
|
+
if (this.current.type === 'COLON') {
|
|
13759
|
+
this.eat('COLON');
|
|
13760
|
+
if (this.current.type !== 'RBRACKET') {
|
|
13761
|
+
step = this.expression();
|
|
13762
|
+
}
|
|
13763
|
+
}
|
|
13681
13764
|
if (this.current.type !== 'RBRACKET') {
|
|
13682
|
-
|
|
13765
|
+
throw new ParseError("Expected ']' after slice", this.current, this.source);
|
|
13683
13766
|
}
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
if (this.current.type !== 'RBRACKET') {
|
|
13687
|
-
throw new ParseError(
|
|
13688
|
-
"Expected ']' after slice",
|
|
13689
|
-
this.current,
|
|
13690
|
-
this.source
|
|
13691
|
-
);
|
|
13692
|
-
}
|
|
13693
|
-
|
|
13694
|
-
this.eat('RBRACKET');
|
|
13767
|
+
this.eat('RBRACKET');
|
|
13695
13768
|
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
start
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13769
|
+
node = { type: 'SliceExpression', object: node, start, end, step, line: startLine, column: startCol };
|
|
13770
|
+
} else {
|
|
13771
|
+
// Normal index or slice starting with an expression
|
|
13772
|
+
start = this.expression();
|
|
13773
|
+
if (this.current.type === 'COLON') {
|
|
13774
|
+
this.eat('COLON');
|
|
13775
|
+
if (this.current.type !== 'RBRACKET' && this.current.type !== 'COLON') {
|
|
13776
|
+
end = this.expression();
|
|
13777
|
+
}
|
|
13778
|
+
if (this.current.type === 'COLON') {
|
|
13779
|
+
this.eat('COLON');
|
|
13780
|
+
if (this.current.type !== 'RBRACKET') {
|
|
13781
|
+
step = this.expression();
|
|
13782
|
+
}
|
|
13783
|
+
}
|
|
13784
|
+
if (this.current.type !== 'RBRACKET') {
|
|
13785
|
+
throw new ParseError("Expected ']' after slice", this.current, this.source);
|
|
13786
|
+
}
|
|
13787
|
+
this.eat('RBRACKET');
|
|
13705
13788
|
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
column: startCol
|
|
13714
|
-
};
|
|
13789
|
+
node = { type: 'SliceExpression', object: node, start, end, step, line: startLine, column: startCol };
|
|
13790
|
+
} else {
|
|
13791
|
+
this.eat('RBRACKET');
|
|
13792
|
+
node = { type: 'IndexExpression', object: node, indexer: start, line: startLine, column: startCol };
|
|
13793
|
+
}
|
|
13794
|
+
}
|
|
13795
|
+
continue; // continue to allow . or () after []
|
|
13715
13796
|
}
|
|
13716
|
-
}
|
|
13717
|
-
}
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
13797
|
|
|
13798
|
+
// Handle function call
|
|
13721
13799
|
if (t.type === 'LPAREN') {
|
|
13722
13800
|
const startLine = t.line;
|
|
13723
13801
|
const startCol = t.column;
|
|
13724
13802
|
this.eat('LPAREN');
|
|
13725
13803
|
|
|
13726
13804
|
const args = [];
|
|
13727
|
-
|
|
13728
13805
|
while (this.current.type !== 'RPAREN') {
|
|
13729
13806
|
if (this.current.type === 'EOF') {
|
|
13730
|
-
throw new ParseError(
|
|
13731
|
-
"Unterminated function call",
|
|
13732
|
-
this.current,
|
|
13733
|
-
this.source,
|
|
13734
|
-
"Did you forget to close ')'?"
|
|
13735
|
-
);
|
|
13807
|
+
throw new ParseError("Unterminated function call", this.current, this.source, "Did you forget to close ')'?");
|
|
13736
13808
|
}
|
|
13737
|
-
|
|
13738
13809
|
args.push(this.expression());
|
|
13739
|
-
|
|
13740
|
-
if (this.current.type === 'COMMA') {
|
|
13741
|
-
this.eat('COMMA');
|
|
13742
|
-
} else {
|
|
13743
|
-
break;
|
|
13744
|
-
}
|
|
13745
|
-
}
|
|
13746
|
-
|
|
13747
|
-
if (this.current.type !== 'RPAREN') {
|
|
13748
|
-
throw new ParseError(
|
|
13749
|
-
"Expected ')' after function arguments",
|
|
13750
|
-
this.current,
|
|
13751
|
-
this.source,
|
|
13752
|
-
"Function calls must be closed with ')'"
|
|
13753
|
-
);
|
|
13810
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
13754
13811
|
}
|
|
13755
13812
|
|
|
13756
13813
|
this.eat('RPAREN');
|
|
13757
13814
|
|
|
13758
|
-
node = {
|
|
13759
|
-
type: 'CallExpression',
|
|
13760
|
-
callee: node,
|
|
13761
|
-
arguments: args,
|
|
13762
|
-
line: startLine,
|
|
13763
|
-
column: startCol
|
|
13764
|
-
};
|
|
13815
|
+
node = { type: 'CallExpression', callee: node, arguments: args, line: startLine, column: startCol };
|
|
13765
13816
|
continue;
|
|
13766
13817
|
}
|
|
13767
13818
|
|
|
13819
|
+
// Handle member access
|
|
13768
13820
|
if (t.type === 'DOT') {
|
|
13769
13821
|
const startLine = t.line;
|
|
13770
13822
|
const startCol = t.column;
|
|
13771
13823
|
this.eat('DOT');
|
|
13772
13824
|
|
|
13773
13825
|
if (this.current.type !== 'IDENTIFIER') {
|
|
13774
|
-
throw new ParseError(
|
|
13775
|
-
"Expected property name after '.'",
|
|
13776
|
-
this.current,
|
|
13777
|
-
this.source,
|
|
13778
|
-
"Member access requires a property name, e.g. obj.value"
|
|
13779
|
-
);
|
|
13826
|
+
throw new ParseError("Expected property name after '.'", this.current, this.source, "Member access requires a property name, e.g. obj.value");
|
|
13780
13827
|
}
|
|
13781
13828
|
|
|
13782
13829
|
const property = this.current.value;
|
|
13783
13830
|
this.eat('IDENTIFIER');
|
|
13784
13831
|
|
|
13785
|
-
node = {
|
|
13786
|
-
type: 'MemberExpression',
|
|
13787
|
-
object: node,
|
|
13788
|
-
property,
|
|
13789
|
-
line: startLine,
|
|
13790
|
-
column: startCol
|
|
13791
|
-
};
|
|
13832
|
+
node = { type: 'MemberExpression', object: node, property, line: startLine, column: startCol };
|
|
13792
13833
|
continue;
|
|
13793
13834
|
}
|
|
13794
13835
|
|
|
13836
|
+
// Handle postfix update operators
|
|
13795
13837
|
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
13796
13838
|
this.eat(t.type);
|
|
13797
|
-
|
|
13798
|
-
node = {
|
|
13799
|
-
type: 'UpdateExpression',
|
|
13800
|
-
operator: t.type,
|
|
13801
|
-
argument: node,
|
|
13802
|
-
prefix: false,
|
|
13803
|
-
line: t.line,
|
|
13804
|
-
column: t.column
|
|
13805
|
-
};
|
|
13839
|
+
node = { type: 'UpdateExpression', operator: t.type, argument: node, prefix: false, line: t.line, column: t.column };
|
|
13806
13840
|
continue;
|
|
13807
13841
|
}
|
|
13808
13842
|
|
|
@@ -13812,6 +13846,7 @@ postfix() {
|
|
|
13812
13846
|
return node;
|
|
13813
13847
|
}
|
|
13814
13848
|
|
|
13849
|
+
|
|
13815
13850
|
arrowFunction(params) {
|
|
13816
13851
|
const t = this.current;
|
|
13817
13852
|
this.eat('ARROW');
|
|
@@ -14162,8 +14197,8 @@ if (t.type === 'FUNC') {
|
|
|
14162
14197
|
}
|
|
14163
14198
|
|
|
14164
14199
|
}
|
|
14165
|
-
|
|
14166
|
-
|
|
14200
|
+
module.exports = Parser;
|
|
14201
|
+
|
|
14167
14202
|
|
|
14168
14203
|
/***/ }),
|
|
14169
14204
|
|
|
@@ -14343,7 +14378,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
14343
14378
|
const Parser = __nccwpck_require__(222);
|
|
14344
14379
|
const Evaluator = __nccwpck_require__(112);
|
|
14345
14380
|
|
|
14346
|
-
const VERSION = '1.1.
|
|
14381
|
+
const VERSION = '1.1.19';
|
|
14347
14382
|
|
|
14348
14383
|
const COLOR = {
|
|
14349
14384
|
reset: '\x1b[0m',
|
|
@@ -14503,7 +14538,7 @@ Usage:
|
|
|
14503
14538
|
|
|
14504
14539
|
if (args[0] === '-v' || args[0] === '--version') {
|
|
14505
14540
|
console.log(`${COLOR.bold}Starlight CLI v${VERSION}${COLOR.reset}`);
|
|
14506
|
-
console.log(`${COLOR.magenta}Developed by Macedon${COLOR.reset}`);
|
|
14541
|
+
console.log(`${COLOR.magenta}Developed by Dominex Macedon${COLOR.reset}`);
|
|
14507
14542
|
process.exit(0);
|
|
14508
14543
|
}
|
|
14509
14544
|
|
package/package.json
CHANGED
package/src/evaluator.js
CHANGED
|
@@ -256,6 +256,8 @@ this.global.define('toStr', arg => String(arg));
|
|
|
256
256
|
this.global.define('isNaN', arg => {
|
|
257
257
|
return typeof arg !== 'number' || Number.isNaN(arg);
|
|
258
258
|
});
|
|
259
|
+
|
|
260
|
+
|
|
259
261
|
this.global.define('random', (min, max) => {
|
|
260
262
|
if (max === undefined) {
|
|
261
263
|
// Only one argument → random between 0 and min
|
|
@@ -305,7 +307,9 @@ this.global.define('encodeURLComponent', arg => {
|
|
|
305
307
|
if (arg === null || arg === undefined) return '';
|
|
306
308
|
return encodeURIComponent(String(arg));
|
|
307
309
|
});
|
|
308
|
-
|
|
310
|
+
this.global.define("Date", Date);
|
|
311
|
+
this.global.define("Math", Math);
|
|
312
|
+
this.global.define("String", String);
|
|
309
313
|
this.global.define('filter', async (array, fn) => {
|
|
310
314
|
if (!Array.isArray(array)) {
|
|
311
315
|
throw new RuntimeError('filter() expects an array', null, evaluator.source);
|
|
@@ -358,6 +362,9 @@ this.global.define('encodeURLComponent', arg => {
|
|
|
358
362
|
|
|
359
363
|
return acc;
|
|
360
364
|
});
|
|
365
|
+
this.global.define('hasOwn', (obj, prop) => {
|
|
366
|
+
return Object.prototype.hasOwnProperty.call(obj, prop)
|
|
367
|
+
});
|
|
361
368
|
|
|
362
369
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
363
370
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
@@ -912,8 +919,6 @@ async evalProgram(node, env) {
|
|
|
912
919
|
async evalSlice(node, env) {
|
|
913
920
|
try {
|
|
914
921
|
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
922
|
|
|
918
923
|
if (!Array.isArray(arr)) {
|
|
919
924
|
throw new RuntimeError(
|
|
@@ -924,10 +929,37 @@ async evalSlice(node, env) {
|
|
|
924
929
|
);
|
|
925
930
|
}
|
|
926
931
|
|
|
927
|
-
|
|
928
|
-
|
|
932
|
+
let start = node.start ? await this.evaluate(node.start, env) : 0;
|
|
933
|
+
let end = node.end ? await this.evaluate(node.end, env) : arr.length;
|
|
934
|
+
let step = node.step ? await this.evaluate(node.step, env) : 1;
|
|
935
|
+
|
|
936
|
+
if (step === 0) {
|
|
937
|
+
throw new RuntimeError(
|
|
938
|
+
'Slice step cannot be zero',
|
|
939
|
+
node,
|
|
940
|
+
this.source,
|
|
941
|
+
env
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
start = start < 0 ? arr.length + start : start;
|
|
945
|
+
end = end < 0 ? arr.length + end : end;
|
|
946
|
+
|
|
947
|
+
start = Math.min(Math.max(start, 0), arr.length);
|
|
948
|
+
end = Math.min(Math.max(end, 0), arr.length);
|
|
929
949
|
|
|
930
|
-
|
|
950
|
+
const result = [];
|
|
951
|
+
|
|
952
|
+
if (step > 0) {
|
|
953
|
+
for (let i = start; i < end; i += step) {
|
|
954
|
+
result.push(arr[i]);
|
|
955
|
+
}
|
|
956
|
+
} else {
|
|
957
|
+
for (let i = start; i > end; i += step) {
|
|
958
|
+
result.push(arr[i]);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
return result;
|
|
931
963
|
|
|
932
964
|
} catch (err) {
|
|
933
965
|
if (err instanceof RuntimeError) throw err;
|
|
@@ -940,6 +972,7 @@ async evalSlice(node, env) {
|
|
|
940
972
|
}
|
|
941
973
|
}
|
|
942
974
|
|
|
975
|
+
|
|
943
976
|
async evalStartStatement(node, env) {
|
|
944
977
|
try {
|
|
945
978
|
const value = await this.evaluate(node.discriminant, env);
|
|
@@ -1464,12 +1497,49 @@ async evalLogical(node, env) {
|
|
|
1464
1497
|
|
|
1465
1498
|
async evalUnary(node, env) {
|
|
1466
1499
|
try {
|
|
1500
|
+
// Handle prefix update operators
|
|
1501
|
+
if (node.type === 'UpdateExpression' && node.prefix) {
|
|
1502
|
+
if (node.argument.type !== 'Identifier') {
|
|
1503
|
+
throw new RuntimeError(
|
|
1504
|
+
`Invalid operand for update operator: ${node.operator}`,
|
|
1505
|
+
node,
|
|
1506
|
+
this.source,
|
|
1507
|
+
env
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
const varName = node.argument.name;
|
|
1512
|
+
let currentVal = await env.get(varName); // assuming env.get returns the variable value
|
|
1513
|
+
|
|
1514
|
+
switch (node.operator) {
|
|
1515
|
+
case 'PLUSPLUS':
|
|
1516
|
+
currentVal++;
|
|
1517
|
+
await env.set(varName, currentVal);
|
|
1518
|
+
return currentVal;
|
|
1519
|
+
case 'MINUSMINUS':
|
|
1520
|
+
currentVal--;
|
|
1521
|
+
await env.set(varName, currentVal);
|
|
1522
|
+
return currentVal;
|
|
1523
|
+
default:
|
|
1524
|
+
throw new RuntimeError(
|
|
1525
|
+
`Unknown update operator: ${node.operator}`,
|
|
1526
|
+
node,
|
|
1527
|
+
this.source,
|
|
1528
|
+
env
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// Evaluate the argument first
|
|
1467
1534
|
const val = await this.evaluate(node.argument, env);
|
|
1468
1535
|
|
|
1469
1536
|
switch (node.operator) {
|
|
1470
|
-
case 'NOT':
|
|
1471
|
-
|
|
1472
|
-
case '
|
|
1537
|
+
case 'NOT':
|
|
1538
|
+
return !val;
|
|
1539
|
+
case 'MINUS':
|
|
1540
|
+
return -val;
|
|
1541
|
+
case 'PLUS':
|
|
1542
|
+
return +val;
|
|
1473
1543
|
default:
|
|
1474
1544
|
throw new RuntimeError(
|
|
1475
1545
|
`Unknown unary operator: ${node.operator}`,
|
|
@@ -1478,7 +1548,6 @@ async evalUnary(node, env) {
|
|
|
1478
1548
|
env
|
|
1479
1549
|
);
|
|
1480
1550
|
}
|
|
1481
|
-
|
|
1482
1551
|
} catch (e) {
|
|
1483
1552
|
if (e instanceof RuntimeError) throw e;
|
|
1484
1553
|
throw new RuntimeError(
|
|
@@ -1490,6 +1559,51 @@ async evalUnary(node, env) {
|
|
|
1490
1559
|
}
|
|
1491
1560
|
}
|
|
1492
1561
|
|
|
1562
|
+
// Handle postfix updates (x++ or x--)
|
|
1563
|
+
async evalPostfixUpdate(node, env) {
|
|
1564
|
+
if (node.type !== 'UpdateExpression' || node.prefix) {
|
|
1565
|
+
throw new RuntimeError(
|
|
1566
|
+
`Invalid postfix update node`,
|
|
1567
|
+
node,
|
|
1568
|
+
this.source,
|
|
1569
|
+
env
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
if (node.argument.type !== 'Identifier') {
|
|
1574
|
+
throw new RuntimeError(
|
|
1575
|
+
`Invalid operand for postfix update operator: ${node.operator}`,
|
|
1576
|
+
node,
|
|
1577
|
+
this.source,
|
|
1578
|
+
env
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
const varName = node.argument.name;
|
|
1583
|
+
let currentVal = await env.get(varName);
|
|
1584
|
+
|
|
1585
|
+
let returnVal = currentVal;
|
|
1586
|
+
|
|
1587
|
+
switch (node.operator) {
|
|
1588
|
+
case 'PLUSPLUS':
|
|
1589
|
+
currentVal++;
|
|
1590
|
+
await env.set(varName, currentVal);
|
|
1591
|
+
return returnVal; // return original value
|
|
1592
|
+
case 'MINUSMINUS':
|
|
1593
|
+
currentVal--;
|
|
1594
|
+
await env.set(varName, currentVal);
|
|
1595
|
+
return returnVal; // return original value
|
|
1596
|
+
default:
|
|
1597
|
+
throw new RuntimeError(
|
|
1598
|
+
`Unknown postfix operator: ${node.operator}`,
|
|
1599
|
+
node,
|
|
1600
|
+
this.source,
|
|
1601
|
+
env
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
|
|
1493
1607
|
async evalIf(node, env) {
|
|
1494
1608
|
let test = await this.evaluate(node.test, env);
|
|
1495
1609
|
test = !!test; // coerce to boolean
|
|
@@ -1768,7 +1882,8 @@ async evalIndex(node, env) {
|
|
|
1768
1882
|
env
|
|
1769
1883
|
);
|
|
1770
1884
|
}
|
|
1771
|
-
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1772
1887
|
async evalObject(node, env) {
|
|
1773
1888
|
try {
|
|
1774
1889
|
const out = {};
|
package/src/parser.js
CHANGED
|
@@ -1286,192 +1286,111 @@ unary() {
|
|
|
1286
1286
|
|
|
1287
1287
|
return this.postfix();
|
|
1288
1288
|
}
|
|
1289
|
+
|
|
1289
1290
|
postfix() {
|
|
1290
1291
|
let node = this.primary();
|
|
1291
1292
|
|
|
1292
1293
|
while (true) {
|
|
1293
1294
|
const t = this.current;
|
|
1294
1295
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
let start = null;
|
|
1301
|
-
let end = null;
|
|
1302
|
-
let step = null;
|
|
1303
|
-
|
|
1304
|
-
if (this.current.type === 'COLON') {
|
|
1305
|
-
this.eat('COLON');
|
|
1306
|
-
|
|
1307
|
-
if (this.current.type !== 'RBRACKET' && this.current.type !== 'COLON') {
|
|
1308
|
-
end = this.expression();
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
if (this.current.type === 'COLON') {
|
|
1312
|
-
this.eat('COLON');
|
|
1313
|
-
if (this.current.type !== 'RBRACKET') {
|
|
1314
|
-
step = this.expression();
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
if (this.current.type !== 'RBRACKET') {
|
|
1319
|
-
throw new ParseError(
|
|
1320
|
-
"Expected ']' after slice",
|
|
1321
|
-
this.current,
|
|
1322
|
-
this.source
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
this.eat('RBRACKET');
|
|
1327
|
-
|
|
1328
|
-
node = {
|
|
1329
|
-
type: 'SliceExpression',
|
|
1330
|
-
object: node,
|
|
1331
|
-
start,
|
|
1332
|
-
end,
|
|
1333
|
-
step,
|
|
1334
|
-
line: startLine,
|
|
1335
|
-
column: startCol
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
} else {
|
|
1339
|
-
start = this.expression();
|
|
1340
|
-
|
|
1341
|
-
if (this.current.type === 'COLON') {
|
|
1342
|
-
this.eat('COLON');
|
|
1296
|
+
// Handle indexing / slicing
|
|
1297
|
+
if (t.type === 'LBRACKET') {
|
|
1298
|
+
const startLine = t.line;
|
|
1299
|
+
const startCol = t.column;
|
|
1300
|
+
this.eat('LBRACKET');
|
|
1343
1301
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1302
|
+
let start = null;
|
|
1303
|
+
let end = null;
|
|
1304
|
+
let step = null;
|
|
1347
1305
|
|
|
1306
|
+
// Slice: [:end:step] or [start:end:step]
|
|
1348
1307
|
if (this.current.type === 'COLON') {
|
|
1349
1308
|
this.eat('COLON');
|
|
1309
|
+
if (this.current.type !== 'RBRACKET' && this.current.type !== 'COLON') {
|
|
1310
|
+
end = this.expression();
|
|
1311
|
+
}
|
|
1312
|
+
if (this.current.type === 'COLON') {
|
|
1313
|
+
this.eat('COLON');
|
|
1314
|
+
if (this.current.type !== 'RBRACKET') {
|
|
1315
|
+
step = this.expression();
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1350
1318
|
if (this.current.type !== 'RBRACKET') {
|
|
1351
|
-
|
|
1319
|
+
throw new ParseError("Expected ']' after slice", this.current, this.source);
|
|
1352
1320
|
}
|
|
1353
|
-
|
|
1321
|
+
this.eat('RBRACKET');
|
|
1322
|
+
|
|
1323
|
+
node = { type: 'SliceExpression', object: node, start, end, step, line: startLine, column: startCol };
|
|
1324
|
+
} else {
|
|
1325
|
+
// Normal index or slice starting with an expression
|
|
1326
|
+
start = this.expression();
|
|
1327
|
+
if (this.current.type === 'COLON') {
|
|
1328
|
+
this.eat('COLON');
|
|
1329
|
+
if (this.current.type !== 'RBRACKET' && this.current.type !== 'COLON') {
|
|
1330
|
+
end = this.expression();
|
|
1331
|
+
}
|
|
1332
|
+
if (this.current.type === 'COLON') {
|
|
1333
|
+
this.eat('COLON');
|
|
1334
|
+
if (this.current.type !== 'RBRACKET') {
|
|
1335
|
+
step = this.expression();
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
if (this.current.type !== 'RBRACKET') {
|
|
1339
|
+
throw new ParseError("Expected ']' after slice", this.current, this.source);
|
|
1340
|
+
}
|
|
1341
|
+
this.eat('RBRACKET');
|
|
1354
1342
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
);
|
|
1343
|
+
node = { type: 'SliceExpression', object: node, start, end, step, line: startLine, column: startCol };
|
|
1344
|
+
} else {
|
|
1345
|
+
this.eat('RBRACKET');
|
|
1346
|
+
node = { type: 'IndexExpression', object: node, indexer: start, line: startLine, column: startCol };
|
|
1347
|
+
}
|
|
1361
1348
|
}
|
|
1362
|
-
|
|
1363
|
-
this.eat('RBRACKET');
|
|
1364
|
-
|
|
1365
|
-
node = {
|
|
1366
|
-
type: 'SliceExpression',
|
|
1367
|
-
object: node,
|
|
1368
|
-
start,
|
|
1369
|
-
end,
|
|
1370
|
-
step,
|
|
1371
|
-
line: startLine,
|
|
1372
|
-
column: startCol
|
|
1373
|
-
};
|
|
1374
|
-
|
|
1375
|
-
} else {
|
|
1376
|
-
this.eat('RBRACKET');
|
|
1377
|
-
node = {
|
|
1378
|
-
type: 'IndexExpression',
|
|
1379
|
-
object: node,
|
|
1380
|
-
indexer: start,
|
|
1381
|
-
line: startLine,
|
|
1382
|
-
column: startCol
|
|
1383
|
-
};
|
|
1349
|
+
continue; // continue to allow . or () after []
|
|
1384
1350
|
}
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
1351
|
|
|
1352
|
+
// Handle function call
|
|
1390
1353
|
if (t.type === 'LPAREN') {
|
|
1391
1354
|
const startLine = t.line;
|
|
1392
1355
|
const startCol = t.column;
|
|
1393
1356
|
this.eat('LPAREN');
|
|
1394
1357
|
|
|
1395
1358
|
const args = [];
|
|
1396
|
-
|
|
1397
1359
|
while (this.current.type !== 'RPAREN') {
|
|
1398
1360
|
if (this.current.type === 'EOF') {
|
|
1399
|
-
throw new ParseError(
|
|
1400
|
-
"Unterminated function call",
|
|
1401
|
-
this.current,
|
|
1402
|
-
this.source,
|
|
1403
|
-
"Did you forget to close ')'?"
|
|
1404
|
-
);
|
|
1361
|
+
throw new ParseError("Unterminated function call", this.current, this.source, "Did you forget to close ')'?");
|
|
1405
1362
|
}
|
|
1406
|
-
|
|
1407
1363
|
args.push(this.expression());
|
|
1408
|
-
|
|
1409
|
-
if (this.current.type === 'COMMA') {
|
|
1410
|
-
this.eat('COMMA');
|
|
1411
|
-
} else {
|
|
1412
|
-
break;
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
if (this.current.type !== 'RPAREN') {
|
|
1417
|
-
throw new ParseError(
|
|
1418
|
-
"Expected ')' after function arguments",
|
|
1419
|
-
this.current,
|
|
1420
|
-
this.source,
|
|
1421
|
-
"Function calls must be closed with ')'"
|
|
1422
|
-
);
|
|
1364
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
1423
1365
|
}
|
|
1424
1366
|
|
|
1425
1367
|
this.eat('RPAREN');
|
|
1426
1368
|
|
|
1427
|
-
node = {
|
|
1428
|
-
type: 'CallExpression',
|
|
1429
|
-
callee: node,
|
|
1430
|
-
arguments: args,
|
|
1431
|
-
line: startLine,
|
|
1432
|
-
column: startCol
|
|
1433
|
-
};
|
|
1369
|
+
node = { type: 'CallExpression', callee: node, arguments: args, line: startLine, column: startCol };
|
|
1434
1370
|
continue;
|
|
1435
1371
|
}
|
|
1436
1372
|
|
|
1373
|
+
// Handle member access
|
|
1437
1374
|
if (t.type === 'DOT') {
|
|
1438
1375
|
const startLine = t.line;
|
|
1439
1376
|
const startCol = t.column;
|
|
1440
1377
|
this.eat('DOT');
|
|
1441
1378
|
|
|
1442
1379
|
if (this.current.type !== 'IDENTIFIER') {
|
|
1443
|
-
throw new ParseError(
|
|
1444
|
-
"Expected property name after '.'",
|
|
1445
|
-
this.current,
|
|
1446
|
-
this.source,
|
|
1447
|
-
"Member access requires a property name, e.g. obj.value"
|
|
1448
|
-
);
|
|
1380
|
+
throw new ParseError("Expected property name after '.'", this.current, this.source, "Member access requires a property name, e.g. obj.value");
|
|
1449
1381
|
}
|
|
1450
1382
|
|
|
1451
1383
|
const property = this.current.value;
|
|
1452
1384
|
this.eat('IDENTIFIER');
|
|
1453
1385
|
|
|
1454
|
-
node = {
|
|
1455
|
-
type: 'MemberExpression',
|
|
1456
|
-
object: node,
|
|
1457
|
-
property,
|
|
1458
|
-
line: startLine,
|
|
1459
|
-
column: startCol
|
|
1460
|
-
};
|
|
1386
|
+
node = { type: 'MemberExpression', object: node, property, line: startLine, column: startCol };
|
|
1461
1387
|
continue;
|
|
1462
1388
|
}
|
|
1463
1389
|
|
|
1390
|
+
// Handle postfix update operators
|
|
1464
1391
|
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
1465
1392
|
this.eat(t.type);
|
|
1466
|
-
|
|
1467
|
-
node = {
|
|
1468
|
-
type: 'UpdateExpression',
|
|
1469
|
-
operator: t.type,
|
|
1470
|
-
argument: node,
|
|
1471
|
-
prefix: false,
|
|
1472
|
-
line: t.line,
|
|
1473
|
-
column: t.column
|
|
1474
|
-
};
|
|
1393
|
+
node = { type: 'UpdateExpression', operator: t.type, argument: node, prefix: false, line: t.line, column: t.column };
|
|
1475
1394
|
continue;
|
|
1476
1395
|
}
|
|
1477
1396
|
|
|
@@ -1481,6 +1400,7 @@ postfix() {
|
|
|
1481
1400
|
return node;
|
|
1482
1401
|
}
|
|
1483
1402
|
|
|
1403
|
+
|
|
1484
1404
|
arrowFunction(params) {
|
|
1485
1405
|
const t = this.current;
|
|
1486
1406
|
this.eat('ARROW');
|
|
@@ -1831,5 +1751,4 @@ if (t.type === 'FUNC') {
|
|
|
1831
1751
|
}
|
|
1832
1752
|
|
|
1833
1753
|
}
|
|
1834
|
-
|
|
1835
|
-
module.exports = Parser;
|
|
1754
|
+
module.exports = Parser;
|
package/src/starlight.js
CHANGED
|
@@ -12,7 +12,7 @@ const Lexer = require('./lexer');
|
|
|
12
12
|
const Parser = require('./parser');
|
|
13
13
|
const Evaluator = require('./evaluator');
|
|
14
14
|
|
|
15
|
-
const VERSION = '1.1.
|
|
15
|
+
const VERSION = '1.1.19';
|
|
16
16
|
|
|
17
17
|
const COLOR = {
|
|
18
18
|
reset: '\x1b[0m',
|
|
@@ -172,7 +172,7 @@ Usage:
|
|
|
172
172
|
|
|
173
173
|
if (args[0] === '-v' || args[0] === '--version') {
|
|
174
174
|
console.log(`${COLOR.bold}Starlight CLI v${VERSION}${COLOR.reset}`);
|
|
175
|
-
console.log(`${COLOR.magenta}Developed by Macedon${COLOR.reset}`);
|
|
175
|
+
console.log(`${COLOR.magenta}Developed by Dominex Macedon${COLOR.reset}`);
|
|
176
176
|
process.exit(0);
|
|
177
177
|
}
|
|
178
178
|
|