tova 0.3.3 → 0.3.5
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 +21 -6
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +2 -3
- package/src/codegen/codegen.js +6 -16
- package/src/codegen/server-codegen.js +43 -18
- package/src/parser/parser.js +2 -2
- package/src/version.js +1 -1
package/bin/tova.js
CHANGED
|
@@ -345,10 +345,8 @@ async function runTests(args) {
|
|
|
345
345
|
const outDir = dirname(outPath);
|
|
346
346
|
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
347
347
|
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
const fullTest = result.test;
|
|
351
|
-
writeFileSync(outPath, fullTest);
|
|
348
|
+
// Shared code (top-level definitions) is now included by generateTests()
|
|
349
|
+
writeFileSync(outPath, result.test);
|
|
352
350
|
compiledFiles.push(outPath);
|
|
353
351
|
console.log(` Compiled: ${relative('.', file)}`);
|
|
354
352
|
}
|
|
@@ -883,9 +881,16 @@ async function checkProject(args) {
|
|
|
883
881
|
}
|
|
884
882
|
|
|
885
883
|
const explicitSrc = args.filter(a => !a.startsWith('--'))[0];
|
|
886
|
-
const
|
|
884
|
+
const srcPath = resolve(explicitSrc || '.');
|
|
887
885
|
|
|
888
|
-
|
|
886
|
+
// Support both single file and directory arguments
|
|
887
|
+
let tovaFiles;
|
|
888
|
+
if (existsSync(srcPath) && statSync(srcPath).isFile()) {
|
|
889
|
+
tovaFiles = srcPath.endsWith('.tova') ? [srcPath] : [];
|
|
890
|
+
} else {
|
|
891
|
+
tovaFiles = findFiles(srcPath, '.tova');
|
|
892
|
+
}
|
|
893
|
+
const srcDir = existsSync(srcPath) && statSync(srcPath).isFile() ? dirname(srcPath) : srcPath;
|
|
889
894
|
if (tovaFiles.length === 0) {
|
|
890
895
|
console.error('No .tova files found');
|
|
891
896
|
process.exit(1);
|
|
@@ -2780,6 +2785,16 @@ async function productionBuild(srcDir, outDir) {
|
|
|
2780
2785
|
console.log(` server.${hash}.js`);
|
|
2781
2786
|
}
|
|
2782
2787
|
|
|
2788
|
+
// Write script bundle for plain scripts (no server/client blocks)
|
|
2789
|
+
if (!allServerCode.trim() && !allClientCode.trim() && allSharedCode.trim()) {
|
|
2790
|
+
const stdlib = getRunStdlib();
|
|
2791
|
+
const scriptBundle = stdlib + '\n' + allSharedCode;
|
|
2792
|
+
const hash = hashCode(scriptBundle);
|
|
2793
|
+
const scriptPath = join(outDir, `script.${hash}.js`);
|
|
2794
|
+
writeFileSync(scriptPath, scriptBundle);
|
|
2795
|
+
console.log(` script.${hash}.js`);
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2783
2798
|
// Write client bundle
|
|
2784
2799
|
if (allClientCode.trim()) {
|
|
2785
2800
|
const fullClientModule = allSharedCode + '\n' + allClientCode;
|
package/package.json
CHANGED
package/src/analyzer/analyzer.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Scope, Symbol } from './scope.js';
|
|
2
2
|
import { PIPE_TARGET } from '../parser/ast.js';
|
|
3
3
|
import { BUILTIN_NAMES } from '../stdlib/inline.js';
|
|
4
|
+
import { collectServerBlockFunctions, installServerAnalyzer } from './server-analyzer.js';
|
|
5
|
+
import { installClientAnalyzer } from './client-analyzer.js';
|
|
4
6
|
import {
|
|
5
7
|
Type, PrimitiveType, NilType, AnyType, UnknownType,
|
|
6
8
|
ArrayType, TupleType, FunctionType, RecordType, ADTType,
|
|
@@ -267,7 +269,6 @@ export class Analyzer {
|
|
|
267
269
|
// Pre-pass: collect named server block functions for inter-server RPC validation
|
|
268
270
|
const hasServerBlocks = this.ast.body.some(n => n.type === 'ServerBlock');
|
|
269
271
|
if (hasServerBlocks) {
|
|
270
|
-
const { collectServerBlockFunctions, installServerAnalyzer } = import.meta.require('./server-analyzer.js');
|
|
271
272
|
installServerAnalyzer(Analyzer);
|
|
272
273
|
this.serverBlockFunctions = collectServerBlockFunctions(this.ast);
|
|
273
274
|
} else {
|
|
@@ -734,7 +735,6 @@ export class Analyzer {
|
|
|
734
735
|
|
|
735
736
|
_visitServerNode(node) {
|
|
736
737
|
if (!Analyzer.prototype._serverAnalyzerInstalled) {
|
|
737
|
-
const { installServerAnalyzer } = import.meta.require('./server-analyzer.js');
|
|
738
738
|
installServerAnalyzer(Analyzer);
|
|
739
739
|
}
|
|
740
740
|
const methodName = 'visit' + node.type;
|
|
@@ -743,7 +743,6 @@ export class Analyzer {
|
|
|
743
743
|
|
|
744
744
|
_visitClientNode(node) {
|
|
745
745
|
if (!Analyzer.prototype._clientAnalyzerInstalled) {
|
|
746
|
-
const { installClientAnalyzer } = import.meta.require('./client-analyzer.js');
|
|
747
746
|
installClientAnalyzer(Analyzer);
|
|
748
747
|
}
|
|
749
748
|
const methodName = 'visit' + node.type;
|
package/src/codegen/codegen.js
CHANGED
|
@@ -4,25 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import { SharedCodegen } from './shared-codegen.js';
|
|
6
6
|
import { BUILTIN_NAMES } from '../stdlib/inline.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let _ServerCodegen = null;
|
|
10
|
-
let _ClientCodegen = null;
|
|
7
|
+
import { ServerCodegen } from './server-codegen.js';
|
|
8
|
+
import { ClientCodegen } from './client-codegen.js';
|
|
11
9
|
|
|
12
10
|
function getServerCodegen() {
|
|
13
|
-
|
|
14
|
-
// Dynamic require avoids loading server-codegen.js for client-only builds
|
|
15
|
-
_ServerCodegen = import.meta.require('./server-codegen.js').ServerCodegen;
|
|
16
|
-
}
|
|
17
|
-
return _ServerCodegen;
|
|
11
|
+
return ServerCodegen;
|
|
18
12
|
}
|
|
19
13
|
|
|
20
14
|
function getClientCodegen() {
|
|
21
|
-
|
|
22
|
-
// Dynamic require avoids loading client-codegen.js for server-only builds
|
|
23
|
-
_ClientCodegen = import.meta.require('./client-codegen.js').ClientCodegen;
|
|
24
|
-
}
|
|
25
|
-
return _ClientCodegen;
|
|
15
|
+
return ClientCodegen;
|
|
26
16
|
}
|
|
27
17
|
|
|
28
18
|
export class CodeGenerator {
|
|
@@ -160,7 +150,7 @@ export class CodeGenerator {
|
|
|
160
150
|
let testCode = '';
|
|
161
151
|
if (testBlocks.length > 0) {
|
|
162
152
|
const testGen = new (getServerCodegen())();
|
|
163
|
-
testCode = testGen.generateTests(testBlocks);
|
|
153
|
+
testCode = testGen.generateTests(testBlocks, combinedShared);
|
|
164
154
|
|
|
165
155
|
// Add __handleRequest export to server code
|
|
166
156
|
const defaultServer = servers['default'] || '';
|
|
@@ -173,7 +163,7 @@ export class CodeGenerator {
|
|
|
173
163
|
let benchCode = '';
|
|
174
164
|
if (benchBlocks.length > 0) {
|
|
175
165
|
const benchGen = new (getServerCodegen())();
|
|
176
|
-
benchCode = benchGen.generateBench(benchBlocks);
|
|
166
|
+
benchCode = benchGen.generateBench(benchBlocks, combinedShared);
|
|
177
167
|
}
|
|
178
168
|
|
|
179
169
|
// Backward-compatible: if only unnamed blocks, return flat structure
|
|
@@ -2677,7 +2677,7 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2677
2677
|
this._emitHandlerCall(lines, `__grpChain(req)`, timeoutMs);
|
|
2678
2678
|
}
|
|
2679
2679
|
|
|
2680
|
-
generateTests(testBlocks) {
|
|
2680
|
+
generateTests(testBlocks, sharedCode) {
|
|
2681
2681
|
const lines = [];
|
|
2682
2682
|
lines.push('import { describe, test, expect } from "bun:test";');
|
|
2683
2683
|
lines.push('');
|
|
@@ -2701,6 +2701,12 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2701
2701
|
lines.push(' if (!condition) throw new Error(message || "Assertion failed");');
|
|
2702
2702
|
lines.push('}');
|
|
2703
2703
|
lines.push('');
|
|
2704
|
+
// Include top-level definitions (functions, variables) so tests can reference them
|
|
2705
|
+
if (sharedCode && sharedCode.trim()) {
|
|
2706
|
+
lines.push('// ── Module Code ──');
|
|
2707
|
+
lines.push(sharedCode);
|
|
2708
|
+
lines.push('');
|
|
2709
|
+
}
|
|
2704
2710
|
|
|
2705
2711
|
for (const block of testBlocks) {
|
|
2706
2712
|
const name = block.name || 'Tests';
|
|
@@ -2729,24 +2735,37 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2729
2735
|
lines.push(' });');
|
|
2730
2736
|
}
|
|
2731
2737
|
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2738
|
+
const hasFnTests = block.body.some(s => s.type === 'FunctionDeclaration');
|
|
2739
|
+
|
|
2740
|
+
if (hasFnTests) {
|
|
2741
|
+
// Function declarations become individual test cases
|
|
2742
|
+
for (const stmt of block.body) {
|
|
2743
|
+
if (stmt.type === 'FunctionDeclaration') {
|
|
2744
|
+
const fnName = stmt.name;
|
|
2745
|
+
const displayName = fnName.replace(/_/g, ' ');
|
|
2746
|
+
this.pushScope();
|
|
2747
|
+
for (const p of (stmt.params || [])) {
|
|
2748
|
+
const pName = typeof p === 'string' ? p : (p.name || p.identifier);
|
|
2749
|
+
if (pName) this.declareVar(pName);
|
|
2750
|
+
}
|
|
2751
|
+
const body = this.genBlockBody(stmt.body);
|
|
2752
|
+
this.popScope();
|
|
2753
|
+
const timeoutArg = blockTimeout ? `, ${blockTimeout}` : '';
|
|
2754
|
+
lines.push(` test(${JSON.stringify(displayName)}, async () => {`);
|
|
2755
|
+
lines.push(body);
|
|
2756
|
+
lines.push(` }${timeoutArg});`);
|
|
2757
|
+
} else {
|
|
2758
|
+
lines.push(' ' + this.generateStatement(stmt));
|
|
2740
2759
|
}
|
|
2741
|
-
const body = this.genBlockBody(stmt.body);
|
|
2742
|
-
this.popScope();
|
|
2743
|
-
const timeoutArg = blockTimeout ? `, ${blockTimeout}` : '';
|
|
2744
|
-
lines.push(` test(${JSON.stringify(displayName)}, async () => {`);
|
|
2745
|
-
lines.push(body);
|
|
2746
|
-
lines.push(` }${timeoutArg});`);
|
|
2747
|
-
} else {
|
|
2748
|
-
lines.push(' ' + this.generateStatement(stmt));
|
|
2749
2760
|
}
|
|
2761
|
+
} else {
|
|
2762
|
+
// No function declarations — wrap all statements in a single test case
|
|
2763
|
+
const timeoutArg = blockTimeout ? `, ${blockTimeout}` : '';
|
|
2764
|
+
lines.push(` test(${JSON.stringify(name)}, async () => {`);
|
|
2765
|
+
for (const stmt of block.body) {
|
|
2766
|
+
lines.push(' ' + this.generateStatement(stmt));
|
|
2767
|
+
}
|
|
2768
|
+
lines.push(` }${timeoutArg});`);
|
|
2750
2769
|
}
|
|
2751
2770
|
lines.push('});');
|
|
2752
2771
|
lines.push('');
|
|
@@ -2755,10 +2774,16 @@ export class ServerCodegen extends BaseCodegen {
|
|
|
2755
2774
|
return lines.join('\n');
|
|
2756
2775
|
}
|
|
2757
2776
|
|
|
2758
|
-
generateBench(benchBlocks) {
|
|
2777
|
+
generateBench(benchBlocks, sharedCode) {
|
|
2759
2778
|
const lines = [];
|
|
2760
2779
|
lines.push('// ── Tova Benchmark Runner ──');
|
|
2761
2780
|
lines.push('');
|
|
2781
|
+
// Include top-level definitions (functions, variables) so benchmarks can reference them
|
|
2782
|
+
if (sharedCode && sharedCode.trim()) {
|
|
2783
|
+
lines.push('// ── Module Code ──');
|
|
2784
|
+
lines.push(sharedCode);
|
|
2785
|
+
lines.push('');
|
|
2786
|
+
}
|
|
2762
2787
|
lines.push('async function __runBench(name, fn, runs) {');
|
|
2763
2788
|
lines.push(' runs = runs || 100;');
|
|
2764
2789
|
lines.push(' // Warmup');
|
package/src/parser/parser.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { TokenType } from '../lexer/tokens.js';
|
|
2
2
|
import * as AST from './ast.js';
|
|
3
|
+
import { installServerParser } from './server-parser.js';
|
|
4
|
+
import { installClientParser } from './client-parser.js';
|
|
3
5
|
|
|
4
6
|
export class Parser {
|
|
5
7
|
static MAX_EXPRESSION_DEPTH = 200;
|
|
@@ -295,14 +297,12 @@ export class Parser {
|
|
|
295
297
|
parseTopLevel() {
|
|
296
298
|
if (this.check(TokenType.SERVER)) {
|
|
297
299
|
if (!Parser.prototype._serverParserInstalled) {
|
|
298
|
-
const { installServerParser } = import.meta.require('./server-parser.js');
|
|
299
300
|
installServerParser(Parser);
|
|
300
301
|
}
|
|
301
302
|
return this.parseServerBlock();
|
|
302
303
|
}
|
|
303
304
|
if (this.check(TokenType.CLIENT)) {
|
|
304
305
|
if (!Parser.prototype._clientParserInstalled) {
|
|
305
|
-
const { installClientParser } = import.meta.require('./client-parser.js');
|
|
306
306
|
installClientParser(Parser);
|
|
307
307
|
}
|
|
308
308
|
return this.parseClientBlock();
|
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.3.
|
|
2
|
+
export const VERSION = "0.3.5";
|