starlight-cli 1.1.10 → 1.1.12
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 +1574 -520
- package/package.json +1 -1
- package/src/evaluator.js +648 -355
- package/src/lexer.js +3 -1
- package/src/parser.js +917 -158
- package/src/starlight.js +1 -1
package/dist/index.js
CHANGED
|
@@ -10220,10 +10220,9 @@ class ReturnValue {
|
|
|
10220
10220
|
class BreakSignal {}
|
|
10221
10221
|
class ContinueSignal {}
|
|
10222
10222
|
class RuntimeError extends Error {
|
|
10223
|
-
constructor(message, node, source) {
|
|
10223
|
+
constructor(message, node, source, env = null) {
|
|
10224
10224
|
const line = node?.line ?? '?';
|
|
10225
10225
|
const column = node?.column ?? '?';
|
|
10226
|
-
|
|
10227
10226
|
let output = ` ${message} at line ${line}, column ${column}\n`;
|
|
10228
10227
|
|
|
10229
10228
|
if (source && node?.line != null) {
|
|
@@ -10231,23 +10230,55 @@ class RuntimeError extends Error {
|
|
|
10231
10230
|
const srcLine = lines[node.line - 1] || '';
|
|
10232
10231
|
output += ` ${srcLine}\n`;
|
|
10233
10232
|
const caretPos =
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10233
|
+
typeof column === 'number' && column > 0
|
|
10234
|
+
? column - 1
|
|
10235
|
+
: 0;
|
|
10236
|
+
output += ` ${' '.repeat(caretPos)}^\n`;
|
|
10237
|
+
}
|
|
10237
10238
|
|
|
10238
|
-
|
|
10239
|
-
;
|
|
10239
|
+
if (env && message.startsWith('Undefined variable:')) {
|
|
10240
|
+
const nameMatch = message.match(/"(.+?)"/);
|
|
10241
|
+
if (nameMatch) {
|
|
10242
|
+
const name = nameMatch[1];
|
|
10243
|
+
const suggestion = RuntimeError.suggest(name, env);
|
|
10244
|
+
if (suggestion) {
|
|
10245
|
+
output += `Did you mean "${suggestion}"?\n`;
|
|
10246
|
+
}
|
|
10247
|
+
}
|
|
10240
10248
|
}
|
|
10241
10249
|
|
|
10242
10250
|
super(output);
|
|
10243
|
-
|
|
10244
10251
|
this.name = 'RuntimeError';
|
|
10245
10252
|
this.line = line;
|
|
10246
10253
|
this.column = column;
|
|
10247
10254
|
}
|
|
10248
|
-
}
|
|
10249
10255
|
|
|
10256
|
+
static suggest(name, env) {
|
|
10257
|
+
const names = new Set();
|
|
10258
|
+
let current = env;
|
|
10259
|
+
while (current) {
|
|
10260
|
+
for (const key of Object.keys(current.store)) {
|
|
10261
|
+
names.add(key);
|
|
10262
|
+
}
|
|
10263
|
+
current = current.parent;
|
|
10264
|
+
}
|
|
10265
|
+
|
|
10266
|
+
let best = null;
|
|
10267
|
+
let bestScore = Infinity;
|
|
10268
|
+
|
|
10269
|
+
for (const item of names) {
|
|
10270
|
+
const dist = Math.abs(item.length - name.length) +
|
|
10271
|
+
[...name].filter((c, i) => c !== item[i]).length;
|
|
10272
|
+
|
|
10273
|
+
if (dist < bestScore && dist <= 2) {
|
|
10274
|
+
bestScore = dist;
|
|
10275
|
+
best = item;
|
|
10276
|
+
}
|
|
10277
|
+
}
|
|
10250
10278
|
|
|
10279
|
+
return best;
|
|
10280
|
+
}
|
|
10281
|
+
}
|
|
10251
10282
|
|
|
10252
10283
|
class Environment {
|
|
10253
10284
|
constructor(parent = null) {
|
|
@@ -10265,16 +10296,7 @@ class Environment {
|
|
|
10265
10296
|
if (name in this.store) return this.store[name];
|
|
10266
10297
|
if (this.parent) return this.parent.get(name, node, source);
|
|
10267
10298
|
|
|
10268
|
-
|
|
10269
|
-
if (node && source) {
|
|
10270
|
-
suggestion = this.suggest?.(name, this) || null;
|
|
10271
|
-
}
|
|
10272
|
-
|
|
10273
|
-
const message = suggestion
|
|
10274
|
-
? `Undefined variable: "${name}". Did you mean "${suggestion}"?`
|
|
10275
|
-
: `Undefined variable: "${name}"`;
|
|
10276
|
-
|
|
10277
|
-
throw new RuntimeError(message, node, source);
|
|
10299
|
+
throw new RuntimeError(`Undefined variable: "${name}"`, node, source, this);
|
|
10278
10300
|
}
|
|
10279
10301
|
|
|
10280
10302
|
|
|
@@ -10326,31 +10348,7 @@ async callFunction(fn, args, env, node = null) {
|
|
|
10326
10348
|
}
|
|
10327
10349
|
|
|
10328
10350
|
|
|
10329
|
-
suggest(name, env) {
|
|
10330
|
-
const names = new Set();
|
|
10331
|
-
let current = env;
|
|
10332
|
-
while (current) {
|
|
10333
|
-
for (const key of Object.keys(current.store)) {
|
|
10334
|
-
names.add(key);
|
|
10335
|
-
}
|
|
10336
|
-
current = current.parent;
|
|
10337
|
-
}
|
|
10338
|
-
|
|
10339
|
-
let best = null;
|
|
10340
|
-
let bestScore = Infinity;
|
|
10341
|
-
|
|
10342
|
-
for (const item of names) {
|
|
10343
|
-
const dist = Math.abs(item.length - name.length) +
|
|
10344
|
-
[...name].filter((c, i) => c !== item[i]).length;
|
|
10345
|
-
|
|
10346
|
-
if (dist < bestScore && dist <= 2) {
|
|
10347
|
-
bestScore = dist;
|
|
10348
|
-
best = item;
|
|
10349
|
-
}
|
|
10350
|
-
}
|
|
10351
10351
|
|
|
10352
|
-
return best;
|
|
10353
|
-
}
|
|
10354
10352
|
formatValue(value, seen = new Set()) {
|
|
10355
10353
|
const color = __nccwpck_require__(55);
|
|
10356
10354
|
|
|
@@ -10411,6 +10409,20 @@ formatValue(value, seen = new Set()) {
|
|
|
10411
10409
|
if (Array.isArray(arg)) return 'array';
|
|
10412
10410
|
return typeof arg;
|
|
10413
10411
|
});
|
|
10412
|
+
this.global.define('isNaN', arg => {
|
|
10413
|
+
return typeof arg !== 'number' || Number.isNaN(arg);
|
|
10414
|
+
});
|
|
10415
|
+
this.global.define('random', (min, max) => {
|
|
10416
|
+
if (max === undefined) {
|
|
10417
|
+
// Only one argument → random between 0 and min
|
|
10418
|
+
return Math.floor(Math.random() * min);
|
|
10419
|
+
}
|
|
10420
|
+
min = Number(min);
|
|
10421
|
+
max = Number(max);
|
|
10422
|
+
if (isNaN(min) || isNaN(max)) return 0;
|
|
10423
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
10424
|
+
});
|
|
10425
|
+
|
|
10414
10426
|
this.global.define('map', async (array, fn) => {
|
|
10415
10427
|
if (!Array.isArray(array)) {
|
|
10416
10428
|
throw new RuntimeError('map() expects an array', null, evaluator.source);
|
|
@@ -10679,10 +10691,17 @@ async evalProgram(node, env) {
|
|
|
10679
10691
|
try {
|
|
10680
10692
|
result = await this.evaluate(stmt, env);
|
|
10681
10693
|
} catch (e) {
|
|
10694
|
+
// Re-throw known runtime control signals
|
|
10682
10695
|
if (e instanceof RuntimeError || e instanceof BreakSignal || e instanceof ContinueSignal || e instanceof ReturnValue) {
|
|
10683
10696
|
throw e;
|
|
10684
10697
|
}
|
|
10685
|
-
|
|
10698
|
+
// Wrap unexpected errors with RuntimeError including env for suggestions
|
|
10699
|
+
throw new RuntimeError(
|
|
10700
|
+
e.message || 'Error in program',
|
|
10701
|
+
stmt,
|
|
10702
|
+
this.source,
|
|
10703
|
+
env
|
|
10704
|
+
);
|
|
10686
10705
|
}
|
|
10687
10706
|
}
|
|
10688
10707
|
return result;
|
|
@@ -10692,6 +10711,7 @@ async evalStartStatement(node, env) {
|
|
|
10692
10711
|
try {
|
|
10693
10712
|
const value = await this.evaluate(node.discriminant, env);
|
|
10694
10713
|
let executing = false;
|
|
10714
|
+
|
|
10695
10715
|
for (const c of node.cases) {
|
|
10696
10716
|
try {
|
|
10697
10717
|
if (!executing) {
|
|
@@ -10709,11 +10729,11 @@ async evalStartStatement(node, env) {
|
|
|
10709
10729
|
caseErr instanceof ContinueSignal) {
|
|
10710
10730
|
throw caseErr; // propagate signals
|
|
10711
10731
|
}
|
|
10712
|
-
|
|
10713
10732
|
throw new RuntimeError(
|
|
10714
10733
|
caseErr.message || 'Error evaluating case in start statement',
|
|
10715
10734
|
c,
|
|
10716
|
-
this.source
|
|
10735
|
+
this.source,
|
|
10736
|
+
env
|
|
10717
10737
|
);
|
|
10718
10738
|
}
|
|
10719
10739
|
}
|
|
@@ -10729,28 +10749,31 @@ async evalStartStatement(node, env) {
|
|
|
10729
10749
|
throw new RuntimeError(
|
|
10730
10750
|
err.message || 'Error evaluating start statement',
|
|
10731
10751
|
node,
|
|
10732
|
-
this.source
|
|
10752
|
+
this.source,
|
|
10753
|
+
env
|
|
10733
10754
|
);
|
|
10734
10755
|
}
|
|
10735
10756
|
}
|
|
10736
10757
|
|
|
10737
|
-
|
|
10738
10758
|
async evalRaceClause(node, env) {
|
|
10739
10759
|
try {
|
|
10740
10760
|
const testValue = await this.evaluate(node.test, env);
|
|
10741
10761
|
const result = await this.evaluate(node.consequent, new Environment(env));
|
|
10742
10762
|
return { testValue, result };
|
|
10743
10763
|
} catch (err) {
|
|
10744
|
-
if (
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10764
|
+
if (
|
|
10765
|
+
err instanceof RuntimeError ||
|
|
10766
|
+
err instanceof ReturnValue ||
|
|
10767
|
+
err instanceof BreakSignal ||
|
|
10768
|
+
err instanceof ContinueSignal
|
|
10769
|
+
) {
|
|
10748
10770
|
throw err;
|
|
10749
10771
|
}
|
|
10750
10772
|
throw new RuntimeError(
|
|
10751
10773
|
err.message || 'Error evaluating race clause',
|
|
10752
10774
|
node,
|
|
10753
|
-
this.source
|
|
10775
|
+
this.source,
|
|
10776
|
+
env
|
|
10754
10777
|
);
|
|
10755
10778
|
}
|
|
10756
10779
|
}
|
|
@@ -10761,7 +10784,12 @@ async evalDoTrack(node, env) {
|
|
|
10761
10784
|
} catch (err) {
|
|
10762
10785
|
if (!node.handler) {
|
|
10763
10786
|
if (err instanceof RuntimeError) throw err;
|
|
10764
|
-
throw new RuntimeError(
|
|
10787
|
+
throw new RuntimeError(
|
|
10788
|
+
err.message || 'Error in doTrack body',
|
|
10789
|
+
node.body,
|
|
10790
|
+
this.source,
|
|
10791
|
+
env
|
|
10792
|
+
);
|
|
10765
10793
|
}
|
|
10766
10794
|
|
|
10767
10795
|
const trackEnv = new Environment(env);
|
|
@@ -10771,12 +10799,16 @@ async evalDoTrack(node, env) {
|
|
|
10771
10799
|
return await this.evaluate(node.handler, trackEnv);
|
|
10772
10800
|
} catch (handlerErr) {
|
|
10773
10801
|
if (handlerErr instanceof RuntimeError) throw handlerErr;
|
|
10774
|
-
throw new RuntimeError(
|
|
10802
|
+
throw new RuntimeError(
|
|
10803
|
+
handlerErr.message || 'Error in doTrack handler',
|
|
10804
|
+
node.handler,
|
|
10805
|
+
this.source,
|
|
10806
|
+
trackEnv
|
|
10807
|
+
);
|
|
10775
10808
|
}
|
|
10776
10809
|
}
|
|
10777
10810
|
}
|
|
10778
10811
|
|
|
10779
|
-
|
|
10780
10812
|
async evalImport(node, env) {
|
|
10781
10813
|
const spec = node.path;
|
|
10782
10814
|
let lib;
|
|
@@ -10792,34 +10824,51 @@ async evalImport(node, env) {
|
|
|
10792
10824
|
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
10793
10825
|
|
|
10794
10826
|
if (!fs.existsSync(fullPath)) {
|
|
10795
|
-
throw new RuntimeError(
|
|
10827
|
+
throw new RuntimeError(
|
|
10828
|
+
`Import not found: ${spec}`,
|
|
10829
|
+
node,
|
|
10830
|
+
this.source,
|
|
10831
|
+
env
|
|
10832
|
+
);
|
|
10796
10833
|
}
|
|
10797
10834
|
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10835
|
+
try {
|
|
10836
|
+
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
10837
|
+
const tokens = new Lexer(code).getTokens();
|
|
10838
|
+
const ast = new Parser(tokens).parse();
|
|
10801
10839
|
|
|
10802
|
-
|
|
10803
|
-
|
|
10840
|
+
const moduleEnv = new Environment(env);
|
|
10841
|
+
await this.evaluate(ast, moduleEnv);
|
|
10804
10842
|
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10843
|
+
lib = {};
|
|
10844
|
+
for (const key of Object.keys(moduleEnv.store)) {
|
|
10845
|
+
lib[key] = moduleEnv.store[key];
|
|
10846
|
+
}
|
|
10809
10847
|
|
|
10810
|
-
|
|
10848
|
+
lib.default = lib;
|
|
10849
|
+
} catch (parseErr) {
|
|
10850
|
+
throw new RuntimeError(
|
|
10851
|
+
parseErr.message || `Failed to import module: ${spec}`,
|
|
10852
|
+
node,
|
|
10853
|
+
this.source,
|
|
10854
|
+
env
|
|
10855
|
+
);
|
|
10856
|
+
}
|
|
10811
10857
|
}
|
|
10812
10858
|
|
|
10813
10859
|
for (const imp of node.specifiers) {
|
|
10814
10860
|
if (imp.type === 'DefaultImport') {
|
|
10815
10861
|
env.define(imp.local, lib.default ?? lib);
|
|
10816
|
-
}
|
|
10817
|
-
if (imp.type === 'NamespaceImport') {
|
|
10862
|
+
} else if (imp.type === 'NamespaceImport') {
|
|
10818
10863
|
env.define(imp.local, lib);
|
|
10819
|
-
}
|
|
10820
|
-
if (imp.type === 'NamedImport') {
|
|
10864
|
+
} else if (imp.type === 'NamedImport') {
|
|
10821
10865
|
if (!(imp.imported in lib)) {
|
|
10822
|
-
throw new RuntimeError(
|
|
10866
|
+
throw new RuntimeError(
|
|
10867
|
+
`Module '${spec}' has no export '${imp.imported}'`,
|
|
10868
|
+
node,
|
|
10869
|
+
this.source,
|
|
10870
|
+
env
|
|
10871
|
+
);
|
|
10823
10872
|
}
|
|
10824
10873
|
env.define(imp.local, lib[imp.imported]);
|
|
10825
10874
|
}
|
|
@@ -10827,7 +10876,6 @@ async evalImport(node, env) {
|
|
|
10827
10876
|
|
|
10828
10877
|
return null;
|
|
10829
10878
|
}
|
|
10830
|
-
|
|
10831
10879
|
async evalBlock(node, env) {
|
|
10832
10880
|
let result = null;
|
|
10833
10881
|
for (const stmt of node.body) {
|
|
@@ -10846,32 +10894,55 @@ async evalBlock(node, env) {
|
|
|
10846
10894
|
throw new RuntimeError(
|
|
10847
10895
|
e.message || 'Error in block',
|
|
10848
10896
|
stmt,
|
|
10849
|
-
this.source
|
|
10897
|
+
this.source,
|
|
10898
|
+
env // pass env for suggestions
|
|
10850
10899
|
);
|
|
10851
10900
|
}
|
|
10852
10901
|
}
|
|
10853
10902
|
return result;
|
|
10854
10903
|
}
|
|
10855
10904
|
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
10905
|
async evalVarDeclaration(node, env) {
|
|
10859
10906
|
if (!node.expr) {
|
|
10860
|
-
throw new RuntimeError(
|
|
10907
|
+
throw new RuntimeError(
|
|
10908
|
+
'Variable declaration requires an initializer',
|
|
10909
|
+
node,
|
|
10910
|
+
this.source,
|
|
10911
|
+
env // pass env for suggestions
|
|
10912
|
+
);
|
|
10861
10913
|
}
|
|
10862
10914
|
|
|
10863
|
-
|
|
10864
|
-
|
|
10915
|
+
try {
|
|
10916
|
+
const val = await this.evaluate(node.expr, env);
|
|
10917
|
+
return env.define(node.id, val);
|
|
10918
|
+
} catch (e) {
|
|
10919
|
+
if (e instanceof RuntimeError) throw e;
|
|
10920
|
+
throw new RuntimeError(
|
|
10921
|
+
e.message || 'Error evaluating variable declaration',
|
|
10922
|
+
node,
|
|
10923
|
+
this.source,
|
|
10924
|
+
env
|
|
10925
|
+
);
|
|
10926
|
+
}
|
|
10865
10927
|
}
|
|
10866
10928
|
|
|
10867
|
-
|
|
10868
10929
|
evalArrowFunction(node, env) {
|
|
10869
10930
|
if (!node.body) {
|
|
10870
|
-
throw new RuntimeError(
|
|
10931
|
+
throw new RuntimeError(
|
|
10932
|
+
'Arrow function missing body',
|
|
10933
|
+
node,
|
|
10934
|
+
this.source,
|
|
10935
|
+
env
|
|
10936
|
+
);
|
|
10871
10937
|
}
|
|
10872
10938
|
|
|
10873
10939
|
if (!Array.isArray(node.params)) {
|
|
10874
|
-
throw new RuntimeError(
|
|
10940
|
+
throw new RuntimeError(
|
|
10941
|
+
'Invalid arrow function parameters',
|
|
10942
|
+
node,
|
|
10943
|
+
this.source,
|
|
10944
|
+
env
|
|
10945
|
+
);
|
|
10875
10946
|
}
|
|
10876
10947
|
|
|
10877
10948
|
const evaluator = this;
|
|
@@ -10879,7 +10950,6 @@ evalArrowFunction(node, env) {
|
|
|
10879
10950
|
return async function (...args) {
|
|
10880
10951
|
const localEnv = new Environment(env);
|
|
10881
10952
|
|
|
10882
|
-
// Bind parameters safely
|
|
10883
10953
|
node.params.forEach((p, i) => {
|
|
10884
10954
|
const paramName = typeof p === 'string' ? p : p.name;
|
|
10885
10955
|
localEnv.define(paramName, args[i]);
|
|
@@ -10887,78 +10957,128 @@ evalArrowFunction(node, env) {
|
|
|
10887
10957
|
|
|
10888
10958
|
try {
|
|
10889
10959
|
if (node.isBlock) {
|
|
10890
|
-
// Block body
|
|
10891
10960
|
const result = await evaluator.evaluate(node.body, localEnv);
|
|
10892
|
-
return result === undefined ? null : result;
|
|
10961
|
+
return result === undefined ? null : result;
|
|
10893
10962
|
} else {
|
|
10894
|
-
// Expression body
|
|
10895
10963
|
const result = await evaluator.evaluate(node.body, localEnv);
|
|
10896
|
-
return result === undefined ? null : result;
|
|
10964
|
+
return result === undefined ? null : result;
|
|
10897
10965
|
}
|
|
10898
10966
|
} catch (err) {
|
|
10899
10967
|
if (err instanceof ReturnValue) return err.value === undefined ? null : err.value;
|
|
10900
|
-
throw err;
|
|
10968
|
+
if (err instanceof RuntimeError) throw err; // preserve RuntimeErrors
|
|
10969
|
+
throw new RuntimeError(
|
|
10970
|
+
err.message || 'Error evaluating arrow function',
|
|
10971
|
+
node,
|
|
10972
|
+
evaluator.source,
|
|
10973
|
+
localEnv
|
|
10974
|
+
);
|
|
10901
10975
|
}
|
|
10902
10976
|
};
|
|
10903
10977
|
}
|
|
10904
10978
|
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
10979
|
async evalAssignment(node, env) {
|
|
10908
10980
|
const rightVal = await this.evaluate(node.right, env);
|
|
10909
10981
|
const left = node.left;
|
|
10910
10982
|
|
|
10911
|
-
|
|
10983
|
+
try {
|
|
10984
|
+
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
10985
|
+
|
|
10986
|
+
if (left.type === 'MemberExpression') {
|
|
10987
|
+
const obj = await this.evaluate(left.object, env);
|
|
10988
|
+
if (obj == null) throw new RuntimeError(
|
|
10989
|
+
'Cannot assign to null or undefined',
|
|
10990
|
+
node,
|
|
10991
|
+
this.source,
|
|
10992
|
+
env
|
|
10993
|
+
);
|
|
10994
|
+
obj[left.property] = rightVal; // dynamic creation allowed
|
|
10995
|
+
return rightVal;
|
|
10996
|
+
}
|
|
10912
10997
|
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10998
|
+
if (left.type === 'IndexExpression') {
|
|
10999
|
+
const obj = await this.evaluate(left.object, env);
|
|
11000
|
+
const idx = await this.evaluate(left.indexer, env);
|
|
11001
|
+
if (obj == null) throw new RuntimeError(
|
|
11002
|
+
'Cannot assign to null or undefined',
|
|
11003
|
+
node,
|
|
11004
|
+
this.source,
|
|
11005
|
+
env
|
|
11006
|
+
);
|
|
11007
|
+
obj[idx] = rightVal; // dynamic creation allowed
|
|
11008
|
+
return rightVal;
|
|
11009
|
+
}
|
|
10919
11010
|
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
}
|
|
11011
|
+
throw new RuntimeError(
|
|
11012
|
+
'Invalid assignment target',
|
|
11013
|
+
node,
|
|
11014
|
+
this.source,
|
|
11015
|
+
env
|
|
11016
|
+
);
|
|
10927
11017
|
|
|
10928
|
-
|
|
11018
|
+
} catch (e) {
|
|
11019
|
+
if (e instanceof RuntimeError) throw e;
|
|
11020
|
+
throw new RuntimeError(
|
|
11021
|
+
e.message || 'Error in assignment',
|
|
11022
|
+
node,
|
|
11023
|
+
this.source,
|
|
11024
|
+
env
|
|
11025
|
+
);
|
|
11026
|
+
}
|
|
10929
11027
|
}
|
|
10930
11028
|
|
|
10931
11029
|
async evalCompoundAssignment(node, env) {
|
|
10932
11030
|
const left = node.left;
|
|
10933
11031
|
let current;
|
|
10934
11032
|
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
11033
|
+
try {
|
|
11034
|
+
if (left.type === 'Identifier') current = env.get(left.name, left, this.source);
|
|
11035
|
+
else if (left.type === 'MemberExpression') current = await this.evalMember(left, env) ?? 0;
|
|
11036
|
+
else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env) ?? 0;
|
|
11037
|
+
else throw new RuntimeError(
|
|
11038
|
+
'Invalid compound assignment target',
|
|
11039
|
+
node,
|
|
11040
|
+
this.source,
|
|
11041
|
+
env
|
|
11042
|
+
);
|
|
10939
11043
|
|
|
10940
|
-
|
|
10941
|
-
|
|
11044
|
+
const rhs = await this.evaluate(node.right, env);
|
|
11045
|
+
let computed;
|
|
11046
|
+
|
|
11047
|
+
switch (node.operator) {
|
|
11048
|
+
case 'PLUSEQ': computed = current + rhs; break;
|
|
11049
|
+
case 'MINUSEQ': computed = current - rhs; break;
|
|
11050
|
+
case 'STAREQ': computed = current * rhs; break;
|
|
11051
|
+
case 'SLASHEQ': computed = current / rhs; break;
|
|
11052
|
+
case 'MODEQ': computed = current % rhs; break;
|
|
11053
|
+
default: throw new RuntimeError(
|
|
11054
|
+
`Unknown compound operator: ${node.operator}`,
|
|
11055
|
+
node,
|
|
11056
|
+
this.source,
|
|
11057
|
+
env
|
|
11058
|
+
);
|
|
11059
|
+
}
|
|
10942
11060
|
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
}
|
|
11061
|
+
if (left.type === 'Identifier') env.set(left.name, computed);
|
|
11062
|
+
else if (left.type === 'MemberExpression' || left.type === 'IndexExpression') {
|
|
11063
|
+
await this.evalAssignment(
|
|
11064
|
+
{ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' },
|
|
11065
|
+
env
|
|
11066
|
+
);
|
|
11067
|
+
}
|
|
10951
11068
|
|
|
10952
|
-
|
|
10953
|
-
else if (left.type === 'MemberExpression')
|
|
10954
|
-
await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
10955
|
-
else
|
|
10956
|
-
await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
11069
|
+
return computed;
|
|
10957
11070
|
|
|
10958
|
-
|
|
11071
|
+
} catch (e) {
|
|
11072
|
+
if (e instanceof RuntimeError) throw e;
|
|
11073
|
+
throw new RuntimeError(
|
|
11074
|
+
e.message || 'Error in compound assignment',
|
|
11075
|
+
node,
|
|
11076
|
+
this.source,
|
|
11077
|
+
env
|
|
11078
|
+
);
|
|
11079
|
+
}
|
|
10959
11080
|
}
|
|
10960
11081
|
|
|
10961
|
-
|
|
10962
11082
|
async evalSldeploy(node, env) {
|
|
10963
11083
|
const val = await this.evaluate(node.expr, env);
|
|
10964
11084
|
console.log(this.formatValue(val));
|
|
@@ -10966,111 +11086,162 @@ async evalSldeploy(node, env) {
|
|
|
10966
11086
|
}
|
|
10967
11087
|
|
|
10968
11088
|
|
|
10969
|
-
|
|
10970
11089
|
async evalAsk(node, env) {
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
if (typeof prompt !== 'string') {
|
|
10974
|
-
throw new RuntimeError('ask() prompt must be a string', node, this.source);
|
|
10975
|
-
}
|
|
11090
|
+
try {
|
|
11091
|
+
const prompt = await this.evaluate(node.prompt, env);
|
|
10976
11092
|
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
}
|
|
11093
|
+
if (typeof prompt !== 'string') {
|
|
11094
|
+
throw new RuntimeError('ask() prompt must be a string', node, this.source, env);
|
|
11095
|
+
}
|
|
10980
11096
|
|
|
11097
|
+
const input = readlineSync.question(prompt + ' ');
|
|
11098
|
+
return input;
|
|
10981
11099
|
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
throw new RuntimeError(
|
|
11100
|
+
} catch (e) {
|
|
11101
|
+
if (e instanceof RuntimeError) throw e;
|
|
11102
|
+
throw new RuntimeError(
|
|
11103
|
+
e.message || 'Error evaluating ask()',
|
|
11104
|
+
node,
|
|
11105
|
+
this.source,
|
|
11106
|
+
env
|
|
11107
|
+
);
|
|
10985
11108
|
}
|
|
10986
|
-
|
|
10987
|
-
const val = node.expr ? await this.evaluate(node.expr, env) : null;
|
|
10988
|
-
return env.define(node.id, val);
|
|
10989
|
-
|
|
10990
11109
|
}
|
|
10991
11110
|
|
|
11111
|
+
async evalDefine(node, env) {
|
|
11112
|
+
try {
|
|
11113
|
+
if (!node.id || typeof node.id !== 'string') {
|
|
11114
|
+
throw new RuntimeError('Invalid identifier in define statement', node, this.source, env);
|
|
11115
|
+
}
|
|
10992
11116
|
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
const r = await this.evaluate(node.right, env);
|
|
10996
|
-
|
|
10997
|
-
if (node.operator === 'SLASH' && r === 0) {
|
|
10998
|
-
throw new RuntimeError('Division by zero', node, this.source);
|
|
10999
|
-
}
|
|
11000
|
-
|
|
11001
|
-
switch (node.operator) {
|
|
11002
|
-
case 'PLUS': {
|
|
11003
|
-
if (Array.isArray(l) && Array.isArray(r)) {
|
|
11004
|
-
return l.concat(r);
|
|
11005
|
-
}
|
|
11117
|
+
const val = node.expr ? await this.evaluate(node.expr, env) : null;
|
|
11118
|
+
return env.define(node.id, val);
|
|
11006
11119
|
|
|
11007
|
-
|
|
11008
|
-
|
|
11120
|
+
} catch (e) {
|
|
11121
|
+
if (e instanceof RuntimeError) throw e;
|
|
11122
|
+
throw new RuntimeError(
|
|
11123
|
+
e.message || 'Error in define statement',
|
|
11124
|
+
node,
|
|
11125
|
+
this.source,
|
|
11126
|
+
env
|
|
11127
|
+
);
|
|
11009
11128
|
}
|
|
11129
|
+
}
|
|
11010
11130
|
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11131
|
+
async evalBinary(node, env) {
|
|
11132
|
+
try {
|
|
11133
|
+
const l = await this.evaluate(node.left, env);
|
|
11134
|
+
const r = await this.evaluate(node.right, env);
|
|
11014
11135
|
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11136
|
+
if (node.operator === 'SLASH' && r === 0) {
|
|
11137
|
+
throw new RuntimeError('Division by zero', node, this.source, env);
|
|
11138
|
+
}
|
|
11018
11139
|
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
}
|
|
11140
|
+
switch (node.operator) {
|
|
11141
|
+
case 'PLUS': {
|
|
11142
|
+
if (Array.isArray(l) && Array.isArray(r)) return l.concat(r);
|
|
11143
|
+
if (typeof l === 'string' || typeof r === 'string') return String(l) + String(r);
|
|
11144
|
+
if (typeof l === 'number' && typeof r === 'number') return l + r;
|
|
11145
|
+
if (typeof l === 'object' && typeof r === 'object') return { ...l, ...r };
|
|
11025
11146
|
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11147
|
+
throw new RuntimeError(
|
|
11148
|
+
`Unsupported operands for +: ${typeof l} and ${typeof r}`,
|
|
11149
|
+
node,
|
|
11150
|
+
this.source,
|
|
11151
|
+
env
|
|
11152
|
+
);
|
|
11153
|
+
}
|
|
11154
|
+
case 'MINUS': return l - r;
|
|
11155
|
+
case 'STAR': return l * r;
|
|
11156
|
+
case 'SLASH': return l / r;
|
|
11157
|
+
case 'MOD': return l % r;
|
|
11158
|
+
case 'EQEQ': return l === r;
|
|
11159
|
+
case 'NOTEQ': return l !== r;
|
|
11160
|
+
case 'LT': return l < r;
|
|
11161
|
+
case 'LTE': return l <= r;
|
|
11162
|
+
case 'GT': return l > r;
|
|
11163
|
+
case 'GTE': return l >= r;
|
|
11164
|
+
default:
|
|
11165
|
+
throw new RuntimeError(
|
|
11166
|
+
`Unknown binary operator: ${node.operator}`,
|
|
11167
|
+
node,
|
|
11168
|
+
this.source,
|
|
11169
|
+
env
|
|
11170
|
+
);
|
|
11171
|
+
}
|
|
11037
11172
|
|
|
11173
|
+
} catch (e) {
|
|
11174
|
+
if (e instanceof RuntimeError) throw e;
|
|
11175
|
+
throw new RuntimeError(
|
|
11176
|
+
e.message || 'Error evaluating binary expression',
|
|
11177
|
+
node,
|
|
11178
|
+
this.source,
|
|
11179
|
+
env
|
|
11180
|
+
);
|
|
11038
11181
|
}
|
|
11039
11182
|
}
|
|
11040
|
-
|
|
11041
11183
|
async evalLogical(node, env) {
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11184
|
+
try {
|
|
11185
|
+
const l = await this.evaluate(node.left, env);
|
|
11186
|
+
|
|
11187
|
+
switch (node.operator) {
|
|
11188
|
+
case 'AND': return l && await this.evaluate(node.right, env);
|
|
11189
|
+
case 'OR': return l || await this.evaluate(node.right, env);
|
|
11190
|
+
case '??': {
|
|
11191
|
+
const r = await this.evaluate(node.right, env);
|
|
11192
|
+
return (l !== null && l !== undefined) ? l : r;
|
|
11193
|
+
}
|
|
11194
|
+
default:
|
|
11195
|
+
throw new RuntimeError(
|
|
11196
|
+
`Unknown logical operator: ${node.operator}`,
|
|
11197
|
+
node,
|
|
11198
|
+
this.source,
|
|
11199
|
+
env
|
|
11200
|
+
);
|
|
11050
11201
|
}
|
|
11051
|
-
|
|
11052
|
-
|
|
11202
|
+
|
|
11203
|
+
} catch (e) {
|
|
11204
|
+
if (e instanceof RuntimeError) throw e;
|
|
11205
|
+
throw new RuntimeError(
|
|
11206
|
+
e.message || 'Error evaluating logical expression',
|
|
11207
|
+
node,
|
|
11208
|
+
this.source,
|
|
11209
|
+
env
|
|
11210
|
+
);
|
|
11053
11211
|
}
|
|
11054
11212
|
}
|
|
11055
11213
|
|
|
11056
|
-
|
|
11057
11214
|
async evalUnary(node, env) {
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11215
|
+
try {
|
|
11216
|
+
const val = await this.evaluate(node.argument, env);
|
|
11217
|
+
|
|
11218
|
+
switch (node.operator) {
|
|
11219
|
+
case 'NOT': return !val;
|
|
11220
|
+
case 'MINUS': return -val;
|
|
11221
|
+
case 'PLUS': return +val;
|
|
11222
|
+
default:
|
|
11223
|
+
throw new RuntimeError(
|
|
11224
|
+
`Unknown unary operator: ${node.operator}`,
|
|
11225
|
+
node,
|
|
11226
|
+
this.source,
|
|
11227
|
+
env
|
|
11228
|
+
);
|
|
11229
|
+
}
|
|
11064
11230
|
|
|
11231
|
+
} catch (e) {
|
|
11232
|
+
if (e instanceof RuntimeError) throw e;
|
|
11233
|
+
throw new RuntimeError(
|
|
11234
|
+
e.message || 'Error evaluating unary expression',
|
|
11235
|
+
node,
|
|
11236
|
+
this.source,
|
|
11237
|
+
env
|
|
11238
|
+
);
|
|
11065
11239
|
}
|
|
11066
11240
|
}
|
|
11067
11241
|
|
|
11068
11242
|
async evalIf(node, env) {
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
if (typeof test !== 'boolean') {
|
|
11072
|
-
throw new RuntimeError('If condition must evaluate to a boolean', node.test, this.source);
|
|
11073
|
-
}
|
|
11243
|
+
let test = await this.evaluate(node.test, env);
|
|
11244
|
+
test = !!test; // coerce to boolean
|
|
11074
11245
|
|
|
11075
11246
|
if (test) {
|
|
11076
11247
|
return await this.evaluate(node.consequent, env);
|
|
@@ -11083,222 +11254,344 @@ async evalIf(node, env) {
|
|
|
11083
11254
|
return null;
|
|
11084
11255
|
}
|
|
11085
11256
|
|
|
11086
|
-
|
|
11087
11257
|
async evalWhile(node, env) {
|
|
11088
|
-
|
|
11089
|
-
|
|
11258
|
+
try {
|
|
11259
|
+
while (true) {
|
|
11260
|
+
let test = await this.evaluate(node.test, env);
|
|
11261
|
+
test = !!test;
|
|
11262
|
+
|
|
11263
|
+
if (!test) break;
|
|
11090
11264
|
|
|
11091
|
-
|
|
11092
|
-
|
|
11265
|
+
try {
|
|
11266
|
+
await this.evaluate(node.body, env);
|
|
11267
|
+
} catch (e) {
|
|
11268
|
+
if (e instanceof BreakSignal) break;
|
|
11269
|
+
if (e instanceof ContinueSignal) continue;
|
|
11270
|
+
throw e;
|
|
11271
|
+
}
|
|
11093
11272
|
}
|
|
11094
11273
|
|
|
11095
|
-
|
|
11274
|
+
return null;
|
|
11096
11275
|
|
|
11097
|
-
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11276
|
+
} catch (e) {
|
|
11277
|
+
if (e instanceof RuntimeError || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
11278
|
+
throw new RuntimeError(
|
|
11279
|
+
e.message || 'Error evaluating while loop',
|
|
11280
|
+
node,
|
|
11281
|
+
this.source,
|
|
11282
|
+
env
|
|
11283
|
+
);
|
|
11104
11284
|
}
|
|
11105
|
-
return null;
|
|
11106
11285
|
}
|
|
11107
11286
|
async evalFor(node, env) {
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
loopEnv.define(loopVar, value);
|
|
11121
|
-
|
|
11122
|
-
try {
|
|
11123
|
-
await this.evaluate(node.body, loopEnv);
|
|
11124
|
-
} catch (e) {
|
|
11125
|
-
if (e instanceof BreakSignal) break;
|
|
11126
|
-
if (e instanceof ContinueSignal) continue;
|
|
11127
|
-
throw e;
|
|
11128
|
-
}
|
|
11287
|
+
try {
|
|
11288
|
+
// ForInStatement
|
|
11289
|
+
if (node.type === 'ForInStatement') {
|
|
11290
|
+
const iterable = await this.evaluate(node.iterable, env);
|
|
11291
|
+
|
|
11292
|
+
if (iterable == null || typeof iterable !== 'object') {
|
|
11293
|
+
throw new RuntimeError(
|
|
11294
|
+
'Cannot iterate over non-iterable',
|
|
11295
|
+
node,
|
|
11296
|
+
this.source,
|
|
11297
|
+
env
|
|
11298
|
+
);
|
|
11129
11299
|
}
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11300
|
+
|
|
11301
|
+
const loopVar = node.variable; // string name of the loop variable
|
|
11302
|
+
const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
|
|
11303
|
+
|
|
11304
|
+
if (Array.isArray(iterable)) {
|
|
11305
|
+
for (const value of iterable) {
|
|
11306
|
+
const loopEnv = createLoopEnv();
|
|
11307
|
+
loopEnv.define(loopVar, value);
|
|
11308
|
+
|
|
11309
|
+
try {
|
|
11310
|
+
await this.evaluate(node.body, loopEnv);
|
|
11311
|
+
} catch (e) {
|
|
11312
|
+
if (e instanceof BreakSignal) break;
|
|
11313
|
+
if (e instanceof ContinueSignal) continue;
|
|
11314
|
+
throw e;
|
|
11315
|
+
}
|
|
11316
|
+
}
|
|
11317
|
+
} else {
|
|
11318
|
+
for (const key of Object.keys(iterable)) {
|
|
11319
|
+
const loopEnv = createLoopEnv();
|
|
11320
|
+
loopEnv.define(loopVar, key);
|
|
11321
|
+
|
|
11322
|
+
try {
|
|
11323
|
+
await this.evaluate(node.body, loopEnv);
|
|
11324
|
+
} catch (e) {
|
|
11325
|
+
if (e instanceof BreakSignal) break;
|
|
11326
|
+
if (e instanceof ContinueSignal) continue;
|
|
11327
|
+
throw e;
|
|
11328
|
+
}
|
|
11142
11329
|
}
|
|
11143
11330
|
}
|
|
11144
|
-
}
|
|
11145
11331
|
|
|
11146
|
-
|
|
11147
|
-
|
|
11332
|
+
return null;
|
|
11333
|
+
}
|
|
11148
11334
|
|
|
11149
|
-
|
|
11335
|
+
// Standard for loop
|
|
11336
|
+
const local = new Environment(env);
|
|
11150
11337
|
|
|
11151
|
-
|
|
11338
|
+
if (node.init) await this.evaluate(node.init, local);
|
|
11152
11339
|
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11340
|
+
while (!node.test || await this.evaluate(node.test, local)) {
|
|
11341
|
+
try {
|
|
11342
|
+
await this.evaluate(node.body, local);
|
|
11343
|
+
} catch (e) {
|
|
11344
|
+
if (e instanceof BreakSignal) break;
|
|
11345
|
+
if (e instanceof ContinueSignal) {
|
|
11346
|
+
if (node.update) await this.evaluate(node.update, local);
|
|
11347
|
+
continue;
|
|
11348
|
+
}
|
|
11349
|
+
throw e;
|
|
11161
11350
|
}
|
|
11162
|
-
|
|
11351
|
+
|
|
11352
|
+
if (node.update) await this.evaluate(node.update, local);
|
|
11163
11353
|
}
|
|
11164
11354
|
|
|
11165
|
-
|
|
11166
|
-
}
|
|
11355
|
+
return null;
|
|
11167
11356
|
|
|
11168
|
-
|
|
11357
|
+
} catch (err) {
|
|
11358
|
+
if (err instanceof RuntimeError || err instanceof BreakSignal || err instanceof ContinueSignal) throw err;
|
|
11359
|
+
throw new RuntimeError(
|
|
11360
|
+
err.message || 'Error evaluating for loop',
|
|
11361
|
+
node,
|
|
11362
|
+
this.source,
|
|
11363
|
+
env
|
|
11364
|
+
);
|
|
11365
|
+
}
|
|
11169
11366
|
}
|
|
11170
11367
|
|
|
11171
11368
|
evalFunctionDeclaration(node, env) {
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11369
|
+
try {
|
|
11370
|
+
if (!node.name || typeof node.name !== 'string') {
|
|
11371
|
+
throw new RuntimeError('Function declaration requires a valid name', node, this.source, env);
|
|
11372
|
+
}
|
|
11175
11373
|
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11374
|
+
if (!Array.isArray(node.params)) {
|
|
11375
|
+
throw new RuntimeError(`Invalid parameter list in function '${node.name}'`, node, this.source, env);
|
|
11376
|
+
}
|
|
11179
11377
|
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11378
|
+
if (!node.body) {
|
|
11379
|
+
throw new RuntimeError(`Function '${node.name}' has no body`, node, this.source, env);
|
|
11380
|
+
}
|
|
11183
11381
|
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11382
|
+
const fn = {
|
|
11383
|
+
params: node.params,
|
|
11384
|
+
body: node.body,
|
|
11385
|
+
env,
|
|
11386
|
+
async: node.async || false
|
|
11387
|
+
};
|
|
11190
11388
|
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
}
|
|
11389
|
+
env.define(node.name, fn);
|
|
11390
|
+
return null;
|
|
11194
11391
|
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
throw new RuntimeError('Call to non-function', node, this.source);
|
|
11392
|
+
} catch (err) {
|
|
11393
|
+
if (err instanceof RuntimeError) throw err;
|
|
11394
|
+
throw new RuntimeError(
|
|
11395
|
+
err.message || 'Error defining function',
|
|
11396
|
+
node,
|
|
11397
|
+
this.source,
|
|
11398
|
+
env
|
|
11399
|
+
);
|
|
11204
11400
|
}
|
|
11401
|
+
}
|
|
11402
|
+
async evalCall(node, env) {
|
|
11403
|
+
try {
|
|
11404
|
+
const calleeEvaluated = await this.evaluate(node.callee, env);
|
|
11205
11405
|
|
|
11206
|
-
|
|
11207
|
-
|
|
11406
|
+
// Native JS function
|
|
11407
|
+
if (typeof calleeEvaluated === 'function') {
|
|
11408
|
+
const args = [];
|
|
11409
|
+
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
11410
|
+
return await calleeEvaluated(...args);
|
|
11411
|
+
}
|
|
11208
11412
|
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11212
|
-
|
|
11413
|
+
// Not a callable object
|
|
11414
|
+
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
|
|
11415
|
+
throw new RuntimeError(
|
|
11416
|
+
'Call to non-function',
|
|
11417
|
+
node,
|
|
11418
|
+
this.source,
|
|
11419
|
+
env
|
|
11420
|
+
);
|
|
11421
|
+
}
|
|
11213
11422
|
|
|
11214
|
-
|
|
11215
|
-
|
|
11423
|
+
const fn = calleeEvaluated;
|
|
11424
|
+
const callEnv = new Environment(fn.env);
|
|
11216
11425
|
|
|
11217
|
-
|
|
11218
|
-
|
|
11426
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
11427
|
+
const argVal = node.arguments[i]
|
|
11428
|
+
? await this.evaluate(node.arguments[i], env)
|
|
11429
|
+
: null;
|
|
11219
11430
|
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11431
|
+
const param = fn.params[i];
|
|
11432
|
+
const paramName = typeof param === 'string' ? param : param.name;
|
|
11433
|
+
callEnv.define(paramName, argVal);
|
|
11434
|
+
}
|
|
11435
|
+
|
|
11436
|
+
try {
|
|
11437
|
+
const result = await this.evaluate(fn.body, callEnv);
|
|
11438
|
+
return result === undefined ? null : result; // enforce null instead of undefined
|
|
11439
|
+
} catch (e) {
|
|
11440
|
+
if (e instanceof ReturnValue) return e.value === undefined ? null : e.value;
|
|
11441
|
+
throw e;
|
|
11442
|
+
}
|
|
11443
|
+
|
|
11444
|
+
} catch (err) {
|
|
11445
|
+
if (err instanceof RuntimeError) throw err;
|
|
11446
|
+
throw new RuntimeError(
|
|
11447
|
+
err.message || 'Error during function call',
|
|
11448
|
+
node,
|
|
11449
|
+
this.source,
|
|
11450
|
+
env
|
|
11451
|
+
);
|
|
11226
11452
|
}
|
|
11227
11453
|
}
|
|
11228
11454
|
|
|
11229
11455
|
async evalIndex(node, env) {
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
|
|
11234
|
-
if (Array.isArray(obj)) {
|
|
11235
|
-
if (idx < 0 || idx >= obj.length) return undefined;
|
|
11236
|
-
return obj[idx];
|
|
11237
|
-
}
|
|
11238
|
-
if (typeof obj === 'object') {
|
|
11239
|
-
return obj[idx]; // undefined if missing
|
|
11240
|
-
}
|
|
11456
|
+
try {
|
|
11457
|
+
const obj = await this.evaluate(node.object, env);
|
|
11458
|
+
const idx = await this.evaluate(node.indexer, env);
|
|
11241
11459
|
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11460
|
+
if (obj == null) {
|
|
11461
|
+
throw new RuntimeError(
|
|
11462
|
+
'Cannot index null or undefined',
|
|
11463
|
+
node,
|
|
11464
|
+
this.source,
|
|
11465
|
+
env
|
|
11466
|
+
);
|
|
11249
11467
|
}
|
|
11250
11468
|
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
if (p.value) {
|
|
11255
|
-
value = await this.evaluate(p.value, env);
|
|
11256
|
-
if (value === undefined) value = null; // <- force null instead of undefined
|
|
11469
|
+
if (Array.isArray(obj)) {
|
|
11470
|
+
if (idx < 0 || idx >= obj.length) return undefined;
|
|
11471
|
+
return obj[idx];
|
|
11257
11472
|
}
|
|
11258
11473
|
|
|
11259
|
-
|
|
11474
|
+
if (typeof obj === 'object') return obj[idx]; // undefined if missing
|
|
11475
|
+
|
|
11476
|
+
return undefined;
|
|
11477
|
+
} catch (err) {
|
|
11478
|
+
if (err instanceof RuntimeError) throw err;
|
|
11479
|
+
throw new RuntimeError(
|
|
11480
|
+
err.message || 'Error during index access',
|
|
11481
|
+
node,
|
|
11482
|
+
this.source,
|
|
11483
|
+
env
|
|
11484
|
+
);
|
|
11260
11485
|
}
|
|
11261
|
-
return out;
|
|
11262
11486
|
}
|
|
11263
11487
|
|
|
11488
|
+
async evalObject(node, env) {
|
|
11489
|
+
try {
|
|
11490
|
+
const out = {};
|
|
11491
|
+
for (const p of node.props) {
|
|
11492
|
+
if (!p.key) {
|
|
11493
|
+
throw new RuntimeError(
|
|
11494
|
+
'Object property must have a key',
|
|
11495
|
+
node,
|
|
11496
|
+
this.source,
|
|
11497
|
+
env
|
|
11498
|
+
);
|
|
11499
|
+
}
|
|
11500
|
+
|
|
11501
|
+
const key = await this.evaluate(p.key, env);
|
|
11502
|
+
let value = null;
|
|
11264
11503
|
|
|
11504
|
+
if (p.value) {
|
|
11505
|
+
value = await this.evaluate(p.value, env);
|
|
11506
|
+
if (value === undefined) value = null; // force null instead of undefined
|
|
11507
|
+
}
|
|
11508
|
+
|
|
11509
|
+
out[key] = value;
|
|
11510
|
+
}
|
|
11511
|
+
return out;
|
|
11512
|
+
} catch (err) {
|
|
11513
|
+
if (err instanceof RuntimeError) throw err;
|
|
11514
|
+
throw new RuntimeError(
|
|
11515
|
+
err.message || 'Error evaluating object literal',
|
|
11516
|
+
node,
|
|
11517
|
+
this.source,
|
|
11518
|
+
env
|
|
11519
|
+
);
|
|
11520
|
+
}
|
|
11521
|
+
}
|
|
11265
11522
|
|
|
11266
11523
|
|
|
11267
11524
|
async evalMember(node, env) {
|
|
11268
11525
|
const obj = await this.evaluate(node.object, env);
|
|
11269
11526
|
|
|
11270
|
-
if (obj == null)
|
|
11527
|
+
if (obj == null) {
|
|
11528
|
+
throw new RuntimeError('Member access of null or undefined', node, this.source);
|
|
11529
|
+
}
|
|
11530
|
+
|
|
11531
|
+
const prop = obj[node.property];
|
|
11271
11532
|
|
|
11272
|
-
|
|
11273
|
-
|
|
11533
|
+
if (typeof prop === 'function') {
|
|
11534
|
+
return prop.bind(obj);
|
|
11535
|
+
}
|
|
11274
11536
|
|
|
11537
|
+
return prop;
|
|
11538
|
+
}
|
|
11275
11539
|
|
|
11276
11540
|
async evalUpdate(node, env) {
|
|
11277
11541
|
const arg = node.argument;
|
|
11278
|
-
const getCurrent = async () => {
|
|
11279
|
-
if (arg.type === 'Identifier') return env.get(arg.name, arg, this.source);
|
|
11280
|
-
if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
|
|
11281
|
-
if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
|
|
11282
|
-
throw new RuntimeError('Invalid update target', node, this.source);
|
|
11283
11542
|
|
|
11543
|
+
const getCurrent = async () => {
|
|
11544
|
+
try {
|
|
11545
|
+
if (arg.type === 'Identifier') return env.get(arg.name, arg, this.source);
|
|
11546
|
+
if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
|
|
11547
|
+
if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
|
|
11548
|
+
throw new RuntimeError('Invalid update target', node, this.source, env);
|
|
11549
|
+
} catch (err) {
|
|
11550
|
+
if (err instanceof RuntimeError) throw err;
|
|
11551
|
+
throw new RuntimeError(
|
|
11552
|
+
err.message || 'Error accessing update target',
|
|
11553
|
+
node,
|
|
11554
|
+
this.source,
|
|
11555
|
+
env
|
|
11556
|
+
);
|
|
11557
|
+
}
|
|
11284
11558
|
};
|
|
11559
|
+
|
|
11285
11560
|
const setValue = async (v) => {
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11561
|
+
try {
|
|
11562
|
+
if (arg.type === 'Identifier') {
|
|
11563
|
+
env.set(arg.name, v);
|
|
11564
|
+
} else if (arg.type === 'MemberExpression') {
|
|
11565
|
+
const obj = await this.evaluate(arg.object, env);
|
|
11566
|
+
if (obj == null) throw new RuntimeError('Cannot update property of null or undefined', node, this.source, env);
|
|
11567
|
+
obj[arg.property] = v;
|
|
11568
|
+
} else if (arg.type === 'IndexExpression') {
|
|
11569
|
+
const obj = await this.evaluate(arg.object, env);
|
|
11570
|
+
const idx = await this.evaluate(arg.indexer, env);
|
|
11571
|
+
if (obj == null) throw new RuntimeError('Cannot update index of null or undefined', node, this.source, env);
|
|
11572
|
+
obj[idx] = v;
|
|
11573
|
+
}
|
|
11574
|
+
} catch (err) {
|
|
11575
|
+
if (err instanceof RuntimeError) throw err;
|
|
11576
|
+
throw new RuntimeError(
|
|
11577
|
+
err.message || 'Error setting update target',
|
|
11578
|
+
node,
|
|
11579
|
+
this.source,
|
|
11580
|
+
env
|
|
11581
|
+
);
|
|
11294
11582
|
}
|
|
11295
11583
|
};
|
|
11296
11584
|
|
|
11297
11585
|
const current = await getCurrent();
|
|
11298
|
-
const newVal =
|
|
11586
|
+
const newVal = node.operator === 'PLUSPLUS' ? current + 1 : current - 1;
|
|
11299
11587
|
|
|
11300
|
-
if (node.prefix) {
|
|
11301
|
-
|
|
11588
|
+
if (node.prefix) {
|
|
11589
|
+
await setValue(newVal);
|
|
11590
|
+
return newVal;
|
|
11591
|
+
} else {
|
|
11592
|
+
await setValue(newVal);
|
|
11593
|
+
return current;
|
|
11594
|
+
}
|
|
11302
11595
|
}
|
|
11303
11596
|
|
|
11304
11597
|
}
|
|
@@ -11340,7 +11633,7 @@ class Lexer {
|
|
|
11340
11633
|
'break', 'continue', 'func', 'return',
|
|
11341
11634
|
'true', 'false', 'null',
|
|
11342
11635
|
'ask', 'define', 'import', 'from', 'as',
|
|
11343
|
-
'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race'
|
|
11636
|
+
'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race', 'not', 'and', 'or'
|
|
11344
11637
|
];
|
|
11345
11638
|
}
|
|
11346
11639
|
|
|
@@ -11423,6 +11716,8 @@ class Lexer {
|
|
|
11423
11716
|
}
|
|
11424
11717
|
|
|
11425
11718
|
if (this.keywords.includes(result)) {
|
|
11719
|
+
if (result === 'and') return { type: 'AND', value: 'and', line: startLine, column: startCol };
|
|
11720
|
+
if (result === 'or') return { type: 'OR', value: 'or', line: startLine, column: startCol };
|
|
11426
11721
|
return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
|
|
11427
11722
|
}
|
|
11428
11723
|
|
|
@@ -11538,15 +11833,19 @@ module.exports = Lexer;
|
|
|
11538
11833
|
/***/ }),
|
|
11539
11834
|
|
|
11540
11835
|
/***/ 222:
|
|
11541
|
-
/***/ ((module) => {
|
|
11836
|
+
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
|
11542
11837
|
|
|
11543
11838
|
class ParseError extends Error {
|
|
11544
|
-
constructor(message, token, source) {
|
|
11839
|
+
constructor(message, token, source, suggestion = null) {
|
|
11545
11840
|
const line = token?.line ?? '?';
|
|
11546
11841
|
const column = token?.column ?? '?';
|
|
11547
11842
|
|
|
11548
11843
|
let output = `${message}\n`;
|
|
11549
11844
|
|
|
11845
|
+
if (suggestion) {
|
|
11846
|
+
output += `Hint: ${suggestion}\n`;
|
|
11847
|
+
}
|
|
11848
|
+
|
|
11550
11849
|
if (source && token?.line != null) {
|
|
11551
11850
|
const lines = source.split('\n');
|
|
11552
11851
|
const srcLine = lines[token.line - 1] || '';
|
|
@@ -11562,6 +11861,7 @@ class ParseError extends Error {
|
|
|
11562
11861
|
}
|
|
11563
11862
|
}
|
|
11564
11863
|
|
|
11864
|
+
|
|
11565
11865
|
class Parser {
|
|
11566
11866
|
constructor(tokens, source = '') {
|
|
11567
11867
|
this.tokens = tokens;
|
|
@@ -11629,17 +11929,49 @@ class Parser {
|
|
|
11629
11929
|
varDeclaration() {
|
|
11630
11930
|
const t = this.current;
|
|
11631
11931
|
this.eat('LET');
|
|
11932
|
+
|
|
11933
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
11934
|
+
throw new ParseError(
|
|
11935
|
+
"Expected variable name after 'let'",
|
|
11936
|
+
this.current,
|
|
11937
|
+
this.source,
|
|
11938
|
+
"Variable declarations must be followed by an identifier, e.g. let x = 5"
|
|
11939
|
+
);
|
|
11940
|
+
}
|
|
11941
|
+
|
|
11632
11942
|
const idToken = this.current;
|
|
11633
11943
|
const id = idToken.value;
|
|
11634
11944
|
this.eat('IDENTIFIER');
|
|
11635
11945
|
|
|
11636
11946
|
let expr = null;
|
|
11947
|
+
|
|
11948
|
+
if (this.current.type === 'EQEQ') {
|
|
11949
|
+
throw new ParseError(
|
|
11950
|
+
"Invalid '==' in variable declaration",
|
|
11951
|
+
this.current,
|
|
11952
|
+
this.source,
|
|
11953
|
+
"Did you mean '=' for assignment?"
|
|
11954
|
+
);
|
|
11955
|
+
}
|
|
11956
|
+
|
|
11637
11957
|
if (this.current.type === 'EQUAL') {
|
|
11638
11958
|
this.eat('EQUAL');
|
|
11959
|
+
|
|
11960
|
+
if (this.current.type === 'SEMICOLON') {
|
|
11961
|
+
throw new ParseError(
|
|
11962
|
+
"Expected expression after '='",
|
|
11963
|
+
this.current,
|
|
11964
|
+
this.source,
|
|
11965
|
+
"Assignments require a value, e.g. let x = 10"
|
|
11966
|
+
);
|
|
11967
|
+
}
|
|
11968
|
+
|
|
11639
11969
|
expr = this.expression();
|
|
11640
11970
|
}
|
|
11641
11971
|
|
|
11642
|
-
if (this.current.type === 'SEMICOLON')
|
|
11972
|
+
if (this.current.type === 'SEMICOLON') {
|
|
11973
|
+
this.eat('SEMICOLON');
|
|
11974
|
+
}
|
|
11643
11975
|
|
|
11644
11976
|
return {
|
|
11645
11977
|
type: 'VarDeclaration',
|
|
@@ -11649,12 +11981,31 @@ varDeclaration() {
|
|
|
11649
11981
|
column: t.column
|
|
11650
11982
|
};
|
|
11651
11983
|
}
|
|
11984
|
+
|
|
11652
11985
|
startStatement() {
|
|
11653
11986
|
const t = this.current;
|
|
11654
11987
|
this.eat('START');
|
|
11655
11988
|
|
|
11989
|
+
if (this.current.type === 'LBRACE') {
|
|
11990
|
+
throw new ParseError(
|
|
11991
|
+
"Expected expression after 'start'",
|
|
11992
|
+
this.current,
|
|
11993
|
+
this.source,
|
|
11994
|
+
"The 'start' statement requires a discriminant expression"
|
|
11995
|
+
);
|
|
11996
|
+
}
|
|
11997
|
+
|
|
11656
11998
|
const discriminant = this.expression();
|
|
11657
11999
|
|
|
12000
|
+
if (this.current.type !== 'LBRACE') {
|
|
12001
|
+
throw new ParseError(
|
|
12002
|
+
"Expected '{' to start start-block",
|
|
12003
|
+
this.current,
|
|
12004
|
+
this.source,
|
|
12005
|
+
"Start blocks must be enclosed in braces"
|
|
12006
|
+
);
|
|
12007
|
+
}
|
|
12008
|
+
|
|
11658
12009
|
this.eat('LBRACE');
|
|
11659
12010
|
|
|
11660
12011
|
const cases = [];
|
|
@@ -11663,6 +12014,24 @@ startStatement() {
|
|
|
11663
12014
|
cases.push(this.raceClause());
|
|
11664
12015
|
}
|
|
11665
12016
|
|
|
12017
|
+
if (cases.length === 0) {
|
|
12018
|
+
throw new ParseError(
|
|
12019
|
+
"Start statement must contain at least one 'race' clause",
|
|
12020
|
+
this.current,
|
|
12021
|
+
this.source,
|
|
12022
|
+
"Use 'race <condition> { ... }' inside start blocks"
|
|
12023
|
+
);
|
|
12024
|
+
}
|
|
12025
|
+
|
|
12026
|
+
if (this.current.type !== 'RBRACE') {
|
|
12027
|
+
throw new ParseError(
|
|
12028
|
+
"Expected '}' to close start block",
|
|
12029
|
+
this.current,
|
|
12030
|
+
this.source,
|
|
12031
|
+
"Did you forget to close the start block?"
|
|
12032
|
+
);
|
|
12033
|
+
}
|
|
12034
|
+
|
|
11666
12035
|
this.eat('RBRACE');
|
|
11667
12036
|
|
|
11668
12037
|
return {
|
|
@@ -11677,8 +12046,26 @@ raceClause() {
|
|
|
11677
12046
|
const t = this.current;
|
|
11678
12047
|
this.eat('RACE');
|
|
11679
12048
|
|
|
12049
|
+
if (this.current.type === 'LBRACE') {
|
|
12050
|
+
throw new ParseError(
|
|
12051
|
+
"Expected condition after 'race'",
|
|
12052
|
+
this.current,
|
|
12053
|
+
this.source,
|
|
12054
|
+
"Race clauses require a condition before the block"
|
|
12055
|
+
);
|
|
12056
|
+
}
|
|
12057
|
+
|
|
11680
12058
|
const test = this.expression();
|
|
11681
12059
|
|
|
12060
|
+
if (this.current.type !== 'LBRACE') {
|
|
12061
|
+
throw new ParseError(
|
|
12062
|
+
"Expected '{' after race condition",
|
|
12063
|
+
this.current,
|
|
12064
|
+
this.source,
|
|
12065
|
+
"Race clauses must use a block: race condition { ... }"
|
|
12066
|
+
);
|
|
12067
|
+
}
|
|
12068
|
+
|
|
11682
12069
|
const consequent = this.block();
|
|
11683
12070
|
|
|
11684
12071
|
return {
|
|
@@ -11690,11 +12077,26 @@ raceClause() {
|
|
|
11690
12077
|
};
|
|
11691
12078
|
}
|
|
11692
12079
|
|
|
12080
|
+
|
|
11693
12081
|
sldeployStatement() {
|
|
11694
12082
|
const t = this.current;
|
|
11695
12083
|
this.eat('SLDEPLOY');
|
|
12084
|
+
|
|
12085
|
+
if (this.current.type === 'SEMICOLON') {
|
|
12086
|
+
throw new ParseError(
|
|
12087
|
+
"Expected expression after 'sldeploy'",
|
|
12088
|
+
this.current,
|
|
12089
|
+
this.source,
|
|
12090
|
+
"sldeploy requires a value or expression to deploy"
|
|
12091
|
+
);
|
|
12092
|
+
}
|
|
12093
|
+
|
|
11696
12094
|
const expr = this.expression();
|
|
11697
|
-
|
|
12095
|
+
|
|
12096
|
+
if (this.current.type === 'SEMICOLON') {
|
|
12097
|
+
this.eat('SEMICOLON');
|
|
12098
|
+
}
|
|
12099
|
+
|
|
11698
12100
|
return {
|
|
11699
12101
|
type: 'SldeployStatement',
|
|
11700
12102
|
expr,
|
|
@@ -11707,11 +12109,31 @@ doTrackStatement() {
|
|
|
11707
12109
|
const t = this.current;
|
|
11708
12110
|
this.eat('DO');
|
|
11709
12111
|
|
|
12112
|
+
if (this.current.type !== 'LBRACE') {
|
|
12113
|
+
throw new ParseError(
|
|
12114
|
+
"Expected '{' after 'do'",
|
|
12115
|
+
this.current,
|
|
12116
|
+
this.source,
|
|
12117
|
+
"The 'do' statement must be followed by a block"
|
|
12118
|
+
);
|
|
12119
|
+
}
|
|
12120
|
+
|
|
11710
12121
|
const body = this.block();
|
|
11711
12122
|
|
|
11712
12123
|
let handler = null;
|
|
12124
|
+
|
|
11713
12125
|
if (this.current.type === 'TRACK') {
|
|
11714
12126
|
this.eat('TRACK');
|
|
12127
|
+
|
|
12128
|
+
if (this.current.type !== 'LBRACE') {
|
|
12129
|
+
throw new ParseError(
|
|
12130
|
+
"Expected '{' after 'track'",
|
|
12131
|
+
this.current,
|
|
12132
|
+
this.source,
|
|
12133
|
+
"Track handlers must be blocks"
|
|
12134
|
+
);
|
|
12135
|
+
}
|
|
12136
|
+
|
|
11715
12137
|
handler = this.block();
|
|
11716
12138
|
}
|
|
11717
12139
|
|
|
@@ -11727,17 +12149,49 @@ doTrackStatement() {
|
|
|
11727
12149
|
defineStatement() {
|
|
11728
12150
|
const t = this.current;
|
|
11729
12151
|
this.eat('DEFINE');
|
|
12152
|
+
|
|
12153
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12154
|
+
throw new ParseError(
|
|
12155
|
+
"Expected identifier after 'define'",
|
|
12156
|
+
this.current,
|
|
12157
|
+
this.source,
|
|
12158
|
+
"Definitions must be followed by a name, e.g. define PI = 3.14"
|
|
12159
|
+
);
|
|
12160
|
+
}
|
|
12161
|
+
|
|
11730
12162
|
const idToken = this.current;
|
|
11731
12163
|
const id = idToken.value;
|
|
11732
12164
|
this.eat('IDENTIFIER');
|
|
11733
12165
|
|
|
11734
12166
|
let expr = null;
|
|
12167
|
+
|
|
12168
|
+
if (this.current.type === 'EQEQ') {
|
|
12169
|
+
throw new ParseError(
|
|
12170
|
+
"Invalid '==' in define statement",
|
|
12171
|
+
this.current,
|
|
12172
|
+
this.source,
|
|
12173
|
+
"Did you mean '=' to assign a value?"
|
|
12174
|
+
);
|
|
12175
|
+
}
|
|
12176
|
+
|
|
11735
12177
|
if (this.current.type === 'EQUAL') {
|
|
11736
12178
|
this.eat('EQUAL');
|
|
12179
|
+
|
|
12180
|
+
if (this.current.type === 'SEMICOLON') {
|
|
12181
|
+
throw new ParseError(
|
|
12182
|
+
"Expected expression after '='",
|
|
12183
|
+
this.current,
|
|
12184
|
+
this.source,
|
|
12185
|
+
"Definitions require a value, e.g. define X = 10"
|
|
12186
|
+
);
|
|
12187
|
+
}
|
|
12188
|
+
|
|
11737
12189
|
expr = this.expression();
|
|
11738
12190
|
}
|
|
11739
12191
|
|
|
11740
|
-
if (this.current.type === 'SEMICOLON')
|
|
12192
|
+
if (this.current.type === 'SEMICOLON') {
|
|
12193
|
+
this.eat('SEMICOLON');
|
|
12194
|
+
}
|
|
11741
12195
|
|
|
11742
12196
|
return {
|
|
11743
12197
|
type: 'DefineStatement',
|
|
@@ -11750,30 +12204,80 @@ defineStatement() {
|
|
|
11750
12204
|
|
|
11751
12205
|
asyncFuncDeclaration() {
|
|
11752
12206
|
const t = this.current;
|
|
12207
|
+
|
|
12208
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12209
|
+
throw new ParseError(
|
|
12210
|
+
"Expected function name after 'async func'",
|
|
12211
|
+
this.current,
|
|
12212
|
+
this.source,
|
|
12213
|
+
"Async functions must have a name"
|
|
12214
|
+
);
|
|
12215
|
+
}
|
|
12216
|
+
|
|
11753
12217
|
const name = this.current.value;
|
|
11754
12218
|
this.eat('IDENTIFIER');
|
|
11755
12219
|
|
|
11756
12220
|
let params = [];
|
|
12221
|
+
|
|
11757
12222
|
if (this.current.type === 'LPAREN') {
|
|
11758
12223
|
this.eat('LPAREN');
|
|
12224
|
+
|
|
11759
12225
|
if (this.current.type !== 'RPAREN') {
|
|
12226
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12227
|
+
throw new ParseError(
|
|
12228
|
+
"Expected parameter name",
|
|
12229
|
+
this.current,
|
|
12230
|
+
this.source,
|
|
12231
|
+
"Function parameters must be identifiers"
|
|
12232
|
+
);
|
|
12233
|
+
}
|
|
12234
|
+
|
|
11760
12235
|
params.push(this.current.value);
|
|
11761
12236
|
this.eat('IDENTIFIER');
|
|
12237
|
+
|
|
11762
12238
|
while (this.current.type === 'COMMA') {
|
|
11763
12239
|
this.eat('COMMA');
|
|
12240
|
+
|
|
12241
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12242
|
+
throw new ParseError(
|
|
12243
|
+
"Expected parameter name after ','",
|
|
12244
|
+
this.current,
|
|
12245
|
+
this.source,
|
|
12246
|
+
"Separate parameters with commas"
|
|
12247
|
+
);
|
|
12248
|
+
}
|
|
12249
|
+
|
|
11764
12250
|
params.push(this.current.value);
|
|
11765
12251
|
this.eat('IDENTIFIER');
|
|
11766
12252
|
}
|
|
11767
12253
|
}
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
12254
|
+
|
|
12255
|
+
if (this.current.type !== 'RPAREN') {
|
|
12256
|
+
throw new ParseError(
|
|
12257
|
+
"Expected ')' after function parameters",
|
|
12258
|
+
this.current,
|
|
12259
|
+
this.source,
|
|
12260
|
+
"Did you forget to close the parameter list?"
|
|
12261
|
+
);
|
|
11773
12262
|
}
|
|
12263
|
+
|
|
12264
|
+
this.eat('RPAREN');
|
|
12265
|
+
} else if (this.current.type === 'IDENTIFIER') {
|
|
12266
|
+
params.push(this.current.value);
|
|
12267
|
+
this.eat('IDENTIFIER');
|
|
12268
|
+
}
|
|
12269
|
+
|
|
12270
|
+
if (this.current.type !== 'LBRACE') {
|
|
12271
|
+
throw new ParseError(
|
|
12272
|
+
"Expected '{' to start function body",
|
|
12273
|
+
this.current,
|
|
12274
|
+
this.source,
|
|
12275
|
+
"Function declarations require a block body"
|
|
12276
|
+
);
|
|
11774
12277
|
}
|
|
11775
12278
|
|
|
11776
12279
|
const body = this.block();
|
|
12280
|
+
|
|
11777
12281
|
return {
|
|
11778
12282
|
type: 'FunctionDeclaration',
|
|
11779
12283
|
name,
|
|
@@ -11784,32 +12288,62 @@ asyncFuncDeclaration() {
|
|
|
11784
12288
|
column: t.column
|
|
11785
12289
|
};
|
|
11786
12290
|
}
|
|
11787
|
-
|
|
11788
12291
|
ifStatement() {
|
|
11789
12292
|
const t = this.current;
|
|
11790
12293
|
this.eat('IF');
|
|
11791
12294
|
|
|
11792
12295
|
let test;
|
|
12296
|
+
|
|
11793
12297
|
if (this.current.type === 'LPAREN') {
|
|
11794
12298
|
this.eat('LPAREN');
|
|
12299
|
+
|
|
12300
|
+
if (this.current.type === 'RPAREN') {
|
|
12301
|
+
throw new ParseError(
|
|
12302
|
+
"Missing condition in if statement",
|
|
12303
|
+
this.current,
|
|
12304
|
+
this.source,
|
|
12305
|
+
"If statements require a condition"
|
|
12306
|
+
);
|
|
12307
|
+
}
|
|
12308
|
+
|
|
11795
12309
|
test = this.expression();
|
|
12310
|
+
|
|
12311
|
+
if (this.current.type !== 'RPAREN') {
|
|
12312
|
+
throw new ParseError(
|
|
12313
|
+
"Expected ')' after if condition",
|
|
12314
|
+
this.current,
|
|
12315
|
+
this.source,
|
|
12316
|
+
"Did you forget to close the condition?"
|
|
12317
|
+
);
|
|
12318
|
+
}
|
|
12319
|
+
|
|
11796
12320
|
this.eat('RPAREN');
|
|
11797
12321
|
} else {
|
|
11798
12322
|
test = this.expression();
|
|
11799
12323
|
}
|
|
11800
12324
|
|
|
12325
|
+
if (this.current.type === 'EQUAL') {
|
|
12326
|
+
throw new ParseError(
|
|
12327
|
+
"Invalid assignment in if condition",
|
|
12328
|
+
this.current,
|
|
12329
|
+
this.source,
|
|
12330
|
+
"Did you mean '==' for comparison?"
|
|
12331
|
+
);
|
|
12332
|
+
}
|
|
12333
|
+
|
|
11801
12334
|
const consequent = this.statementOrBlock();
|
|
11802
12335
|
|
|
11803
12336
|
let alternate = null;
|
|
12337
|
+
|
|
11804
12338
|
if (this.current.type === 'ELSE') {
|
|
11805
|
-
|
|
11806
|
-
if (this.current.type === 'IF') {
|
|
11807
|
-
alternate = this.ifStatement();
|
|
11808
|
-
} else {
|
|
11809
|
-
alternate = this.statementOrBlock();
|
|
11810
|
-
}
|
|
11811
|
-
}
|
|
12339
|
+
this.eat('ELSE');
|
|
11812
12340
|
|
|
12341
|
+
if (this.current.type === 'IF') {
|
|
12342
|
+
alternate = this.ifStatement();
|
|
12343
|
+
} else {
|
|
12344
|
+
alternate = this.statementOrBlock();
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
11813
12347
|
|
|
11814
12348
|
return {
|
|
11815
12349
|
type: 'IfStatement',
|
|
@@ -11821,21 +12355,83 @@ ifStatement() {
|
|
|
11821
12355
|
};
|
|
11822
12356
|
}
|
|
11823
12357
|
|
|
12358
|
+
parseExpressionOnly() {
|
|
12359
|
+
return this.expression();
|
|
12360
|
+
}
|
|
12361
|
+
|
|
11824
12362
|
whileStatement() {
|
|
11825
12363
|
const t = this.current;
|
|
11826
12364
|
this.eat('WHILE');
|
|
11827
12365
|
|
|
11828
12366
|
let test;
|
|
12367
|
+
|
|
11829
12368
|
if (this.current.type === 'LPAREN') {
|
|
11830
12369
|
this.eat('LPAREN');
|
|
12370
|
+
|
|
12371
|
+
if (this.current.type === 'RPAREN') {
|
|
12372
|
+
throw new ParseError(
|
|
12373
|
+
"Missing condition in while statement",
|
|
12374
|
+
this.current,
|
|
12375
|
+
this.source,
|
|
12376
|
+
"While loops require a condition"
|
|
12377
|
+
);
|
|
12378
|
+
}
|
|
12379
|
+
|
|
11831
12380
|
test = this.expression();
|
|
11832
|
-
this.eat('RPAREN');
|
|
11833
|
-
} else {
|
|
11834
|
-
test = this.expression();
|
|
11835
|
-
}
|
|
11836
12381
|
|
|
11837
|
-
|
|
11838
|
-
|
|
12382
|
+
if (this.current.type !== 'RPAREN') {
|
|
12383
|
+
throw new ParseError(
|
|
12384
|
+
"Expected ')' after while condition",
|
|
12385
|
+
this.current,
|
|
12386
|
+
this.source,
|
|
12387
|
+
"Did you forget to close the condition?"
|
|
12388
|
+
);
|
|
12389
|
+
}
|
|
12390
|
+
|
|
12391
|
+
this.eat('RPAREN');
|
|
12392
|
+
}
|
|
12393
|
+
else {
|
|
12394
|
+
const exprTokens = [];
|
|
12395
|
+
let braceFound = false;
|
|
12396
|
+
let depth = 0;
|
|
12397
|
+
|
|
12398
|
+
while (this.current.type !== 'EOF') {
|
|
12399
|
+
if (this.current.type === 'LBRACE' && depth === 0) {
|
|
12400
|
+
braceFound = true;
|
|
12401
|
+
break;
|
|
12402
|
+
}
|
|
12403
|
+
if (this.current.type === 'LPAREN') depth++;
|
|
12404
|
+
if (this.current.type === 'RPAREN') depth--;
|
|
12405
|
+
exprTokens.push(this.current);
|
|
12406
|
+
this.advance();
|
|
12407
|
+
}
|
|
12408
|
+
|
|
12409
|
+
if (!braceFound) {
|
|
12410
|
+
throw new ParseError(
|
|
12411
|
+
"Expected '{' after while condition",
|
|
12412
|
+
this.current,
|
|
12413
|
+
this.source,
|
|
12414
|
+
"While loops must be followed by a block"
|
|
12415
|
+
);
|
|
12416
|
+
}
|
|
12417
|
+
|
|
12418
|
+
const Parser = __nccwpck_require__(222);
|
|
12419
|
+
const exprParser = new Parser(exprTokens, this.source);
|
|
12420
|
+
test = exprParser.parseExpressionOnly();
|
|
12421
|
+
}
|
|
12422
|
+
|
|
12423
|
+
if (this.current.type !== 'LBRACE') {
|
|
12424
|
+
throw new ParseError(
|
|
12425
|
+
"Expected '{' to start while loop body",
|
|
12426
|
+
this.current,
|
|
12427
|
+
this.source,
|
|
12428
|
+
"While loop bodies must be enclosed in braces"
|
|
12429
|
+
);
|
|
12430
|
+
}
|
|
12431
|
+
|
|
12432
|
+
const body = this.block();
|
|
12433
|
+
|
|
12434
|
+
return {
|
|
11839
12435
|
type: 'WhileStatement',
|
|
11840
12436
|
test,
|
|
11841
12437
|
body,
|
|
@@ -11849,15 +12445,62 @@ importStatement() {
|
|
|
11849
12445
|
this.eat('IMPORT');
|
|
11850
12446
|
|
|
11851
12447
|
let specifiers = [];
|
|
12448
|
+
|
|
11852
12449
|
if (this.current.type === 'STAR') {
|
|
11853
12450
|
this.eat('STAR');
|
|
12451
|
+
|
|
12452
|
+
if (this.current.type !== 'AS') {
|
|
12453
|
+
throw new ParseError(
|
|
12454
|
+
"Expected 'as' after '*' in import",
|
|
12455
|
+
this.current,
|
|
12456
|
+
this.source,
|
|
12457
|
+
"Namespace imports require 'as', e.g. import * as name from 'mod'"
|
|
12458
|
+
);
|
|
12459
|
+
}
|
|
12460
|
+
|
|
11854
12461
|
this.eat('AS');
|
|
12462
|
+
|
|
12463
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12464
|
+
throw new ParseError(
|
|
12465
|
+
"Expected identifier after 'as'",
|
|
12466
|
+
this.current,
|
|
12467
|
+
this.source,
|
|
12468
|
+
"The namespace must have a local name"
|
|
12469
|
+
);
|
|
12470
|
+
}
|
|
12471
|
+
|
|
11855
12472
|
const name = this.current.value;
|
|
11856
12473
|
this.eat('IDENTIFIER');
|
|
11857
|
-
|
|
12474
|
+
|
|
12475
|
+
specifiers.push({
|
|
12476
|
+
type: 'NamespaceImport',
|
|
12477
|
+
local: name,
|
|
12478
|
+
line: t.line,
|
|
12479
|
+
column: t.column
|
|
12480
|
+
});
|
|
12481
|
+
|
|
11858
12482
|
} else if (this.current.type === 'LBRACE') {
|
|
11859
12483
|
this.eat('LBRACE');
|
|
12484
|
+
|
|
12485
|
+
if (this.current.type === 'RBRACE') {
|
|
12486
|
+
throw new ParseError(
|
|
12487
|
+
"Empty import specifier list",
|
|
12488
|
+
this.current,
|
|
12489
|
+
this.source,
|
|
12490
|
+
"Specify at least one name inside '{ }'"
|
|
12491
|
+
);
|
|
12492
|
+
}
|
|
12493
|
+
|
|
11860
12494
|
while (this.current.type !== 'RBRACE') {
|
|
12495
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12496
|
+
throw new ParseError(
|
|
12497
|
+
"Expected identifier in import specifier",
|
|
12498
|
+
this.current,
|
|
12499
|
+
this.source,
|
|
12500
|
+
"Import names must be identifiers"
|
|
12501
|
+
);
|
|
12502
|
+
}
|
|
12503
|
+
|
|
11861
12504
|
const importedName = this.current.value;
|
|
11862
12505
|
const importedLine = this.current.line;
|
|
11863
12506
|
const importedColumn = this.current.column;
|
|
@@ -11866,27 +12509,77 @@ importStatement() {
|
|
|
11866
12509
|
let localName = importedName;
|
|
11867
12510
|
if (this.current.type === 'AS') {
|
|
11868
12511
|
this.eat('AS');
|
|
12512
|
+
|
|
12513
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12514
|
+
throw new ParseError(
|
|
12515
|
+
"Expected identifier after 'as'",
|
|
12516
|
+
this.current,
|
|
12517
|
+
this.source,
|
|
12518
|
+
"Aliases must be valid identifiers"
|
|
12519
|
+
);
|
|
12520
|
+
}
|
|
12521
|
+
|
|
11869
12522
|
localName = this.current.value;
|
|
11870
12523
|
this.eat('IDENTIFIER');
|
|
11871
12524
|
}
|
|
11872
12525
|
|
|
11873
|
-
specifiers.push({
|
|
12526
|
+
specifiers.push({
|
|
12527
|
+
type: 'NamedImport',
|
|
12528
|
+
imported: importedName,
|
|
12529
|
+
local: localName,
|
|
12530
|
+
line: importedLine,
|
|
12531
|
+
column: importedColumn
|
|
12532
|
+
});
|
|
12533
|
+
|
|
11874
12534
|
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
11875
12535
|
}
|
|
12536
|
+
|
|
11876
12537
|
this.eat('RBRACE');
|
|
12538
|
+
|
|
11877
12539
|
} else if (this.current.type === 'IDENTIFIER') {
|
|
11878
12540
|
const localName = this.current.value;
|
|
11879
12541
|
const localLine = this.current.line;
|
|
11880
12542
|
const localColumn = this.current.column;
|
|
11881
12543
|
this.eat('IDENTIFIER');
|
|
11882
|
-
|
|
12544
|
+
|
|
12545
|
+
specifiers.push({
|
|
12546
|
+
type: 'DefaultImport',
|
|
12547
|
+
local: localName,
|
|
12548
|
+
line: localLine,
|
|
12549
|
+
column: localColumn
|
|
12550
|
+
});
|
|
12551
|
+
|
|
11883
12552
|
} else {
|
|
11884
|
-
throw new
|
|
12553
|
+
throw new ParseError(
|
|
12554
|
+
"Invalid import syntax",
|
|
12555
|
+
this.current,
|
|
12556
|
+
this.source,
|
|
12557
|
+
"Use import name, import { a }, or import * as name"
|
|
12558
|
+
);
|
|
12559
|
+
}
|
|
12560
|
+
|
|
12561
|
+
if (this.current.type !== 'FROM') {
|
|
12562
|
+
throw new ParseError(
|
|
12563
|
+
"Expected 'from' in import statement",
|
|
12564
|
+
this.current,
|
|
12565
|
+
this.source,
|
|
12566
|
+
"Imports must specify a source module"
|
|
12567
|
+
);
|
|
11885
12568
|
}
|
|
11886
12569
|
|
|
11887
12570
|
this.eat('FROM');
|
|
12571
|
+
|
|
11888
12572
|
const pathToken = this.current;
|
|
11889
|
-
|
|
12573
|
+
|
|
12574
|
+
if (pathToken.type !== 'STRING') {
|
|
12575
|
+
throw new ParseError(
|
|
12576
|
+
"Expected string after 'from'",
|
|
12577
|
+
pathToken,
|
|
12578
|
+
this.source,
|
|
12579
|
+
"Module paths must be strings"
|
|
12580
|
+
);
|
|
12581
|
+
}
|
|
12582
|
+
|
|
11890
12583
|
this.eat('STRING');
|
|
11891
12584
|
|
|
11892
12585
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
@@ -11899,14 +12592,14 @@ importStatement() {
|
|
|
11899
12592
|
column: t.column
|
|
11900
12593
|
};
|
|
11901
12594
|
}
|
|
11902
|
-
|
|
11903
12595
|
forStatement() {
|
|
11904
|
-
const t = this.current;
|
|
12596
|
+
const t = this.current;
|
|
11905
12597
|
this.eat('FOR');
|
|
11906
12598
|
|
|
11907
|
-
if (
|
|
11908
|
-
|
|
11909
|
-
|
|
12599
|
+
if (
|
|
12600
|
+
(this.current.type === 'LET' && this.peekType() === 'IDENTIFIER' && this.peekType(2) === 'IN') ||
|
|
12601
|
+
(this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')
|
|
12602
|
+
) {
|
|
11910
12603
|
let variable;
|
|
11911
12604
|
let variableLine, variableColumn;
|
|
11912
12605
|
let iterable;
|
|
@@ -11915,22 +12608,45 @@ forStatement() {
|
|
|
11915
12608
|
if (this.current.type === 'LET') {
|
|
11916
12609
|
letKeyword = true;
|
|
11917
12610
|
this.eat('LET');
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
12611
|
+
}
|
|
12612
|
+
|
|
12613
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12614
|
+
throw new ParseError(
|
|
12615
|
+
"Expected identifier in for-in loop",
|
|
12616
|
+
this.current,
|
|
12617
|
+
this.source,
|
|
12618
|
+
"for-in loops require a loop variable"
|
|
12619
|
+
);
|
|
12620
|
+
}
|
|
12621
|
+
|
|
12622
|
+
variableLine = this.current.line;
|
|
12623
|
+
variableColumn = this.current.column;
|
|
12624
|
+
variable = this.current.value;
|
|
12625
|
+
this.eat('IDENTIFIER');
|
|
12626
|
+
|
|
12627
|
+
if (this.current.type !== 'IN') {
|
|
12628
|
+
throw new ParseError(
|
|
12629
|
+
"Expected 'in' in for-in loop",
|
|
12630
|
+
this.current,
|
|
12631
|
+
this.source,
|
|
12632
|
+
"Use: for item in iterable { ... }"
|
|
12633
|
+
);
|
|
12634
|
+
}
|
|
12635
|
+
|
|
12636
|
+
this.eat('IN');
|
|
12637
|
+
iterable = this.expression();
|
|
12638
|
+
|
|
12639
|
+
if (this.current.type !== 'LBRACE') {
|
|
12640
|
+
throw new ParseError(
|
|
12641
|
+
"Expected '{' after for-in iterable",
|
|
12642
|
+
this.current,
|
|
12643
|
+
this.source,
|
|
12644
|
+
"For-in loops require a block body"
|
|
12645
|
+
);
|
|
11931
12646
|
}
|
|
11932
12647
|
|
|
11933
12648
|
const body = this.block();
|
|
12649
|
+
|
|
11934
12650
|
return {
|
|
11935
12651
|
type: 'ForInStatement',
|
|
11936
12652
|
variable,
|
|
@@ -11952,25 +12668,53 @@ forStatement() {
|
|
|
11952
12668
|
this.eat('LPAREN');
|
|
11953
12669
|
|
|
11954
12670
|
if (this.current.type !== 'SEMICOLON') {
|
|
11955
|
-
init = this.current.type === 'LET'
|
|
12671
|
+
init = this.current.type === 'LET'
|
|
12672
|
+
? this.varDeclaration()
|
|
12673
|
+
: this.expressionStatement();
|
|
11956
12674
|
} else {
|
|
11957
12675
|
this.eat('SEMICOLON');
|
|
11958
12676
|
}
|
|
11959
12677
|
|
|
11960
|
-
if (this.current.type !== 'SEMICOLON')
|
|
12678
|
+
if (this.current.type !== 'SEMICOLON') {
|
|
12679
|
+
test = this.expression();
|
|
12680
|
+
}
|
|
12681
|
+
|
|
11961
12682
|
this.eat('SEMICOLON');
|
|
11962
12683
|
|
|
11963
|
-
if (this.current.type !== 'RPAREN')
|
|
12684
|
+
if (this.current.type !== 'RPAREN') {
|
|
12685
|
+
update = this.expression();
|
|
12686
|
+
}
|
|
12687
|
+
|
|
12688
|
+
if (this.current.type !== 'RPAREN') {
|
|
12689
|
+
throw new ParseError(
|
|
12690
|
+
"Expected ')' after for loop clauses",
|
|
12691
|
+
this.current,
|
|
12692
|
+
this.source,
|
|
12693
|
+
"Did you forget to close the for loop header?"
|
|
12694
|
+
);
|
|
12695
|
+
}
|
|
12696
|
+
|
|
11964
12697
|
this.eat('RPAREN');
|
|
11965
12698
|
} else {
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
this.
|
|
11969
|
-
|
|
11970
|
-
|
|
12699
|
+
throw new ParseError(
|
|
12700
|
+
"Expected '(' after 'for'",
|
|
12701
|
+
this.current,
|
|
12702
|
+
this.source,
|
|
12703
|
+
"Classic for loops require parentheses"
|
|
12704
|
+
);
|
|
12705
|
+
}
|
|
12706
|
+
|
|
12707
|
+
if (this.current.type !== 'LBRACE') {
|
|
12708
|
+
throw new ParseError(
|
|
12709
|
+
"Expected '{' to start for loop body",
|
|
12710
|
+
this.current,
|
|
12711
|
+
this.source,
|
|
12712
|
+
"For loops require a block body"
|
|
12713
|
+
);
|
|
11971
12714
|
}
|
|
11972
12715
|
|
|
11973
12716
|
const body = this.block();
|
|
12717
|
+
|
|
11974
12718
|
return {
|
|
11975
12719
|
type: 'ForStatement',
|
|
11976
12720
|
init,
|
|
@@ -11995,41 +12739,111 @@ continueStatement() {
|
|
|
11995
12739
|
if (this.current.type === 'SEMICOLON') this.advance();
|
|
11996
12740
|
return { type: 'ContinueStatement', line: t.line, column: t.column };
|
|
11997
12741
|
}
|
|
11998
|
-
|
|
11999
12742
|
funcDeclaration() {
|
|
12000
12743
|
const t = this.current;
|
|
12001
12744
|
this.eat('FUNC');
|
|
12745
|
+
|
|
12746
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12747
|
+
throw new ParseError(
|
|
12748
|
+
"Expected function name after 'func'",
|
|
12749
|
+
this.current,
|
|
12750
|
+
this.source,
|
|
12751
|
+
"Functions must have a name, e.g. func add(a, b) { ... }"
|
|
12752
|
+
);
|
|
12753
|
+
}
|
|
12754
|
+
|
|
12002
12755
|
const nameToken = this.current;
|
|
12003
12756
|
const name = nameToken.value;
|
|
12004
12757
|
this.eat('IDENTIFIER');
|
|
12005
12758
|
|
|
12006
12759
|
let params = [];
|
|
12760
|
+
|
|
12007
12761
|
if (this.current.type === 'LPAREN') {
|
|
12008
12762
|
this.eat('LPAREN');
|
|
12763
|
+
|
|
12009
12764
|
if (this.current.type !== 'RPAREN') {
|
|
12010
|
-
|
|
12011
|
-
|
|
12765
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12766
|
+
throw new ParseError(
|
|
12767
|
+
"Expected parameter name",
|
|
12768
|
+
this.current,
|
|
12769
|
+
this.source,
|
|
12770
|
+
"Function parameters must be identifiers"
|
|
12771
|
+
);
|
|
12772
|
+
}
|
|
12773
|
+
|
|
12774
|
+
let paramToken = this.current;
|
|
12775
|
+
params.push({
|
|
12776
|
+
name: paramToken.value,
|
|
12777
|
+
line: paramToken.line,
|
|
12778
|
+
column: paramToken.column
|
|
12779
|
+
});
|
|
12012
12780
|
this.eat('IDENTIFIER');
|
|
12781
|
+
|
|
12013
12782
|
while (this.current.type === 'COMMA') {
|
|
12014
12783
|
this.eat('COMMA');
|
|
12015
|
-
|
|
12016
|
-
|
|
12784
|
+
|
|
12785
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
12786
|
+
throw new ParseError(
|
|
12787
|
+
"Expected parameter name after ','",
|
|
12788
|
+
this.current,
|
|
12789
|
+
this.source,
|
|
12790
|
+
"Each parameter must be an identifier"
|
|
12791
|
+
);
|
|
12792
|
+
}
|
|
12793
|
+
|
|
12794
|
+
paramToken = this.current;
|
|
12795
|
+
params.push({
|
|
12796
|
+
name: paramToken.value,
|
|
12797
|
+
line: paramToken.line,
|
|
12798
|
+
column: paramToken.column
|
|
12799
|
+
});
|
|
12017
12800
|
this.eat('IDENTIFIER');
|
|
12018
12801
|
}
|
|
12019
12802
|
}
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12803
|
+
|
|
12804
|
+
if (this.current.type !== 'RPAREN') {
|
|
12805
|
+
throw new ParseError(
|
|
12806
|
+
"Expected ')' after function parameters",
|
|
12807
|
+
this.current,
|
|
12808
|
+
this.source,
|
|
12809
|
+
"Did you forget to close the parameter list?"
|
|
12810
|
+
);
|
|
12026
12811
|
}
|
|
12812
|
+
|
|
12813
|
+
this.eat('RPAREN');
|
|
12814
|
+
}
|
|
12815
|
+
else if (this.current.type === 'IDENTIFIER') {
|
|
12816
|
+
const paramToken = this.current;
|
|
12817
|
+
params.push({
|
|
12818
|
+
name: paramToken.value,
|
|
12819
|
+
line: paramToken.line,
|
|
12820
|
+
column: paramToken.column
|
|
12821
|
+
});
|
|
12822
|
+
this.eat('IDENTIFIER');
|
|
12823
|
+
}
|
|
12824
|
+
|
|
12825
|
+
if (this.current.type !== 'LBRACE') {
|
|
12826
|
+
throw new ParseError(
|
|
12827
|
+
"Expected '{' to start function body",
|
|
12828
|
+
this.current,
|
|
12829
|
+
this.source,
|
|
12830
|
+
"Functions must have a block body"
|
|
12831
|
+
);
|
|
12027
12832
|
}
|
|
12028
12833
|
|
|
12029
12834
|
const body = this.block();
|
|
12030
|
-
|
|
12835
|
+
|
|
12836
|
+
return {
|
|
12837
|
+
type: 'FunctionDeclaration',
|
|
12838
|
+
name,
|
|
12839
|
+
params,
|
|
12840
|
+
body,
|
|
12841
|
+
line: t.line,
|
|
12842
|
+
column: t.column
|
|
12843
|
+
};
|
|
12031
12844
|
}
|
|
12032
12845
|
|
|
12846
|
+
|
|
12033
12847
|
returnStatement() {
|
|
12034
12848
|
const t = this.current; // RETURN token
|
|
12035
12849
|
this.eat('RETURN');
|
|
@@ -12049,18 +12863,35 @@ statementOrBlock() {
|
|
|
12049
12863
|
}
|
|
12050
12864
|
return this.statement();
|
|
12051
12865
|
}
|
|
12052
|
-
|
|
12053
12866
|
block() {
|
|
12054
12867
|
const t = this.current; // LBRACE token
|
|
12055
12868
|
this.eat('LBRACE');
|
|
12869
|
+
|
|
12056
12870
|
const body = [];
|
|
12871
|
+
|
|
12057
12872
|
while (this.current.type !== 'RBRACE') {
|
|
12873
|
+
if (this.current.type === 'EOF') {
|
|
12874
|
+
throw new ParseError(
|
|
12875
|
+
"Unterminated block",
|
|
12876
|
+
this.current,
|
|
12877
|
+
this.source,
|
|
12878
|
+
"Did you forget to close the block with '}'?"
|
|
12879
|
+
);
|
|
12880
|
+
}
|
|
12058
12881
|
body.push(this.statement());
|
|
12059
12882
|
}
|
|
12883
|
+
|
|
12060
12884
|
this.eat('RBRACE');
|
|
12061
|
-
|
|
12885
|
+
|
|
12886
|
+
return {
|
|
12887
|
+
type: 'BlockStatement',
|
|
12888
|
+
body,
|
|
12889
|
+
line: t.line,
|
|
12890
|
+
column: t.column
|
|
12891
|
+
};
|
|
12062
12892
|
}
|
|
12063
12893
|
|
|
12894
|
+
|
|
12064
12895
|
expressionStatement() {
|
|
12065
12896
|
const exprToken = this.current;
|
|
12066
12897
|
const expr = this.expression();
|
|
@@ -12071,7 +12902,6 @@ expressionStatement() {
|
|
|
12071
12902
|
expression() {
|
|
12072
12903
|
return this.assignment();
|
|
12073
12904
|
}
|
|
12074
|
-
|
|
12075
12905
|
assignment() {
|
|
12076
12906
|
const node = this.ternary();
|
|
12077
12907
|
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
@@ -12082,29 +12912,76 @@ assignment() {
|
|
|
12082
12912
|
const op = t.type;
|
|
12083
12913
|
this.eat(op);
|
|
12084
12914
|
const right = this.assignment();
|
|
12085
|
-
|
|
12915
|
+
|
|
12916
|
+
return {
|
|
12917
|
+
type: 'CompoundAssignment',
|
|
12918
|
+
operator: op,
|
|
12919
|
+
left: node,
|
|
12920
|
+
right,
|
|
12921
|
+
line: t.line,
|
|
12922
|
+
column: t.column
|
|
12923
|
+
};
|
|
12924
|
+
}
|
|
12925
|
+
|
|
12926
|
+
if (t.type === 'EQEQ') {
|
|
12927
|
+
throw new ParseError(
|
|
12928
|
+
"Unexpected '==' in assignment",
|
|
12929
|
+
t,
|
|
12930
|
+
this.source,
|
|
12931
|
+
"Did you mean '=' to assign a value?"
|
|
12932
|
+
);
|
|
12086
12933
|
}
|
|
12087
12934
|
|
|
12088
12935
|
if (t.type === 'EQUAL') {
|
|
12089
12936
|
this.eat('EQUAL');
|
|
12090
12937
|
const right = this.assignment();
|
|
12091
|
-
|
|
12938
|
+
|
|
12939
|
+
return {
|
|
12940
|
+
type: 'AssignmentExpression',
|
|
12941
|
+
left: node,
|
|
12942
|
+
right,
|
|
12943
|
+
line: t.line,
|
|
12944
|
+
column: t.column
|
|
12945
|
+
};
|
|
12092
12946
|
}
|
|
12093
12947
|
|
|
12094
12948
|
return node;
|
|
12095
12949
|
}
|
|
12950
|
+
|
|
12096
12951
|
ternary() {
|
|
12097
12952
|
let node = this.nullishCoalescing();
|
|
12953
|
+
|
|
12098
12954
|
while (this.current.type === 'QUESTION') {
|
|
12099
12955
|
const t = this.current;
|
|
12100
12956
|
this.eat('QUESTION');
|
|
12101
|
-
|
|
12957
|
+
|
|
12958
|
+
const consequent = this.expression();
|
|
12959
|
+
|
|
12960
|
+
if (this.current.type !== 'COLON') {
|
|
12961
|
+
throw new ParseError(
|
|
12962
|
+
"Expected ':' in conditional expression",
|
|
12963
|
+
this.current,
|
|
12964
|
+
this.source,
|
|
12965
|
+
"Ternary expressions must follow the form: condition ? a : b"
|
|
12966
|
+
);
|
|
12967
|
+
}
|
|
12968
|
+
|
|
12102
12969
|
this.eat('COLON');
|
|
12103
|
-
const alternate = this.expression();
|
|
12104
|
-
|
|
12970
|
+
const alternate = this.expression();
|
|
12971
|
+
|
|
12972
|
+
node = {
|
|
12973
|
+
type: 'ConditionalExpression',
|
|
12974
|
+
test: node,
|
|
12975
|
+
consequent,
|
|
12976
|
+
alternate,
|
|
12977
|
+
line: t.line,
|
|
12978
|
+
column: t.column
|
|
12979
|
+
};
|
|
12105
12980
|
}
|
|
12981
|
+
|
|
12106
12982
|
return node;
|
|
12107
12983
|
}
|
|
12984
|
+
|
|
12108
12985
|
nullishCoalescing() {
|
|
12109
12986
|
let node = this.logicalOr();
|
|
12110
12987
|
while (this.current.type === 'NULLISH_COALESCING') {
|
|
@@ -12187,6 +13064,16 @@ unary() {
|
|
|
12187
13064
|
if (['NOT', 'MINUS', 'PLUS'].includes(t.type)) {
|
|
12188
13065
|
const op = t.type;
|
|
12189
13066
|
this.eat(op);
|
|
13067
|
+
|
|
13068
|
+
if (this.current.type === 'EOF') {
|
|
13069
|
+
throw new ParseError(
|
|
13070
|
+
"Missing operand for unary operator",
|
|
13071
|
+
this.current,
|
|
13072
|
+
this.source,
|
|
13073
|
+
"Unary operators must be followed by an expression"
|
|
13074
|
+
);
|
|
13075
|
+
}
|
|
13076
|
+
|
|
12190
13077
|
return {
|
|
12191
13078
|
type: 'UnaryExpression',
|
|
12192
13079
|
operator: op,
|
|
@@ -12199,7 +13086,18 @@ unary() {
|
|
|
12199
13086
|
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
12200
13087
|
const op = t.type;
|
|
12201
13088
|
this.eat(op);
|
|
13089
|
+
|
|
12202
13090
|
const argument = this.unary();
|
|
13091
|
+
|
|
13092
|
+
if (!argument || !argument.type) {
|
|
13093
|
+
throw new ParseError(
|
|
13094
|
+
"Invalid operand for update operator",
|
|
13095
|
+
t,
|
|
13096
|
+
this.source,
|
|
13097
|
+
"Increment and decrement operators must apply to a variable"
|
|
13098
|
+
);
|
|
13099
|
+
}
|
|
13100
|
+
|
|
12203
13101
|
return {
|
|
12204
13102
|
type: 'UpdateExpression',
|
|
12205
13103
|
operator: op,
|
|
@@ -12212,9 +13110,9 @@ unary() {
|
|
|
12212
13110
|
|
|
12213
13111
|
return this.postfix();
|
|
12214
13112
|
}
|
|
12215
|
-
|
|
12216
13113
|
postfix() {
|
|
12217
13114
|
let node = this.primary();
|
|
13115
|
+
|
|
12218
13116
|
while (true) {
|
|
12219
13117
|
const t = this.current;
|
|
12220
13118
|
|
|
@@ -12222,9 +13120,27 @@ postfix() {
|
|
|
12222
13120
|
const startLine = t.line;
|
|
12223
13121
|
const startCol = t.column;
|
|
12224
13122
|
this.eat('LBRACKET');
|
|
13123
|
+
|
|
12225
13124
|
const index = this.expression();
|
|
12226
|
-
|
|
12227
|
-
|
|
13125
|
+
|
|
13126
|
+
if (this.current.type !== 'RBRACKET') {
|
|
13127
|
+
throw new ParseError(
|
|
13128
|
+
"Expected ']' after index expression",
|
|
13129
|
+
this.current,
|
|
13130
|
+
this.source,
|
|
13131
|
+
"Index access must be closed, e.g. arr[0]"
|
|
13132
|
+
);
|
|
13133
|
+
}
|
|
13134
|
+
|
|
13135
|
+
this.eat('RBRACKET');
|
|
13136
|
+
|
|
13137
|
+
node = {
|
|
13138
|
+
type: 'IndexExpression',
|
|
13139
|
+
object: node,
|
|
13140
|
+
indexer: index,
|
|
13141
|
+
line: startLine,
|
|
13142
|
+
column: startCol
|
|
13143
|
+
};
|
|
12228
13144
|
continue;
|
|
12229
13145
|
}
|
|
12230
13146
|
|
|
@@ -12232,13 +13148,46 @@ postfix() {
|
|
|
12232
13148
|
const startLine = t.line;
|
|
12233
13149
|
const startCol = t.column;
|
|
12234
13150
|
this.eat('LPAREN');
|
|
13151
|
+
|
|
12235
13152
|
const args = [];
|
|
12236
|
-
|
|
13153
|
+
|
|
13154
|
+
while (this.current.type !== 'RPAREN') {
|
|
13155
|
+
if (this.current.type === 'EOF') {
|
|
13156
|
+
throw new ParseError(
|
|
13157
|
+
"Unterminated function call",
|
|
13158
|
+
this.current,
|
|
13159
|
+
this.source,
|
|
13160
|
+
"Did you forget to close ')'?"
|
|
13161
|
+
);
|
|
13162
|
+
}
|
|
13163
|
+
|
|
12237
13164
|
args.push(this.expression());
|
|
12238
|
-
|
|
13165
|
+
|
|
13166
|
+
if (this.current.type === 'COMMA') {
|
|
13167
|
+
this.eat('COMMA');
|
|
13168
|
+
} else {
|
|
13169
|
+
break;
|
|
13170
|
+
}
|
|
13171
|
+
}
|
|
13172
|
+
|
|
13173
|
+
if (this.current.type !== 'RPAREN') {
|
|
13174
|
+
throw new ParseError(
|
|
13175
|
+
"Expected ')' after function arguments",
|
|
13176
|
+
this.current,
|
|
13177
|
+
this.source,
|
|
13178
|
+
"Function calls must be closed with ')'"
|
|
13179
|
+
);
|
|
12239
13180
|
}
|
|
12240
|
-
|
|
12241
|
-
|
|
13181
|
+
|
|
13182
|
+
this.eat('RPAREN');
|
|
13183
|
+
|
|
13184
|
+
node = {
|
|
13185
|
+
type: 'CallExpression',
|
|
13186
|
+
callee: node,
|
|
13187
|
+
arguments: args,
|
|
13188
|
+
line: startLine,
|
|
13189
|
+
column: startCol
|
|
13190
|
+
};
|
|
12242
13191
|
continue;
|
|
12243
13192
|
}
|
|
12244
13193
|
|
|
@@ -12246,21 +13195,46 @@ postfix() {
|
|
|
12246
13195
|
const startLine = t.line;
|
|
12247
13196
|
const startCol = t.column;
|
|
12248
13197
|
this.eat('DOT');
|
|
12249
|
-
|
|
12250
|
-
if (this.current.type
|
|
12251
|
-
|
|
13198
|
+
|
|
13199
|
+
if (this.current.type !== 'IDENTIFIER') {
|
|
13200
|
+
throw new ParseError(
|
|
13201
|
+
"Expected property name after '.'",
|
|
13202
|
+
this.current,
|
|
13203
|
+
this.source,
|
|
13204
|
+
"Member access requires a property name, e.g. obj.value"
|
|
13205
|
+
);
|
|
13206
|
+
}
|
|
13207
|
+
|
|
13208
|
+
const property = this.current.value;
|
|
13209
|
+
this.eat('IDENTIFIER');
|
|
13210
|
+
|
|
13211
|
+
node = {
|
|
13212
|
+
type: 'MemberExpression',
|
|
13213
|
+
object: node,
|
|
13214
|
+
property,
|
|
13215
|
+
line: startLine,
|
|
13216
|
+
column: startCol
|
|
13217
|
+
};
|
|
12252
13218
|
continue;
|
|
12253
13219
|
}
|
|
12254
13220
|
|
|
12255
13221
|
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
12256
13222
|
this.eat(t.type);
|
|
12257
|
-
|
|
13223
|
+
|
|
13224
|
+
node = {
|
|
13225
|
+
type: 'UpdateExpression',
|
|
13226
|
+
operator: t.type,
|
|
13227
|
+
argument: node,
|
|
13228
|
+
prefix: false,
|
|
13229
|
+
line: t.line,
|
|
13230
|
+
column: t.column
|
|
13231
|
+
};
|
|
12258
13232
|
continue;
|
|
12259
13233
|
}
|
|
12260
13234
|
|
|
12261
|
-
|
|
12262
13235
|
break;
|
|
12263
13236
|
}
|
|
13237
|
+
|
|
12264
13238
|
return node;
|
|
12265
13239
|
}
|
|
12266
13240
|
|
|
@@ -12272,10 +13246,19 @@ arrowFunction(params) {
|
|
|
12272
13246
|
let isBlock = false;
|
|
12273
13247
|
|
|
12274
13248
|
if (this.current.type === 'LBRACE') {
|
|
12275
|
-
body = this.block();
|
|
13249
|
+
body = this.block();
|
|
12276
13250
|
isBlock = true;
|
|
12277
|
-
}
|
|
12278
|
-
|
|
13251
|
+
}
|
|
13252
|
+
else if (this.current.type === 'EOF') {
|
|
13253
|
+
throw new ParseError(
|
|
13254
|
+
"Missing arrow function body",
|
|
13255
|
+
this.current,
|
|
13256
|
+
this.source,
|
|
13257
|
+
"Arrow functions require a body, e.g. x => x * 2"
|
|
13258
|
+
);
|
|
13259
|
+
}
|
|
13260
|
+
else {
|
|
13261
|
+
body = this.expression();
|
|
12279
13262
|
}
|
|
12280
13263
|
|
|
12281
13264
|
const startLine = params.length > 0 ? params[0].line : t.line;
|
|
@@ -12291,75 +13274,116 @@ arrowFunction(params) {
|
|
|
12291
13274
|
};
|
|
12292
13275
|
}
|
|
12293
13276
|
|
|
12294
|
-
|
|
12295
|
-
primary() {
|
|
13277
|
+
primary() {
|
|
12296
13278
|
const t = this.current;
|
|
12297
13279
|
|
|
12298
|
-
if (t.type === 'NUMBER') {
|
|
12299
|
-
this.eat('NUMBER');
|
|
12300
|
-
return { type: 'Literal', value: t.value, line: t.line, column: t.column };
|
|
13280
|
+
if (t.type === 'NUMBER') {
|
|
13281
|
+
this.eat('NUMBER');
|
|
13282
|
+
return { type: 'Literal', value: t.value, line: t.line, column: t.column };
|
|
12301
13283
|
}
|
|
12302
13284
|
|
|
12303
|
-
if (t.type === 'STRING') {
|
|
12304
|
-
this.eat('STRING');
|
|
12305
|
-
return { type: 'Literal', value: t.value, line: t.line, column: t.column };
|
|
13285
|
+
if (t.type === 'STRING') {
|
|
13286
|
+
this.eat('STRING');
|
|
13287
|
+
return { type: 'Literal', value: t.value, line: t.line, column: t.column };
|
|
12306
13288
|
}
|
|
12307
13289
|
|
|
12308
|
-
if (t.type === 'TRUE') {
|
|
12309
|
-
this.eat('TRUE');
|
|
12310
|
-
return { type: 'Literal', value: true, line: t.line, column: t.column };
|
|
13290
|
+
if (t.type === 'TRUE') {
|
|
13291
|
+
this.eat('TRUE');
|
|
13292
|
+
return { type: 'Literal', value: true, line: t.line, column: t.column };
|
|
12311
13293
|
}
|
|
12312
13294
|
|
|
12313
|
-
if (t.type === 'FALSE') {
|
|
12314
|
-
this.eat('FALSE');
|
|
12315
|
-
return { type: 'Literal', value: false, line: t.line, column: t.column };
|
|
13295
|
+
if (t.type === 'FALSE') {
|
|
13296
|
+
this.eat('FALSE');
|
|
13297
|
+
return { type: 'Literal', value: false, line: t.line, column: t.column };
|
|
12316
13298
|
}
|
|
12317
13299
|
|
|
12318
|
-
if (t.type === 'NULL') {
|
|
12319
|
-
this.eat('NULL');
|
|
12320
|
-
return { type: 'Literal', value: null, line: t.line, column: t.column };
|
|
13300
|
+
if (t.type === 'NULL') {
|
|
13301
|
+
this.eat('NULL');
|
|
13302
|
+
return { type: 'Literal', value: null, line: t.line, column: t.column };
|
|
12321
13303
|
}
|
|
12322
13304
|
|
|
12323
13305
|
if (t.type === 'AWAIT') {
|
|
12324
13306
|
this.eat('AWAIT');
|
|
12325
|
-
const argument = this.expression();
|
|
13307
|
+
const argument = this.expression();
|
|
12326
13308
|
return { type: 'AwaitExpression', argument, line: t.line, column: t.column };
|
|
12327
13309
|
}
|
|
12328
13310
|
|
|
12329
13311
|
if (t.type === 'NEW') {
|
|
12330
13312
|
this.eat('NEW');
|
|
12331
13313
|
const callee = this.primary();
|
|
13314
|
+
|
|
13315
|
+
if (this.current.type !== 'LPAREN') {
|
|
13316
|
+
throw new ParseError(
|
|
13317
|
+
"Expected '(' after 'new'",
|
|
13318
|
+
this.current,
|
|
13319
|
+
this.source,
|
|
13320
|
+
"Use 'new ClassName(...)'"
|
|
13321
|
+
);
|
|
13322
|
+
}
|
|
13323
|
+
|
|
12332
13324
|
this.eat('LPAREN');
|
|
12333
13325
|
const args = [];
|
|
12334
13326
|
if (this.current.type !== 'RPAREN') {
|
|
12335
13327
|
args.push(this.expression());
|
|
12336
13328
|
while (this.current.type === 'COMMA') {
|
|
12337
13329
|
this.eat('COMMA');
|
|
12338
|
-
if (this.current.type
|
|
13330
|
+
if (this.current.type === 'RPAREN') break;
|
|
13331
|
+
args.push(this.expression());
|
|
12339
13332
|
}
|
|
12340
13333
|
}
|
|
13334
|
+
|
|
13335
|
+
if (this.current.type !== 'RPAREN') {
|
|
13336
|
+
throw new ParseError(
|
|
13337
|
+
"Expected ')' to close arguments for 'new'",
|
|
13338
|
+
this.current,
|
|
13339
|
+
this.source
|
|
13340
|
+
);
|
|
13341
|
+
}
|
|
13342
|
+
|
|
12341
13343
|
this.eat('RPAREN');
|
|
13344
|
+
|
|
12342
13345
|
return { type: 'NewExpression', callee, arguments: args, line: t.line, column: t.column };
|
|
12343
13346
|
}
|
|
12344
13347
|
|
|
13348
|
+
// ---- ask(...) function call ----
|
|
12345
13349
|
if (t.type === 'ASK') {
|
|
12346
13350
|
this.eat('ASK');
|
|
13351
|
+
|
|
13352
|
+
if (this.current.type !== 'LPAREN') {
|
|
13353
|
+
throw new ParseError(
|
|
13354
|
+
"Expected '(' after 'ask'",
|
|
13355
|
+
this.current,
|
|
13356
|
+
this.source
|
|
13357
|
+
);
|
|
13358
|
+
}
|
|
13359
|
+
|
|
12347
13360
|
this.eat('LPAREN');
|
|
12348
13361
|
const args = [];
|
|
12349
13362
|
if (this.current.type !== 'RPAREN') {
|
|
12350
13363
|
args.push(this.expression());
|
|
12351
13364
|
while (this.current.type === 'COMMA') {
|
|
12352
13365
|
this.eat('COMMA');
|
|
12353
|
-
if (this.current.type
|
|
13366
|
+
if (this.current.type === 'RPAREN') break;
|
|
13367
|
+
args.push(this.expression());
|
|
12354
13368
|
}
|
|
12355
13369
|
}
|
|
13370
|
+
|
|
13371
|
+
if (this.current.type !== 'RPAREN') {
|
|
13372
|
+
throw new ParseError(
|
|
13373
|
+
"Expected ')' after arguments to 'ask'",
|
|
13374
|
+
this.current,
|
|
13375
|
+
this.source
|
|
13376
|
+
);
|
|
13377
|
+
}
|
|
13378
|
+
|
|
12356
13379
|
this.eat('RPAREN');
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
13380
|
+
|
|
13381
|
+
return {
|
|
13382
|
+
type: 'CallExpression',
|
|
13383
|
+
callee: { type: 'Identifier', name: 'ask', line: t.line, column: t.column },
|
|
13384
|
+
arguments: args,
|
|
13385
|
+
line: t.line,
|
|
13386
|
+
column: t.column
|
|
12363
13387
|
};
|
|
12364
13388
|
}
|
|
12365
13389
|
|
|
@@ -12378,89 +13402,120 @@ arrowFunction(params) {
|
|
|
12378
13402
|
const startLine = t.line;
|
|
12379
13403
|
const startCol = t.column;
|
|
12380
13404
|
this.eat('LPAREN');
|
|
12381
|
-
const elements = [];
|
|
12382
13405
|
|
|
13406
|
+
const elements = [];
|
|
12383
13407
|
if (this.current.type !== 'RPAREN') {
|
|
12384
13408
|
elements.push(this.expression());
|
|
12385
13409
|
while (this.current.type === 'COMMA') {
|
|
12386
13410
|
this.eat('COMMA');
|
|
12387
|
-
if (this.current.type
|
|
13411
|
+
if (this.current.type === 'RPAREN') break;
|
|
13412
|
+
elements.push(this.expression());
|
|
12388
13413
|
}
|
|
12389
13414
|
}
|
|
12390
13415
|
|
|
13416
|
+
if (this.current.type !== 'RPAREN') {
|
|
13417
|
+
throw new ParseError(
|
|
13418
|
+
"Expected ')' after expression",
|
|
13419
|
+
this.current,
|
|
13420
|
+
this.source
|
|
13421
|
+
);
|
|
13422
|
+
}
|
|
13423
|
+
|
|
12391
13424
|
this.eat('RPAREN');
|
|
12392
13425
|
|
|
12393
|
-
if (this.current.type === 'ARROW')
|
|
13426
|
+
if (this.current.type === 'ARROW') {
|
|
13427
|
+
return this.arrowFunction(elements);
|
|
13428
|
+
}
|
|
12394
13429
|
|
|
12395
|
-
return elements.length === 1
|
|
13430
|
+
return elements.length === 1
|
|
13431
|
+
? elements[0]
|
|
13432
|
+
: { type: 'ArrayExpression', elements, line: startLine, column: startCol };
|
|
12396
13433
|
}
|
|
12397
13434
|
|
|
12398
13435
|
if (t.type === 'LBRACKET') {
|
|
12399
13436
|
const startLine = t.line;
|
|
12400
13437
|
const startCol = t.column;
|
|
12401
13438
|
this.eat('LBRACKET');
|
|
13439
|
+
|
|
12402
13440
|
const elements = [];
|
|
12403
13441
|
if (this.current.type !== 'RBRACKET') {
|
|
12404
13442
|
elements.push(this.expression());
|
|
12405
13443
|
while (this.current.type === 'COMMA') {
|
|
12406
13444
|
this.eat('COMMA');
|
|
12407
|
-
if (this.current.type
|
|
13445
|
+
if (this.current.type === 'RBRACKET') break;
|
|
13446
|
+
elements.push(this.expression());
|
|
12408
13447
|
}
|
|
12409
13448
|
}
|
|
13449
|
+
|
|
13450
|
+
if (this.current.type !== 'RBRACKET') {
|
|
13451
|
+
throw new ParseError(
|
|
13452
|
+
"Expected ']' after array elements",
|
|
13453
|
+
this.current,
|
|
13454
|
+
this.source
|
|
13455
|
+
);
|
|
13456
|
+
}
|
|
13457
|
+
|
|
12410
13458
|
this.eat('RBRACKET');
|
|
13459
|
+
|
|
12411
13460
|
return { type: 'ArrayExpression', elements, line: startLine, column: startCol };
|
|
12412
13461
|
}
|
|
12413
13462
|
|
|
12414
13463
|
if (t.type === 'LBRACE') {
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
13464
|
+
const startLine = t.line;
|
|
13465
|
+
const startCol = t.column;
|
|
13466
|
+
this.eat('LBRACE');
|
|
12418
13467
|
|
|
12419
|
-
|
|
13468
|
+
const props = [];
|
|
12420
13469
|
|
|
12421
|
-
|
|
12422
|
-
|
|
13470
|
+
while (this.current.type !== 'RBRACE') {
|
|
13471
|
+
if (this.current.type === 'EOF') {
|
|
13472
|
+
throw new ParseError(
|
|
13473
|
+
"Unterminated object literal",
|
|
13474
|
+
this.current,
|
|
13475
|
+
this.source,
|
|
13476
|
+
"Did you forget to close '}'?"
|
|
13477
|
+
);
|
|
13478
|
+
}
|
|
12423
13479
|
|
|
12424
|
-
|
|
12425
|
-
const k = this.current;
|
|
12426
|
-
this.eat('IDENTIFIER');
|
|
12427
|
-
key = {
|
|
12428
|
-
type: 'Literal',
|
|
12429
|
-
value: k.value,
|
|
12430
|
-
line: k.line,
|
|
12431
|
-
column: k.column
|
|
12432
|
-
};
|
|
12433
|
-
}
|
|
12434
|
-
else if (this.current.type === 'STRING') {
|
|
12435
|
-
const k = this.current;
|
|
12436
|
-
this.eat('STRING');
|
|
12437
|
-
key = {
|
|
12438
|
-
type: 'Literal',
|
|
12439
|
-
value: k.value,
|
|
12440
|
-
line: k.line,
|
|
12441
|
-
column: k.column
|
|
12442
|
-
};
|
|
12443
|
-
}
|
|
12444
|
-
else {
|
|
12445
|
-
throw new ParseError(
|
|
12446
|
-
'Invalid object key',
|
|
12447
|
-
this.current,
|
|
12448
|
-
this.source
|
|
12449
|
-
);
|
|
12450
|
-
}
|
|
13480
|
+
let key;
|
|
12451
13481
|
|
|
12452
|
-
|
|
12453
|
-
|
|
13482
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
13483
|
+
const k = this.current;
|
|
13484
|
+
this.eat('IDENTIFIER');
|
|
13485
|
+
key = { type: 'Literal', value: k.value, line: k.line, column: k.column };
|
|
13486
|
+
}
|
|
13487
|
+
else if (this.current.type === 'STRING') {
|
|
13488
|
+
const k = this.current;
|
|
13489
|
+
this.eat('STRING');
|
|
13490
|
+
key = { type: 'Literal', value: k.value, line: k.line, column: k.column };
|
|
13491
|
+
}
|
|
13492
|
+
else {
|
|
13493
|
+
throw new ParseError(
|
|
13494
|
+
'Invalid object key',
|
|
13495
|
+
this.current,
|
|
13496
|
+
this.source,
|
|
13497
|
+
"Object keys must be identifiers or strings"
|
|
13498
|
+
);
|
|
13499
|
+
}
|
|
12454
13500
|
|
|
12455
|
-
|
|
13501
|
+
if (this.current.type !== 'COLON') {
|
|
13502
|
+
throw new ParseError(
|
|
13503
|
+
"Expected ':' after object key",
|
|
13504
|
+
this.current,
|
|
13505
|
+
this.source
|
|
13506
|
+
);
|
|
13507
|
+
}
|
|
12456
13508
|
|
|
12457
|
-
|
|
12458
|
-
|
|
13509
|
+
this.eat('COLON');
|
|
13510
|
+
const value = this.expression();
|
|
13511
|
+
props.push({ key, value });
|
|
12459
13512
|
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
}
|
|
13513
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
13514
|
+
}
|
|
12463
13515
|
|
|
13516
|
+
this.eat('RBRACE');
|
|
13517
|
+
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|
|
13518
|
+
}
|
|
12464
13519
|
|
|
12465
13520
|
throw new ParseError(
|
|
12466
13521
|
`Unexpected token '${t.type}'`,
|
|
@@ -12469,7 +13524,6 @@ arrowFunction(params) {
|
|
|
12469
13524
|
);
|
|
12470
13525
|
}
|
|
12471
13526
|
|
|
12472
|
-
|
|
12473
13527
|
}
|
|
12474
13528
|
|
|
12475
13529
|
module.exports = Parser;
|
|
@@ -12652,7 +13706,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
12652
13706
|
const Parser = __nccwpck_require__(222);
|
|
12653
13707
|
const Evaluator = __nccwpck_require__(112);
|
|
12654
13708
|
|
|
12655
|
-
const VERSION = '1.1.
|
|
13709
|
+
const VERSION = '1.1.12';
|
|
12656
13710
|
|
|
12657
13711
|
const COLOR = {
|
|
12658
13712
|
reset: '\x1b[0m',
|