starlight-cli 1.1.11 → 1.1.13

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