starlight-cli 1.1.0 → 1.1.2

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
@@ -1388,13 +1388,25 @@ class Environment {
1388
1388
  return false;
1389
1389
  }
1390
1390
 
1391
- get(name, node, source) {
1391
+ get(name, node, source) {
1392
1392
  if (name in this.store) return this.store[name];
1393
1393
  if (this.parent) return this.parent.get(name, node, source);
1394
- throw new RuntimeError(`Undefined variable: ${name}`, node, source);
1394
+
1395
+ // Only suggest for top-level variable/function names
1396
+ let suggestion = null;
1397
+ if (node && source) {
1398
+ suggestion = this.suggest?.(name, this) || null;
1399
+ }
1400
+
1401
+ const message = suggestion
1402
+ ? `Undefined variable: "${name}". Did you mean "${suggestion}"?`
1403
+ : `Undefined variable: "${name}"`;
1404
+
1405
+ throw new RuntimeError(message, node, source);
1395
1406
  }
1396
1407
 
1397
1408
 
1409
+
1398
1410
  set(name, value) {
1399
1411
  if (name in this.store) { this.store[name] = value; return value; }
1400
1412
  if (this.parent && this.parent.has(name)) { return this.parent.set(name, value); }
@@ -1414,57 +1426,87 @@ class Evaluator {
1414
1426
  this.global = new Environment();
1415
1427
  this.setupBuiltins();
1416
1428
  }
1429
+ suggest(name, env) {
1430
+ // Collect all variable/function names from the environment chain
1431
+ const names = new Set();
1432
+ let current = env;
1433
+ while (current) {
1434
+ for (const key of Object.keys(current.store)) {
1435
+ names.add(key);
1436
+ }
1437
+ current = current.parent;
1438
+ }
1439
+
1440
+ let best = null;
1441
+ let bestScore = Infinity;
1442
+
1443
+ for (const item of names) {
1444
+ // simple edit distance approximation
1445
+ const dist = Math.abs(item.length - name.length) +
1446
+ [...name].filter((c, i) => c !== item[i]).length;
1447
+
1448
+ if (dist < bestScore && dist <= 2) { // max distance 2
1449
+ bestScore = dist;
1450
+ best = item;
1451
+ }
1452
+ }
1453
+
1454
+ return best;
1455
+ }
1456
+
1417
1457
  formatValue(value, seen = new Set()) {
1418
- // Circular reference handling
1458
+ const color = __nccwpck_require__(55);
1459
+
1460
+ // Handle circular references
1419
1461
  if (typeof value === 'object' && value !== null) {
1420
- if (seen.has(value)) return '[Circular]';
1462
+ if (seen.has(value)) return color.red('[Circular]');
1421
1463
  seen.add(value);
1422
1464
  }
1423
1465
 
1424
1466
  // Python-style null / undefined
1425
- if (value === null) return 'None';
1426
- if (value === undefined) return 'undefined';
1467
+ if (value === null) return color.yellow('None');
1468
+ if (value === undefined) return color.yellow('undefined');
1427
1469
 
1428
1470
  const t = typeof value;
1429
1471
 
1430
1472
  // Strings (no quotes)
1431
- if (t === 'string') return value;
1473
+ if (t === 'string') return color.cyan(value);
1432
1474
 
1433
1475
  // Numbers
1434
- if (t === 'number') return String(value);
1476
+ if (t === 'number') return color.green(String(value));
1435
1477
 
1436
- // Booleans (Python-style)
1437
- if (t === 'boolean') return value ? 'True' : 'False';
1478
+ // Booleans
1479
+ if (t === 'boolean') return color.yellow(value ? 'true' : 'false');
1438
1480
 
1439
1481
  // Native JS functions
1440
1482
  if (t === 'function') {
1441
- return value.name
1442
- ? `<function ${value.name}>`
1443
- : '<function>';
1483
+ return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
1444
1484
  }
1445
1485
 
1446
1486
  // Arrays
1447
1487
  if (Array.isArray(value)) {
1448
- return '[' + value.map(v => this.formatValue(v, seen)).join(', ') + ']';
1488
+ const items = value.map(v => this.formatValue(v, seen));
1489
+ return color.white('[ ' + items.join(', ') + ' ]');
1449
1490
  }
1450
1491
 
1451
1492
  // Objects (including user-defined functions)
1452
1493
  if (t === 'object') {
1453
- // Detect user-defined functions (AST-based)
1454
1494
  if (value.params && value.body) {
1455
- return value.name
1456
- ? `<function ${value.name}>`
1457
- : '<function>';
1495
+ return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
1458
1496
  }
1459
1497
 
1460
1498
  const entries = Object.entries(value).map(
1461
- ([k, v]) => `${k}: ${this.formatValue(v, seen)}`
1499
+ ([k, v]) => color.magenta(`${k}: `) + this.formatValue(v, seen)
1462
1500
  );
1463
- return '{ ' + entries.join(', ') + ' }';
1501
+ return color.magenta('{ ') + entries.join(color.magenta(', ')) + color.magenta(' }');
1464
1502
  }
1465
1503
 
1466
1504
  // Fallback
1467
- return String(value);
1505
+ try {
1506
+ return String(value);
1507
+ } catch {
1508
+ return color.red('[Unprintable]');
1509
+ }
1468
1510
  }
1469
1511
 
1470
1512
 
@@ -1795,23 +1837,23 @@ async evalAssignment(node, env) {
1795
1837
  const left = node.left;
1796
1838
 
1797
1839
  if (left.type === 'Identifier') return env.set(left.name, rightVal);
1798
- if (left.type === 'MemberExpression') {
1799
- const obj = await this.evaluate(left.object, env);
1800
- if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
1801
- obj[left.property] = rightVal;
1802
- return rightVal;
1803
- }
1804
- if (left.type === 'IndexExpression') {
1805
- const obj = await this.evaluate(left.object, env);
1806
- const idx = await this.evaluate(left.indexer, env);
1807
- if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
1808
- obj[idx] = rightVal;
1809
- return rightVal;
1810
- }
1811
1840
 
1841
+ if (left.type === 'MemberExpression') {
1842
+ const obj = await this.evaluate(left.object, env);
1843
+ if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
1844
+ obj[left.property] = rightVal; // dynamic creation of new properties allowed
1845
+ return rightVal;
1846
+ }
1812
1847
 
1813
- throw new RuntimeError('Invalid assignment target', node, this.source);
1848
+ if (left.type === 'IndexExpression') {
1849
+ const obj = await this.evaluate(left.object, env);
1850
+ const idx = await this.evaluate(left.indexer, env);
1851
+ if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
1852
+ obj[idx] = rightVal; // dynamic creation allowed
1853
+ return rightVal;
1854
+ }
1814
1855
 
1856
+ throw new RuntimeError('Invalid assignment target', node, this.source);
1815
1857
  }
1816
1858
 
1817
1859
  async evalCompoundAssignment(node, env) {
@@ -1819,13 +1861,13 @@ async evalCompoundAssignment(node, env) {
1819
1861
  let current;
1820
1862
 
1821
1863
  if (left.type === 'Identifier') current = env.get(left.name, left, this.source);
1822
- else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
1823
- else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
1864
+ else if (left.type === 'MemberExpression') current = await this.evalMember(left, env) ?? 0;
1865
+ else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env) ?? 0;
1824
1866
  else throw new RuntimeError('Invalid compound assignment target', node, this.source);
1825
1867
 
1826
-
1827
1868
  const rhs = await this.evaluate(node.right, env);
1828
1869
  let computed;
1870
+
1829
1871
  switch (node.operator) {
1830
1872
  case 'PLUSEQ': computed = current + rhs; break;
1831
1873
  case 'MINUSEQ': computed = current - rhs; break;
@@ -1833,16 +1875,18 @@ async evalCompoundAssignment(node, env) {
1833
1875
  case 'SLASHEQ': computed = current / rhs; break;
1834
1876
  case 'MODEQ': computed = current % rhs; break;
1835
1877
  default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node, this.source);
1836
-
1837
1878
  }
1838
1879
 
1839
1880
  if (left.type === 'Identifier') env.set(left.name, computed);
1840
- else if (left.type === 'MemberExpression') await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
1841
- else await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
1881
+ else if (left.type === 'MemberExpression')
1882
+ await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
1883
+ else
1884
+ await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
1842
1885
 
1843
1886
  return computed;
1844
1887
  }
1845
1888
 
1889
+
1846
1890
  async evalSldeploy(node, env) {
1847
1891
  const val = await this.evaluate(node.expr, env);
1848
1892
  console.log(this.formatValue(val));
@@ -1850,6 +1894,7 @@ async evalSldeploy(node, env) {
1850
1894
  }
1851
1895
 
1852
1896
 
1897
+
1853
1898
  async evalAsk(node, env) {
1854
1899
  const prompt = await this.evaluate(node.prompt, env);
1855
1900
 
@@ -1956,11 +2001,8 @@ async evalWhile(node, env) {
1956
2001
  }
1957
2002
  return null;
1958
2003
  }
1959
-
1960
2004
  async evalFor(node, env) {
1961
- // -------------------------------
1962
2005
  // Python-style: for x in iterable (with optional 'let')
1963
- // -------------------------------
1964
2006
  if (node.type === 'ForInStatement') {
1965
2007
  const iterable = await this.evaluate(node.iterable, env);
1966
2008
 
@@ -1968,11 +2010,10 @@ async evalFor(node, env) {
1968
2010
  throw new RuntimeError('Cannot iterate over non-iterable', node, this.source);
1969
2011
  }
1970
2012
 
1971
- const loopVar = node.variable; // STRING from parser
1972
-
2013
+ const loopVar = node.variable; // string name of the loop variable
1973
2014
  const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
1974
2015
 
1975
- // Arrays
2016
+ // Arrays: iterate over elements
1976
2017
  if (Array.isArray(iterable)) {
1977
2018
  for (const value of iterable) {
1978
2019
  const loopEnv = createLoopEnv();
@@ -1987,7 +2028,7 @@ async evalFor(node, env) {
1987
2028
  }
1988
2029
  }
1989
2030
  }
1990
- // Objects (keys)
2031
+ // Objects: iterate over keys
1991
2032
  else {
1992
2033
  for (const key of Object.keys(iterable)) {
1993
2034
  const loopEnv = createLoopEnv();
@@ -2006,9 +2047,7 @@ async evalFor(node, env) {
2006
2047
  return null;
2007
2048
  }
2008
2049
 
2009
- // -------------------------------
2010
- // C-style for loop
2011
- // -------------------------------
2050
+ // C-style for loop (classic JS style)
2012
2051
  const local = new Environment(env);
2013
2052
 
2014
2053
  if (node.init) await this.evaluate(node.init, local);
@@ -2030,6 +2069,7 @@ async evalFor(node, env) {
2030
2069
 
2031
2070
  return null;
2032
2071
  }
2072
+
2033
2073
  evalFunctionDeclaration(node, env) {
2034
2074
  if (!node.name || typeof node.name !== 'string') {
2035
2075
  throw new RuntimeError('Function declaration requires a valid name', node, this.source);
@@ -2092,18 +2132,22 @@ async evalIndex(node, env) {
2092
2132
  const obj = await this.evaluate(node.object, env);
2093
2133
  const idx = await this.evaluate(node.indexer, env);
2094
2134
 
2095
- if (obj == null) throw new RuntimeError('Indexing null or undefined', node, this.source);
2135
+ if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
2096
2136
 
2097
- if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
2098
- throw new RuntimeError('Array index out of bounds', node, this.source);
2137
+ // Array access: return undefined if out of bounds
2138
+ if (Array.isArray(obj)) {
2139
+ if (idx < 0 || idx >= obj.length) return undefined;
2140
+ return obj[idx];
2099
2141
  }
2100
- if (typeof obj === 'object' && !(idx in obj)) {
2101
- throw new RuntimeError(`Property '${idx}' does not exist`, node, this.source);
2142
+
2143
+ // Object access: return undefined if property missing
2144
+ if (typeof obj === 'object') {
2145
+ return obj[idx]; // undefined if missing
2102
2146
  }
2103
2147
 
2104
- return obj[idx];
2148
+ // Fallback for non-object non-array
2149
+ return undefined;
2105
2150
  }
2106
-
2107
2151
  async evalObject(node, env) {
2108
2152
  const out = {};
2109
2153
  for (const p of node.props) {
@@ -2112,19 +2156,22 @@ async evalObject(node, env) {
2112
2156
  }
2113
2157
  const key = await this.evaluate(p.key, env);
2114
2158
  const value = await this.evaluate(p.value, env);
2115
- out[key] = value;
2159
+ out[key] = value; // dynamic property assignment
2116
2160
  }
2117
2161
  return out;
2118
2162
  }
2119
2163
 
2120
2164
 
2165
+
2121
2166
  async evalMember(node, env) {
2122
2167
  const obj = await this.evaluate(node.object, env);
2123
- if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
2124
- if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node, this.source);
2168
+
2169
+ if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
2170
+
2125
2171
  return obj[node.property];
2126
2172
  }
2127
2173
 
2174
+
2128
2175
  async evalUpdate(node, env) {
2129
2176
  const arg = node.argument;
2130
2177
  const getCurrent = async () => {
@@ -2211,22 +2258,7 @@ class Lexer {
2211
2258
  peek() {
2212
2259
  return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
2213
2260
  }
2214
- suggest(word, list) {
2215
- let best = null;
2216
- let bestScore = Infinity;
2217
2261
 
2218
- for (const item of list) {
2219
- const dist =
2220
- Math.abs(item.length - word.length) +
2221
- [...word].filter((c, i) => c !== item[i]).length;
2222
-
2223
- if (dist < bestScore && dist <= 2) {
2224
- bestScore = dist;
2225
- best = item;
2226
- }
2227
- }
2228
- return best;
2229
- }
2230
2262
  error(msg) {
2231
2263
  throw new LexerError(
2232
2264
  msg,
@@ -2289,27 +2321,12 @@ suggest(word, list) {
2289
2321
  this.advance();
2290
2322
  }
2291
2323
 
2292
-
2293
2324
  if (this.keywords.includes(result)) {
2294
- return {
2295
- type: result.toUpperCase(),
2296
- value: result,
2297
- line: startLine,
2298
- column: startCol
2299
- };
2325
+ return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
2300
2326
  }
2301
2327
 
2302
- const suggestion = this.suggest(result, this.keywords);
2303
- if (suggestion) {
2304
- this.error(`Unknown identifier "${result}". Did you mean "${suggestion}"?`);
2305
- }
2328
+ return { type: 'IDENTIFIER', value: result, line: startLine, column: startCol };
2306
2329
 
2307
- return {
2308
- type: 'IDENTIFIER',
2309
- value: result,
2310
- line: startLine,
2311
- column: startCol
2312
- };
2313
2330
 
2314
2331
  }
2315
2332
 
@@ -3296,6 +3313,53 @@ module.exports = require("path");
3296
3313
  "use strict";
3297
3314
  module.exports = require("readline");
3298
3315
 
3316
+ /***/ }),
3317
+
3318
+ /***/ 55:
3319
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
3320
+
3321
+ "use strict";
3322
+ __nccwpck_require__.r(__webpack_exports__);
3323
+ /* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
3324
+ /* harmony export */ blue: () => (/* binding */ blue),
3325
+ /* harmony export */ brightBlue: () => (/* binding */ brightBlue),
3326
+ /* harmony export */ brightCyan: () => (/* binding */ brightCyan),
3327
+ /* harmony export */ brightGreen: () => (/* binding */ brightGreen),
3328
+ /* harmony export */ brightMagenta: () => (/* binding */ brightMagenta),
3329
+ /* harmony export */ brightRed: () => (/* binding */ brightRed),
3330
+ /* harmony export */ brightWhite: () => (/* binding */ brightWhite),
3331
+ /* harmony export */ brightYellow: () => (/* binding */ brightYellow),
3332
+ /* harmony export */ cyan: () => (/* binding */ cyan),
3333
+ /* harmony export */ gray: () => (/* binding */ gray),
3334
+ /* harmony export */ green: () => (/* binding */ green),
3335
+ /* harmony export */ magenta: () => (/* binding */ magenta),
3336
+ /* harmony export */ red: () => (/* binding */ red),
3337
+ /* harmony export */ white: () => (/* binding */ white),
3338
+ /* harmony export */ yellow: () => (/* binding */ yellow)
3339
+ /* harmony export */ });
3340
+ const RESET = "\x1b[0m";
3341
+
3342
+ function wrap(code, text) {
3343
+ return code + String(text) + RESET;
3344
+ }
3345
+
3346
+ const red = text => wrap("\x1b[31m", text);
3347
+ const green = text => wrap("\x1b[32m", text);
3348
+ const yellow = text => wrap("\x1b[33m", text);
3349
+ const blue = text => wrap("\x1b[34m", text);
3350
+ const magenta = text => wrap("\x1b[35m", text);
3351
+ const cyan = text => wrap("\x1b[36m", text);
3352
+ const white = text => wrap("\x1b[37m", text);
3353
+ const gray = text => wrap("\x1b[90m", text);
3354
+ const brightRed = text => wrap("\x1b[91m", text);
3355
+ const brightGreen = text => wrap("\x1b[92m", text);
3356
+ const brightYellow = text => wrap("\x1b[93m", text);
3357
+ const brightBlue = text => wrap("\x1b[94m", text);
3358
+ const brightMagenta = text => wrap("\x1b[95m", text);
3359
+ const brightCyan = text => wrap("\x1b[96m", text);
3360
+ const brightWhite = text => wrap("\x1b[97m", text);
3361
+
3362
+
3299
3363
  /***/ })
3300
3364
 
3301
3365
  /******/ });
@@ -3331,6 +3395,34 @@ module.exports = require("readline");
3331
3395
  /******/ }
3332
3396
  /******/
3333
3397
  /************************************************************************/
3398
+ /******/ /* webpack/runtime/define property getters */
3399
+ /******/ (() => {
3400
+ /******/ // define getter functions for harmony exports
3401
+ /******/ __nccwpck_require__.d = (exports, definition) => {
3402
+ /******/ for(var key in definition) {
3403
+ /******/ if(__nccwpck_require__.o(definition, key) && !__nccwpck_require__.o(exports, key)) {
3404
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
3405
+ /******/ }
3406
+ /******/ }
3407
+ /******/ };
3408
+ /******/ })();
3409
+ /******/
3410
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
3411
+ /******/ (() => {
3412
+ /******/ __nccwpck_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
3413
+ /******/ })();
3414
+ /******/
3415
+ /******/ /* webpack/runtime/make namespace object */
3416
+ /******/ (() => {
3417
+ /******/ // define __esModule on exports
3418
+ /******/ __nccwpck_require__.r = (exports) => {
3419
+ /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
3420
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3421
+ /******/ }
3422
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
3423
+ /******/ };
3424
+ /******/ })();
3425
+ /******/
3334
3426
  /******/ /* webpack/runtime/compat */
3335
3427
  /******/
3336
3428
  /******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
@@ -3348,7 +3440,7 @@ const Lexer = __nccwpck_require__(211);
3348
3440
  const Parser = __nccwpck_require__(222);
3349
3441
  const Evaluator = __nccwpck_require__(112);
3350
3442
 
3351
- const VERSION = '1.1.0';
3443
+ const VERSION = '1.1.2';
3352
3444
 
3353
3445
  const COLOR = {
3354
3446
  reset: '\x1b[0m',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
@@ -13,7 +13,8 @@
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
15
  "blessed": "^0.1.81",
16
- "readline-sync": "^1.4.10"
16
+ "readline-sync": "^1.4.10",
17
+ "starlight-color": "^1.0.4"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@vercel/ncc": "^0.38.4"
package/src/evaluator.js CHANGED
@@ -45,13 +45,25 @@ class Environment {
45
45
  return false;
46
46
  }
47
47
 
48
- get(name, node, source) {
48
+ get(name, node, source) {
49
49
  if (name in this.store) return this.store[name];
50
50
  if (this.parent) return this.parent.get(name, node, source);
51
- throw new RuntimeError(`Undefined variable: ${name}`, node, source);
51
+
52
+ // Only suggest for top-level variable/function names
53
+ let suggestion = null;
54
+ if (node && source) {
55
+ suggestion = this.suggest?.(name, this) || null;
56
+ }
57
+
58
+ const message = suggestion
59
+ ? `Undefined variable: "${name}". Did you mean "${suggestion}"?`
60
+ : `Undefined variable: "${name}"`;
61
+
62
+ throw new RuntimeError(message, node, source);
52
63
  }
53
64
 
54
65
 
66
+
55
67
  set(name, value) {
56
68
  if (name in this.store) { this.store[name] = value; return value; }
57
69
  if (this.parent && this.parent.has(name)) { return this.parent.set(name, value); }
@@ -71,57 +83,87 @@ class Evaluator {
71
83
  this.global = new Environment();
72
84
  this.setupBuiltins();
73
85
  }
86
+ suggest(name, env) {
87
+ // Collect all variable/function names from the environment chain
88
+ const names = new Set();
89
+ let current = env;
90
+ while (current) {
91
+ for (const key of Object.keys(current.store)) {
92
+ names.add(key);
93
+ }
94
+ current = current.parent;
95
+ }
96
+
97
+ let best = null;
98
+ let bestScore = Infinity;
99
+
100
+ for (const item of names) {
101
+ // simple edit distance approximation
102
+ const dist = Math.abs(item.length - name.length) +
103
+ [...name].filter((c, i) => c !== item[i]).length;
104
+
105
+ if (dist < bestScore && dist <= 2) { // max distance 2
106
+ bestScore = dist;
107
+ best = item;
108
+ }
109
+ }
110
+
111
+ return best;
112
+ }
113
+
74
114
  formatValue(value, seen = new Set()) {
75
- // Circular reference handling
115
+ const color = require('starlight-color');
116
+
117
+ // Handle circular references
76
118
  if (typeof value === 'object' && value !== null) {
77
- if (seen.has(value)) return '[Circular]';
119
+ if (seen.has(value)) return color.red('[Circular]');
78
120
  seen.add(value);
79
121
  }
80
122
 
81
123
  // Python-style null / undefined
82
- if (value === null) return 'None';
83
- if (value === undefined) return 'undefined';
124
+ if (value === null) return color.yellow('None');
125
+ if (value === undefined) return color.yellow('undefined');
84
126
 
85
127
  const t = typeof value;
86
128
 
87
129
  // Strings (no quotes)
88
- if (t === 'string') return value;
130
+ if (t === 'string') return color.cyan(value);
89
131
 
90
132
  // Numbers
91
- if (t === 'number') return String(value);
133
+ if (t === 'number') return color.green(String(value));
92
134
 
93
- // Booleans (Python-style)
94
- if (t === 'boolean') return value ? 'True' : 'False';
135
+ // Booleans
136
+ if (t === 'boolean') return color.yellow(value ? 'true' : 'false');
95
137
 
96
138
  // Native JS functions
97
139
  if (t === 'function') {
98
- return value.name
99
- ? `<function ${value.name}>`
100
- : '<function>';
140
+ return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
101
141
  }
102
142
 
103
143
  // Arrays
104
144
  if (Array.isArray(value)) {
105
- return '[' + value.map(v => this.formatValue(v, seen)).join(', ') + ']';
145
+ const items = value.map(v => this.formatValue(v, seen));
146
+ return color.white('[ ' + items.join(', ') + ' ]');
106
147
  }
107
148
 
108
149
  // Objects (including user-defined functions)
109
150
  if (t === 'object') {
110
- // Detect user-defined functions (AST-based)
111
151
  if (value.params && value.body) {
112
- return value.name
113
- ? `<function ${value.name}>`
114
- : '<function>';
152
+ return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
115
153
  }
116
154
 
117
155
  const entries = Object.entries(value).map(
118
- ([k, v]) => `${k}: ${this.formatValue(v, seen)}`
156
+ ([k, v]) => color.magenta(`${k}: `) + this.formatValue(v, seen)
119
157
  );
120
- return '{ ' + entries.join(', ') + ' }';
158
+ return color.magenta('{ ') + entries.join(color.magenta(', ')) + color.magenta(' }');
121
159
  }
122
160
 
123
161
  // Fallback
124
- return String(value);
162
+ try {
163
+ return String(value);
164
+ } catch {
165
+ return color.red('[Unprintable]');
166
+ }
125
167
  }
126
168
 
127
169
 
@@ -452,23 +494,23 @@ async evalAssignment(node, env) {
452
494
  const left = node.left;
453
495
 
454
496
  if (left.type === 'Identifier') return env.set(left.name, rightVal);
455
- if (left.type === 'MemberExpression') {
456
- const obj = await this.evaluate(left.object, env);
457
- if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
458
- obj[left.property] = rightVal;
459
- return rightVal;
460
- }
461
- if (left.type === 'IndexExpression') {
462
- const obj = await this.evaluate(left.object, env);
463
- const idx = await this.evaluate(left.indexer, env);
464
- if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
465
- obj[idx] = rightVal;
466
- return rightVal;
467
- }
468
497
 
498
+ if (left.type === 'MemberExpression') {
499
+ const obj = await this.evaluate(left.object, env);
500
+ if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
501
+ obj[left.property] = rightVal; // dynamic creation of new properties allowed
502
+ return rightVal;
503
+ }
469
504
 
470
- throw new RuntimeError('Invalid assignment target', node, this.source);
505
+ if (left.type === 'IndexExpression') {
506
+ const obj = await this.evaluate(left.object, env);
507
+ const idx = await this.evaluate(left.indexer, env);
508
+ if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
509
+ obj[idx] = rightVal; // dynamic creation allowed
510
+ return rightVal;
511
+ }
471
512
 
513
+ throw new RuntimeError('Invalid assignment target', node, this.source);
472
514
  }
473
515
 
474
516
  async evalCompoundAssignment(node, env) {
@@ -476,13 +518,13 @@ async evalCompoundAssignment(node, env) {
476
518
  let current;
477
519
 
478
520
  if (left.type === 'Identifier') current = env.get(left.name, left, this.source);
479
- else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
480
- else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
521
+ else if (left.type === 'MemberExpression') current = await this.evalMember(left, env) ?? 0;
522
+ else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env) ?? 0;
481
523
  else throw new RuntimeError('Invalid compound assignment target', node, this.source);
482
524
 
483
-
484
525
  const rhs = await this.evaluate(node.right, env);
485
526
  let computed;
527
+
486
528
  switch (node.operator) {
487
529
  case 'PLUSEQ': computed = current + rhs; break;
488
530
  case 'MINUSEQ': computed = current - rhs; break;
@@ -490,16 +532,18 @@ async evalCompoundAssignment(node, env) {
490
532
  case 'SLASHEQ': computed = current / rhs; break;
491
533
  case 'MODEQ': computed = current % rhs; break;
492
534
  default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node, this.source);
493
-
494
535
  }
495
536
 
496
537
  if (left.type === 'Identifier') env.set(left.name, computed);
497
- else if (left.type === 'MemberExpression') await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
498
- else await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
538
+ else if (left.type === 'MemberExpression')
539
+ await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
540
+ else
541
+ await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
499
542
 
500
543
  return computed;
501
544
  }
502
545
 
546
+
503
547
  async evalSldeploy(node, env) {
504
548
  const val = await this.evaluate(node.expr, env);
505
549
  console.log(this.formatValue(val));
@@ -507,6 +551,7 @@ async evalSldeploy(node, env) {
507
551
  }
508
552
 
509
553
 
554
+
510
555
  async evalAsk(node, env) {
511
556
  const prompt = await this.evaluate(node.prompt, env);
512
557
 
@@ -613,11 +658,8 @@ async evalWhile(node, env) {
613
658
  }
614
659
  return null;
615
660
  }
616
-
617
661
  async evalFor(node, env) {
618
- // -------------------------------
619
662
  // Python-style: for x in iterable (with optional 'let')
620
- // -------------------------------
621
663
  if (node.type === 'ForInStatement') {
622
664
  const iterable = await this.evaluate(node.iterable, env);
623
665
 
@@ -625,11 +667,10 @@ async evalFor(node, env) {
625
667
  throw new RuntimeError('Cannot iterate over non-iterable', node, this.source);
626
668
  }
627
669
 
628
- const loopVar = node.variable; // STRING from parser
629
-
670
+ const loopVar = node.variable; // string name of the loop variable
630
671
  const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
631
672
 
632
- // Arrays
673
+ // Arrays: iterate over elements
633
674
  if (Array.isArray(iterable)) {
634
675
  for (const value of iterable) {
635
676
  const loopEnv = createLoopEnv();
@@ -644,7 +685,7 @@ async evalFor(node, env) {
644
685
  }
645
686
  }
646
687
  }
647
- // Objects (keys)
688
+ // Objects: iterate over keys
648
689
  else {
649
690
  for (const key of Object.keys(iterable)) {
650
691
  const loopEnv = createLoopEnv();
@@ -663,9 +704,7 @@ async evalFor(node, env) {
663
704
  return null;
664
705
  }
665
706
 
666
- // -------------------------------
667
- // C-style for loop
668
- // -------------------------------
707
+ // C-style for loop (classic JS style)
669
708
  const local = new Environment(env);
670
709
 
671
710
  if (node.init) await this.evaluate(node.init, local);
@@ -687,6 +726,7 @@ async evalFor(node, env) {
687
726
 
688
727
  return null;
689
728
  }
729
+
690
730
  evalFunctionDeclaration(node, env) {
691
731
  if (!node.name || typeof node.name !== 'string') {
692
732
  throw new RuntimeError('Function declaration requires a valid name', node, this.source);
@@ -749,18 +789,22 @@ async evalIndex(node, env) {
749
789
  const obj = await this.evaluate(node.object, env);
750
790
  const idx = await this.evaluate(node.indexer, env);
751
791
 
752
- if (obj == null) throw new RuntimeError('Indexing null or undefined', node, this.source);
792
+ if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
753
793
 
754
- if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
755
- throw new RuntimeError('Array index out of bounds', node, this.source);
794
+ // Array access: return undefined if out of bounds
795
+ if (Array.isArray(obj)) {
796
+ if (idx < 0 || idx >= obj.length) return undefined;
797
+ return obj[idx];
756
798
  }
757
- if (typeof obj === 'object' && !(idx in obj)) {
758
- throw new RuntimeError(`Property '${idx}' does not exist`, node, this.source);
799
+
800
+ // Object access: return undefined if property missing
801
+ if (typeof obj === 'object') {
802
+ return obj[idx]; // undefined if missing
759
803
  }
760
804
 
761
- return obj[idx];
805
+ // Fallback for non-object non-array
806
+ return undefined;
762
807
  }
763
-
764
808
  async evalObject(node, env) {
765
809
  const out = {};
766
810
  for (const p of node.props) {
@@ -769,19 +813,22 @@ async evalObject(node, env) {
769
813
  }
770
814
  const key = await this.evaluate(p.key, env);
771
815
  const value = await this.evaluate(p.value, env);
772
- out[key] = value;
816
+ out[key] = value; // dynamic property assignment
773
817
  }
774
818
  return out;
775
819
  }
776
820
 
777
821
 
822
+
778
823
  async evalMember(node, env) {
779
824
  const obj = await this.evaluate(node.object, env);
780
- if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
781
- if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node, this.source);
825
+
826
+ if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
827
+
782
828
  return obj[node.property];
783
829
  }
784
830
 
831
+
785
832
  async evalUpdate(node, env) {
786
833
  const arg = node.argument;
787
834
  const getCurrent = async () => {
package/src/lexer.js CHANGED
@@ -47,22 +47,7 @@ class Lexer {
47
47
  peek() {
48
48
  return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
49
49
  }
50
- suggest(word, list) {
51
- let best = null;
52
- let bestScore = Infinity;
53
-
54
- for (const item of list) {
55
- const dist =
56
- Math.abs(item.length - word.length) +
57
- [...word].filter((c, i) => c !== item[i]).length;
58
-
59
- if (dist < bestScore && dist <= 2) {
60
- bestScore = dist;
61
- best = item;
62
- }
63
- }
64
- return best;
65
- }
50
+
66
51
  error(msg) {
67
52
  throw new LexerError(
68
53
  msg,
@@ -125,27 +110,12 @@ suggest(word, list) {
125
110
  this.advance();
126
111
  }
127
112
 
128
-
129
113
  if (this.keywords.includes(result)) {
130
- return {
131
- type: result.toUpperCase(),
132
- value: result,
133
- line: startLine,
134
- column: startCol
135
- };
114
+ return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
136
115
  }
137
116
 
138
- const suggestion = this.suggest(result, this.keywords);
139
- if (suggestion) {
140
- this.error(`Unknown identifier "${result}". Did you mean "${suggestion}"?`);
141
- }
117
+ return { type: 'IDENTIFIER', value: result, line: startLine, column: startCol };
142
118
 
143
- return {
144
- type: 'IDENTIFIER',
145
- value: result,
146
- line: startLine,
147
- column: startCol
148
- };
149
119
 
150
120
  }
151
121
 
package/src/starlight.js CHANGED
@@ -9,7 +9,7 @@ const Lexer = require('./lexer');
9
9
  const Parser = require('./parser');
10
10
  const Evaluator = require('./evaluator');
11
11
 
12
- const VERSION = '1.1.0';
12
+ const VERSION = '1.1.2';
13
13
 
14
14
  const COLOR = {
15
15
  reset: '\x1b[0m',