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 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
- // Include stdlib + shared code + test code
349
- const stdlib = getFullStdlib();
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 srcDir = resolve(explicitSrc || '.');
884
+ const srcPath = resolve(explicitSrc || '.');
887
885
 
888
- const tovaFiles = findFiles(srcDir, '.tova');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tova",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Tova — a modern programming language that transpiles to JavaScript, unifying frontend and backend",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -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;
@@ -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
- // Lazy-loaded codegen modules only imported when server/client blocks exist
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
- if (!_ServerCodegen) {
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
- if (!_ClientCodegen) {
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
- for (const stmt of block.body) {
2733
- if (stmt.type === 'FunctionDeclaration') {
2734
- const fnName = stmt.name;
2735
- const displayName = fnName.replace(/_/g, ' ');
2736
- this.pushScope();
2737
- for (const p of (stmt.params || [])) {
2738
- const pName = typeof p === 'string' ? p : (p.name || p.identifier);
2739
- if (pName) this.declareVar(pName);
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');
@@ -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.3";
2
+ export const VERSION = "0.3.5";