starlight-cli 1.0.17 → 1.0.18
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 +150 -145
- package/package.json +1 -1
- package/src/evaluator.js +62 -111
- package/src/lexer.js +57 -8
- package/src/parser.js +29 -27
- package/src/starlight.js +1 -1
package/dist/index.js
CHANGED
|
@@ -1347,9 +1347,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
1347
1347
|
const Parser = __nccwpck_require__(222);
|
|
1348
1348
|
const path = __nccwpck_require__(928);
|
|
1349
1349
|
|
|
1350
|
-
class ReturnValue {
|
|
1351
|
-
constructor(value) { this.value = value; }
|
|
1352
|
-
}
|
|
1350
|
+
class ReturnValue { constructor(value) { this.value = value; } }
|
|
1353
1351
|
class BreakSignal {}
|
|
1354
1352
|
class ContinueSignal {}
|
|
1355
1353
|
|
|
@@ -1392,37 +1390,27 @@ class Evaluator {
|
|
|
1392
1390
|
|
|
1393
1391
|
setupBuiltins() {
|
|
1394
1392
|
this.global.define('len', arg => {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1393
|
+
if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
|
|
1394
|
+
if (arg && typeof arg === 'object') return Object.keys(arg).length;
|
|
1395
|
+
return 0;
|
|
1398
1396
|
});
|
|
1399
1397
|
|
|
1400
1398
|
this.global.define('print', arg => { console.log(arg); return null; });
|
|
1401
1399
|
this.global.define('type', arg => {
|
|
1402
|
-
|
|
1403
|
-
|
|
1400
|
+
if (Array.isArray(arg)) return 'array';
|
|
1401
|
+
return typeof arg;
|
|
1404
1402
|
});
|
|
1405
1403
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
1406
1404
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
1407
1405
|
|
|
1408
|
-
this.global.define('ask', prompt =>
|
|
1409
|
-
const readlineSync = __nccwpck_require__(552);
|
|
1410
|
-
return readlineSync.question(prompt + ' ');
|
|
1411
|
-
});
|
|
1406
|
+
this.global.define('ask', prompt => readlineSync.question(prompt + ' '));
|
|
1412
1407
|
this.global.define('num', arg => {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
this.global.define('str', arg => {
|
|
1421
|
-
return String(arg);
|
|
1422
|
-
});
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1408
|
+
const n = Number(arg);
|
|
1409
|
+
if (Number.isNaN(n)) throw new Error('Cannot convert value to number');
|
|
1410
|
+
return n;
|
|
1411
|
+
});
|
|
1412
|
+
this.global.define('str', arg => String(arg));
|
|
1413
|
+
}
|
|
1426
1414
|
|
|
1427
1415
|
evaluate(node, env = this.global) {
|
|
1428
1416
|
switch (node.type) {
|
|
@@ -1439,6 +1427,7 @@ this.global.define('str', arg => {
|
|
|
1439
1427
|
case 'LogicalExpression': return this.evalLogical(node, env);
|
|
1440
1428
|
case 'UnaryExpression': return this.evalUnary(node, env);
|
|
1441
1429
|
case 'Literal': return node.value;
|
|
1430
|
+
case 'TemplateLiteral': return this.evalTemplateLiteral(node, env);
|
|
1442
1431
|
case 'Identifier': return env.get(node.name);
|
|
1443
1432
|
case 'IfStatement': return this.evalIf(node, env);
|
|
1444
1433
|
case 'WhileStatement': return this.evalWhile(node, env);
|
|
@@ -1464,72 +1453,54 @@ this.global.define('str', arg => {
|
|
|
1464
1453
|
|
|
1465
1454
|
evalProgram(node, env) {
|
|
1466
1455
|
let result = null;
|
|
1467
|
-
for (const stmt of node.body)
|
|
1468
|
-
result = this.evaluate(stmt, env);
|
|
1469
|
-
}
|
|
1456
|
+
for (const stmt of node.body) result = this.evaluate(stmt, env);
|
|
1470
1457
|
return result;
|
|
1471
1458
|
}
|
|
1472
|
-
evalImport(node, env) {
|
|
1473
|
-
const spec = node.path;
|
|
1474
|
-
let lib;
|
|
1475
1459
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
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}`);
|
|
1488
|
-
}
|
|
1460
|
+
evalTemplateLiteral(node, env) {
|
|
1461
|
+
return node.value;
|
|
1462
|
+
}
|
|
1489
1463
|
|
|
1490
|
-
|
|
1491
|
-
const
|
|
1492
|
-
|
|
1464
|
+
evalImport(node, env) {
|
|
1465
|
+
const spec = node.path;
|
|
1466
|
+
let lib;
|
|
1467
|
+
try {
|
|
1468
|
+
const resolved = require.resolve(spec, { paths: [process.cwd()] });
|
|
1469
|
+
lib = require(resolved);
|
|
1470
|
+
} catch (e) {
|
|
1471
|
+
const fullPath = path.isAbsolute(spec)
|
|
1472
|
+
? spec
|
|
1473
|
+
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
1493
1474
|
|
|
1494
|
-
|
|
1495
|
-
this.evaluate(ast, moduleEnv);
|
|
1475
|
+
if (!fs.existsSync(fullPath)) throw new Error(`Import not found: ${spec}`);
|
|
1496
1476
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1477
|
+
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
1478
|
+
const tokens = new Lexer(code).getTokens();
|
|
1479
|
+
const ast = new Parser(tokens).parse();
|
|
1480
|
+
const moduleEnv = new Environment(env);
|
|
1481
|
+
this.evaluate(ast, moduleEnv);
|
|
1501
1482
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
for (const imp of node.specifiers) {
|
|
1506
|
-
if (imp.type === 'DefaultImport') {
|
|
1507
|
-
env.define(imp.local, lib.default ?? lib);
|
|
1508
|
-
}
|
|
1509
|
-
if (imp.type === 'NamespaceImport') {
|
|
1510
|
-
env.define(imp.local, lib);
|
|
1483
|
+
lib = {};
|
|
1484
|
+
for (const key of Object.keys(moduleEnv.store)) lib[key] = moduleEnv.store[key];
|
|
1485
|
+
lib.default = lib;
|
|
1511
1486
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1487
|
+
|
|
1488
|
+
for (const imp of node.specifiers) {
|
|
1489
|
+
if (imp.type === 'DefaultImport') env.define(imp.local, lib.default ?? lib);
|
|
1490
|
+
if (imp.type === 'NamespaceImport') env.define(imp.local, lib);
|
|
1491
|
+
if (imp.type === 'NamedImport') {
|
|
1492
|
+
if (!(imp.imported in lib)) throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
|
|
1493
|
+
env.define(imp.local, lib[imp.imported]);
|
|
1515
1494
|
}
|
|
1516
|
-
env.define(imp.local, lib[imp.imported]);
|
|
1517
1495
|
}
|
|
1496
|
+
return null;
|
|
1518
1497
|
}
|
|
1519
1498
|
|
|
1520
|
-
return null;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
1499
|
evalBlock(node, env) {
|
|
1525
1500
|
let result = null;
|
|
1526
1501
|
for (const stmt of node.body) {
|
|
1527
|
-
try {
|
|
1528
|
-
|
|
1529
|
-
} catch (e) {
|
|
1530
|
-
if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
1531
|
-
throw e;
|
|
1532
|
-
}
|
|
1502
|
+
try { result = this.evaluate(stmt, env); }
|
|
1503
|
+
catch (e) { if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e; else throw e; }
|
|
1533
1504
|
}
|
|
1534
1505
|
return result;
|
|
1535
1506
|
}
|
|
@@ -1542,27 +1513,15 @@ evalImport(node, env) {
|
|
|
1542
1513
|
evalAssignment(node, env) {
|
|
1543
1514
|
const rightVal = this.evaluate(node.right, env);
|
|
1544
1515
|
const left = node.left;
|
|
1545
|
-
|
|
1546
1516
|
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
1547
|
-
if (left.type === 'MemberExpression') {
|
|
1548
|
-
|
|
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
|
-
|
|
1517
|
+
if (left.type === 'MemberExpression') { const obj = this.evaluate(left.object, env); obj[left.property] = rightVal; return rightVal; }
|
|
1518
|
+
if (left.type === 'IndexExpression') { const obj = this.evaluate(left.object, env); const idx = this.evaluate(left.indexer, env); obj[idx] = rightVal; return rightVal; }
|
|
1559
1519
|
throw new Error('Invalid assignment target');
|
|
1560
1520
|
}
|
|
1561
1521
|
|
|
1562
1522
|
evalCompoundAssignment(node, env) {
|
|
1563
1523
|
const left = node.left;
|
|
1564
1524
|
let current;
|
|
1565
|
-
|
|
1566
1525
|
if (left.type === 'Identifier') current = env.get(left.name);
|
|
1567
1526
|
else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
|
|
1568
1527
|
else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
|
|
@@ -1580,9 +1539,7 @@ evalImport(node, env) {
|
|
|
1580
1539
|
}
|
|
1581
1540
|
|
|
1582
1541
|
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);
|
|
1584
1542
|
else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
1585
|
-
|
|
1586
1543
|
return computed;
|
|
1587
1544
|
}
|
|
1588
1545
|
|
|
@@ -1594,8 +1551,7 @@ evalImport(node, env) {
|
|
|
1594
1551
|
|
|
1595
1552
|
evalAsk(node, env) {
|
|
1596
1553
|
const prompt = this.evaluate(node.prompt, env);
|
|
1597
|
-
|
|
1598
|
-
return input;
|
|
1554
|
+
return readlineSync.question(prompt + ' ');
|
|
1599
1555
|
}
|
|
1600
1556
|
|
|
1601
1557
|
evalDefine(node, env) {
|
|
@@ -1612,12 +1568,16 @@ evalImport(node, env) {
|
|
|
1612
1568
|
case 'STAR': return l * r;
|
|
1613
1569
|
case 'SLASH': return l / r;
|
|
1614
1570
|
case 'MOD': return l % r;
|
|
1615
|
-
case 'EQEQ': return l
|
|
1616
|
-
case 'NOTEQ': return l
|
|
1571
|
+
case 'EQEQ': return l == r;
|
|
1572
|
+
case 'NOTEQ': return l != r;
|
|
1573
|
+
case 'STRICT_EQ': return l === r;
|
|
1574
|
+
case 'STRICT_NOTEQ': return l !== r;
|
|
1617
1575
|
case 'LT': return l < r;
|
|
1618
1576
|
case 'LTE': return l <= r;
|
|
1619
1577
|
case 'GT': return l > r;
|
|
1620
1578
|
case 'GTE': return l >= r;
|
|
1579
|
+
case 'LSHIFT': return l << r;
|
|
1580
|
+
case 'RSHIFT': return l >> r;
|
|
1621
1581
|
default: throw new Error(`Unknown binary operator ${node.operator}`);
|
|
1622
1582
|
}
|
|
1623
1583
|
}
|
|
@@ -1685,9 +1645,7 @@ evalImport(node, env) {
|
|
|
1685
1645
|
const args = node.arguments.map(a => this.evaluate(a, env));
|
|
1686
1646
|
return calleeEvaluated(...args);
|
|
1687
1647
|
}
|
|
1688
|
-
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body)
|
|
1689
|
-
throw new Error('Call to non-function');
|
|
1690
|
-
}
|
|
1648
|
+
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) throw new Error('Call to non-function');
|
|
1691
1649
|
const fn = calleeEvaluated;
|
|
1692
1650
|
const callEnv = new Environment(fn.env);
|
|
1693
1651
|
fn.params.forEach((p, i) => {
|
|
@@ -1727,15 +1685,8 @@ evalImport(node, env) {
|
|
|
1727
1685
|
};
|
|
1728
1686
|
const setValue = (v) => {
|
|
1729
1687
|
if (arg.type === 'Identifier') env.set(arg.name, v);
|
|
1730
|
-
else if (arg.type === 'MemberExpression') {
|
|
1731
|
-
|
|
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
|
-
}
|
|
1688
|
+
else if (arg.type === 'MemberExpression') { const obj = this.evaluate(arg.object, env); obj[arg.property] = v; }
|
|
1689
|
+
else if (arg.type === 'IndexExpression') { const obj = this.evaluate(arg.object, env); const idx = this.evaluate(arg.indexer, env); obj[idx] = v; }
|
|
1739
1690
|
};
|
|
1740
1691
|
const current = getCurrent();
|
|
1741
1692
|
const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
|
|
@@ -1744,7 +1695,8 @@ evalImport(node, env) {
|
|
|
1744
1695
|
}
|
|
1745
1696
|
}
|
|
1746
1697
|
|
|
1747
|
-
module.exports = Evaluator;
|
|
1698
|
+
module.exports = Evaluator;
|
|
1699
|
+
|
|
1748
1700
|
|
|
1749
1701
|
/***/ }),
|
|
1750
1702
|
|
|
@@ -1763,8 +1715,8 @@ class Lexer {
|
|
|
1763
1715
|
this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
1764
1716
|
}
|
|
1765
1717
|
|
|
1766
|
-
peek() {
|
|
1767
|
-
return this.pos +
|
|
1718
|
+
peek(n = 1) {
|
|
1719
|
+
return this.pos + n < this.input.length ? this.input[this.pos + n] : null;
|
|
1768
1720
|
}
|
|
1769
1721
|
|
|
1770
1722
|
error(msg) {
|
|
@@ -1806,10 +1758,21 @@ class Lexer {
|
|
|
1806
1758
|
result += this.currentChar;
|
|
1807
1759
|
this.advance();
|
|
1808
1760
|
}
|
|
1809
|
-
return { type: 'NUMBER', value: parseFloat(result) };
|
|
1810
1761
|
}
|
|
1811
1762
|
|
|
1812
|
-
|
|
1763
|
+
if (this.currentChar && /[eE]/.test(this.currentChar)) {
|
|
1764
|
+
result += this.currentChar; this.advance();
|
|
1765
|
+
if (this.currentChar === '+' || this.currentChar === '-') {
|
|
1766
|
+
result += this.currentChar; this.advance();
|
|
1767
|
+
}
|
|
1768
|
+
if (!/[0-9]/.test(this.currentChar)) this.error("Invalid exponent in number");
|
|
1769
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1770
|
+
result += this.currentChar;
|
|
1771
|
+
this.advance();
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
return { type: 'NUMBER', value: parseFloat(result) };
|
|
1813
1776
|
}
|
|
1814
1777
|
|
|
1815
1778
|
identifier() {
|
|
@@ -1822,7 +1785,7 @@ class Lexer {
|
|
|
1822
1785
|
const keywords = [
|
|
1823
1786
|
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
1824
1787
|
'break', 'continue', 'func', 'return', 'true', 'false', 'null',
|
|
1825
|
-
'ask', 'define', 'import', 'from', 'as'
|
|
1788
|
+
'ask', 'define', 'import', 'from', 'as', 'undefined'
|
|
1826
1789
|
];
|
|
1827
1790
|
|
|
1828
1791
|
if (keywords.includes(result)) {
|
|
@@ -1830,8 +1793,7 @@ class Lexer {
|
|
|
1830
1793
|
}
|
|
1831
1794
|
|
|
1832
1795
|
return { type: 'IDENTIFIER', value: result };
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1796
|
+
}
|
|
1835
1797
|
|
|
1836
1798
|
string() {
|
|
1837
1799
|
const quote = this.currentChar;
|
|
@@ -1863,6 +1825,39 @@ class Lexer {
|
|
|
1863
1825
|
return { type: 'STRING', value: result };
|
|
1864
1826
|
}
|
|
1865
1827
|
|
|
1828
|
+
templateString() {
|
|
1829
|
+
this.advance();
|
|
1830
|
+
let result = '';
|
|
1831
|
+
while (this.currentChar && this.currentChar !== '`') {
|
|
1832
|
+
if (this.currentChar === '$' && this.peek() === '{') {
|
|
1833
|
+
this.advance(); this.advance(); // skip ${
|
|
1834
|
+
result += '${'; // we leave the interpolation as-is for parser
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
if (this.currentChar === '\\') {
|
|
1839
|
+
this.advance();
|
|
1840
|
+
switch (this.currentChar) {
|
|
1841
|
+
case 'n': result += '\n'; break;
|
|
1842
|
+
case 't': result += '\t'; break;
|
|
1843
|
+
case '`': result += '`'; break;
|
|
1844
|
+
case '\\': result += '\\'; break;
|
|
1845
|
+
default: result += this.currentChar;
|
|
1846
|
+
}
|
|
1847
|
+
} else {
|
|
1848
|
+
result += this.currentChar;
|
|
1849
|
+
}
|
|
1850
|
+
this.advance();
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
if (this.currentChar !== '`') {
|
|
1854
|
+
this.error('Unterminated template string');
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
this.advance();
|
|
1858
|
+
return { type: 'TEMPLATE_STRING', value: result };
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1866
1861
|
getTokens() {
|
|
1867
1862
|
const tokens = [];
|
|
1868
1863
|
|
|
@@ -1872,15 +1867,21 @@ class Lexer {
|
|
|
1872
1867
|
if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
|
|
1873
1868
|
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
1874
1869
|
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
1870
|
+
if (this.currentChar === '`') { tokens.push(this.templateString()); continue; }
|
|
1875
1871
|
|
|
1876
1872
|
const char = this.currentChar;
|
|
1877
1873
|
const next = this.peek();
|
|
1874
|
+
const next2 = this.peek(2);
|
|
1878
1875
|
|
|
1876
|
+
if (char === '=' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_EQ' }); this.advance(); this.advance(); this.advance(); continue; }
|
|
1877
|
+
if (char === '!' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_NOTEQ' }); this.advance(); this.advance(); this.advance(); continue; }
|
|
1879
1878
|
if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
|
|
1880
1879
|
if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
|
|
1881
1880
|
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
|
|
1882
1881
|
if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
|
|
1883
1882
|
if (char === '>' && next === '=') { tokens.push({ type: 'GTE' }); this.advance(); this.advance(); continue; }
|
|
1883
|
+
if (char === '<' && next === '<') { tokens.push({ type: 'LSHIFT' }); this.advance(); this.advance(); continue; }
|
|
1884
|
+
if (char === '>' && next === '>') { tokens.push({ type: 'RSHIFT' }); this.advance(); this.advance(); continue; }
|
|
1884
1885
|
if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
|
|
1885
1886
|
if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
|
|
1886
1887
|
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
|
|
@@ -1907,7 +1908,8 @@ class Lexer {
|
|
|
1907
1908
|
}
|
|
1908
1909
|
}
|
|
1909
1910
|
|
|
1910
|
-
module.exports = Lexer;
|
|
1911
|
+
module.exports = Lexer;
|
|
1912
|
+
|
|
1911
1913
|
|
|
1912
1914
|
/***/ }),
|
|
1913
1915
|
|
|
@@ -2015,40 +2017,41 @@ class Parser {
|
|
|
2015
2017
|
const body = this.block();
|
|
2016
2018
|
return { type: 'WhileStatement', test, body };
|
|
2017
2019
|
}
|
|
2020
|
+
|
|
2018
2021
|
importStatement() {
|
|
2019
2022
|
this.eat('IMPORT');
|
|
2020
2023
|
|
|
2021
2024
|
let specifiers = [];
|
|
2022
2025
|
|
|
2023
2026
|
if (this.current.type === 'STAR') {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2027
|
+
this.eat('STAR');
|
|
2028
|
+
this.eat('AS');
|
|
2029
|
+
const name = this.current.value;
|
|
2030
|
+
this.eat('IDENTIFIER');
|
|
2031
|
+
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2029
2032
|
}
|
|
2030
2033
|
else if (this.current.type === 'LBRACE') {
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
}
|
|
2041
|
-
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2042
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2034
|
+
this.eat('LBRACE');
|
|
2035
|
+
while (this.current.type !== 'RBRACE') {
|
|
2036
|
+
const importedName = this.current.value;
|
|
2037
|
+
this.eat('IDENTIFIER');
|
|
2038
|
+
let localName = importedName;
|
|
2039
|
+
if (this.current.type === 'AS') {
|
|
2040
|
+
this.eat('AS');
|
|
2041
|
+
localName = this.current.value;
|
|
2042
|
+
this.eat('IDENTIFIER');
|
|
2043
2043
|
}
|
|
2044
|
-
|
|
2044
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2045
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2046
|
+
}
|
|
2047
|
+
this.eat('RBRACE');
|
|
2045
2048
|
}
|
|
2046
2049
|
else if (this.current.type === 'IDENTIFIER') {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
+
const localName = this.current.value;
|
|
2051
|
+
this.eat('IDENTIFIER');
|
|
2052
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2050
2053
|
} else {
|
|
2051
|
-
|
|
2054
|
+
throw new Error('Unexpected token in import statement');
|
|
2052
2055
|
}
|
|
2053
2056
|
|
|
2054
2057
|
this.eat('FROM');
|
|
@@ -2059,8 +2062,7 @@ class Parser {
|
|
|
2059
2062
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2060
2063
|
|
|
2061
2064
|
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2065
|
+
}
|
|
2064
2066
|
|
|
2065
2067
|
forStatement() {
|
|
2066
2068
|
this.eat('FOR');
|
|
@@ -2186,7 +2188,7 @@ class Parser {
|
|
|
2186
2188
|
|
|
2187
2189
|
equality() {
|
|
2188
2190
|
let node = this.comparison();
|
|
2189
|
-
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2191
|
+
while (['EQEQ', 'NOTEQ', 'STRICT_EQ', 'STRICT_NOTEQ'].includes(this.current.type)) {
|
|
2190
2192
|
const op = this.current.type;
|
|
2191
2193
|
this.eat(op);
|
|
2192
2194
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
@@ -2196,7 +2198,7 @@ class Parser {
|
|
|
2196
2198
|
|
|
2197
2199
|
comparison() {
|
|
2198
2200
|
let node = this.term();
|
|
2199
|
-
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2201
|
+
while (['LT', 'LTE', 'GT', 'GTE', 'LSHIFT', 'RSHIFT'].includes(this.current.type)) {
|
|
2200
2202
|
const op = this.current.type;
|
|
2201
2203
|
this.eat(op);
|
|
2202
2204
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
@@ -2287,9 +2289,11 @@ class Parser {
|
|
|
2287
2289
|
|
|
2288
2290
|
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2289
2291
|
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
2292
|
+
if (t.type === 'TEMPLATE_STRING') { this.eat('TEMPLATE_STRING'); return { type: 'TemplateLiteral', value: t.value }; }
|
|
2290
2293
|
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2291
2294
|
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2292
2295
|
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
2296
|
+
if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
|
|
2293
2297
|
|
|
2294
2298
|
if (t.type === 'ASK') {
|
|
2295
2299
|
this.eat('ASK');
|
|
@@ -2354,7 +2358,8 @@ class Parser {
|
|
|
2354
2358
|
}
|
|
2355
2359
|
}
|
|
2356
2360
|
|
|
2357
|
-
module.exports = Parser;
|
|
2361
|
+
module.exports = Parser;
|
|
2362
|
+
|
|
2358
2363
|
|
|
2359
2364
|
/***/ }),
|
|
2360
2365
|
|
|
@@ -2456,7 +2461,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
2456
2461
|
const Parser = __nccwpck_require__(222);
|
|
2457
2462
|
const Evaluator = __nccwpck_require__(112);
|
|
2458
2463
|
|
|
2459
|
-
const VERSION = '1.0.
|
|
2464
|
+
const VERSION = '1.0.18';
|
|
2460
2465
|
|
|
2461
2466
|
const COLOR = {
|
|
2462
2467
|
reset: '\x1b[0m',
|
package/package.json
CHANGED
package/src/evaluator.js
CHANGED
|
@@ -4,9 +4,7 @@ const Lexer = require('./lexer');
|
|
|
4
4
|
const Parser = require('./parser');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
-
class ReturnValue {
|
|
8
|
-
constructor(value) { this.value = value; }
|
|
9
|
-
}
|
|
7
|
+
class ReturnValue { constructor(value) { this.value = value; } }
|
|
10
8
|
class BreakSignal {}
|
|
11
9
|
class ContinueSignal {}
|
|
12
10
|
|
|
@@ -49,37 +47,27 @@ class Evaluator {
|
|
|
49
47
|
|
|
50
48
|
setupBuiltins() {
|
|
51
49
|
this.global.define('len', arg => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
|
|
51
|
+
if (arg && typeof arg === 'object') return Object.keys(arg).length;
|
|
52
|
+
return 0;
|
|
55
53
|
});
|
|
56
54
|
|
|
57
55
|
this.global.define('print', arg => { console.log(arg); return null; });
|
|
58
56
|
this.global.define('type', arg => {
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (Array.isArray(arg)) return 'array';
|
|
58
|
+
return typeof arg;
|
|
61
59
|
});
|
|
62
60
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
63
61
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
64
62
|
|
|
65
|
-
this.global.define('ask', prompt =>
|
|
66
|
-
const readlineSync = require('readline-sync');
|
|
67
|
-
return readlineSync.question(prompt + ' ');
|
|
68
|
-
});
|
|
63
|
+
this.global.define('ask', prompt => readlineSync.question(prompt + ' '));
|
|
69
64
|
this.global.define('num', arg => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
this.global.define('str', arg => {
|
|
78
|
-
return String(arg);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
65
|
+
const n = Number(arg);
|
|
66
|
+
if (Number.isNaN(n)) throw new Error('Cannot convert value to number');
|
|
67
|
+
return n;
|
|
68
|
+
});
|
|
69
|
+
this.global.define('str', arg => String(arg));
|
|
70
|
+
}
|
|
83
71
|
|
|
84
72
|
evaluate(node, env = this.global) {
|
|
85
73
|
switch (node.type) {
|
|
@@ -96,6 +84,7 @@ this.global.define('str', arg => {
|
|
|
96
84
|
case 'LogicalExpression': return this.evalLogical(node, env);
|
|
97
85
|
case 'UnaryExpression': return this.evalUnary(node, env);
|
|
98
86
|
case 'Literal': return node.value;
|
|
87
|
+
case 'TemplateLiteral': return this.evalTemplateLiteral(node, env);
|
|
99
88
|
case 'Identifier': return env.get(node.name);
|
|
100
89
|
case 'IfStatement': return this.evalIf(node, env);
|
|
101
90
|
case 'WhileStatement': return this.evalWhile(node, env);
|
|
@@ -121,72 +110,54 @@ this.global.define('str', arg => {
|
|
|
121
110
|
|
|
122
111
|
evalProgram(node, env) {
|
|
123
112
|
let result = null;
|
|
124
|
-
for (const stmt of node.body)
|
|
125
|
-
result = this.evaluate(stmt, env);
|
|
126
|
-
}
|
|
113
|
+
for (const stmt of node.body) result = this.evaluate(stmt, env);
|
|
127
114
|
return result;
|
|
128
115
|
}
|
|
129
|
-
evalImport(node, env) {
|
|
130
|
-
const spec = node.path;
|
|
131
|
-
let lib;
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
const resolved = require.resolve(spec, {
|
|
135
|
-
paths: [process.cwd()]
|
|
136
|
-
});
|
|
137
|
-
lib = require(resolved);
|
|
138
|
-
} catch (e) {
|
|
139
|
-
const fullPath = path.isAbsolute(spec)
|
|
140
|
-
? spec
|
|
141
|
-
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
142
|
-
|
|
143
|
-
if (!fs.existsSync(fullPath)) {
|
|
144
|
-
throw new Error(`Import not found: ${spec}`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
148
|
-
const tokens = new Lexer(code).getTokens();
|
|
149
|
-
const ast = new Parser(tokens).parse();
|
|
150
|
-
|
|
151
|
-
const moduleEnv = new Environment(env);
|
|
152
|
-
this.evaluate(ast, moduleEnv);
|
|
153
|
-
|
|
154
|
-
lib = {};
|
|
155
|
-
for (const key of Object.keys(moduleEnv.store)) {
|
|
156
|
-
lib[key] = moduleEnv.store[key];
|
|
157
|
-
}
|
|
158
116
|
|
|
159
|
-
|
|
117
|
+
evalTemplateLiteral(node, env) {
|
|
118
|
+
return node.value;
|
|
160
119
|
}
|
|
161
120
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
121
|
+
evalImport(node, env) {
|
|
122
|
+
const spec = node.path;
|
|
123
|
+
let lib;
|
|
124
|
+
try {
|
|
125
|
+
const resolved = require.resolve(spec, { paths: [process.cwd()] });
|
|
126
|
+
lib = require(resolved);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
const fullPath = path.isAbsolute(spec)
|
|
129
|
+
? spec
|
|
130
|
+
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(fullPath)) throw new Error(`Import not found: ${spec}`);
|
|
133
|
+
|
|
134
|
+
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
135
|
+
const tokens = new Lexer(code).getTokens();
|
|
136
|
+
const ast = new Parser(tokens).parse();
|
|
137
|
+
const moduleEnv = new Environment(env);
|
|
138
|
+
this.evaluate(ast, moduleEnv);
|
|
139
|
+
|
|
140
|
+
lib = {};
|
|
141
|
+
for (const key of Object.keys(moduleEnv.store)) lib[key] = moduleEnv.store[key];
|
|
142
|
+
lib.default = lib;
|
|
165
143
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
|
|
144
|
+
|
|
145
|
+
for (const imp of node.specifiers) {
|
|
146
|
+
if (imp.type === 'DefaultImport') env.define(imp.local, lib.default ?? lib);
|
|
147
|
+
if (imp.type === 'NamespaceImport') env.define(imp.local, lib);
|
|
148
|
+
if (imp.type === 'NamedImport') {
|
|
149
|
+
if (!(imp.imported in lib)) throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
|
|
150
|
+
env.define(imp.local, lib[imp.imported]);
|
|
172
151
|
}
|
|
173
|
-
env.define(imp.local, lib[imp.imported]);
|
|
174
152
|
}
|
|
153
|
+
return null;
|
|
175
154
|
}
|
|
176
155
|
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
156
|
evalBlock(node, env) {
|
|
182
157
|
let result = null;
|
|
183
158
|
for (const stmt of node.body) {
|
|
184
|
-
try {
|
|
185
|
-
|
|
186
|
-
} catch (e) {
|
|
187
|
-
if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
188
|
-
throw e;
|
|
189
|
-
}
|
|
159
|
+
try { result = this.evaluate(stmt, env); }
|
|
160
|
+
catch (e) { if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e; else throw e; }
|
|
190
161
|
}
|
|
191
162
|
return result;
|
|
192
163
|
}
|
|
@@ -199,27 +170,15 @@ evalImport(node, env) {
|
|
|
199
170
|
evalAssignment(node, env) {
|
|
200
171
|
const rightVal = this.evaluate(node.right, env);
|
|
201
172
|
const left = node.left;
|
|
202
|
-
|
|
203
173
|
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
204
|
-
if (left.type === 'MemberExpression') {
|
|
205
|
-
|
|
206
|
-
obj[left.property] = rightVal;
|
|
207
|
-
return rightVal;
|
|
208
|
-
}
|
|
209
|
-
if (left.type === 'IndexExpression') {
|
|
210
|
-
const obj = this.evaluate(left.object, env);
|
|
211
|
-
const idx = this.evaluate(left.indexer, env);
|
|
212
|
-
obj[idx] = rightVal;
|
|
213
|
-
return rightVal;
|
|
214
|
-
}
|
|
215
|
-
|
|
174
|
+
if (left.type === 'MemberExpression') { const obj = this.evaluate(left.object, env); obj[left.property] = rightVal; return rightVal; }
|
|
175
|
+
if (left.type === 'IndexExpression') { const obj = this.evaluate(left.object, env); const idx = this.evaluate(left.indexer, env); obj[idx] = rightVal; return rightVal; }
|
|
216
176
|
throw new Error('Invalid assignment target');
|
|
217
177
|
}
|
|
218
178
|
|
|
219
179
|
evalCompoundAssignment(node, env) {
|
|
220
180
|
const left = node.left;
|
|
221
181
|
let current;
|
|
222
|
-
|
|
223
182
|
if (left.type === 'Identifier') current = env.get(left.name);
|
|
224
183
|
else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
|
|
225
184
|
else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
|
|
@@ -237,9 +196,7 @@ evalImport(node, env) {
|
|
|
237
196
|
}
|
|
238
197
|
|
|
239
198
|
if (left.type === 'Identifier') env.set(left.name, computed);
|
|
240
|
-
else if (left.type === 'MemberExpression') this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
241
199
|
else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
242
|
-
|
|
243
200
|
return computed;
|
|
244
201
|
}
|
|
245
202
|
|
|
@@ -251,8 +208,7 @@ evalImport(node, env) {
|
|
|
251
208
|
|
|
252
209
|
evalAsk(node, env) {
|
|
253
210
|
const prompt = this.evaluate(node.prompt, env);
|
|
254
|
-
|
|
255
|
-
return input;
|
|
211
|
+
return readlineSync.question(prompt + ' ');
|
|
256
212
|
}
|
|
257
213
|
|
|
258
214
|
evalDefine(node, env) {
|
|
@@ -269,12 +225,16 @@ evalImport(node, env) {
|
|
|
269
225
|
case 'STAR': return l * r;
|
|
270
226
|
case 'SLASH': return l / r;
|
|
271
227
|
case 'MOD': return l % r;
|
|
272
|
-
case 'EQEQ': return l
|
|
273
|
-
case 'NOTEQ': return l
|
|
228
|
+
case 'EQEQ': return l == r;
|
|
229
|
+
case 'NOTEQ': return l != r;
|
|
230
|
+
case 'STRICT_EQ': return l === r;
|
|
231
|
+
case 'STRICT_NOTEQ': return l !== r;
|
|
274
232
|
case 'LT': return l < r;
|
|
275
233
|
case 'LTE': return l <= r;
|
|
276
234
|
case 'GT': return l > r;
|
|
277
235
|
case 'GTE': return l >= r;
|
|
236
|
+
case 'LSHIFT': return l << r;
|
|
237
|
+
case 'RSHIFT': return l >> r;
|
|
278
238
|
default: throw new Error(`Unknown binary operator ${node.operator}`);
|
|
279
239
|
}
|
|
280
240
|
}
|
|
@@ -342,9 +302,7 @@ evalImport(node, env) {
|
|
|
342
302
|
const args = node.arguments.map(a => this.evaluate(a, env));
|
|
343
303
|
return calleeEvaluated(...args);
|
|
344
304
|
}
|
|
345
|
-
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body)
|
|
346
|
-
throw new Error('Call to non-function');
|
|
347
|
-
}
|
|
305
|
+
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) throw new Error('Call to non-function');
|
|
348
306
|
const fn = calleeEvaluated;
|
|
349
307
|
const callEnv = new Environment(fn.env);
|
|
350
308
|
fn.params.forEach((p, i) => {
|
|
@@ -384,15 +342,8 @@ evalImport(node, env) {
|
|
|
384
342
|
};
|
|
385
343
|
const setValue = (v) => {
|
|
386
344
|
if (arg.type === 'Identifier') env.set(arg.name, v);
|
|
387
|
-
else if (arg.type === 'MemberExpression') {
|
|
388
|
-
|
|
389
|
-
obj[arg.property] = v;
|
|
390
|
-
}
|
|
391
|
-
else if (arg.type === 'IndexExpression') {
|
|
392
|
-
const obj = this.evaluate(arg.object, env);
|
|
393
|
-
const idx = this.evaluate(arg.indexer, env);
|
|
394
|
-
obj[idx] = v;
|
|
395
|
-
}
|
|
345
|
+
else if (arg.type === 'MemberExpression') { const obj = this.evaluate(arg.object, env); obj[arg.property] = v; }
|
|
346
|
+
else if (arg.type === 'IndexExpression') { const obj = this.evaluate(arg.object, env); const idx = this.evaluate(arg.indexer, env); obj[idx] = v; }
|
|
396
347
|
};
|
|
397
348
|
const current = getCurrent();
|
|
398
349
|
const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
|
|
@@ -401,4 +352,4 @@ evalImport(node, env) {
|
|
|
401
352
|
}
|
|
402
353
|
}
|
|
403
354
|
|
|
404
|
-
module.exports = Evaluator;
|
|
355
|
+
module.exports = Evaluator;
|
package/src/lexer.js
CHANGED
|
@@ -10,8 +10,8 @@ class Lexer {
|
|
|
10
10
|
this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
peek() {
|
|
14
|
-
return this.pos +
|
|
13
|
+
peek(n = 1) {
|
|
14
|
+
return this.pos + n < this.input.length ? this.input[this.pos + n] : null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
error(msg) {
|
|
@@ -53,10 +53,21 @@ class Lexer {
|
|
|
53
53
|
result += this.currentChar;
|
|
54
54
|
this.advance();
|
|
55
55
|
}
|
|
56
|
-
return { type: 'NUMBER', value: parseFloat(result) };
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
if (this.currentChar && /[eE]/.test(this.currentChar)) {
|
|
59
|
+
result += this.currentChar; this.advance();
|
|
60
|
+
if (this.currentChar === '+' || this.currentChar === '-') {
|
|
61
|
+
result += this.currentChar; this.advance();
|
|
62
|
+
}
|
|
63
|
+
if (!/[0-9]/.test(this.currentChar)) this.error("Invalid exponent in number");
|
|
64
|
+
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
65
|
+
result += this.currentChar;
|
|
66
|
+
this.advance();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { type: 'NUMBER', value: parseFloat(result) };
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
identifier() {
|
|
@@ -69,7 +80,7 @@ class Lexer {
|
|
|
69
80
|
const keywords = [
|
|
70
81
|
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
71
82
|
'break', 'continue', 'func', 'return', 'true', 'false', 'null',
|
|
72
|
-
'ask', 'define', 'import', 'from', 'as'
|
|
83
|
+
'ask', 'define', 'import', 'from', 'as', 'undefined'
|
|
73
84
|
];
|
|
74
85
|
|
|
75
86
|
if (keywords.includes(result)) {
|
|
@@ -77,8 +88,7 @@ class Lexer {
|
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
return { type: 'IDENTIFIER', value: result };
|
|
80
|
-
}
|
|
81
|
-
|
|
91
|
+
}
|
|
82
92
|
|
|
83
93
|
string() {
|
|
84
94
|
const quote = this.currentChar;
|
|
@@ -110,6 +120,39 @@ class Lexer {
|
|
|
110
120
|
return { type: 'STRING', value: result };
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
templateString() {
|
|
124
|
+
this.advance();
|
|
125
|
+
let result = '';
|
|
126
|
+
while (this.currentChar && this.currentChar !== '`') {
|
|
127
|
+
if (this.currentChar === '$' && this.peek() === '{') {
|
|
128
|
+
this.advance(); this.advance(); // skip ${
|
|
129
|
+
result += '${'; // we leave the interpolation as-is for parser
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (this.currentChar === '\\') {
|
|
134
|
+
this.advance();
|
|
135
|
+
switch (this.currentChar) {
|
|
136
|
+
case 'n': result += '\n'; break;
|
|
137
|
+
case 't': result += '\t'; break;
|
|
138
|
+
case '`': result += '`'; break;
|
|
139
|
+
case '\\': result += '\\'; break;
|
|
140
|
+
default: result += this.currentChar;
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
result += this.currentChar;
|
|
144
|
+
}
|
|
145
|
+
this.advance();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (this.currentChar !== '`') {
|
|
149
|
+
this.error('Unterminated template string');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.advance();
|
|
153
|
+
return { type: 'TEMPLATE_STRING', value: result };
|
|
154
|
+
}
|
|
155
|
+
|
|
113
156
|
getTokens() {
|
|
114
157
|
const tokens = [];
|
|
115
158
|
|
|
@@ -119,15 +162,21 @@ class Lexer {
|
|
|
119
162
|
if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
|
|
120
163
|
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
121
164
|
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
165
|
+
if (this.currentChar === '`') { tokens.push(this.templateString()); continue; }
|
|
122
166
|
|
|
123
167
|
const char = this.currentChar;
|
|
124
168
|
const next = this.peek();
|
|
169
|
+
const next2 = this.peek(2);
|
|
125
170
|
|
|
171
|
+
if (char === '=' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_EQ' }); this.advance(); this.advance(); this.advance(); continue; }
|
|
172
|
+
if (char === '!' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_NOTEQ' }); this.advance(); this.advance(); this.advance(); continue; }
|
|
126
173
|
if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
|
|
127
174
|
if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
|
|
128
175
|
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
|
|
129
176
|
if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
|
|
130
177
|
if (char === '>' && next === '=') { tokens.push({ type: 'GTE' }); this.advance(); this.advance(); continue; }
|
|
178
|
+
if (char === '<' && next === '<') { tokens.push({ type: 'LSHIFT' }); this.advance(); this.advance(); continue; }
|
|
179
|
+
if (char === '>' && next === '>') { tokens.push({ type: 'RSHIFT' }); this.advance(); this.advance(); continue; }
|
|
131
180
|
if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
|
|
132
181
|
if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
|
|
133
182
|
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
|
|
@@ -154,4 +203,4 @@ class Lexer {
|
|
|
154
203
|
}
|
|
155
204
|
}
|
|
156
205
|
|
|
157
|
-
module.exports = Lexer;
|
|
206
|
+
module.exports = Lexer;
|
package/src/parser.js
CHANGED
|
@@ -99,40 +99,41 @@ class Parser {
|
|
|
99
99
|
const body = this.block();
|
|
100
100
|
return { type: 'WhileStatement', test, body };
|
|
101
101
|
}
|
|
102
|
+
|
|
102
103
|
importStatement() {
|
|
103
104
|
this.eat('IMPORT');
|
|
104
105
|
|
|
105
106
|
let specifiers = [];
|
|
106
107
|
|
|
107
108
|
if (this.current.type === 'STAR') {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
this.eat('STAR');
|
|
110
|
+
this.eat('AS');
|
|
111
|
+
const name = this.current.value;
|
|
112
|
+
this.eat('IDENTIFIER');
|
|
113
|
+
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
113
114
|
}
|
|
114
115
|
else if (this.current.type === 'LBRACE') {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
126
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
116
|
+
this.eat('LBRACE');
|
|
117
|
+
while (this.current.type !== 'RBRACE') {
|
|
118
|
+
const importedName = this.current.value;
|
|
119
|
+
this.eat('IDENTIFIER');
|
|
120
|
+
let localName = importedName;
|
|
121
|
+
if (this.current.type === 'AS') {
|
|
122
|
+
this.eat('AS');
|
|
123
|
+
localName = this.current.value;
|
|
124
|
+
this.eat('IDENTIFIER');
|
|
127
125
|
}
|
|
128
|
-
|
|
126
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
127
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
128
|
+
}
|
|
129
|
+
this.eat('RBRACE');
|
|
129
130
|
}
|
|
130
131
|
else if (this.current.type === 'IDENTIFIER') {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
const localName = this.current.value;
|
|
133
|
+
this.eat('IDENTIFIER');
|
|
134
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
134
135
|
} else {
|
|
135
|
-
|
|
136
|
+
throw new Error('Unexpected token in import statement');
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
this.eat('FROM');
|
|
@@ -143,8 +144,7 @@ class Parser {
|
|
|
143
144
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
144
145
|
|
|
145
146
|
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
146
|
-
}
|
|
147
|
-
|
|
147
|
+
}
|
|
148
148
|
|
|
149
149
|
forStatement() {
|
|
150
150
|
this.eat('FOR');
|
|
@@ -270,7 +270,7 @@ class Parser {
|
|
|
270
270
|
|
|
271
271
|
equality() {
|
|
272
272
|
let node = this.comparison();
|
|
273
|
-
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
273
|
+
while (['EQEQ', 'NOTEQ', 'STRICT_EQ', 'STRICT_NOTEQ'].includes(this.current.type)) {
|
|
274
274
|
const op = this.current.type;
|
|
275
275
|
this.eat(op);
|
|
276
276
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
@@ -280,7 +280,7 @@ class Parser {
|
|
|
280
280
|
|
|
281
281
|
comparison() {
|
|
282
282
|
let node = this.term();
|
|
283
|
-
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
283
|
+
while (['LT', 'LTE', 'GT', 'GTE', 'LSHIFT', 'RSHIFT'].includes(this.current.type)) {
|
|
284
284
|
const op = this.current.type;
|
|
285
285
|
this.eat(op);
|
|
286
286
|
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
@@ -371,9 +371,11 @@ class Parser {
|
|
|
371
371
|
|
|
372
372
|
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
373
373
|
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
374
|
+
if (t.type === 'TEMPLATE_STRING') { this.eat('TEMPLATE_STRING'); return { type: 'TemplateLiteral', value: t.value }; }
|
|
374
375
|
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
375
376
|
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
376
377
|
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
378
|
+
if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
|
|
377
379
|
|
|
378
380
|
if (t.type === 'ASK') {
|
|
379
381
|
this.eat('ASK');
|
|
@@ -438,4 +440,4 @@ class Parser {
|
|
|
438
440
|
}
|
|
439
441
|
}
|
|
440
442
|
|
|
441
|
-
module.exports = Parser;
|
|
443
|
+
module.exports = Parser;
|