starlight-cli 1.1.9 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  Starlight is a lightweight, developer-oriented programming language designed for **server-side scripting, automation, and general-purpose programming**. It combines a clean, readable syntax inspired by JavaScript and Python with powerful runtime features such as async/await, modules, and interactive I/O.
5
5
 
6
6
  **Official Reference:**
7
- https://developerdominex.github.io/starlight-programming-language/
7
+ https://dominexmacedon.github.io/starlight-programming-language/learn.html
8
8
 
9
9
  ---
10
10
 
package/dist/index.js CHANGED
@@ -10230,7 +10230,13 @@ class RuntimeError extends Error {
10230
10230
  const lines = source.split('\n');
10231
10231
  const srcLine = lines[node.line - 1] || '';
10232
10232
  output += ` ${srcLine}\n`;
10233
- output += ` ${' '.repeat(column - 1)}^\n`;
10233
+ const caretPos =
10234
+ typeof column === 'number' && column > 0
10235
+ ? column - 1
10236
+ : 0;
10237
+
10238
+ output += ` ${' '.repeat(caretPos)}^\n`;
10239
+ ;
10234
10240
  }
10235
10241
 
10236
10242
  super(output);
@@ -10292,6 +10298,34 @@ class Evaluator {
10292
10298
  this.global = new Environment();
10293
10299
  this.setupBuiltins();
10294
10300
  }
