tova 0.2.8 → 0.2.9
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/bin/tova.js +46 -15
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +9 -2
- package/src/codegen/base-codegen.js +9 -3
- package/src/lexer/lexer.js +1 -0
- package/src/parser/parser.js +33 -14
- package/src/version.js +1 -1
package/bin/tova.js
CHANGED
|
@@ -352,7 +352,9 @@ async function runFile(filePath, options = {}) {
|
|
|
352
352
|
// Execute the generated JavaScript (with stdlib)
|
|
353
353
|
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
354
354
|
const stdlib = getRunStdlib();
|
|
355
|
-
|
|
355
|
+
let code = stdlib + '\n' + (output.shared || '') + '\n' + (output.server || output.client || '');
|
|
356
|
+
// Strip 'export ' keywords — not valid inside AsyncFunction (used in tova build only)
|
|
357
|
+
code = code.replace(/^export /gm, '');
|
|
356
358
|
const fn = new AsyncFunction(code);
|
|
357
359
|
await fn();
|
|
358
360
|
} catch (err) {
|
|
@@ -1477,6 +1479,8 @@ async function startRepl() {
|
|
|
1477
1479
|
}
|
|
1478
1480
|
|
|
1479
1481
|
if (trimmed === ':clear') {
|
|
1482
|
+
for (const key of Object.keys(context)) delete context[key];
|
|
1483
|
+
delete context.__mutable;
|
|
1480
1484
|
initFn.call(context);
|
|
1481
1485
|
console.log(' Context cleared.\n');
|
|
1482
1486
|
rl.prompt();
|
|
@@ -1512,6 +1516,36 @@ async function startRepl() {
|
|
|
1512
1516
|
const output = compileTova(input, '<repl>');
|
|
1513
1517
|
const code = output.shared || '';
|
|
1514
1518
|
if (code.trim()) {
|
|
1519
|
+
// Extract function/const/let names from compiled code
|
|
1520
|
+
const declaredInCode = new Set();
|
|
1521
|
+
for (const m of code.matchAll(/\bfunction\s+([a-zA-Z_]\w*)/g)) declaredInCode.add(m[1]);
|
|
1522
|
+
for (const m of code.matchAll(/\bconst\s+([a-zA-Z_]\w*)/g)) declaredInCode.add(m[1]);
|
|
1523
|
+
for (const m of code.matchAll(/\blet\s+([a-zA-Z_]\w*)/g)) {
|
|
1524
|
+
declaredInCode.add(m[1]);
|
|
1525
|
+
// Track mutable variables for proper let destructuring
|
|
1526
|
+
if (!context.__mutable) context.__mutable = new Set();
|
|
1527
|
+
context.__mutable.add(m[1]);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// Save declared variables back to context for persistence across inputs
|
|
1531
|
+
const saveNewDecls = declaredInCode.size > 0
|
|
1532
|
+
? [...declaredInCode].map(n => `if(typeof ${n}!=='undefined')__ctx.${n}=${n};`).join('\n')
|
|
1533
|
+
: '';
|
|
1534
|
+
// Also save mutable variables that may have been modified (not newly declared)
|
|
1535
|
+
const mutKeys = context.__mutable
|
|
1536
|
+
? [...context.__mutable].filter(n => !declaredInCode.has(n) && n in context)
|
|
1537
|
+
: [];
|
|
1538
|
+
const saveMut = mutKeys.map(n => `__ctx.${n}=${n};`).join('\n');
|
|
1539
|
+
const allSave = [saveNewDecls, saveMut].filter(Boolean).join('\n');
|
|
1540
|
+
|
|
1541
|
+
// Context destructuring: use let for mutable, const for immutable
|
|
1542
|
+
const ctxKeys = Object.keys(context).filter(k => !declaredInCode.has(k) && k !== '__mutable');
|
|
1543
|
+
const constKeys = ctxKeys.filter(k => !context.__mutable || !context.__mutable.has(k));
|
|
1544
|
+
const letKeys = ctxKeys.filter(k => context.__mutable && context.__mutable.has(k));
|
|
1545
|
+
const destructure =
|
|
1546
|
+
(constKeys.length > 0 ? `const {${constKeys.join(',')}} = __ctx;\n` : '') +
|
|
1547
|
+
(letKeys.length > 0 ? `let {${letKeys.join(',')}} = __ctx;\n` : '');
|
|
1548
|
+
|
|
1515
1549
|
// Try wrapping last expression statement as a return for value display
|
|
1516
1550
|
const lines = code.trim().split('\n');
|
|
1517
1551
|
const lastLine = lines[lines.length - 1].trim();
|
|
@@ -1522,28 +1556,25 @@ async function startRepl() {
|
|
|
1522
1556
|
const allButLast = lines.slice(0, -1).join('\n');
|
|
1523
1557
|
// Strip trailing semicolon from last line for the return
|
|
1524
1558
|
const returnExpr = lastLine.endsWith(';') ? lastLine.slice(0, -1) : lastLine;
|
|
1525
|
-
|
|
1559
|
+
// Use try/finally so save runs after return expression evaluates (captures updated mutable values)
|
|
1560
|
+
if (allSave) {
|
|
1561
|
+
evalCode = `try {\n${allButLast}\nreturn (${returnExpr});\n} finally {\n${allSave}\n}`;
|
|
1562
|
+
} else {
|
|
1563
|
+
evalCode = allButLast + (allButLast ? '\n' : '') + `return (${returnExpr});`;
|
|
1564
|
+
}
|
|
1565
|
+
} else {
|
|
1566
|
+
evalCode = code + (allSave ? '\n' + allSave : '');
|
|
1526
1567
|
}
|
|
1527
1568
|
try {
|
|
1528
|
-
|
|
1529
|
-
const declaredInCode = new Set();
|
|
1530
|
-
for (const m of evalCode.matchAll(/\bfunction\s+([a-zA-Z_]\w*)/g)) declaredInCode.add(m[1]);
|
|
1531
|
-
for (const m of evalCode.matchAll(/\bconst\s+([a-zA-Z_]\w*)/g)) declaredInCode.add(m[1]);
|
|
1532
|
-
const ctxKeys = Object.keys(context).filter(k => !declaredInCode.has(k));
|
|
1533
|
-
const destructure = ctxKeys.length > 0 ? `const {${ctxKeys.join(',')}} = __ctx;` : '';
|
|
1534
|
-
const fn = new Function('__ctx', `${destructure}\n${evalCode}`);
|
|
1569
|
+
const fn = new Function('__ctx', `${destructure}${evalCode}`);
|
|
1535
1570
|
const result = fn(context);
|
|
1536
1571
|
if (result !== undefined) {
|
|
1537
1572
|
console.log(' ', result);
|
|
1538
1573
|
}
|
|
1539
1574
|
} catch (e) {
|
|
1540
1575
|
// If return-wrapping fails, fall back to plain execution
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
for (const m of code.matchAll(/\bconst\s+([a-zA-Z_]\w*)/g)) declaredInCode.add(m[1]);
|
|
1544
|
-
const ctxKeys = Object.keys(context).filter(k => !declaredInCode.has(k));
|
|
1545
|
-
const destructure = ctxKeys.length > 0 ? `const {${ctxKeys.join(',')}} = __ctx;` : '';
|
|
1546
|
-
const fn = new Function('__ctx', `${destructure}\n${code}`);
|
|
1576
|
+
const fallbackCode = code + (allSave ? '\n' + allSave : '');
|
|
1577
|
+
const fn = new Function('__ctx', `${destructure}${fallbackCode}`);
|
|
1547
1578
|
fn(context);
|
|
1548
1579
|
}
|
|
1549
1580
|
}
|
package/package.json
CHANGED
package/src/analyzer/analyzer.js
CHANGED
|
@@ -2006,7 +2006,14 @@ export class Analyzer {
|
|
|
2006
2006
|
const hasSpread = node.arguments.some(a => a.type === 'SpreadExpression');
|
|
2007
2007
|
if (hasSpread) return;
|
|
2008
2008
|
|
|
2009
|
-
|
|
2009
|
+
// Named arguments are collapsed into a single object at codegen
|
|
2010
|
+
const hasNamedArgs = node.arguments.some(a => a.type === 'NamedArgument');
|
|
2011
|
+
if (hasNamedArgs) {
|
|
2012
|
+
const positionalCount = node.arguments.filter(a => a.type !== 'NamedArgument').length;
|
|
2013
|
+
var actualCount = positionalCount + 1; // named args become one object
|
|
2014
|
+
} else {
|
|
2015
|
+
var actualCount = node.arguments.length;
|
|
2016
|
+
}
|
|
2010
2017
|
const name = node.callee.name;
|
|
2011
2018
|
|
|
2012
2019
|
if (actualCount > fnSym._totalParamCount) {
|
|
@@ -2144,7 +2151,7 @@ export class Analyzer {
|
|
|
2144
2151
|
name: m.name,
|
|
2145
2152
|
paramTypes: (m.params || []).map(p => typeAnnotationToType(p.typeAnnotation)),
|
|
2146
2153
|
returnType: typeAnnotationToType(m.returnType),
|
|
2147
|
-
paramCount: (m.params || []).length,
|
|
2154
|
+
paramCount: (m.params || []).filter(p => p.name !== 'self').length,
|
|
2148
2155
|
}));
|
|
2149
2156
|
this.currentScope.define(node.name, sym);
|
|
2150
2157
|
|
|
@@ -1462,6 +1462,9 @@ export class BaseCodegen {
|
|
|
1462
1462
|
|
|
1463
1463
|
genObjectLiteral(node) {
|
|
1464
1464
|
const props = node.properties.map(p => {
|
|
1465
|
+
if (p.spread) {
|
|
1466
|
+
return `...${this.genExpression(p.argument)}`;
|
|
1467
|
+
}
|
|
1465
1468
|
if (p.shorthand) {
|
|
1466
1469
|
return this.genExpression(p.key);
|
|
1467
1470
|
}
|
|
@@ -1590,7 +1593,7 @@ export class BaseCodegen {
|
|
|
1590
1593
|
const fieldNames = node.variants.map(f => f.name);
|
|
1591
1594
|
const params = fieldNames.join(', ');
|
|
1592
1595
|
const obj = fieldNames.map(f => `${f}`).join(', ');
|
|
1593
|
-
lines.push(`${this.i()}${exportPrefix}function ${node.name}(${params}) { return { ${obj} }; }`);
|
|
1596
|
+
lines.push(`${this.i()}${exportPrefix}function ${node.name}(${params}) { return Object.assign(Object.create(${node.name}.prototype), { ${obj} }); }`);
|
|
1594
1597
|
}
|
|
1595
1598
|
|
|
1596
1599
|
// Derive clause: generate methods
|
|
@@ -1636,21 +1639,24 @@ export class BaseCodegen {
|
|
|
1636
1639
|
genImplDeclaration(node) {
|
|
1637
1640
|
const lines = [];
|
|
1638
1641
|
for (const method of node.methods) {
|
|
1642
|
+
const hasSelf = method.params.some(p => p.name === 'self');
|
|
1639
1643
|
const params = method.params.filter(p => p.name !== 'self');
|
|
1640
1644
|
const paramStr = this.genParams(params);
|
|
1641
1645
|
const hasPropagate = this._containsPropagate(method.body);
|
|
1642
1646
|
const asyncPrefix = method.isAsync ? 'async ' : '';
|
|
1643
1647
|
this.pushScope();
|
|
1648
|
+
if (hasSelf) this.declareVar('self');
|
|
1644
1649
|
for (const p of params) {
|
|
1645
1650
|
if (p.destructure) this._declareDestructureVars(p.destructure);
|
|
1646
1651
|
else this.declareVar(p.name);
|
|
1647
1652
|
}
|
|
1648
1653
|
const body = this.genBlockBody(method.body);
|
|
1649
1654
|
this.popScope();
|
|
1655
|
+
const selfBinding = hasSelf ? `\n${this.i()} const self = this;` : '';
|
|
1650
1656
|
if (hasPropagate) {
|
|
1651
|
-
lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {\n${this.i()} try {\n${body}\n${this.i()} } catch (__e) {\n${this.i()} if (__e && __e.__tova_propagate) return __e.value;\n${this.i()} throw __e;\n${this.i()} }\n${this.i()}};`);
|
|
1657
|
+
lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {${selfBinding}\n${this.i()} try {\n${body}\n${this.i()} } catch (__e) {\n${this.i()} if (__e && __e.__tova_propagate) return __e.value;\n${this.i()} throw __e;\n${this.i()} }\n${this.i()}};`);
|
|
1652
1658
|
} else {
|
|
1653
|
-
lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {\n${body}\n${this.i()}};`);
|
|
1659
|
+
lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {${selfBinding}\n${body}\n${this.i()}};`);
|
|
1654
1660
|
}
|
|
1655
1661
|
}
|
|
1656
1662
|
return lines.join('\n');
|
package/src/lexer/lexer.js
CHANGED
package/src/parser/parser.js
CHANGED
|
@@ -5,7 +5,7 @@ export class Parser {
|
|
|
5
5
|
static MAX_EXPRESSION_DEPTH = 200;
|
|
6
6
|
|
|
7
7
|
constructor(tokens, filename = '<stdin>') {
|
|
8
|
-
this.tokens = tokens.filter(t => t.type !== TokenType.NEWLINE && t.type !== TokenType.DOCSTRING);
|
|
8
|
+
this.tokens = tokens.filter(t => t.type !== TokenType.NEWLINE && t.type !== TokenType.DOCSTRING && t.type !== TokenType.SEMICOLON);
|
|
9
9
|
this.rawTokens = tokens;
|
|
10
10
|
this.filename = filename;
|
|
11
11
|
this.pos = 0;
|
|
@@ -2846,6 +2846,25 @@ export class Parser {
|
|
|
2846
2846
|
return new AST.ArrayLiteral(elements, l);
|
|
2847
2847
|
}
|
|
2848
2848
|
|
|
2849
|
+
_parseObjectProperty() {
|
|
2850
|
+
// Spread property: ...expr
|
|
2851
|
+
if (this.check(TokenType.SPREAD)) {
|
|
2852
|
+
const sl = this.loc();
|
|
2853
|
+
this.advance();
|
|
2854
|
+
const argument = this.parseUnary();
|
|
2855
|
+
return { spread: true, argument };
|
|
2856
|
+
}
|
|
2857
|
+
const key = this.parseExpression();
|
|
2858
|
+
if (this.match(TokenType.COLON)) {
|
|
2859
|
+
const value = this.parseExpression();
|
|
2860
|
+
return { key, value, shorthand: false };
|
|
2861
|
+
}
|
|
2862
|
+
if (key.type === 'Identifier') {
|
|
2863
|
+
return { key, value: key, shorthand: true };
|
|
2864
|
+
}
|
|
2865
|
+
this.error("Expected ':' in object literal");
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2849
2868
|
parseObjectOrDictComprehension() {
|
|
2850
2869
|
const l = this.loc();
|
|
2851
2870
|
this.expect(TokenType.LBRACE);
|
|
@@ -2855,6 +2874,17 @@ export class Parser {
|
|
|
2855
2874
|
return new AST.ObjectLiteral([], l);
|
|
2856
2875
|
}
|
|
2857
2876
|
|
|
2877
|
+
// Check for spread as first element — always an object literal
|
|
2878
|
+
if (this.check(TokenType.SPREAD)) {
|
|
2879
|
+
const properties = [this._parseObjectProperty()];
|
|
2880
|
+
while (this.match(TokenType.COMMA)) {
|
|
2881
|
+
if (this.check(TokenType.RBRACE)) break;
|
|
2882
|
+
properties.push(this._parseObjectProperty());
|
|
2883
|
+
}
|
|
2884
|
+
this.expect(TokenType.RBRACE, "Expected '}'");
|
|
2885
|
+
return new AST.ObjectLiteral(properties, l);
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2858
2888
|
// Try to parse first key: value pair
|
|
2859
2889
|
const firstKey = this.parseExpression();
|
|
2860
2890
|
|
|
@@ -2883,10 +2913,7 @@ export class Parser {
|
|
|
2883
2913
|
const properties = [{ key: firstKey, value: firstValue, shorthand: false }];
|
|
2884
2914
|
while (this.match(TokenType.COMMA)) {
|
|
2885
2915
|
if (this.check(TokenType.RBRACE)) break;
|
|
2886
|
-
|
|
2887
|
-
this.expect(TokenType.COLON, "Expected ':' in object literal");
|
|
2888
|
-
const value = this.parseExpression();
|
|
2889
|
-
properties.push({ key, value, shorthand: false });
|
|
2916
|
+
properties.push(this._parseObjectProperty());
|
|
2890
2917
|
}
|
|
2891
2918
|
|
|
2892
2919
|
this.expect(TokenType.RBRACE, "Expected '}'");
|
|
@@ -2898,15 +2925,7 @@ export class Parser {
|
|
|
2898
2925
|
const properties = [{ key: firstKey, value: firstKey, shorthand: true }];
|
|
2899
2926
|
while (this.match(TokenType.COMMA)) {
|
|
2900
2927
|
if (this.check(TokenType.RBRACE)) break;
|
|
2901
|
-
|
|
2902
|
-
if (this.match(TokenType.COLON)) {
|
|
2903
|
-
// Colon property: y: 10
|
|
2904
|
-
const value = this.parseExpression();
|
|
2905
|
-
properties.push({ key, value, shorthand: false });
|
|
2906
|
-
} else {
|
|
2907
|
-
// Shorthand property: y
|
|
2908
|
-
properties.push({ key, value: key, shorthand: true });
|
|
2909
|
-
}
|
|
2928
|
+
properties.push(this._parseObjectProperty());
|
|
2910
2929
|
}
|
|
2911
2930
|
this.expect(TokenType.RBRACE, "Expected '}'");
|
|
2912
2931
|
return new AST.ObjectLiteral(properties, l);
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/embed-runtime.js — do not edit
|
|
2
|
-
export const VERSION = "0.2.
|
|
2
|
+
export const VERSION = "0.2.9";
|