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