10301
+ async callFunction(fn, args, env, node = null) {
10302
+ if (typeof fn === 'function') {
10303
+ const val = await fn(...args);
10304
+ return val === undefined ? null : val; // <<< enforce null
10305
+ }
10306
+
10307
+ if (fn && typeof fn === 'object' && fn.body && fn.params) {
10308
+ const callEnv = new Environment(fn.env);
10309
+
10310
+ for (let i = 0; i < fn.params.length; i++) {
10311
+ const param = fn.params[i];
10312
+ const name = typeof param === 'string' ? param : param.name;
10313
+ callEnv.define(name, args[i]);
10314
+ }
10315
+
10316
+ try {
10317
+ const val = await this.evaluate(fn.body, callEnv);
10318
+ return val === undefined ? null : val; // <<< enforce null
10319
+ } catch (e) {
10320
+ if (e instanceof ReturnValue) return e.value === undefined ? null : e.value; // <<< enforce null
10321
+ throw e;
10322
+ }
10323
+ }
10324
+
10325
+ throw new RuntimeError('Value is not callable', node, this.source);
10326
+ }
10327
+
10328
+
10295
10329
  suggest(name, env) {
10296
10330
  const names = new Set();
10297
10331
  let current = env;
@@ -10365,6 +10399,7 @@ formatValue(value, seen = new Set()) {
10365
10399
 
10366
10400
 
10367
10401
  setupBuiltins() {
10402
+ const evaluator = this;
10368
10403
  this.global.define('len', arg => {
10369
10404
  if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
10370
10405
  if (arg && typeof arg === 'object') return Object.keys(arg).length;
@@ -10376,63 +10411,76 @@ formatValue(value, seen = new Set()) {
10376
10411
  if (Array.isArray(arg)) return 'array';
10377
10412
  return typeof arg;
10378
10413
  });
10379
- this.global.define('map', async (array, fn) => {
10380
- if (!Array.isArray(array)) {
10381
- throw new RuntimeError('map() expects an array', null, this.source);
10382
- }
10383
- if (typeof fn !== 'function') {
10384
- throw new RuntimeError('map() expects a function', null, this.source);
10385
- }
10414
+ this.global.define('map', async (array, fn) => {
10415
+ if (!Array.isArray(array)) {
10416
+ throw new RuntimeError('map() expects an array', null, evaluator.source);
10417
+ }
10386
10418
 
10387
- const result = [];
10388
- for (let i = 0; i < array.length; i++) {
10389
- result.push(await fn(array[i], i, array));
10390
- }
10391
- return result;
10392
- });
10393
- this.global.define('filter', async (array, fn) => {
10394
- if (!Array.isArray(array)) {
10395
- throw new RuntimeError('filter() expects an array', null, this.source);
10396
- }
10397
- if (typeof fn !== 'function') {
10398
- throw new RuntimeError('filter() expects a function', null, this.source);
10399
- }
10419
+ const result = [];
10420
+ for (let i = 0; i < array.length; i++) {
10421
+ result.push(
10422
+ await evaluator.callFunction(
10423
+ fn,
10424
+ [array[i], i, array],
10425
+ evaluator.global
10426
+ )
10427
+ );
10428
+ }
10429
+ return result;
10430
+ });
10400
10431
 
10401
- const result = [];
10402
- for (let i = 0; i < array.length; i++) {
10403
- if (await fn(array[i], i, array)) {
10404
- result.push(array[i]);
10432
+ this.global.define('filter', async (array, fn) => {
10433
+ if (!Array.isArray(array)) {
10434
+ throw new RuntimeError('filter() expects an array', null, evaluator.source);
10405
10435
  }
10406
- }
10407
- return result;
10408
- });
10409
- this.global.define('reduce', async (array, fn, initial) => {
10410
- if (!Array.isArray(array)) {
10411
- throw new RuntimeError('reduce() expects an array', null, this.source);
10412
- }
10413
- if (typeof fn !== 'function') {
10414
- throw new RuntimeError('reduce() expects a function', null, this.source);
10415
- }
10416
10436
 
10417
- let acc;
10418
- let startIndex = 0;
10437
+ const result = [];
10438
+ for (let i = 0; i < array.length; i++) {
10439
+ if (
10440
+ await evaluator.callFunction(
10441
+ fn,
10442
+ [array[i], i, array],
10443
+ evaluator.global
10444
+ )
10445
+ ) {
10446
+ result.push(array[i]);
10447
+ }
10448
+ }
10449
+ return result;
10450
+ });
10419
10451
 
10420
- if (initial !== undefined) {
10421
- acc = initial;
10422
- } else {
10423
- if (array.length === 0) {
10424
- throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
10452
+ this.global.define('reduce', async (array, fn, initial) => {
10453
+ if (!Array.isArray(array)) {
10454
+ throw new RuntimeError('reduce() expects an array', null, evaluator.source);
10425
10455
  }
10426
- acc = array[0];
10427
- startIndex = 1;
10428
- }
10429
10456
 
10430
- for (let i = startIndex; i < array.length; i++) {
10431
- acc = await fn(acc, array[i], i, array);
10432
- }
10457
+ let acc;
10458
+ let i = 0;
10433
10459
 
10434
- return acc;
10435
- });
10460
+ if (initial !== undefined) {
10461
+ acc = initial;
10462
+ } else {
10463
+ if (array.length === 0) {
10464
+ throw new RuntimeError(
10465
+ 'reduce() of empty array with no initial value',
10466
+ null,
10467
+ evaluator.source
10468
+ );
10469
+ }
10470
+ acc = array[0];
10471
+ i = 1;
10472
+ }
10473
+
10474
+ for (; i < array.length; i++) {
10475
+ acc = await evaluator.callFunction(
10476
+ fn,
10477
+ [acc, array[i], i, array],
10478
+ evaluator.global
10479
+ );
10480
+ }
10481
+
10482
+ return acc;
10483
+ });
10436
10484
 
10437
10485
  this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
10438
10486
  this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
@@ -10586,21 +10634,30 @@ case 'RaceClause':
10586
10634
  const callee = await this.evaluate(node.callee, env);
10587
10635
 
10588
10636
  if (typeof callee === 'object' && callee.body) {
10589
- const evaluator = this;
10590
- const Constructor = function(...args) {
10637
+ const evaluator = this;
10638
+
10639
+ const Constructor = function (...args) {
10591
10640
  const newEnv = new Environment(callee.env);
10592
10641
  newEnv.define('this', this);
10642
+
10593
10643
  for (let i = 0; i < callee.params.length; i++) {
10594
- newEnv.define(callee.params[i], args[i]);
10644
+ const param = callee.params[i];
10645
+ const paramName = typeof param === 'string' ? param : param.name;
10646
+ newEnv.define(paramName, args[i]);
10595
10647
  }
10596
- return evaluator.evaluate(callee.body, newEnv);
10648
+
10649
+ return (async () => {
10650
+ await evaluator.evaluate(callee.body, newEnv);
10651
+ return this;
10652
+ })();
10597
10653
  };
10598
10654
 
10599
10655
  const args = [];
10600
10656
  for (const a of node.arguments) args.push(await this.evaluate(a, env));
10601
- return new Constructor(...args);
10657
+ return await new Constructor(...args);
10602
10658
  }
10603
10659
 
10660
+ // native JS constructor fallback
10604
10661
  if (typeof callee !== 'function') {
10605
10662
  throw new RuntimeError('NewExpression callee is not a function', node, this.source);
10606
10663
  }
@@ -10822,27 +10879,31 @@ evalArrowFunction(node, env) {
10822
10879
  return async function (...args) {
10823
10880
  const localEnv = new Environment(env);
10824
10881
 
10882
+ // Bind parameters safely
10825
10883
  node.params.forEach((p, i) => {
10826
- localEnv.define(p.name, args[i]);
10884
+ const paramName = typeof p === 'string' ? p : p.name;
10885
+ localEnv.define(paramName, args[i]);
10827
10886
  });
10828
10887
 
10829
10888
  try {
10830
10889
  if (node.isBlock) {
10890
+ // Block body
10831
10891
  const result = await evaluator.evaluate(node.body, localEnv);
10832
- return result ?? null;
10892
+ return result === undefined ? null : result; // ensure null instead of undefined
10833
10893
  } else {
10834
- return await evaluator.evaluate(node.body, localEnv);
10894
+ // Expression body
10895
+ const result = await evaluator.evaluate(node.body, localEnv);
10896
+ return result === undefined ? null : result; // ensure null instead of undefined
10835
10897
  }
10836
10898
  } catch (err) {
10837
- if (err instanceof ReturnValue) {
10838
- return err.value;
10839
- }
10899
+ if (err instanceof ReturnValue) return err.value === undefined ? null : err.value;
10840
10900
  throw err;
10841
10901
  }
10842
10902
  };
10843
10903
  }
10844
10904
 
10845
10905
 
10906
+
10846
10907
  async evalAssignment(node, env) {
10847
10908
  const rightVal = await this.evaluate(node.right, env);
10848
10909
  const left = node.left;
@@ -11183,18 +11244,26 @@ async evalIndex(node, env) {
11183
11244
  async evalObject(node, env) {
11184
11245
  const out = {};
11185
11246
  for (const p of node.props) {
11186
- if (!p.key || !p.value) {
11187
- throw new RuntimeError('Invalid object property', node, this.source);
11247
+ if (!p.key) {
11248
+ throw new RuntimeError('Object property must have a key', node, this.source);
11188
11249
  }
11250
+
11189
11251
  const key = await this.evaluate(p.key, env);
11190
- const value = await this.evaluate(p.value, env);
11191
- out[key] = value; // dynamic property assignment
11252
+ let value = null;
11253
+
11254
+ if (p.value) {
11255
+ value = await this.evaluate(p.value, env);
11256
+ if (value === undefined) value = null; // <- force null instead of undefined
11257
+ }
11258
+
11259
+ out[key] = value;
11192
11260
  }
11193
11261
  return out;
11194
11262
  }
11195
11263
 
11196
11264
 
11197
11265
 
11266
+
11198
11267
  async evalMember(node, env) {
11199
11268
  const obj = await this.evaluate(node.object, env);
11200
11269
 
@@ -11234,7 +11303,7 @@ async evalUpdate(node, env) {
11234
11303
 
11235
11304
  }
11236
11305
 
11237
- module.exports = Evaluator;
11306
+ module.exports = Evaluator;
11238
11307
 
11239
11308
  /***/ }),
11240
11309
 
@@ -11729,14 +11798,18 @@ ifStatement() {
11729
11798
  test = this.expression();
11730
11799
  }
11731
11800
 
11732
- const consequent = this.block();
11801
+ const consequent = this.statementOrBlock();
11733
11802
 
11734
11803
  let alternate = null;
11735
11804
  if (this.current.type === 'ELSE') {
11736
- this.eat('ELSE');
11737
- if (this.current.type === 'IF') alternate = this.ifStatement();
11738
- else alternate = this.block();
11805
+ this.eat('ELSE');
11806
+ if (this.current.type === 'IF') {
11807
+ alternate = this.ifStatement();
11808
+ } else {
11809
+ alternate = this.statementOrBlock();
11739
11810
  }
11811
+ }
11812
+
11740
11813
 
11741
11814
  return {
11742
11815
  type: 'IfStatement',
@@ -11970,6 +12043,12 @@ returnStatement() {
11970
12043
 
11971
12044
  return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
11972
12045
  }
12046
+ statementOrBlock() {
12047
+ if (this.current.type === 'LBRACE') {
12048
+ return this.block();
12049
+ }
12050
+ return this.statement();
12051
+ }
11973
12052
 
11974
12053
  block() {
11975
12054
  const t = this.current; // LBRACE token
@@ -12179,13 +12258,7 @@ postfix() {
12179
12258
  continue;
12180
12259
  }
12181
12260
 
12182
- if (node.type === 'Identifier' && t.type === 'IDENTIFIER') {
12183
- const argNode = { type: 'Identifier', name: t.value, line: t.line, column: t.column };
12184
- this.eat('IDENTIFIER');
12185
- node = { type: 'CallExpression', callee: node, arguments: [argNode], line: node.line, column: node.column };
12186
- continue;
12187
- }
12188
-
12261
+
12189
12262
  break;
12190
12263
  }
12191
12264
  return node;
@@ -12339,21 +12412,56 @@ arrowFunction(params) {
12339
12412
  }
12340
12413
 
12341
12414
  if (t.type === 'LBRACE') {
12342
- const startLine = t.line;
12343
- const startCol = t.column;
12344
- this.eat('LBRACE');
12345
- const props = [];
12346
- while (this.current.type !== 'RBRACE') {
12347
- const key = this.expression();
12348
- this.eat('COLON');
12349
- const value = this.expression();
12350
- props.push({ key, value });
12351
- if (this.current.type === 'COMMA') this.eat('COMMA');
12415
+ const startLine = t.line;
12416
+ const startCol = t.column;
12417
+ this.eat('LBRACE');
12418
+
12419
+ const props = [];
12420
+
12421
+ while (this.current.type !== 'RBRACE') {
12422
+ let key;
12423
+
12424
+ if (this.current.type === 'IDENTIFIER') {
12425
+ const k = this.current;
12426
+ this.eat('IDENTIFIER');
12427
+ key = {
12428
+ type: 'Literal',
12429
+ value: k.value,
12430
+ line: k.line,
12431
+ column: k.column
12432
+ };
12352
12433
  }
12353
- this.eat('RBRACE');
12354
- return { type: 'ObjectExpression', props, line: startLine, column: startCol };
12434
+ else if (this.current.type === 'STRING') {
12435
+ const k = this.current;
12436
+ this.eat('STRING');
12437
+ key = {
12438
+ type: 'Literal',
12439
+ value: k.value,
12440
+ line: k.line,
12441
+ column: k.column
12442
+ };
12443
+ }
12444
+ else {
12445
+ throw new ParseError(
12446
+ 'Invalid object key',
12447
+ this.current,
12448
+ this.source
12449
+ );
12450
+ }
12451
+
12452
+ this.eat('COLON');
12453
+ const value = this.expression();
12454
+
12455
+ props.push({ key, value });
12456
+
12457
+ if (this.current.type === 'COMMA') this.eat('COMMA');
12355
12458
  }
12356
12459
 
12460
+ this.eat('RBRACE');
12461
+ return { type: 'ObjectExpression', props, line: startLine, column: startCol };
12462
+ }
12463
+
12464
+
12357
12465
  throw new ParseError(
12358
12466
  `Unexpected token '${t.type}'`,
12359
12467
  t,
@@ -12544,7 +12652,7 @@ const Lexer = __nccwpck_require__(211);
12544
12652
  const Parser = __nccwpck_require__(222);
12545
12653
  const Evaluator = __nccwpck_require__(112);
12546
12654
 
12547
- const VERSION = '1.1.9';
12655
+ const VERSION = '1.1.10';
12548
12656
 
12549
12657
  const COLOR = {
12550
12658
  reset: '\x1b[0m',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -20,7 +20,13 @@ class RuntimeError extends Error {
20
20
  const lines = source.split('\n');
21
21
  const srcLine = lines[node.line - 1] || '';
22
22
  output += ` ${srcLine}\n`;
23
- output += ` ${' '.repeat(column - 1)}^\n`;
23
+ const caretPos =
24
+ typeof column === 'number' && column > 0
25
+ ? column - 1
26
+ : 0;
27
+
28
+ output += ` ${' '.repeat(caretPos)}^\n`;
29
+ ;
24
30
  }
25
31
 
26
32
  super(output);
@@ -82,6 +88,34 @@ class Evaluator {
82
88
  this.global = new Environment();
83
89
  this.setupBuiltins();
84
90
  }
91
+ async callFunction(fn, args, env, node = null) {
92
+ if (typeof fn === 'function') {
93
+ const val = await fn(...args);
94
+ return val === undefined ? null : val; // <<< enforce null
95
+ }
96
+
97
+ if (fn && typeof fn === 'object' && fn.body && fn.params) {
98
+ const callEnv = new Environment(fn.env);
99
+
100
+ for (let i = 0; i < fn.params.length; i++) {
101
+ const param = fn.params[i];
102
+ const name = typeof param === 'string' ? param : param.name;
103
+ callEnv.define(name, args[i]);
104
+ }
105
+
106
+ try {
107
+ const val = await this.evaluate(fn.body, callEnv);
108
+ return val === undefined ? null : val; // <<< enforce null
109
+ } catch (e) {
110
+ if (e instanceof ReturnValue) return e.value === undefined ? null : e.value; // <<< enforce null
111
+ throw e;
112
+ }
113
+ }
114
+
115
+ throw new RuntimeError('Value is not callable', node, this.source);
116
+ }
117
+
118
+
85
119
  suggest(name, env) {
86
120
  const names = new Set();
87
121
  let current = env;
@@ -155,6 +189,7 @@ formatValue(value, seen = new Set()) {
155
189
 
156
190
 
157
191
  setupBuiltins() {
192
+ const evaluator = this;
158
193
  this.global.define('len', arg => {
159
194
  if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
160
195
  if (arg && typeof arg === 'object') return Object.keys(arg).length;
@@ -166,63 +201,76 @@ formatValue(value, seen = new Set()) {
166
201
  if (Array.isArray(arg)) return 'array';
167
202
  return typeof arg;
168
203
  });
169
- this.global.define('map', async (array, fn) => {
170
- if (!Array.isArray(array)) {
171
- throw new RuntimeError('map() expects an array', null, this.source);
172
- }
173
- if (typeof fn !== 'function') {
174
- throw new RuntimeError('map() expects a function', null, this.source);
175
- }
204
+ this.global.define('map', async (array, fn) => {
205
+ if (!Array.isArray(array)) {
206
+ throw new RuntimeError('map() expects an array', null, evaluator.source);
207
+ }
176
208
 
177
- const result = [];
178
- for (let i = 0; i < array.length; i++) {
179
- result.push(await fn(array[i], i, array));
180
- }
181
- return result;
182
- });
183
- this.global.define('filter', async (array, fn) => {
184
- if (!Array.isArray(array)) {
185
- throw new RuntimeError('filter() expects an array', null, this.source);
186
- }
187
- if (typeof fn !== 'function') {
188
- throw new RuntimeError('filter() expects a function', null, this.source);
189
- }
209
+ const result = [];
210
+ for (let i = 0; i < array.length; i++) {
211
+ result.push(
212
+ await evaluator.callFunction(
213
+ fn,
214
+ [array[i], i, array],
215
+ evaluator.global
216
+ )
217
+ );
218
+ }
219
+ return result;
220
+ });
190
221
 
191
- const result = [];
192
- for (let i = 0; i < array.length; i++) {
193
- if (await fn(array[i], i, array)) {
194
- result.push(array[i]);
222
+ this.global.define('filter', async (array, fn) => {
223
+ if (!Array.isArray(array)) {
224
+ throw new RuntimeError('filter() expects an array', null, evaluator.source);
195
225
  }
196
- }
197
- return result;
198
- });
199
- this.global.define('reduce', async (array, fn, initial) => {
200
- if (!Array.isArray(array)) {
201
- throw new RuntimeError('reduce() expects an array', null, this.source);
202
- }
203
- if (typeof fn !== 'function') {
204
- throw new RuntimeError('reduce() expects a function', null, this.source);
205
- }
206
226
 
207
- let acc;
208
- let startIndex = 0;
227
+ const result = [];
228
+ for (let i = 0; i < array.length; i++) {
229
+ if (
230
+ await evaluator.callFunction(
231
+ fn,
232
+ [array[i], i, array],
233
+ evaluator.global
234
+ )
235
+ ) {
236
+ result.push(array[i]);
237
+ }
238
+ }
239
+ return result;
240
+ });
209
241
 
210
- if (initial !== undefined) {
211
- acc = initial;
212
- } else {
213
- if (array.length === 0) {
214
- throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
242
+ this.global.define('reduce', async (array, fn, initial) => {
243
+ if (!Array.isArray(array)) {
244
+ throw new RuntimeError('reduce() expects an array', null, evaluator.source);
215
245
  }
216
- acc = array[0];
217
- startIndex = 1;
218
- }
219
246
 
220
- for (let i = startIndex; i < array.length; i++) {
221
- acc = await fn(acc, array[i], i, array);
222
- }
247
+ let acc;
248
+ let i = 0;
223
249
 
224
- return acc;
225
- });
250
+ if (initial !== undefined) {
251
+ acc = initial;
252
+ } else {
253
+ if (array.length === 0) {
254
+ throw new RuntimeError(
255
+ 'reduce() of empty array with no initial value',
256
+ null,
257
+ evaluator.source
258
+ );
259
+ }
260
+ acc = array[0];
261
+ i = 1;
262
+ }
263
+
264
+ for (; i < array.length; i++) {
265
+ acc = await evaluator.callFunction(
266
+ fn,
267
+ [acc, array[i], i, array],
268
+ evaluator.global
269
+ );
270
+ }
271
+
272
+ return acc;
273
+ });
226
274
 
227
275
  this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
228
276
  this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
@@ -376,21 +424,30 @@ case 'RaceClause':
376
424
  const callee = await this.evaluate(node.callee, env);
377
425
 
378
426
  if (typeof callee === 'object' && callee.body) {
379
- const evaluator = this;
380
- const Constructor = function(...args) {
427
+ const evaluator = this;
428
+
429
+ const Constructor = function (...args) {
381
430
  const newEnv = new Environment(callee.env);
382
431
  newEnv.define('this', this);
432
+
383
433
  for (let i = 0; i < callee.params.length; i++) {
384
- newEnv.define(callee.params[i], args[i]);
434
+ const param = callee.params[i];
435
+ const paramName = typeof param === 'string' ? param : param.name;
436
+ newEnv.define(paramName, args[i]);
385
437
  }
386
- return evaluator.evaluate(callee.body, newEnv);
438
+
439
+ return (async () => {
440
+ await evaluator.evaluate(callee.body, newEnv);
441
+ return this;
442
+ })();
387
443
  };
388
444
 
389
445
  const args = [];
390
446
  for (const a of node.arguments) args.push(await this.evaluate(a, env));
391
- return new Constructor(...args);
447
+ return await new Constructor(...args);
392
448
  }
393
449
 
450
+ // native JS constructor fallback
394
451
  if (typeof callee !== 'function') {
395
452
  throw new RuntimeError('NewExpression callee is not a function', node, this.source);
396
453
  }
@@ -612,27 +669,31 @@ evalArrowFunction(node, env) {
612
669
  return async function (...args) {
613
670
  const localEnv = new Environment(env);
614
671
 
672
+ // Bind parameters safely
615
673
  node.params.forEach((p, i) => {
616
- localEnv.define(p.name, args[i]);
674
+ const paramName = typeof p === 'string' ? p : p.name;
675
+ localEnv.define(paramName, args[i]);
617
676
  });
618
677
 
619
678
  try {
620
679
  if (node.isBlock) {
680
+ // Block body
621
681
  const result = await evaluator.evaluate(node.body, localEnv);
622
- return result ?? null;
682
+ return result === undefined ? null : result; // ensure null instead of undefined
623
683
  } else {
624
- return await evaluator.evaluate(node.body, localEnv);
684
+ // Expression body
685
+ const result = await evaluator.evaluate(node.body, localEnv);
686
+ return result === undefined ? null : result; // ensure null instead of undefined
625
687
  }
626
688
  } catch (err) {
627
- if (err instanceof ReturnValue) {
628
- return err.value;
629
- }
689
+ if (err instanceof ReturnValue) return err.value === undefined ? null : err.value;
630
690
  throw err;
631
691
  }
632
692
  };
633
693
  }
634
694
 
635
695
 
696
+
636
697
  async evalAssignment(node, env) {
637
698
  const rightVal = await this.evaluate(node.right, env);
638
699
  const left = node.left;
@@ -973,18 +1034,26 @@ async evalIndex(node, env) {
973
1034
  async evalObject(node, env) {
974
1035
  const out = {};
975
1036
  for (const p of node.props) {
976
- if (!p.key || !p.value) {
977
- throw new RuntimeError('Invalid object property', node, this.source);
1037
+ if (!p.key) {
1038
+ throw new RuntimeError('Object property must have a key', node, this.source);
978
1039
  }
1040
+
979
1041
  const key = await this.evaluate(p.key, env);
980
- const value = await this.evaluate(p.value, env);
981
- out[key] = value; // dynamic property assignment
1042
+ let value = null;
1043
+
1044
+ if (p.value) {
1045
+ value = await this.evaluate(p.value, env);
1046
+ if (value === undefined) value = null; // <- force null instead of undefined
1047
+ }
1048
+
1049
+ out[key] = value;
982
1050
  }
983
1051
  return out;
984
1052
  }
985
1053
 
986
1054
 
987
1055
 
1056
+
988
1057
  async evalMember(node, env) {
989
1058
  const obj = await this.evaluate(node.object, env);
990
1059
 
@@ -1024,4 +1093,4 @@ async evalUpdate(node, env) {
1024
1093
 
1025
1094
  }
1026
1095
 
1027
- module.exports = Evaluator;
1096
+ module.exports = Evaluator;
package/src/parser.js CHANGED
@@ -256,14 +256,18 @@ ifStatement() {
256
256
  test = this.expression();
257
257
  }
258
258
 
259
- const consequent = this.block();
259
+ const consequent = this.statementOrBlock();
260
260
 
261
261
  let alternate = null;
262
262
  if (this.current.type === 'ELSE') {
263
- this.eat('ELSE');
264
- if (this.current.type === 'IF') alternate = this.ifStatement();
265
- else alternate = this.block();
263
+ this.eat('ELSE');
264
+ if (this.current.type === 'IF') {
265
+ alternate = this.ifStatement();
266
+ } else {
267
+ alternate = this.statementOrBlock();
266
268
  }
269
+ }
270
+
267
271
 
268
272
  return {
269
273
  type: 'IfStatement',
@@ -497,6 +501,12 @@ returnStatement() {
497
501
 
498
502
  return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
499
503
  }
504
+ statementOrBlock() {
505
+ if (this.current.type === 'LBRACE') {
506
+ return this.block();
507
+ }
508
+ return this.statement();
509
+ }
500
510
 
501
511
  block() {
502
512
  const t = this.current; // LBRACE token
@@ -706,13 +716,7 @@ postfix() {
706
716
  continue;
707
717
  }
708
718
 
709
- if (node.type === 'Identifier' && t.type === 'IDENTIFIER') {
710
- const argNode = { type: 'Identifier', name: t.value, line: t.line, column: t.column };
711
- this.eat('IDENTIFIER');
712
- node = { type: 'CallExpression', callee: node, arguments: [argNode], line: node.line, column: node.column };
713
- continue;
714
- }
715
-
719
+
716
720
  break;
717
721
  }
718
722
  return node;
@@ -866,21 +870,56 @@ arrowFunction(params) {
866
870
  }
867
871
 
868
872
  if (t.type === 'LBRACE') {
869
- const startLine = t.line;
870
- const startCol = t.column;
871
- this.eat('LBRACE');
872
- const props = [];
873
- while (this.current.type !== 'RBRACE') {
874
- const key = this.expression();
875
- this.eat('COLON');
876
- const value = this.expression();
877
- props.push({ key, value });
878
- if (this.current.type === 'COMMA') this.eat('COMMA');
873
+ const startLine = t.line;
874
+ const startCol = t.column;
875
+ this.eat('LBRACE');
876
+
877
+ const props = [];
878
+
879
+ while (this.current.type !== 'RBRACE') {
880
+ let key;
881
+
882
+ if (this.current.type === 'IDENTIFIER') {
883
+ const k = this.current;
884
+ this.eat('IDENTIFIER');
885
+ key = {
886
+ type: 'Literal',
887
+ value: k.value,
888
+ line: k.line,
889
+ column: k.column
890
+ };
879
891
  }
880
- this.eat('RBRACE');
881
- return { type: 'ObjectExpression', props, line: startLine, column: startCol };
892
+ else if (this.current.type === 'STRING') {
893
+ const k = this.current;
894
+ this.eat('STRING');
895
+ key = {
896
+ type: 'Literal',
897
+ value: k.value,
898
+ line: k.line,
899
+ column: k.column
900
+ };
901
+ }
902
+ else {
903
+ throw new ParseError(
904
+ 'Invalid object key',
905
+ this.current,
906
+ this.source
907
+ );
908
+ }
909
+
910
+ this.eat('COLON');
911
+ const value = this.expression();
912
+
913
+ props.push({ key, value });
914
+
915
+ if (this.current.type === 'COMMA') this.eat('COMMA');
882
916
  }
883
917
 
918
+ this.eat('RBRACE');
919
+ return { type: 'ObjectExpression', props, line: startLine, column: startCol };
920
+ }
921
+
922
+
884
923
  throw new ParseError(
885
924
  `Unexpected token '${t.type}'`,
886
925
  t,
package/src/starlight.js CHANGED
@@ -12,7 +12,7 @@ const Lexer = require('./lexer');
12
12
  const Parser = require('./parser');
13
13
  const Evaluator = require('./evaluator');
14
14
 
15
- const VERSION = '1.1.9';
15
+ const VERSION = '1.1.10';
16
16
 
17
17
  const COLOR = {
18
18
  reset: '\x1b[0m',