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