starlight-cli 1.0.21 → 1.0.23
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 +291 -253
- package/package.json +1 -1
- package/src/evaluator.js +141 -86
- 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) {
|
|
@@ -1573,25 +1606,27 @@ class Evaluator {
|
|
|
1573
1606
|
evalBinary(node, env) {
|
|
1574
1607
|
const l = this.evaluate(node.left, env);
|
|
1575
1608
|
const r = this.evaluate(node.right, env);
|
|
1609
|
+
|
|
1610
|
+
if (node.operator === 'SLASH' && r === 0) {
|
|
1611
|
+
throw new Error('Division by zero');
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1576
1614
|
switch (node.operator) {
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
case 'GTE': return l >= r;
|
|
1590
|
-
case 'LSHIFT': return l << r;
|
|
1591
|
-
case 'RSHIFT': return l >> r;
|
|
1592
|
-
default: throw new Error(`Unknown binary operator ${node.operator}`);
|
|
1615
|
+
case 'PLUS': return l + r;
|
|
1616
|
+
case 'MINUS': return l - r;
|
|
1617
|
+
case 'STAR': return l * r;
|
|
1618
|
+
case 'SLASH': return l / r;
|
|
1619
|
+
case 'MOD': return l % r;
|
|
1620
|
+
case 'EQEQ': return l === r;
|
|
1621
|
+
case 'NOTEQ': return l !== r;
|
|
1622
|
+
case 'LT': return l < r;
|
|
1623
|
+
case 'LTE': return l <= r;
|
|
1624
|
+
case 'GT': return l > r;
|
|
1625
|
+
case 'GTE': return l >= r;
|
|
1626
|
+
default: throw new Error(`Unknown binary operator ${node.operator}`);
|
|
1593
1627
|
}
|
|
1594
|
-
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1595
1630
|
|
|
1596
1631
|
evalLogical(node, env) {
|
|
1597
1632
|
const l = this.evaluate(node.left, env);
|
|
@@ -1656,7 +1691,9 @@ class Evaluator {
|
|
|
1656
1691
|
const args = node.arguments.map(a => this.evaluate(a, env));
|
|
1657
1692
|
return calleeEvaluated(...args);
|
|
1658
1693
|
}
|
|
1659
|
-
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body)
|
|
1694
|
+
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
|
|
1695
|
+
throw new Error('Call to non-function');
|
|
1696
|
+
}
|
|
1660
1697
|
const fn = calleeEvaluated;
|
|
1661
1698
|
const callEnv = new Environment(fn.env);
|
|
1662
1699
|
fn.params.forEach((p, i) => {
|
|
@@ -1670,9 +1707,18 @@ class Evaluator {
|
|
|
1670
1707
|
evalIndex(node, env) {
|
|
1671
1708
|
const obj = this.evaluate(node.object, env);
|
|
1672
1709
|
const idx = this.evaluate(node.indexer, env);
|
|
1673
|
-
|
|
1710
|
+
|
|
1711
|
+
if (obj == null) throw new Error('Indexing null or undefined');
|
|
1712
|
+
if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
|
|
1713
|
+
throw new Error('Array index out of bounds');
|
|
1714
|
+
}
|
|
1715
|
+
if (typeof obj === 'object' && !(idx in obj)) {
|
|
1716
|
+
throw new Error(`Property '${idx}' does not exist`);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1674
1719
|
return obj[idx];
|
|
1675
|
-
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1676
1722
|
|
|
1677
1723
|
evalObject(node, env) {
|
|
1678
1724
|
const out = {};
|
|
@@ -1682,9 +1728,11 @@ class Evaluator {
|
|
|
1682
1728
|
|
|
1683
1729
|
evalMember(node, env) {
|
|
1684
1730
|
const obj = this.evaluate(node.object, env);
|
|
1685
|
-
if (obj == null) throw new Error('Member access of null
|
|
1731
|
+
if (obj == null) throw new Error('Member access of null or undefined');
|
|
1732
|
+
if (!(node.property in obj)) throw new Error(`Property '${node.property}' does not exist`);
|
|
1686
1733
|
return obj[node.property];
|
|
1687
|
-
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1688
1736
|
|
|
1689
1737
|
evalUpdate(node, env) {
|
|
1690
1738
|
const arg = node.argument;
|
|
@@ -1696,8 +1744,15 @@ class Evaluator {
|
|
|
1696
1744
|
};
|
|
1697
1745
|
const setValue = (v) => {
|
|
1698
1746
|
if (arg.type === 'Identifier') env.set(arg.name, v);
|
|
1699
|
-
else if (arg.type === 'MemberExpression') {
|
|
1700
|
-
|
|
1747
|
+
else if (arg.type === 'MemberExpression') {
|
|
1748
|
+
const obj = this.evaluate(arg.object, env);
|
|
1749
|
+
obj[arg.property] = v;
|
|
1750
|
+
}
|
|
1751
|
+
else if (arg.type === 'IndexExpression') {
|
|
1752
|
+
const obj = this.evaluate(arg.object, env);
|
|
1753
|
+
const idx = this.evaluate(arg.indexer, env);
|
|
1754
|
+
obj[idx] = v;
|
|
1755
|
+
}
|
|
1701
1756
|
};
|
|
1702
1757
|
const current = getCurrent();
|
|
1703
1758
|
const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
|
|
@@ -1706,8 +1761,7 @@ class Evaluator {
|
|
|
1706
1761
|
}
|
|
1707
1762
|
}
|
|
1708
1763
|
|
|
1709
|
-
module.exports = Evaluator;
|
|
1710
|
-
|
|
1764
|
+
module.exports = Evaluator;
|
|
1711
1765
|
|
|
1712
1766
|
/***/ }),
|
|
1713
1767
|
|
|
@@ -1718,10 +1772,18 @@ class Lexer {
|
|
|
1718
1772
|
constructor(input) {
|
|
1719
1773
|
this.input = input;
|
|
1720
1774
|
this.pos = 0;
|
|
1721
|
-
this.currentChar = input[
|
|
1775
|
+
this.currentChar = input[0] || null;
|
|
1776
|
+
this.line = 1; // current line
|
|
1777
|
+
this.column = 1; // current column
|
|
1722
1778
|
}
|
|
1723
1779
|
|
|
1724
1780
|
advance() {
|
|
1781
|
+
if (this.currentChar === '\n') {
|
|
1782
|
+
this.line++;
|
|
1783
|
+
this.column = 0;
|
|
1784
|
+
} else {
|
|
1785
|
+
this.column++;
|
|
1786
|
+
}
|
|
1725
1787
|
this.pos++;
|
|
1726
1788
|
this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
1727
1789
|
}
|
|
@@ -1730,152 +1792,149 @@ class Lexer {
|
|
|
1730
1792
|
return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
|
|
1731
1793
|
}
|
|
1732
1794
|
|
|
1795
|
+
error(msg) {
|
|
1796
|
+
throw new Error(`${msg} at line ${this.line}, column ${this.column}`);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1733
1799
|
skipWhitespace() {
|
|
1734
1800
|
while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
|
|
1735
1801
|
}
|
|
1736
1802
|
|
|
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;
|
|
1803
|
+
skipComment() {
|
|
1804
|
+
if (this.currentChar === '#') {
|
|
1805
|
+
if (this.peek() === '*') {
|
|
1806
|
+
this.advance(); this.advance(); // skip #*
|
|
1807
|
+
while (this.currentChar !== null) {
|
|
1808
|
+
if (this.currentChar === '*' && this.peek() === '#') {
|
|
1809
|
+
this.advance(); this.advance();
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
this.advance();
|
|
1813
|
+
}
|
|
1814
|
+
this.error("Unterminated multi-line comment (#* ... *#)");
|
|
1815
|
+
} else {
|
|
1816
|
+
while (this.currentChar && this.currentChar !== '\n') this.advance();
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1760
1819
|
}
|
|
1761
1820
|
|
|
1762
1821
|
number() {
|
|
1822
|
+
const startLine = this.line;
|
|
1823
|
+
const startCol = this.column;
|
|
1763
1824
|
let result = '';
|
|
1764
|
-
while (this.currentChar && /[0-9
|
|
1825
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1765
1826
|
result += this.currentChar;
|
|
1766
1827
|
this.advance();
|
|
1767
1828
|
}
|
|
1768
|
-
|
|
1829
|
+
|
|
1830
|
+
if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
|
|
1831
|
+
result += '.'; this.advance();
|
|
1832
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1833
|
+
result += this.currentChar;
|
|
1834
|
+
this.advance();
|
|
1835
|
+
}
|
|
1836
|
+
return { type: 'NUMBER', value: parseFloat(result), line: startLine, column: startCol };
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
return { type: 'NUMBER', value: parseInt(result), line: startLine, column: startCol };
|
|
1769
1840
|
}
|
|
1770
1841
|
|
|
1771
1842
|
identifier() {
|
|
1843
|
+
const startLine = this.line;
|
|
1844
|
+
const startCol = this.column;
|
|
1772
1845
|
let result = '';
|
|
1773
|
-
while (this.currentChar && /[
|
|
1846
|
+
while (this.currentChar && /[A-Za-z0-9_]/.test(this.currentChar)) {
|
|
1774
1847
|
result += this.currentChar;
|
|
1775
1848
|
this.advance();
|
|
1776
1849
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1850
|
+
|
|
1851
|
+
const keywords = [
|
|
1852
|
+
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
1853
|
+
'break', 'continue', 'func', 'return', 'true', 'false', 'null',
|
|
1854
|
+
'ask', 'define', 'import', 'from', 'as'
|
|
1855
|
+
];
|
|
1856
|
+
|
|
1857
|
+
if (keywords.includes(result)) {
|
|
1858
|
+
return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
return { type: 'IDENTIFIER', value: result, line: startLine, column: startCol };
|
|
1779
1862
|
}
|
|
1780
1863
|
|
|
1781
|
-
string(
|
|
1782
|
-
this.
|
|
1864
|
+
string() {
|
|
1865
|
+
const startLine = this.line;
|
|
1866
|
+
const startCol = this.column;
|
|
1867
|
+
const quote = this.currentChar;
|
|
1868
|
+
this.advance();
|
|
1783
1869
|
let result = '';
|
|
1784
|
-
|
|
1870
|
+
|
|
1871
|
+
while (this.currentChar && this.currentChar !== quote) {
|
|
1785
1872
|
if (this.currentChar === '\\') {
|
|
1786
1873
|
this.advance();
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
result +=
|
|
1790
|
-
|
|
1874
|
+
switch (this.currentChar) {
|
|
1875
|
+
case 'n': result += '\n'; break;
|
|
1876
|
+
case 't': result += '\t'; break;
|
|
1877
|
+
case '"': result += '"'; break;
|
|
1878
|
+
case "'": result += "'"; break;
|
|
1879
|
+
case '\\': result += '\\'; break;
|
|
1880
|
+
default: result += this.currentChar;
|
|
1791
1881
|
}
|
|
1792
1882
|
} else {
|
|
1793
1883
|
result += this.currentChar;
|
|
1794
|
-
this.advance();
|
|
1795
1884
|
}
|
|
1885
|
+
this.advance();
|
|
1796
1886
|
}
|
|
1797
|
-
this.advance(); // skip closing quote
|
|
1798
|
-
return { type: 'STRING', value: result };
|
|
1799
|
-
}
|
|
1800
1887
|
|
|
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
|
-
}
|
|
1888
|
+
if (this.currentChar !== quote) {
|
|
1889
|
+
this.error('Unterminated string literal');
|
|
1820
1890
|
}
|
|
1821
|
-
|
|
1891
|
+
|
|
1892
|
+
this.advance();
|
|
1893
|
+
return { type: 'STRING', value: result, line: startLine, column: startCol };
|
|
1822
1894
|
}
|
|
1823
1895
|
|
|
1824
1896
|
getTokens() {
|
|
1825
1897
|
const tokens = [];
|
|
1826
|
-
while (this.currentChar !== null) {
|
|
1827
|
-
this.skipWhitespace();
|
|
1828
|
-
|
|
1829
|
-
if (!this.currentChar) break;
|
|
1830
1898
|
|
|
1831
|
-
|
|
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
|
-
|
|
1866
|
-
|
|
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
|
-
}
|
|
1899
|
+
while (this.currentChar !== null) {
|
|
1900
|
+
if (/\s/.test(this.currentChar)) { this.skipWhitespace(); continue; }
|
|
1901
|
+
if (this.currentChar === '#') { this.skipComment(); continue; }
|
|
1902
|
+
if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
|
|
1903
|
+
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
1904
|
+
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
1905
|
+
|
|
1906
|
+
const char = this.currentChar;
|
|
1907
|
+
const next = this.peek();
|
|
1908
|
+
const startLine = this.line;
|
|
1909
|
+
const startCol = this.column;
|
|
1910
|
+
|
|
1911
|
+
if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1912
|
+
if (char === '=' && next === '>') { tokens.push({ type: 'ARROW', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1913
|
+
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1914
|
+
if (char === '<' && next === '=') { tokens.push({ type: 'LTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1915
|
+
if (char === '>' && next === '=') { tokens.push({ type: 'GTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1916
|
+
if (char === '&' && next === '&') { tokens.push({ type: 'AND', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1917
|
+
if (char === '|' && next === '|') { tokens.push({ type: 'OR', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1918
|
+
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1919
|
+
if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1920
|
+
|
|
1921
|
+
const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
|
|
1922
|
+
if (next === '=' && map[char]) { tokens.push({ type: map[char], line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
1923
|
+
|
|
1924
|
+
const singles = {
|
|
1925
|
+
'+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
|
|
1926
|
+
'=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
|
|
1927
|
+
'(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
|
|
1928
|
+
';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
|
|
1929
|
+
':': 'COLON', '.': 'DOT'
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1932
|
+
if (singles[char]) { tokens.push({ type: singles[char], line: startLine, column: startCol }); this.advance(); continue; }
|
|
1933
|
+
|
|
1934
|
+
this.error("Unexpected character: " + char);
|
|
1877
1935
|
}
|
|
1878
|
-
|
|
1936
|
+
|
|
1937
|
+
tokens.push({ type: 'EOF', line: this.line, column: this.column });
|
|
1879
1938
|
return tokens;
|
|
1880
1939
|
}
|
|
1881
1940
|
}
|
|
@@ -1902,7 +1961,10 @@ class Parser {
|
|
|
1902
1961
|
|
|
1903
1962
|
eat(type) {
|
|
1904
1963
|
if (this.current.type === type) this.advance();
|
|
1905
|
-
else throw new Error(
|
|
1964
|
+
else throw new Error(
|
|
1965
|
+
`Expected ${type}, got ${this.current.type} at line ${this.current.line}, column ${this.current.column}`
|
|
1966
|
+
);
|
|
1967
|
+
|
|
1906
1968
|
}
|
|
1907
1969
|
|
|
1908
1970
|
peekType(offset = 1) {
|
|
@@ -1989,52 +2051,52 @@ class Parser {
|
|
|
1989
2051
|
const body = this.block();
|
|
1990
2052
|
return { type: 'WhileStatement', test, body };
|
|
1991
2053
|
}
|
|
1992
|
-
|
|
1993
2054
|
importStatement() {
|
|
1994
2055
|
this.eat('IMPORT');
|
|
1995
2056
|
|
|
1996
2057
|
let specifiers = [];
|
|
1997
2058
|
|
|
1998
2059
|
if (this.current.type === 'STAR') {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2060
|
+
this.eat('STAR');
|
|
2061
|
+
this.eat('AS');
|
|
2062
|
+
const name = this.current.value;
|
|
2063
|
+
this.eat('IDENTIFIER');
|
|
2064
|
+
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2004
2065
|
}
|
|
2005
2066
|
else if (this.current.type === 'LBRACE') {
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2067
|
+
this.eat('LBRACE');
|
|
2068
|
+
while (this.current.type !== 'RBRACE') {
|
|
2069
|
+
const importedName = this.current.value;
|
|
2070
|
+
this.eat('IDENTIFIER');
|
|
2071
|
+
let localName = importedName;
|
|
2072
|
+
if (this.current.type === 'AS') {
|
|
2073
|
+
this.eat('AS');
|
|
2074
|
+
localName = this.current.value;
|
|
2075
|
+
this.eat('IDENTIFIER');
|
|
2076
|
+
}
|
|
2077
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2078
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2015
2079
|
}
|
|
2016
|
-
|
|
2017
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2018
|
-
}
|
|
2019
|
-
this.eat('RBRACE');
|
|
2080
|
+
this.eat('RBRACE');
|
|
2020
2081
|
}
|
|
2021
2082
|
else if (this.current.type === 'IDENTIFIER') {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2083
|
+
const localName = this.current.value;
|
|
2084
|
+
this.eat('IDENTIFIER');
|
|
2085
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2025
2086
|
} else {
|
|
2026
|
-
|
|
2087
|
+
throw new Error(`Unexpected token in import statement at line ${this.current.line}, column ${this.current.column}`);
|
|
2027
2088
|
}
|
|
2028
2089
|
|
|
2029
2090
|
this.eat('FROM');
|
|
2030
2091
|
const pathToken = this.current;
|
|
2031
|
-
if (pathToken.type !== 'STRING') throw new Error(
|
|
2092
|
+
if (pathToken.type !== 'STRING') throw new Error(`Expected string literal after from in import at line ${this.current.line}, column ${this.current.column}`);
|
|
2032
2093
|
this.eat('STRING');
|
|
2033
2094
|
|
|
2034
2095
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2035
2096
|
|
|
2036
2097
|
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2037
|
-
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2038
2100
|
|
|
2039
2101
|
forStatement() {
|
|
2040
2102
|
this.eat('FOR');
|
|
@@ -2114,23 +2176,6 @@ class Parser {
|
|
|
2114
2176
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2115
2177
|
return { type: 'ExpressionStatement', expression: expr };
|
|
2116
2178
|
}
|
|
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
2179
|
|
|
2135
2180
|
expression() {
|
|
2136
2181
|
return this.assignment();
|
|
@@ -2177,7 +2222,7 @@ class Parser {
|
|
|
2177
2222
|
|
|
2178
2223
|
equality() {
|
|
2179
2224
|
let node = this.comparison();
|
|
2180
|
-
while (['EQEQ', 'NOTEQ'
|
|
2225
|
+
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2181
2226
|
const op = this.current.type;
|
|
2182
2227
|
this.eat(op);
|
|
2183
2228
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
@@ -2187,7 +2232,7 @@ class Parser {
|
|
|
2187
2232
|
|
|
2188
2233
|
comparison() {
|
|
2189
2234
|
let node = this.term();
|
|
2190
|
-
while (['LT', 'LTE', 'GT', 'GTE'
|
|
2235
|
+
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2191
2236
|
const op = this.current.type;
|
|
2192
2237
|
this.eat(op);
|
|
2193
2238
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
@@ -2256,7 +2301,7 @@ class Parser {
|
|
|
2256
2301
|
}
|
|
2257
2302
|
if (this.current.type === 'DOT') {
|
|
2258
2303
|
this.eat('DOT');
|
|
2259
|
-
if (this.current.type !== 'IDENTIFIER') throw new Error(
|
|
2304
|
+
if (this.current.type !== 'IDENTIFIER') throw new Error(`Expected property name after dot at line ${this.current.line}, column ${this.current.column}`);
|
|
2260
2305
|
const property = this.current.value;
|
|
2261
2306
|
this.eat('IDENTIFIER');
|
|
2262
2307
|
node = { type: 'MemberExpression', object: node, property };
|
|
@@ -2278,15 +2323,9 @@ class Parser {
|
|
|
2278
2323
|
|
|
2279
2324
|
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2280
2325
|
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
2326
|
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2287
2327
|
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2288
2328
|
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
2329
|
|
|
2291
2330
|
if (t.type === 'ASK') {
|
|
2292
2331
|
this.eat('ASK');
|
|
@@ -2337,7 +2376,7 @@ class Parser {
|
|
|
2337
2376
|
let key;
|
|
2338
2377
|
if (this.current.type === 'STRING') { key = this.current.value; this.eat('STRING'); }
|
|
2339
2378
|
else if (this.current.type === 'IDENTIFIER') { key = this.current.value; this.eat('IDENTIFIER'); }
|
|
2340
|
-
else throw new Error(
|
|
2379
|
+
else throw new Error(`Invalid object key at line ${this.current.line}, column ${this.current.column}`);
|
|
2341
2380
|
this.eat('COLON');
|
|
2342
2381
|
const value = this.expression();
|
|
2343
2382
|
props.push({ key, value });
|
|
@@ -2347,12 +2386,11 @@ class Parser {
|
|
|
2347
2386
|
return { type: 'ObjectExpression', props };
|
|
2348
2387
|
}
|
|
2349
2388
|
|
|
2350
|
-
throw new Error(`Unexpected token in primary: ${t.type}`);
|
|
2389
|
+
throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
|
|
2351
2390
|
}
|
|
2352
2391
|
}
|
|
2353
2392
|
|
|
2354
|
-
module.exports = Parser;
|
|
2355
|
-
|
|
2393
|
+
module.exports = Parser;
|
|
2356
2394
|
|
|
2357
2395
|
/***/ }),
|
|
2358
2396
|
|
|
@@ -2454,7 +2492,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
2454
2492
|
const Parser = __nccwpck_require__(222);
|
|
2455
2493
|
const Evaluator = __nccwpck_require__(112);
|
|
2456
2494
|
|
|
2457
|
-
const VERSION = '1.0.
|
|
2495
|
+
const VERSION = '1.0.23';
|
|
2458
2496
|
|
|
2459
2497
|
const COLOR = {
|
|
2460
2498
|
reset: '\x1b[0m',
|