starlight-cli 1.0.20 → 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 +200 -235
- package/package.json +1 -1
- package/src/evaluator.js +109 -71
- package/src/lexer.js +57 -108
- 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
|
|
|
@@ -1719,19 +1756,27 @@ class Lexer {
|
|
|
1719
1756
|
this.input = input;
|
|
1720
1757
|
this.pos = 0;
|
|
1721
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
|
}
|
|
1728
1773
|
|
|
1729
|
-
peek(
|
|
1730
|
-
return this.pos +
|
|
1774
|
+
peek() {
|
|
1775
|
+
return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
|
|
1731
1776
|
}
|
|
1732
1777
|
|
|
1733
1778
|
error(msg) {
|
|
1734
|
-
throw new Error(
|
|
1779
|
+
throw new Error(`${msg} at line ${this.line}, column ${this.column}`);
|
|
1735
1780
|
}
|
|
1736
1781
|
|
|
1737
1782
|
skipWhitespace() {
|
|
@@ -1741,7 +1786,7 @@ class Lexer {
|
|
|
1741
1786
|
skipComment() {
|
|
1742
1787
|
if (this.currentChar === '#') {
|
|
1743
1788
|
if (this.peek() === '*') {
|
|
1744
|
-
this.advance(); this.advance(); // #*
|
|
1789
|
+
this.advance(); this.advance(); // skip #*
|
|
1745
1790
|
while (this.currentChar !== null) {
|
|
1746
1791
|
if (this.currentChar === '*' && this.peek() === '#') {
|
|
1747
1792
|
this.advance(); this.advance();
|
|
@@ -1749,7 +1794,7 @@ class Lexer {
|
|
|
1749
1794
|
}
|
|
1750
1795
|
this.advance();
|
|
1751
1796
|
}
|
|
1752
|
-
this.error(
|
|
1797
|
+
this.error("Unterminated multi-line comment (#* ... *#)");
|
|
1753
1798
|
} else {
|
|
1754
1799
|
while (this.currentChar && this.currentChar !== '\n') this.advance();
|
|
1755
1800
|
}
|
|
@@ -1757,6 +1802,8 @@ class Lexer {
|
|
|
1757
1802
|
}
|
|
1758
1803
|
|
|
1759
1804
|
number() {
|
|
1805
|
+
const startLine = this.line;
|
|
1806
|
+
const startCol = this.column;
|
|
1760
1807
|
let result = '';
|
|
1761
1808
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1762
1809
|
result += this.currentChar;
|
|
@@ -1764,32 +1811,20 @@ class Lexer {
|
|
|
1764
1811
|
}
|
|
1765
1812
|
|
|
1766
1813
|
if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
|
|
1767
|
-
result += '.';
|
|
1768
|
-
this.advance();
|
|
1814
|
+
result += '.'; this.advance();
|
|
1769
1815
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1770
1816
|
result += this.currentChar;
|
|
1771
1817
|
this.advance();
|
|
1772
1818
|
}
|
|
1819
|
+
return { type: 'NUMBER', value: parseFloat(result), line: startLine, column: startCol };
|
|
1773
1820
|
}
|
|
1774
1821
|
|
|
1775
|
-
|
|
1776
|
-
result += this.currentChar;
|
|
1777
|
-
this.advance();
|
|
1778
|
-
if (this.currentChar === '+' || this.currentChar === '-') {
|
|
1779
|
-
result += this.currentChar;
|
|
1780
|
-
this.advance();
|
|
1781
|
-
}
|
|
1782
|
-
if (!/[0-9]/.test(this.currentChar)) this.error('Invalid exponent in number');
|
|
1783
|
-
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1784
|
-
result += this.currentChar;
|
|
1785
|
-
this.advance();
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
return { type: 'NUMBER', value: parseFloat(result) };
|
|
1822
|
+
return { type: 'NUMBER', value: parseInt(result), line: startLine, column: startCol };
|
|
1790
1823
|
}
|
|
1791
1824
|
|
|
1792
1825
|
identifier() {
|
|
1826
|
+
const startLine = this.line;
|
|
1827
|
+
const startCol = this.column;
|
|
1793
1828
|
let result = '';
|
|
1794
1829
|
while (this.currentChar && /[A-Za-z0-9_]/.test(this.currentChar)) {
|
|
1795
1830
|
result += this.currentChar;
|
|
@@ -1798,19 +1833,20 @@ class Lexer {
|
|
|
1798
1833
|
|
|
1799
1834
|
const keywords = [
|
|
1800
1835
|
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
1801
|
-
'break', 'continue', 'func', 'return',
|
|
1802
|
-
'true', 'false', 'null', 'undefined',
|
|
1836
|
+
'break', 'continue', 'func', 'return', 'true', 'false', 'null',
|
|
1803
1837
|
'ask', 'define', 'import', 'from', 'as'
|
|
1804
1838
|
];
|
|
1805
1839
|
|
|
1806
1840
|
if (keywords.includes(result)) {
|
|
1807
|
-
return { type: result.toUpperCase(), value: result };
|
|
1841
|
+
return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
|
|
1808
1842
|
}
|
|
1809
1843
|
|
|
1810
|
-
return { type: 'IDENTIFIER', value: result };
|
|
1844
|
+
return { type: 'IDENTIFIER', value: result, line: startLine, column: startCol };
|
|
1811
1845
|
}
|
|
1812
1846
|
|
|
1813
1847
|
string() {
|
|
1848
|
+
const startLine = this.line;
|
|
1849
|
+
const startCol = this.column;
|
|
1814
1850
|
const quote = this.currentChar;
|
|
1815
1851
|
this.advance();
|
|
1816
1852
|
let result = '';
|
|
@@ -1818,58 +1854,26 @@ class Lexer {
|
|
|
1818
1854
|
while (this.currentChar && this.currentChar !== quote) {
|
|
1819
1855
|
if (this.currentChar === '\\') {
|
|
1820
1856
|
this.advance();
|
|
1821
|
-
|
|
1822
|
-
|
|
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;
|
|
1864
|
+
}
|
|
1823
1865
|
} else {
|
|
1824
1866
|
result += this.currentChar;
|
|
1825
1867
|
}
|
|
1826
1868
|
this.advance();
|
|
1827
1869
|
}
|
|
1828
1870
|
|
|
1829
|
-
if (this.currentChar !== quote)
|
|
1830
|
-
|
|
1831
|
-
this.advance();
|
|
1832
|
-
return { type: 'STRING', value: result };
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
templateString(tokens) {
|
|
1837
|
-
this.advance(); // skip opening `
|
|
1838
|
-
|
|
1839
|
-
let buffer = '';
|
|
1840
|
-
|
|
1841
|
-
while (this.currentChar !== null) {
|
|
1842
|
-
if (this.currentChar === '`') {
|
|
1843
|
-
if (buffer.length > 0) {
|
|
1844
|
-
tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
|
|
1845
|
-
}
|
|
1846
|
-
this.advance();
|
|
1847
|
-
return;
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
if (this.currentChar === '$' && this.peek() === '{') {
|
|
1851
|
-
if (buffer.length > 0) {
|
|
1852
|
-
tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
|
|
1853
|
-
buffer = '';
|
|
1854
|
-
}
|
|
1855
|
-
this.advance(); // $
|
|
1856
|
-
this.advance(); // {
|
|
1857
|
-
tokens.push({ type: 'DOLLAR_LBRACE' });
|
|
1858
|
-
return;
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
if (this.currentChar === '\\') {
|
|
1862
|
-
this.advance();
|
|
1863
|
-
buffer += this.currentChar;
|
|
1864
|
-
this.advance();
|
|
1865
|
-
continue;
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
buffer += this.currentChar;
|
|
1869
|
-
this.advance();
|
|
1871
|
+
if (this.currentChar !== quote) {
|
|
1872
|
+
this.error('Unterminated string literal');
|
|
1870
1873
|
}
|
|
1871
1874
|
|
|
1872
|
-
this.
|
|
1875
|
+
this.advance();
|
|
1876
|
+
return { type: 'STRING', value: result, line: startLine, column: startCol };
|
|
1873
1877
|
}
|
|
1874
1878
|
|
|
1875
1879
|
getTokens() {
|
|
@@ -1882,56 +1886,38 @@ class Lexer {
|
|
|
1882
1886
|
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
1883
1887
|
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
1884
1888
|
|
|
1885
|
-
if (this.currentChar === '`') {
|
|
1886
|
-
this.templateString(tokens);
|
|
1887
|
-
continue;
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
1889
|
const char = this.currentChar;
|
|
1891
1890
|
const next = this.peek();
|
|
1892
|
-
const
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
if (char === '
|
|
1896
|
-
if (char === '=' && next === '
|
|
1897
|
-
if (char === '
|
|
1898
|
-
if (char === '
|
|
1899
|
-
if (char === '
|
|
1900
|
-
if (char === '
|
|
1901
|
-
if (char === '
|
|
1902
|
-
if (char === '
|
|
1903
|
-
if (char === '
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
if (
|
|
1907
|
-
|
|
1908
|
-
const compound = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
|
|
1909
|
-
if (next === '=' && compound[char]) {
|
|
1910
|
-
tokens.push({ type: compound[char] });
|
|
1911
|
-
this.advance(); this.advance();
|
|
1912
|
-
continue;
|
|
1913
|
-
}
|
|
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; }
|
|
1914
1906
|
|
|
1915
1907
|
const singles = {
|
|
1916
1908
|
'+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
|
|
1917
1909
|
'=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
|
|
1918
|
-
'(': 'LPAREN', ')': 'RPAREN',
|
|
1919
|
-
'
|
|
1920
|
-
'[': 'LBRACKET', ']': 'RBRACKET',
|
|
1921
|
-
';': 'SEMICOLON', ',': 'COMMA',
|
|
1910
|
+
'(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
|
|
1911
|
+
';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
|
|
1922
1912
|
':': 'COLON', '.': 'DOT'
|
|
1923
1913
|
};
|
|
1924
1914
|
|
|
1925
|
-
if (singles[char]) {
|
|
1926
|
-
tokens.push({ type: singles[char] });
|
|
1927
|
-
this.advance();
|
|
1928
|
-
continue;
|
|
1929
|
-
}
|
|
1915
|
+
if (singles[char]) { tokens.push({ type: singles[char], line: startLine, column: startCol }); this.advance(); continue; }
|
|
1930
1916
|
|
|
1931
|
-
this.error(
|
|
1917
|
+
this.error("Unexpected character: " + char);
|
|
1932
1918
|
}
|
|
1933
1919
|
|
|
1934
|
-
tokens.push({ type: 'EOF' });
|
|
1920
|
+
tokens.push({ type: 'EOF', line: this.line, column: this.column });
|
|
1935
1921
|
return tokens;
|
|
1936
1922
|
}
|
|
1937
1923
|
}
|
|
@@ -1958,7 +1944,10 @@ class Parser {
|
|
|
1958
1944
|
|
|
1959
1945
|
eat(type) {
|
|
1960
1946
|
if (this.current.type === type) this.advance();
|
|
1961
|
-
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
|
+
|
|
1962
1951
|
}
|
|
1963
1952
|
|
|
1964
1953
|
peekType(offset = 1) {
|
|
@@ -2045,52 +2034,52 @@ class Parser {
|
|
|
2045
2034
|
const body = this.block();
|
|
2046
2035
|
return { type: 'WhileStatement', test, body };
|
|
2047
2036
|
}
|
|
2048
|
-
|
|
2049
2037
|
importStatement() {
|
|
2050
2038
|
this.eat('IMPORT');
|
|
2051
2039
|
|
|
2052
2040
|
let specifiers = [];
|
|
2053
2041
|
|
|
2054
2042
|
if (this.current.type === 'STAR') {
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
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 });
|
|
2060
2048
|
}
|
|
2061
2049
|
else if (this.current.type === 'LBRACE') {
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
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');
|
|
2071
2062
|
}
|
|
2072
|
-
|
|
2073
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2074
|
-
}
|
|
2075
|
-
this.eat('RBRACE');
|
|
2063
|
+
this.eat('RBRACE');
|
|
2076
2064
|
}
|
|
2077
2065
|
else if (this.current.type === 'IDENTIFIER') {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2066
|
+
const localName = this.current.value;
|
|
2067
|
+
this.eat('IDENTIFIER');
|
|
2068
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2081
2069
|
} else {
|
|
2082
|
-
|
|
2070
|
+
throw new Error(`Unexpected token in import statement at line ${this.current.line}, column ${this.current.column}`);
|
|
2083
2071
|
}
|
|
2084
2072
|
|
|
2085
2073
|
this.eat('FROM');
|
|
2086
2074
|
const pathToken = this.current;
|
|
2087
|
-
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}`);
|
|
2088
2076
|
this.eat('STRING');
|
|
2089
2077
|
|
|
2090
2078
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2091
2079
|
|
|
2092
2080
|
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2093
|
-
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2094
2083
|
|
|
2095
2084
|
forStatement() {
|
|
2096
2085
|
this.eat('FOR');
|
|
@@ -2170,23 +2159,6 @@ class Parser {
|
|
|
2170
2159
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2171
2160
|
return { type: 'ExpressionStatement', expression: expr };
|
|
2172
2161
|
}
|
|
2173
|
-
parseTemplateLiteral() {
|
|
2174
|
-
const parts = [];
|
|
2175
|
-
while (true) {
|
|
2176
|
-
if (this.current.type === 'TEMPLATE_STRING') {
|
|
2177
|
-
parts.push({ type: 'Literal', value: this.current.value });
|
|
2178
|
-
this.eat('TEMPLATE_STRING');
|
|
2179
|
-
} else if (this.current.type === 'DOLLAR_LBRACE') {
|
|
2180
|
-
this.eat('DOLLAR_LBRACE');
|
|
2181
|
-
const expr = this.expression();
|
|
2182
|
-
parts.push(expr);
|
|
2183
|
-
this.eat('RBRACE');
|
|
2184
|
-
} else {
|
|
2185
|
-
break;
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
return { type: 'TemplateLiteral', parts };
|
|
2189
|
-
}
|
|
2190
2162
|
|
|
2191
2163
|
expression() {
|
|
2192
2164
|
return this.assignment();
|
|
@@ -2233,7 +2205,7 @@ class Parser {
|
|
|
2233
2205
|
|
|
2234
2206
|
equality() {
|
|
2235
2207
|
let node = this.comparison();
|
|
2236
|
-
while (['EQEQ', 'NOTEQ'
|
|
2208
|
+
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2237
2209
|
const op = this.current.type;
|
|
2238
2210
|
this.eat(op);
|
|
2239
2211
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
@@ -2243,7 +2215,7 @@ class Parser {
|
|
|
2243
2215
|
|
|
2244
2216
|
comparison() {
|
|
2245
2217
|
let node = this.term();
|
|
2246
|
-
while (['LT', 'LTE', 'GT', 'GTE'
|
|
2218
|
+
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2247
2219
|
const op = this.current.type;
|
|
2248
2220
|
this.eat(op);
|
|
2249
2221
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
@@ -2312,7 +2284,7 @@ class Parser {
|
|
|
2312
2284
|
}
|
|
2313
2285
|
if (this.current.type === 'DOT') {
|
|
2314
2286
|
this.eat('DOT');
|
|
2315
|
-
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}`);
|
|
2316
2288
|
const property = this.current.value;
|
|
2317
2289
|
this.eat('IDENTIFIER');
|
|
2318
2290
|
node = { type: 'MemberExpression', object: node, property };
|
|
@@ -2334,15 +2306,9 @@ class Parser {
|
|
|
2334
2306
|
|
|
2335
2307
|
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2336
2308
|
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
2337
|
-
if (t.type === 'TEMPLATE_STRING') {
|
|
2338
|
-
return this.parseTemplateLiteral();
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
2309
|
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2343
2310
|
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2344
2311
|
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
2345
|
-
if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
|
|
2346
2312
|
|
|
2347
2313
|
if (t.type === 'ASK') {
|
|
2348
2314
|
this.eat('ASK');
|
|
@@ -2393,7 +2359,7 @@ class Parser {
|
|
|
2393
2359
|
let key;
|
|
2394
2360
|
if (this.current.type === 'STRING') { key = this.current.value; this.eat('STRING'); }
|
|
2395
2361
|
else if (this.current.type === 'IDENTIFIER') { key = this.current.value; this.eat('IDENTIFIER'); }
|
|
2396
|
-
else throw new Error(
|
|
2362
|
+
else throw new Error(`Invalid object key at line ${this.current.line}, column ${this.current.column}`);
|
|
2397
2363
|
this.eat('COLON');
|
|
2398
2364
|
const value = this.expression();
|
|
2399
2365
|
props.push({ key, value });
|
|
@@ -2403,12 +2369,11 @@ class Parser {
|
|
|
2403
2369
|
return { type: 'ObjectExpression', props };
|
|
2404
2370
|
}
|
|
2405
2371
|
|
|
2406
|
-
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}`);
|
|
2407
2373
|
}
|
|
2408
2374
|
}
|
|
2409
2375
|
|
|
2410
|
-
module.exports = Parser;
|
|
2411
|
-
|
|
2376
|
+
module.exports = Parser;
|
|
2412
2377
|
|
|
2413
2378
|
/***/ }),
|
|
2414
2379
|
|
|
@@ -2510,7 +2475,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
2510
2475
|
const Parser = __nccwpck_require__(222);
|
|
2511
2476
|
const Evaluator = __nccwpck_require__(112);
|
|
2512
2477
|
|
|
2513
|
-
const VERSION = '1.0.
|
|
2478
|
+
const VERSION = '1.0.22';
|
|
2514
2479
|
|
|
2515
2480
|
const COLOR = {
|
|
2516
2481
|
reset: '\x1b[0m',
|