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 +186 -94
- package/package.json +3 -2
- package/src/evaluator.js +108 -61
- package/src/lexer.js +3 -33
- package/src/starlight.js +1 -1
package/dist/index.js
CHANGED
|
@@ -1388,13 +1388,25 @@ class Environment {
|
|
|
1388
1388
|
return false;
|
|
1389
1389
|
}
|
|
1390
1390
|
|
|
1391
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1437
|
-
if (t === 'boolean') return value ? '
|
|
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
|
-
|
|
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}:
|
|
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
|
-
|
|
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
|
-
|
|
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')
|
|
1841
|
-
|
|
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; //
|
|
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
|
|
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('
|
|
2135
|
+
if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
|
|
2096
2136
|
|
|
2097
|
-
|
|
2098
|
-
|
|
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
|
-
|
|
2101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2124
|
-
if (
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
94
|
-
if (t === 'boolean') return value ? '
|
|
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
|
-
|
|
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}:
|
|
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
|
-
|
|
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
|
-
|
|
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')
|
|
498
|
-
|
|
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; //
|
|
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
|
|
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('
|
|
792
|
+
if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
|
|
753
793
|
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
758
|
-
|
|
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
|
-
|
|
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
|
-
|
|
781
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|