starlight-cli 1.0.21 → 1.0.22
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 +259 -238
- package/package.json +1 -1
- package/src/evaluator.js +109 -71
- package/src/lexer.js +114 -109
- package/src/parser.js +35 -55
- package/src/starlight.js +1 -1
package/dist/index.js
CHANGED
|
@@ -1347,7 +1347,9 @@ const Lexer = __nccwpck_require__(211);
|
|
|
1347
1347
|
const Parser = __nccwpck_require__(222);
|
|
1348
1348
|
const path = __nccwpck_require__(928);
|
|
1349
1349
|
|
|
1350
|
-
class ReturnValue {
|
|
1350
|
+
class ReturnValue {
|
|
1351
|
+
constructor(value) { this.value = value; }
|
|
1352
|
+
}
|
|
1351
1353
|
class BreakSignal {}
|
|
1352
1354
|
class ContinueSignal {}
|
|
1353
1355
|
|
|
@@ -1390,27 +1392,37 @@ class Evaluator {
|
|
|
1390
1392
|
|
|
1391
1393
|
setupBuiltins() {
|
|
1392
1394
|
this.global.define('len', arg => {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1395
|
+
if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
|
|
1396
|
+
if (arg && typeof arg === 'object') return Object.keys(arg).length;
|
|
1397
|
+
return 0;
|
|
1396
1398
|
});
|
|
1397
1399
|
|
|
1398
1400
|
this.global.define('print', arg => { console.log(arg); return null; });
|
|
1399
1401
|
this.global.define('type', arg => {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
+
if (Array.isArray(arg)) return 'array';
|
|
1403
|
+
return typeof arg;
|
|
1402
1404
|
});
|
|
1403
1405
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
1404
1406
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
1405
1407
|
|
|
1406
|
-
this.global.define('ask', prompt =>
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
if (Number.isNaN(n)) throw new Error('Cannot convert value to number');
|
|
1410
|
-
return n;
|
|
1408
|
+
this.global.define('ask', prompt => {
|
|
1409
|
+
const readlineSync = __nccwpck_require__(552);
|
|
1410
|
+
return readlineSync.question(prompt + ' ');
|
|
1411
1411
|
});
|
|
1412
|
-
this.global.define('
|
|
1413
|
-
|
|
1412
|
+
this.global.define('num', arg => {
|
|
1413
|
+
const n = Number(arg);
|
|
1414
|
+
if (Number.isNaN(n)) {
|
|
1415
|
+
throw new Error('Cannot convert value to number');
|
|
1416
|
+
}
|
|
1417
|
+
return n;
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
this.global.define('str', arg => {
|
|
1421
|
+
return String(arg);
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1414
1426
|
|
|
1415
1427
|
evaluate(node, env = this.global) {
|
|
1416
1428
|
switch (node.type) {
|
|
@@ -1427,7 +1439,6 @@ class Evaluator {
|
|
|
1427
1439
|
case 'LogicalExpression': return this.evalLogical(node, env);
|
|
1428
1440
|
case 'UnaryExpression': return this.evalUnary(node, env);
|
|
1429
1441
|
case 'Literal': return node.value;
|
|
1430
|
-
case 'TemplateLiteral': return this.evalTemplateLiteral(node, env);
|
|
1431
1442
|
case 'Identifier': return env.get(node.name);
|
|
1432
1443
|
case 'IfStatement': return this.evalIf(node, env);
|
|
1433
1444
|
case 'WhileStatement': return this.evalWhile(node, env);
|
|
@@ -1453,65 +1464,72 @@ class Evaluator {
|
|
|
1453
1464
|
|
|
1454
1465
|
evalProgram(node, env) {
|
|
1455
1466
|
let result = null;
|
|
1456
|
-
for (const stmt of node.body)
|
|
1467
|
+
for (const stmt of node.body) {
|
|
1468
|
+
result = this.evaluate(stmt, env);
|
|
1469
|
+
}
|
|
1457
1470
|
return result;
|
|
1458
1471
|
}
|
|
1472
|
+
evalImport(node, env) {
|
|
1473
|
+
const spec = node.path;
|
|
1474
|
+
let lib;
|
|
1459
1475
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1476
|
+
try {
|
|
1477
|
+
const resolved = require.resolve(spec, {
|
|
1478
|
+
paths: [process.cwd()]
|
|
1479
|
+
});
|
|
1480
|
+
lib = require(resolved);
|
|
1481
|
+
} catch (e) {
|
|
1482
|
+
const fullPath = path.isAbsolute(spec)
|
|
1483
|
+
? spec
|
|
1484
|
+
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
1485
|
+
|
|
1486
|
+
if (!fs.existsSync(fullPath)) {
|
|
1487
|
+
throw new Error(`Import not found: ${spec}`);
|
|
1469
1488
|
}
|
|
1470
|
-
}
|
|
1471
|
-
return result;
|
|
1472
|
-
}
|
|
1473
1489
|
|
|
1490
|
+
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
1491
|
+
const tokens = new Lexer(code).getTokens();
|
|
1492
|
+
const ast = new Parser(tokens).parse();
|
|
1474
1493
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
let lib;
|
|
1478
|
-
try {
|
|
1479
|
-
const resolved = require.resolve(spec, { paths: [process.cwd()] });
|
|
1480
|
-
lib = require(resolved);
|
|
1481
|
-
} catch (e) {
|
|
1482
|
-
const fullPath = path.isAbsolute(spec)
|
|
1483
|
-
? spec
|
|
1484
|
-
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
1494
|
+
const moduleEnv = new Environment(env);
|
|
1495
|
+
this.evaluate(ast, moduleEnv);
|
|
1485
1496
|
|
|
1486
|
-
|
|
1497
|
+
lib = {};
|
|
1498
|
+
for (const key of Object.keys(moduleEnv.store)) {
|
|
1499
|
+
lib[key] = moduleEnv.store[key];
|
|
1500
|
+
}
|
|
1487
1501
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
const ast = new Parser(tokens).parse();
|
|
1491
|
-
const moduleEnv = new Environment(env);
|
|
1492
|
-
this.evaluate(ast, moduleEnv);
|
|
1502
|
+
lib.default = lib;
|
|
1503
|
+
}
|
|
1493
1504
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
lib.default
|
|
1505
|
+
for (const imp of node.specifiers) {
|
|
1506
|
+
if (imp.type === 'DefaultImport') {
|
|
1507
|
+
env.define(imp.local, lib.default ?? lib);
|
|
1497
1508
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
if (imp.
|
|
1503
|
-
|
|
1504
|
-
env.define(imp.local, lib[imp.imported]);
|
|
1509
|
+
if (imp.type === 'NamespaceImport') {
|
|
1510
|
+
env.define(imp.local, lib);
|
|
1511
|
+
}
|
|
1512
|
+
if (imp.type === 'NamedImport') {
|
|
1513
|
+
if (!(imp.imported in lib)) {
|
|
1514
|
+
throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
|
|
1505
1515
|
}
|
|
1516
|
+
env.define(imp.local, lib[imp.imported]);
|
|
1506
1517
|
}
|
|
1507
|
-
return null;
|
|
1508
1518
|
}
|
|
1509
1519
|
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
|
|
1510
1524
|
evalBlock(node, env) {
|
|
1511
1525
|
let result = null;
|
|
1512
1526
|
for (const stmt of node.body) {
|
|
1513
|
-
try {
|
|
1514
|
-
|
|
1527
|
+
try {
|
|
1528
|
+
result = this.evaluate(stmt, env);
|
|
1529
|
+
} catch (e) {
|
|
1530
|
+
if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
1531
|
+
throw e;
|
|
1532
|
+
}
|
|
1515
1533
|
}
|
|
1516
1534
|
return result;
|
|
1517
1535
|
}
|
|
@@ -1524,15 +1542,27 @@ class Evaluator {
|
|
|
1524
1542
|
evalAssignment(node, env) {
|
|
1525
1543
|
const rightVal = this.evaluate(node.right, env);
|
|
1526
1544
|
const left = node.left;
|
|
1545
|
+
|
|
1527
1546
|
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
1528
|
-
if (left.type === 'MemberExpression') {
|
|
1529
|
-
|
|
1547
|
+
if (left.type === 'MemberExpression') {
|
|
1548
|
+
const obj = this.evaluate(left.object, env);
|
|
1549
|
+
obj[left.property] = rightVal;
|
|
1550
|
+
return rightVal;
|
|
1551
|
+
}
|
|
1552
|
+
if (left.type === 'IndexExpression') {
|
|
1553
|
+
const obj = this.evaluate(left.object, env);
|
|
1554
|
+
const idx = this.evaluate(left.indexer, env);
|
|
1555
|
+
obj[idx] = rightVal;
|
|
1556
|
+
return rightVal;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1530
1559
|
throw new Error('Invalid assignment target');
|
|
1531
1560
|
}
|
|
1532
1561
|
|
|
1533
1562
|
evalCompoundAssignment(node, env) {
|
|
1534
1563
|
const left = node.left;
|
|
1535
1564
|
let current;
|
|
1565
|
+
|
|
1536
1566
|
if (left.type === 'Identifier') current = env.get(left.name);
|
|
1537
1567
|
else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
|
|
1538
1568
|
else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
|
|
@@ -1550,7 +1580,9 @@ class Evaluator {
|
|
|
1550
1580
|
}
|
|
1551
1581
|
|
|
1552
1582
|
if (left.type === 'Identifier') env.set(left.name, computed);
|
|
1583
|
+
else if (left.type === 'MemberExpression') this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
1553
1584
|
else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
1585
|
+
|
|
1554
1586
|
return computed;
|
|
1555
1587
|
}
|
|
1556
1588
|
|
|
@@ -1562,7 +1594,8 @@ class Evaluator {
|
|
|
1562
1594
|
|
|
1563
1595
|
evalAsk(node, env) {
|
|
1564
1596
|
const prompt = this.evaluate(node.prompt, env);
|
|
1565
|
-
|
|
1597
|
+
const input = readlineSync.question(prompt + ' ');
|
|
1598
|
+
return input;
|
|
1566
1599
|
}
|
|
1567
1600
|
|
|
1568
1601
|
evalDefine(node, env) {
|
|
@@ -1579,16 +1612,12 @@ class Evaluator {
|
|
|
1579
1612
|
case 'STAR': return l * r;
|
|
1580
1613
|
case 'SLASH': return l / r;
|
|
1581
1614
|
case 'MOD': return l % r;
|
|
1582
|
-
case 'EQEQ': return l
|
|
1583
|
-
case 'NOTEQ': return l
|
|
1584
|
-
case 'STRICT_EQ': return l === r;
|
|
1585
|
-
case 'STRICT_NOTEQ': return l !== r;
|
|
1615
|
+
case 'EQEQ': return l === r;
|
|
1616
|
+
case 'NOTEQ': return l !== r;
|
|
1586
1617
|
case 'LT': return l < r;
|
|
1587
1618
|
case 'LTE': return l <= r;
|
|
1588
1619
|
case 'GT': return l > r;
|
|
1589
1620
|
case 'GTE': return l >= r;
|
|
1590
|
-
case 'LSHIFT': return l << r;
|
|
1591
|
-
case 'RSHIFT': return l >> r;
|
|
1592
1621
|
default: throw new Error(`Unknown binary operator ${node.operator}`);
|
|
1593
1622
|
}
|
|
1594
1623
|
}
|
|
@@ -1656,7 +1685,9 @@ class Evaluator {
|
|
|
1656
1685
|
const args = node.arguments.map(a => this.evaluate(a, env));
|
|
1657
1686
|
return calleeEvaluated(...args);
|
|
1658
1687
|
}
|
|
1659
|
-
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body)
|
|
1688
|
+
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
|
|
1689
|
+
throw new Error('Call to non-function');
|
|
1690
|
+
}
|
|
1660
1691
|
const fn = calleeEvaluated;
|
|
1661
1692
|
const callEnv = new Environment(fn.env);
|
|
1662
1693
|
fn.params.forEach((p, i) => {
|
|
@@ -1696,8 +1727,15 @@ class Evaluator {
|
|
|
1696
1727
|
};
|
|
1697
1728
|
const setValue = (v) => {
|
|
1698
1729
|
if (arg.type === 'Identifier') env.set(arg.name, v);
|
|
1699
|
-
else if (arg.type === 'MemberExpression') {
|
|
1700
|
-
|
|
1730
|
+
else if (arg.type === 'MemberExpression') {
|
|
1731
|
+
const obj = this.evaluate(arg.object, env);
|
|
1732
|
+
obj[arg.property] = v;
|
|
1733
|
+
}
|
|
1734
|
+
else if (arg.type === 'IndexExpression') {
|
|
1735
|
+
const obj = this.evaluate(arg.object, env);
|
|
1736
|
+
const idx = this.evaluate(arg.indexer, env);
|
|
1737
|
+
obj[idx] = v;
|
|
1738
|
+
}
|
|
1701
1739
|
};
|
|
1702
1740
|
const current = getCurrent();
|
|
1703
1741
|
const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
|
|
@@ -1706,8 +1744,7 @@ class Evaluator {
|
|
|
1706
1744
|
}
|
|
1707
1745
|
}
|
|
1708
1746
|
|
|
1709
|
-
module.exports = Evaluator;
|
|
1710
|
-
|
|
1747
|
+
module.exports = Evaluator;
|
|
1711
1748
|
|
|
1712
1749
|
/***/ }),
|
|
1713
1750
|
|
|
@@ -1718,10 +1755,18 @@ class Lexer {
|
|
|
1718
1755
|
constructor(input) {
|
|
1719
1756
|
this.input = input;
|
|
1720
1757
|
this.pos = 0;
|
|
1721
|
-
this.currentChar = input[
|
|
1758
|
+
this.currentChar = input[0] || null;
|
|
1759
|
+
this.line = 1; // current line
|
|
1760
|
+
this.column = 1; // current column
|
|
1722
1761
|
}
|
|
1723
1762
|
|
|
1724
1763
|
advance() {
|
|
1764
|
+
if (this.currentChar === '\n') {
|
|
1765
|
+
this.line++;
|
|
1766
|
+
this.column = 0;
|
|
1767
|
+
} else {
|
|
1768
|
+
this.column++;
|
|
1769
|
+
}
|
|
1725
1770
|
this.pos++;
|
|
1726
1771
|
this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
1727
1772
|
}
|
|
@@ -1730,152 +1775,149 @@ class Lexer {
|
|
|
1730
1775
|
return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
|
|
1731
1776
|
}
|
|
1732
1777
|
|
|
1778
|
+
error(msg) {
|
|
1779
|
+
throw new Error(`${msg} at line ${this.line}, column ${this.column}`);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1733
1782
|
skipWhitespace() {
|
|
1734
1783
|
while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
|
|
1735
1784
|
}
|
|
1736
1785
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
'
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
'false': 'FALSE',
|
|
1754
|
-
'null': 'NULL',
|
|
1755
|
-
'undefined': 'UNDEFINED',
|
|
1756
|
-
'ask': 'ASK',
|
|
1757
|
-
'sldeploy': 'SLDEPLOY'
|
|
1758
|
-
};
|
|
1759
|
-
return keywords[id] || null;
|
|
1786
|
+
skipComment() {
|
|
1787
|
+
if (this.currentChar === '#') {
|
|
1788
|
+
if (this.peek() === '*') {
|
|
1789
|
+
this.advance(); this.advance(); // skip #*
|
|
1790
|
+
while (this.currentChar !== null) {
|
|
1791
|
+
if (this.currentChar === '*' && this.peek() === '#') {
|
|
1792
|
+
this.advance(); this.advance();
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
this.advance();
|
|
1796
|
+
}
|
|
1797
|
+
this.error("Unterminated multi-line comment (#* ... *#)");
|
|
1798
|
+
} else {
|
|
1799
|
+
while (this.currentChar && this.currentChar !== '\n') this.advance();
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1760
1802
|
}
|
|
1761
1803
|
|
|
1762
1804
|
number() {
|
|
1805
|
+
const startLine = this.line;
|
|
1806
|
+
const startCol = this.column;
|
|
1763
1807
|
let result = '';
|
|
1764
|
-
while (this.currentChar && /[0-9
|
|
1808
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1765
1809
|
result += this.currentChar;
|
|
1766
1810
|
this.advance();
|
|
1767
1811
|
}
|
|
1768
|
-
|
|
1812
|
+
|
|
1813
|
+
if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
|
|
1814
|
+
result += '.'; this.advance();
|
|
1815
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1816
|
+
result += this.currentChar;
|
|
1817
|
+
this.advance();
|
|
1818
|
+
}
|
|
1819
|
+
return { type: 'NUMBER', value: parseFloat(result), line: startLine, column: startCol };
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
return { type: 'NUMBER', value: parseInt(result), line: startLine, column: startCol };
|
|
1769
1823
|
}
|
|
1770
1824
|
|
|
1771
1825
|
identifier() {
|
|
1826
|
+
const startLine = this.line;
|
|
1827
|
+
const startCol = this.column;
|
|
1772
1828
|
let result = '';
|
|
1773
|
-
while (this.currentChar && /[
|
|
1829
|
+
while (this.currentChar && /[A-Za-z0-9_]/.test(this.currentChar)) {
|
|
1774
1830
|
result += this.currentChar;
|
|
1775
1831
|
this.advance();
|
|
1776
1832
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1833
|
+
|
|
1834
|
+
const keywords = [
|
|
1835
|
+
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
1836
|
+
'break', 'continue', 'func', 'return', 'true', 'false', 'null',
|
|
1837
|
+
'ask', 'define', 'import', 'from', 'as'
|
|
1838
|
+
];
|
|
1839
|
+
|
|
1840
|
+
if (keywords.includes(result)) {
|
|
1841
|
+
return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
return { type: 'IDENTIFIER', value: result, line: startLine, column: startCol };
|
|
1779
1845
|
}
|
|
1780
1846
|
|
|
1781
|
-
string(
|
|
1782
|
-
this.
|
|
1847
|
+
string() {
|
|
1848
|
+
const startLine = this.line;
|
|
1849
|
+
const startCol = this.column;
|
|
1850
|
+
const quote = this.currentChar;
|
|
1851
|
+
this.advance();
|
|
1783
1852
|
let result = '';
|
|
1784
|
-
|
|
1853
|
+
|
|
1854
|
+
while (this.currentChar && this.currentChar !== quote) {
|
|
1785
1855
|
if (this.currentChar === '\\') {
|
|
1786
1856
|
this.advance();
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
result +=
|
|
1790
|
-
|
|
1857
|
+
switch (this.currentChar) {
|
|
1858
|
+
case 'n': result += '\n'; break;
|
|
1859
|
+
case 't': result += '\t'; break;
|
|
1860
|
+
case '"': result += '"'; break;
|
|
1861
|
+
case "'": result += "'"; break;
|
|
1862
|
+
case '\\': result += '\\'; break;
|
|
1863
|
+
default: result += this.currentChar;
|
|
1791
1864
|
}
|
|
1792
1865
|
} else {
|
|
1793
1866
|
result += this.currentChar;
|
|
1794
|
-
this.advance();
|
|
1795
1867
|
}
|
|
1868
|
+
this.advance();
|
|
1796
1869
|
}
|
|
1797
|
-
this.advance(); // skip closing quote
|
|
1798
|
-
return { type: 'STRING', value: result };
|
|
1799
|
-
}
|
|
1800
1870
|
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
const tokens = [];
|
|
1804
|
-
this.advance(); // skip initial backtick
|
|
1805
|
-
while (this.currentChar !== null) {
|
|
1806
|
-
if (this.currentChar === '$' && this.peek() === '{') {
|
|
1807
|
-
if (result) tokens.push({ type: 'TEMPLATE_STRING', value: result });
|
|
1808
|
-
result = '';
|
|
1809
|
-
tokens.push({ type: 'DOLLAR_LBRACE' });
|
|
1810
|
-
this.advance();
|
|
1811
|
-
this.advance(); // skip "${"
|
|
1812
|
-
} else if (this.currentChar === '`') {
|
|
1813
|
-
if (result) tokens.push({ type: 'TEMPLATE_STRING', value: result });
|
|
1814
|
-
this.advance();
|
|
1815
|
-
break;
|
|
1816
|
-
} else {
|
|
1817
|
-
result += this.currentChar;
|
|
1818
|
-
this.advance();
|
|
1819
|
-
}
|
|
1871
|
+
if (this.currentChar !== quote) {
|
|
1872
|
+
this.error('Unterminated string literal');
|
|
1820
1873
|
}
|
|
1821
|
-
|
|
1874
|
+
|
|
1875
|
+
this.advance();
|
|
1876
|
+
return { type: 'STRING', value: result, line: startLine, column: startCol };
|
|
1822
1877
|
}
|
|
1823
1878
|
|
|
1824
1879
|
getTokens() {
|
|
1825
1880
|
const tokens = [];
|
|
1826
|
-
while (this.currentChar !== null) {
|
|
1827
|
-
this.skipWhitespace();
|
|
1828
1881
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
if (
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
case ']': tokens.push({ type: 'RBRACKET' }); break;
|
|
1866
|
-
case ',': tokens.push({ type: 'COMMA' }); break;
|
|
1867
|
-
case ';': tokens.push({ type: 'SEMICOLON' }); break;
|
|
1868
|
-
case '.': tokens.push({ type: 'DOT' }); break;
|
|
1869
|
-
case ':': tokens.push({ type: 'COLON' }); break;
|
|
1870
|
-
case '*': tokens.push({ type: 'STAR' }); break;
|
|
1871
|
-
case '$': tokens.push({ type: 'DOLLAR' }); break;
|
|
1872
|
-
case '?': tokens.push({ type: 'QUESTION' }); break;
|
|
1873
|
-
default: throw new Error(`Unexpected character: ${char} at position ${this.pos}`);
|
|
1874
|
-
}
|
|
1875
|
-
this.advance();
|
|
1876
|
-
}
|
|
1882
|
+
while (this.currentChar !== null) {
|
|
1883
|
+
if (/\s/.test(this.currentChar)) { this.skipWhitespace(); continue; }
|
|
1884
|
+
if (this.currentChar === '#') { this.skipComment(); continue; }
|
|
1885
|
+
if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
|
|
1886
|
+
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
1887
|
+
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
1888
|
+
|
|
1889
|
+
const char = this.currentChar;
|
|
1890
|
+
const next = this.peek();
|
|
1891
|
+
const startLine = this.line;
|
|
1892
|
+
const startCol = this.column;
|
|
1893
|
+
|
|
1894
|
+
if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1895
|
+
if (char === '=' && next === '>') { tokens.push({ type: 'ARROW', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1896
|
+
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1897
|
+
if (char === '<' && next === '=') { tokens.push({ type: 'LTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1898
|
+
if (char === '>' && next === '=') { tokens.push({ type: 'GTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1899
|
+
if (char === '&' && next === '&') { tokens.push({ type: 'AND', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1900
|
+
if (char === '|' && next === '|') { tokens.push({ type: 'OR', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1901
|
+
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1902
|
+
if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1903
|
+
|
|
1904
|
+
const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
|
|
1905
|
+
if (next === '=' && map[char]) { tokens.push({ type: map[char], line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1906
|
+
|
|
1907
|
+
const singles = {
|
|
1908
|
+
'+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
|
|
1909
|
+
'=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
|
|
1910
|
+
'(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
|
|
1911
|
+
';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
|
|
1912
|
+
':': 'COLON', '.': 'DOT'
|
|
1913
|
+
};
|
|
1914
|
+
|
|
1915
|
+
if (singles[char]) { tokens.push({ type: singles[char], line: startLine, column: startCol }); this.advance(); continue; }
|
|
1916
|
+
|
|
1917
|
+
this.error("Unexpected character: " + char);
|
|
1877
1918
|
}
|
|
1878
|
-
|
|
1919
|
+
|
|
1920
|
+
tokens.push({ type: 'EOF', line: this.line, column: this.column });
|
|
1879
1921
|
return tokens;
|
|
1880
1922
|
}
|
|
1881
1923
|
}
|
|
@@ -1902,7 +1944,10 @@ class Parser {
|
|
|
1902
1944
|
|
|
1903
1945
|
eat(type) {
|
|
1904
1946
|
if (this.current.type === type) this.advance();
|
|
1905
|
-
else throw new Error(
|
|
1947
|
+
else throw new Error(
|
|
1948
|
+
`Expected ${type}, got ${this.current.type} at line ${this.current.line}, column ${this.current.column}`
|
|
1949
|
+
);
|
|
1950
|
+
|
|
1906
1951
|
}
|
|
1907
1952
|
|
|
1908
1953
|
peekType(offset = 1) {
|
|
@@ -1989,52 +2034,52 @@ class Parser {
|
|
|
1989
2034
|
const body = this.block();
|
|
1990
2035
|
return { type: 'WhileStatement', test, body };
|
|
1991
2036
|
}
|
|
1992
|
-
|
|
1993
2037
|
importStatement() {
|
|
1994
2038
|
this.eat('IMPORT');
|
|
1995
2039
|
|
|
1996
2040
|
let specifiers = [];
|
|
1997
2041
|
|
|
1998
2042
|
if (this.current.type === 'STAR') {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2043
|
+
this.eat('STAR');
|
|
2044
|
+
this.eat('AS');
|
|
2045
|
+
const name = this.current.value;
|
|
2046
|
+
this.eat('IDENTIFIER');
|
|
2047
|
+
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2004
2048
|
}
|
|
2005
2049
|
else if (this.current.type === 'LBRACE') {
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2050
|
+
this.eat('LBRACE');
|
|
2051
|
+
while (this.current.type !== 'RBRACE') {
|
|
2052
|
+
const importedName = this.current.value;
|
|
2053
|
+
this.eat('IDENTIFIER');
|
|
2054
|
+
let localName = importedName;
|
|
2055
|
+
if (this.current.type === 'AS') {
|
|
2056
|
+
this.eat('AS');
|
|
2057
|
+
localName = this.current.value;
|
|
2058
|
+
this.eat('IDENTIFIER');
|
|
2059
|
+
}
|
|
2060
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2061
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2015
2062
|
}
|
|
2016
|
-
|
|
2017
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2018
|
-
}
|
|
2019
|
-
this.eat('RBRACE');
|
|
2063
|
+
this.eat('RBRACE');
|
|
2020
2064
|
}
|
|
2021
2065
|
else if (this.current.type === 'IDENTIFIER') {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2066
|
+
const localName = this.current.value;
|
|
2067
|
+
this.eat('IDENTIFIER');
|
|
2068
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2025
2069
|
} else {
|
|
2026
|
-
|
|
2070
|
+
throw new Error(`Unexpected token in import statement at line ${this.current.line}, column ${this.current.column}`);
|
|
2027
2071
|
}
|
|
2028
2072
|
|
|
2029
2073
|
this.eat('FROM');
|
|
2030
2074
|
const pathToken = this.current;
|
|
2031
|
-
if (pathToken.type !== 'STRING') throw new Error(
|
|
2075
|
+
if (pathToken.type !== 'STRING') throw new Error(`Expected string literal after from in import at line ${this.current.line}, column ${this.current.column}`);
|
|
2032
2076
|
this.eat('STRING');
|
|
2033
2077
|
|
|
2034
2078
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2035
2079
|
|
|
2036
2080
|
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2037
|
-
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2038
2083
|
|
|
2039
2084
|
forStatement() {
|
|
2040
2085
|
this.eat('FOR');
|
|
@@ -2114,23 +2159,6 @@ class Parser {
|
|
|
2114
2159
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2115
2160
|
return { type: 'ExpressionStatement', expression: expr };
|
|
2116
2161
|
}
|
|
2117
|
-
parseTemplateLiteral() {
|
|
2118
|
-
const parts = [];
|
|
2119
|
-
while (true) {
|
|
2120
|
-
if (this.current.type === 'TEMPLATE_STRING') {
|
|
2121
|
-
parts.push({ type: 'Literal', value: this.current.value });
|
|
2122
|
-
this.eat('TEMPLATE_STRING');
|
|
2123
|
-
} else if (this.current.type === 'DOLLAR_LBRACE') {
|
|
2124
|
-
this.eat('DOLLAR_LBRACE');
|
|
2125
|
-
const expr = this.expression();
|
|
2126
|
-
parts.push(expr);
|
|
2127
|
-
this.eat('RBRACE');
|
|
2128
|
-
} else {
|
|
2129
|
-
break;
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
return { type: 'TemplateLiteral', parts };
|
|
2133
|
-
}
|
|
2134
2162
|
|
|
2135
2163
|
expression() {
|
|
2136
2164
|
return this.assignment();
|
|
@@ -2177,7 +2205,7 @@ class Parser {
|
|
|
2177
2205
|
|
|
2178
2206
|
equality() {
|
|
2179
2207
|
let node = this.comparison();
|
|
2180
|
-
while (['EQEQ', 'NOTEQ'
|
|
2208
|
+
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2181
2209
|
const op = this.current.type;
|
|
2182
2210
|
this.eat(op);
|
|
2183
2211
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
@@ -2187,7 +2215,7 @@ class Parser {
|
|
|
2187
2215
|
|
|
2188
2216
|
comparison() {
|
|
2189
2217
|
let node = this.term();
|
|
2190
|
-
while (['LT', 'LTE', 'GT', 'GTE'
|
|
2218
|
+
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2191
2219
|
const op = this.current.type;
|
|
2192
2220
|
this.eat(op);
|
|
2193
2221
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
@@ -2256,7 +2284,7 @@ class Parser {
|
|
|
2256
2284
|
}
|
|
2257
2285
|
if (this.current.type === 'DOT') {
|
|
2258
2286
|
this.eat('DOT');
|
|
2259
|
-
if (this.current.type !== 'IDENTIFIER') throw new Error(
|
|
2287
|
+
if (this.current.type !== 'IDENTIFIER') throw new Error(`Expected property name after dot at line ${this.current.line}, column ${this.current.column}`);
|
|
2260
2288
|
const property = this.current.value;
|
|
2261
2289
|
this.eat('IDENTIFIER');
|
|
2262
2290
|
node = { type: 'MemberExpression', object: node, property };
|
|
@@ -2278,15 +2306,9 @@ class Parser {
|
|
|
2278
2306
|
|
|
2279
2307
|
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2280
2308
|
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
2281
|
-
if (t.type === 'TEMPLATE_STRING') {
|
|
2282
|
-
return this.parseTemplateLiteral();
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
2309
|
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2287
2310
|
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2288
2311
|
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
2289
|
-
if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
|
|
2290
2312
|
|
|
2291
2313
|
if (t.type === 'ASK') {
|
|
2292
2314
|
this.eat('ASK');
|
|
@@ -2337,7 +2359,7 @@ class Parser {
|
|
|
2337
2359
|
let key;
|
|
2338
2360
|
if (this.current.type === 'STRING') { key = this.current.value; this.eat('STRING'); }
|
|
2339
2361
|
else if (this.current.type === 'IDENTIFIER') { key = this.current.value; this.eat('IDENTIFIER'); }
|
|
2340
|
-
else throw new Error(
|
|
2362
|
+
else throw new Error(`Invalid object key at line ${this.current.line}, column ${this.current.column}`);
|
|
2341
2363
|
this.eat('COLON');
|
|
2342
2364
|
const value = this.expression();
|
|
2343
2365
|
props.push({ key, value });
|
|
@@ -2347,12 +2369,11 @@ class Parser {
|
|
|
2347
2369
|
return { type: 'ObjectExpression', props };
|
|
2348
2370
|
}
|
|
2349
2371
|
|
|
2350
|
-
throw new Error(`Unexpected token in primary: ${t.type}`);
|
|
2372
|
+
throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
|
|
2351
2373
|
}
|
|
2352
2374
|
}
|
|
2353
2375
|
|
|
2354
|
-
module.exports = Parser;
|
|
2355
|
-
|
|
2376
|
+
module.exports = Parser;
|
|
2356
2377
|
|
|
2357
2378
|
/***/ }),
|
|
2358
2379
|
|
|
@@ -2454,7 +2475,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
2454
2475
|
const Parser = __nccwpck_require__(222);
|
|
2455
2476
|
const Evaluator = __nccwpck_require__(112);
|
|
2456
2477
|
|
|
2457
|
-
const VERSION = '1.0.
|
|
2478
|
+
const VERSION = '1.0.22';
|
|
2458
2479
|
|
|
2459
2480
|
const COLOR = {
|
|
2460
2481
|
reset: '\x1b[0m',
|