tova 0.3.4 → 0.3.6
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 +438 -58
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +172 -32
- package/src/analyzer/client-analyzer.js +21 -5
- package/src/analyzer/scope.js +78 -3
- package/src/codegen/base-codegen.js +754 -45
- package/src/codegen/client-codegen.js +293 -36
- package/src/codegen/codegen.js +10 -15
- package/src/codegen/server-codegen.js +189 -40
- package/src/codegen/wasm-codegen.js +610 -0
- package/src/lexer/lexer.js +157 -109
- package/src/lexer/tokens.js +3 -0
- package/src/lsp/server.js +148 -12
- package/src/parser/ast.js +2 -1
- package/src/parser/client-parser.js +10 -3
- package/src/parser/parser.js +144 -150
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +307 -59
- package/src/runtime/ssr.js +101 -34
- package/src/stdlib/inline.js +333 -24
- package/src/stdlib/native-bridge.js +150 -0
- package/src/version.js +1 -1
package/src/stdlib/inline.js
CHANGED
|
@@ -2,9 +2,52 @@
|
|
|
2
2
|
// Single source of truth for all inline stdlib code used in code generation.
|
|
3
3
|
// Used by: base-codegen.js, client-codegen.js, bin/tova.js
|
|
4
4
|
|
|
5
|
-
export const RESULT_OPTION = `
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export const RESULT_OPTION = `class _Ok { constructor(value) { this.value = value; } }
|
|
6
|
+
_Ok.prototype.__tag = "Ok";
|
|
7
|
+
_Ok.prototype.map = function(fn) { return new _Ok(fn(this.value)); };
|
|
8
|
+
_Ok.prototype.flatMap = function(fn) { const r = fn(this.value); if (r && r.__tag) return r; throw new Error("flatMap callback must return Ok/Err"); };
|
|
9
|
+
_Ok.prototype.andThen = _Ok.prototype.flatMap;
|
|
10
|
+
_Ok.prototype.unwrap = function() { return this.value; };
|
|
11
|
+
_Ok.prototype.unwrapOr = function(_) { return this.value; };
|
|
12
|
+
_Ok.prototype.expect = function(_) { return this.value; };
|
|
13
|
+
_Ok.prototype.isOk = function() { return true; };
|
|
14
|
+
_Ok.prototype.isErr = function() { return false; };
|
|
15
|
+
_Ok.prototype.mapErr = function(_) { return this; };
|
|
16
|
+
_Ok.prototype.unwrapErr = function() { throw new Error("Called unwrapErr on Ok"); };
|
|
17
|
+
_Ok.prototype.or = function(_) { return this; };
|
|
18
|
+
_Ok.prototype.and = function(other) { return other; };
|
|
19
|
+
_Ok.prototype.context = function(_) { return this; };
|
|
20
|
+
function Ok(value) { return new _Ok(value); }
|
|
21
|
+
class _Err { constructor(error) { this.error = error; } }
|
|
22
|
+
_Err.prototype.__tag = "Err";
|
|
23
|
+
_Err.prototype.map = function(_) { return this; };
|
|
24
|
+
_Err.prototype.flatMap = function(_) { return this; };
|
|
25
|
+
_Err.prototype.andThen = _Err.prototype.flatMap;
|
|
26
|
+
_Err.prototype.unwrap = function() { throw new Error("Called unwrap on Err: " + (typeof this.error === "object" ? JSON.stringify(this.error) : this.error)); };
|
|
27
|
+
_Err.prototype.unwrapOr = function(def) { return def; };
|
|
28
|
+
_Err.prototype.expect = function(msg) { throw new Error(msg); };
|
|
29
|
+
_Err.prototype.isOk = function() { return false; };
|
|
30
|
+
_Err.prototype.isErr = function() { return true; };
|
|
31
|
+
_Err.prototype.mapErr = function(fn) { return new _Err(fn(this.error)); };
|
|
32
|
+
_Err.prototype.unwrapErr = function() { return this.error; };
|
|
33
|
+
_Err.prototype.or = function(other) { return other; };
|
|
34
|
+
_Err.prototype.and = function(_) { return this; };
|
|
35
|
+
_Err.prototype.context = function(msg) { const inner = typeof this.error === "object" ? JSON.stringify(this.error) : String(this.error); return new _Err(msg + " \\u2192 caused by: " + inner); };
|
|
36
|
+
function Err(error) { return new _Err(error); }
|
|
37
|
+
class _Some { constructor(value) { this.value = value; } }
|
|
38
|
+
_Some.prototype.__tag = "Some";
|
|
39
|
+
_Some.prototype.map = function(fn) { return new _Some(fn(this.value)); };
|
|
40
|
+
_Some.prototype.flatMap = function(fn) { const r = fn(this.value); if (r && r.__tag) return r; throw new Error("flatMap callback must return Some/None"); };
|
|
41
|
+
_Some.prototype.andThen = _Some.prototype.flatMap;
|
|
42
|
+
_Some.prototype.unwrap = function() { return this.value; };
|
|
43
|
+
_Some.prototype.unwrapOr = function(_) { return this.value; };
|
|
44
|
+
_Some.prototype.expect = function(_) { return this.value; };
|
|
45
|
+
_Some.prototype.isSome = function() { return true; };
|
|
46
|
+
_Some.prototype.isNone = function() { return false; };
|
|
47
|
+
_Some.prototype.or = function(_) { return this; };
|
|
48
|
+
_Some.prototype.and = function(other) { return other; };
|
|
49
|
+
_Some.prototype.filter = function(pred) { return pred(this.value) ? this : None; };
|
|
50
|
+
function Some(value) { return new _Some(value); }
|
|
8
51
|
const None = Object.freeze({ __tag: "None", map(_) { return None; }, flatMap(_) { return None; }, andThen(_) { return None; }, unwrap() { throw new Error("Called unwrap on None"); }, unwrapOr(def) { return def; }, expect(msg) { throw new Error(msg); }, isSome() { return false; }, isNone() { return true; }, or(other) { return other; }, and(_) { return None; }, filter(_) { return None; } });`;
|
|
9
52
|
|
|
10
53
|
export const PROPAGATE = `function __propagate(val) {
|
|
@@ -18,11 +61,11 @@ export const PROPAGATE = `function __propagate(val) {
|
|
|
18
61
|
// Individual builtin functions for tree-shaking
|
|
19
62
|
export const BUILTIN_FUNCTIONS = {
|
|
20
63
|
print: `function print(...args) { console.log(...args); }`,
|
|
21
|
-
len: `function len(v) { if (v == null) return 0; if (typeof v === 'string' || Array.isArray(v)) return v.length; if (typeof v === 'object') return Object.keys(v).length; return 0; }`,
|
|
64
|
+
len: `function len(v) { if (v == null) return 0; if (typeof v === 'string' || Array.isArray(v) || ArrayBuffer.isView(v)) return v.length; if (typeof v === 'object') return Object.keys(v).length; return 0; }`,
|
|
22
65
|
range: `function range(s, e, st) { if (e === undefined) { e = s; s = 0; } if (st === undefined) st = s < e ? 1 : -1; if (st === 0) return []; const r = []; if (st > 0) { for (let i = s; i < e; i += st) r.push(i); } else { for (let i = s; i > e; i += st) r.push(i); } return r; }`,
|
|
23
66
|
enumerate: `function enumerate(a) { return a.map((v, i) => [i, v]); }`,
|
|
24
67
|
sum: `function sum(a) { return a.reduce((x, y) => x + y, 0); }`,
|
|
25
|
-
sorted: `function sorted(a, k) { const c = [...a]; if (k) c.sort((x, y) => { const kx = k(x), ky = k(y); return kx < ky ? -1 : kx > ky ? 1 : 0; }); else c.sort((x, y) => x < y ? -1 : x > y ? 1 : 0); return c; }`,
|
|
68
|
+
sorted: `function sorted(a, k) { const c = [...a]; if (k) c.sort((x, y) => { const kx = k(x), ky = k(y); return kx < ky ? -1 : kx > ky ? 1 : 0; }); else if (c.length > 0 && typeof c[0] === 'number') { if (typeof __tova_native !== 'undefined' && __tova_native && c.length > 128) { const f = new Float64Array(c); __tova_native.tova_sort_f64(f, f.length); for (let i = 0; i < c.length; i++) c[i] = f[i]; } else if (c.length > 128) { const f = new Float64Array(c); f.sort(); for (let i = 0; i < c.length; i++) c[i] = f[i]; } else { c.sort((a, b) => a - b); } } else c.sort((x, y) => x < y ? -1 : x > y ? 1 : 0); return c; }`,
|
|
26
69
|
reversed: `function reversed(a) { return [...a].reverse(); }`,
|
|
27
70
|
zip: `function zip(...as) { if (as.length === 0) return []; const m = Math.min(...as.map(a => a.length)); const r = []; for (let i = 0; i < m; i++) r.push(as.map(a => a[i])); return r; }`,
|
|
28
71
|
min: `function min(a) { if (a.length === 0) return null; let m = a[0]; for (let i = 1; i < a.length; i++) if (a[i] < m) m = a[i]; return m; }`,
|
|
@@ -65,6 +108,34 @@ export const BUILTIN_FUNCTIONS = {
|
|
|
65
108
|
freeze: `function freeze(obj) { return Object.freeze(obj); }`,
|
|
66
109
|
clone: `function clone(obj) { return structuredClone(obj); }`,
|
|
67
110
|
sleep: `function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }`,
|
|
111
|
+
parallel_map: `async function parallel_map(arr, fn, numWorkers) {
|
|
112
|
+
if (!arr || arr.length === 0) return [];
|
|
113
|
+
const cores = numWorkers || (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : 4) || 4;
|
|
114
|
+
const n = Math.min(cores, arr.length);
|
|
115
|
+
if (n <= 1 || arr.length < 4) return arr.map(fn);
|
|
116
|
+
if (!parallel_map._pool) {
|
|
117
|
+
const { Worker } = await import("worker_threads");
|
|
118
|
+
const wc = 'const{parentPort}=require("worker_threads");parentPort.on("message",m=>{const fn=(0,eval)("("+m.f+")");try{const r=m.c.map(fn);parentPort.postMessage({i:m.i,r})}catch(e){parentPort.postMessage({i:m.i,e:e.message})}})';
|
|
119
|
+
parallel_map._pool = Array.from({length: n}, () => { const w = new Worker(wc, {eval: true}); w.unref(); return w; });
|
|
120
|
+
parallel_map._cid = 0;
|
|
121
|
+
}
|
|
122
|
+
const pool = parallel_map._pool;
|
|
123
|
+
const cs = Math.ceil(arr.length / pool.length);
|
|
124
|
+
const fnStr = fn.toString();
|
|
125
|
+
const cid = ++parallel_map._cid;
|
|
126
|
+
const promises = [];
|
|
127
|
+
for (let ci = 0; ci < pool.length && ci * cs < arr.length; ci++) {
|
|
128
|
+
const chunk = arr.slice(ci * cs, (ci + 1) * cs);
|
|
129
|
+
const mid = cid * 1000 + ci;
|
|
130
|
+
promises.push(new Promise((resolve, reject) => {
|
|
131
|
+
const w = pool[ci];
|
|
132
|
+
const h = (msg) => { if (msg.i === mid) { w.removeListener("message", h); if (msg.e) reject(new Error(msg.e)); else resolve(msg.r); } };
|
|
133
|
+
w.on("message", h);
|
|
134
|
+
w.postMessage({i: mid, c: chunk, f: fnStr});
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
return (await Promise.all(promises)).flat();
|
|
138
|
+
}`,
|
|
68
139
|
upper: `function upper(s) { return s.toUpperCase(); }`,
|
|
69
140
|
lower: `function lower(s) { return s.toLowerCase(); }`,
|
|
70
141
|
contains: `function contains(s, sub) { return s.includes(sub); }`,
|
|
@@ -173,6 +244,102 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
173
244
|
table_drop_duplicates: `function table_drop_duplicates(table, opts) { const by = opts && opts.by; const seen = new Set(); const rows = []; for (const row of table._rows) { const k = by ? (typeof by === 'function' ? String(by(row)) : String(row[by])) : JSON.stringify(row); if (!seen.has(k)) { seen.add(k); rows.push(row); } } return Table(rows, table._columns); }`,
|
|
174
245
|
table_rename: `function table_rename(table, oldName, newName) { const cols = table._columns.map(c => c === oldName ? newName : c); const rows = table._rows.map(r => { const row = {}; for (const c of table._columns) row[c === oldName ? newName : c] = r[c]; return row; }); return Table(rows, cols); }`,
|
|
175
246
|
|
|
247
|
+
// ── Lazy Table Query Builder ────────────────────────
|
|
248
|
+
lazy: `function lazy(table) { return new LazyTable(table); }`,
|
|
249
|
+
collect: `function collect(v) { if (v instanceof LazyTable) return v.collect(); if (v && v._gen) return v.collect(); return v; }`,
|
|
250
|
+
LazyTable: `class LazyTable {
|
|
251
|
+
constructor(source) {
|
|
252
|
+
this._source = source;
|
|
253
|
+
this._steps = [];
|
|
254
|
+
}
|
|
255
|
+
_push(step) { const lt = new LazyTable(this._source); lt._steps = [...this._steps, step]; return lt; }
|
|
256
|
+
where(pred) { return this._push({ op: 'where', fn: pred }); }
|
|
257
|
+
select(...args) {
|
|
258
|
+
let cols;
|
|
259
|
+
if (args.length === 1 && args[0] && args[0].__exclude) {
|
|
260
|
+
cols = { exclude: new Set(Array.isArray(args[0].__exclude) ? args[0].__exclude : [args[0].__exclude]) };
|
|
261
|
+
} else { cols = args.filter(a => typeof a === 'string'); }
|
|
262
|
+
return this._push({ op: 'select', cols });
|
|
263
|
+
}
|
|
264
|
+
derive(derivations) { return this._push({ op: 'derive', derivations }); }
|
|
265
|
+
limit(n) { return this._push({ op: 'limit', n }); }
|
|
266
|
+
drop_duplicates(opts) { return this._push({ op: 'dedup', by: opts && opts.by }); }
|
|
267
|
+
rename(oldName, newName) { return this._push({ op: 'rename', oldName, newName }); }
|
|
268
|
+
sort_by(keyFn, opts) { return this._push({ op: 'sort', keyFn, desc: opts && opts.desc }); }
|
|
269
|
+
group_by(keyFn) {
|
|
270
|
+
const rows = this.collect()._rows;
|
|
271
|
+
const src = Table(rows, this._resolveColumns());
|
|
272
|
+
return table_group_by(src, keyFn);
|
|
273
|
+
}
|
|
274
|
+
_resolveColumns() {
|
|
275
|
+
let cols = [...this._source._columns];
|
|
276
|
+
for (const s of this._steps) {
|
|
277
|
+
if (s.op === 'select') {
|
|
278
|
+
cols = s.cols.exclude ? cols.filter(c => !s.cols.exclude.has(c)) : [...s.cols];
|
|
279
|
+
} else if (s.op === 'derive') {
|
|
280
|
+
for (const k of Object.keys(s.derivations)) { if (!cols.includes(k)) cols.push(k); }
|
|
281
|
+
} else if (s.op === 'rename') {
|
|
282
|
+
cols = cols.map(c => c === s.oldName ? s.newName : c);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return cols;
|
|
286
|
+
}
|
|
287
|
+
collect() {
|
|
288
|
+
let rows = this._source._rows;
|
|
289
|
+
let cols = [...this._source._columns];
|
|
290
|
+
for (const step of this._steps) {
|
|
291
|
+
switch (step.op) {
|
|
292
|
+
case 'where': rows = rows.filter(step.fn); break;
|
|
293
|
+
case 'select': {
|
|
294
|
+
const sc = step.cols.exclude ? cols.filter(c => !step.cols.exclude.has(c)) : step.cols;
|
|
295
|
+
rows = rows.map(r => { const row = {}; for (const c of sc) row[c] = r[c]; return row; });
|
|
296
|
+
cols = [...sc];
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case 'derive': {
|
|
300
|
+
for (const k of Object.keys(step.derivations)) { if (!cols.includes(k)) cols.push(k); }
|
|
301
|
+
rows = rows.map(r => { const row = { ...r }; for (const [k, fn] of Object.entries(step.derivations)) { row[k] = typeof fn === 'function' ? fn(r) : fn; } return row; });
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case 'limit': rows = rows.slice(0, step.n); break;
|
|
305
|
+
case 'dedup': {
|
|
306
|
+
const seen = new Set();
|
|
307
|
+
const filtered = [];
|
|
308
|
+
for (const row of rows) {
|
|
309
|
+
const k = step.by ? (typeof step.by === 'function' ? String(step.by(row)) : String(row[step.by])) : JSON.stringify(row);
|
|
310
|
+
if (!seen.has(k)) { seen.add(k); filtered.push(row); }
|
|
311
|
+
}
|
|
312
|
+
rows = filtered;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'rename': {
|
|
316
|
+
cols = cols.map(c => c === step.oldName ? step.newName : c);
|
|
317
|
+
rows = rows.map(r => { const row = {}; for (const c of cols) row[c === step.newName ? step.newName : c] = r[c === step.newName ? step.oldName : c]; return row; });
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case 'sort': {
|
|
321
|
+
rows = [...rows].sort((a, b) => {
|
|
322
|
+
const ka = typeof step.keyFn === 'function' ? step.keyFn(a) : a[step.keyFn];
|
|
323
|
+
const kb = typeof step.keyFn === 'function' ? step.keyFn(b) : b[step.keyFn];
|
|
324
|
+
let c = ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
325
|
+
return step.desc ? -c : c;
|
|
326
|
+
});
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return Table(rows, cols);
|
|
332
|
+
}
|
|
333
|
+
toArray() { return this.collect()._rows; }
|
|
334
|
+
toJSON() { return this.toArray(); }
|
|
335
|
+
get rows() { return this.collect()._rows.length; }
|
|
336
|
+
get columns() { return this._resolveColumns(); }
|
|
337
|
+
get shape() { const t = this.collect(); return [t._rows.length, t._columns.length]; }
|
|
338
|
+
toString() { return this.collect().toString(); }
|
|
339
|
+
_format(maxRows, title) { return this.collect()._format(maxRows, title); }
|
|
340
|
+
[Symbol.iterator]() { return this.collect()._rows[Symbol.iterator](); }
|
|
341
|
+
}`,
|
|
342
|
+
|
|
176
343
|
// ── Aggregation helpers ─────────────────────────────
|
|
177
344
|
agg_sum: `function agg_sum(fn) { return (rows) => rows.reduce((a, r) => a + (typeof fn === 'function' ? fn(r) : r[fn]), 0); }`,
|
|
178
345
|
agg_count: `function agg_count(fn) { if (!fn) return (rows) => rows.length; return (rows) => rows.filter(fn).length; }`,
|
|
@@ -236,25 +403,26 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
236
403
|
const lines = text.split('\\n').filter(l => l.trim());
|
|
237
404
|
if (lines.length === 0) return Table([]);
|
|
238
405
|
const parseLine = (line) => { const fields = []; let cur = ''; let inQ = false; for (let i = 0; i < line.length; i++) { const ch = line[i]; if (inQ) { if (ch === '"' && line[i+1] === '"') { cur += '"'; i++; } else if (ch === '"') { inQ = false; } else { cur += ch; } } else { if (ch === '"') inQ = true; else if (ch === delim) { fields.push(cur.trim()); cur = ''; } else { cur += ch; } } } fields.push(cur.trim()); return fields; };
|
|
406
|
+
const _reInt = /^-?\\d+$/; const _reFloat = /^-?\\d*\\.\\d+$/;
|
|
239
407
|
let headers, ds; if (hasHeader) { headers = parseLine(lines[0]); ds = 1; } else { const fr = parseLine(lines[0]); headers = fr.map((_, i) => 'col_' + i); ds = 0; }
|
|
240
|
-
const rows = []; for (let i = ds; i < lines.length; i++) { const f = parseLine(lines[i]); const row = {}; for (let j = 0; j < headers.length; j++) { let v = f[j] ?? null; if (v !== null && v !== '') { if (
|
|
408
|
+
const rows = []; for (let i = ds; i < lines.length; i++) { const f = parseLine(lines[i]); const row = {}; for (let j = 0; j < headers.length; j++) { let v = f[j] ?? null; if (v !== null && v !== '') { if (_reInt.test(v)) v = parseInt(v, 10); else if (_reFloat.test(v)) v = parseFloat(v); else if (v === 'true') v = true; else if (v === 'false') v = false; else if (v === 'null' || v === 'nil') v = null; } else if (v === '') v = null; row[headers[j]] = v; } rows.push(row); }
|
|
241
409
|
return Table(rows, headers);
|
|
242
410
|
}`,
|
|
243
411
|
__parseJSONL: `function __parseJSONL(text) { return Table(text.split('\\n').filter(l => l.trim()).map(l => JSON.parse(l))); }`,
|
|
244
412
|
|
|
245
413
|
// ── Table operation aliases (short names for pipe-friendly use) ──
|
|
246
|
-
where: `function where(tableOrArr, pred) { if (tableOrArr && tableOrArr._rows) return table_where(tableOrArr, pred); return tableOrArr.filter(pred); }`,
|
|
247
|
-
select: `function select(table, ...args) { return table_select(table, ...args); }`,
|
|
248
|
-
derive: `function derive(table, derivations) { return table_derive(table, derivations); }`,
|
|
414
|
+
where: `function where(tableOrArr, pred) { if (tableOrArr instanceof LazyTable) return tableOrArr.where(pred); if (tableOrArr && tableOrArr._rows) return table_where(tableOrArr, pred); return tableOrArr.filter(pred); }`,
|
|
415
|
+
select: `function select(table, ...args) { if (table instanceof LazyTable) return table.select(...args); return table_select(table, ...args); }`,
|
|
416
|
+
derive: `function derive(table, derivations) { if (table instanceof LazyTable) return table.derive(derivations); return table_derive(table, derivations); }`,
|
|
249
417
|
agg: `function agg(grouped, aggregations) { return table_agg(grouped, aggregations); }`,
|
|
250
|
-
sort_by: `function sort_by(table, keyFn, opts) { return table_sort_by(table, keyFn, opts); }`,
|
|
251
|
-
limit: `function limit(table, n) { return table_limit(table, n); }`,
|
|
418
|
+
sort_by: `function sort_by(table, keyFn, opts) { if (table instanceof LazyTable) return table.sort_by(keyFn, opts); return table_sort_by(table, keyFn, opts); }`,
|
|
419
|
+
limit: `function limit(table, n) { if (table instanceof LazyTable) return table.limit(n); return table_limit(table, n); }`,
|
|
252
420
|
pivot: `function pivot(table, opts) { return table_pivot(table, opts); }`,
|
|
253
421
|
unpivot: `function unpivot(table, opts) { return table_unpivot(table, opts); }`,
|
|
254
422
|
explode: `function explode(table, colFn) { return table_explode(table, colFn); }`,
|
|
255
423
|
union: `function union(a, b) { if (a && a._rows) return table_union(a, b); return [...new Set([...a, ...b])]; }`,
|
|
256
|
-
drop_duplicates: `function drop_duplicates(table, opts) { return table_drop_duplicates(table, opts); }`,
|
|
257
|
-
rename: `function rename(table, oldName, newName) { return table_rename(table, oldName, newName); }`,
|
|
424
|
+
drop_duplicates: `function drop_duplicates(table, opts) { if (table instanceof LazyTable) return table.drop_duplicates(opts); return table_drop_duplicates(table, opts); }`,
|
|
425
|
+
rename: `function rename(table, oldName, newName) { if (table instanceof LazyTable) return table.rename(oldName, newName); return table_rename(table, oldName, newName); }`,
|
|
258
426
|
mean: `function mean(v) { if (Array.isArray(v)) { return v.length === 0 ? 0 : v.reduce((a, b) => a + b, 0) / v.length; } return agg_mean(v); }`,
|
|
259
427
|
median: `function median(v) { if (Array.isArray(v)) { if (v.length === 0) return null; const s = [...v].sort((a, b) => a - b); const m = Math.floor(s.length / 2); return s.length % 2 === 0 ? (s[m - 1] + s[m]) / 2 : s[m]; } return agg_median(v); }`,
|
|
260
428
|
|
|
@@ -332,13 +500,14 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
332
500
|
date_part: `function date_part(d, part) { if (typeof d === 'number') d = new Date(d); if (part === 'year') return d.getFullYear(); if (part === 'month') return d.getMonth() + 1; if (part === 'day') return d.getDate(); if (part === 'hour') return d.getHours(); if (part === 'minute') return d.getMinutes(); if (part === 'second') return d.getSeconds(); if (part === 'weekday') return d.getDay(); return null; }`,
|
|
333
501
|
time_ago: `function time_ago(d) { if (typeof d === 'number') d = new Date(d); const s = Math.floor((Date.now() - d.getTime()) / 1000); if (s < 60) return s + ' seconds ago'; const m = Math.floor(s / 60); if (m < 60) return m + (m === 1 ? ' minute ago' : ' minutes ago'); const h = Math.floor(m / 60); if (h < 24) return h + (h === 1 ? ' hour ago' : ' hours ago'); const dy = Math.floor(h / 24); if (dy < 30) return dy + (dy === 1 ? ' day ago' : ' days ago'); const mo = Math.floor(dy / 30); if (mo < 12) return mo + (mo === 1 ? ' month ago' : ' months ago'); const yr = Math.floor(mo / 12); return yr + (yr === 1 ? ' year ago' : ' years ago'); }`,
|
|
334
502
|
|
|
335
|
-
// ── Regex
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
503
|
+
// ── Regex (with compiled regex cache) ─────────────────
|
|
504
|
+
__regex_cache: `const __reCache = new Map(); function __re(p, f) { const k = p + '\\0' + (f || ''); let r = __reCache.get(k); if (!r) { r = new RegExp(p, f); __reCache.set(k, r); if (__reCache.size > 1000) { const first = __reCache.keys().next().value; __reCache.delete(first); } } return r; }`,
|
|
505
|
+
regex_test: `function regex_test(s, pattern, flags) { return __re(pattern, flags).test(s); }`,
|
|
506
|
+
regex_match: `function regex_match(s, pattern, flags) { const m = s.match(__re(pattern, flags)); if (!m) return Err('No match'); return Ok({ match: m[0], index: m.index, groups: m.slice(1) }); }`,
|
|
507
|
+
regex_find_all: `function regex_find_all(s, pattern, flags) { const re = __re(pattern, (flags || '') + (flags && flags.includes('g') ? '' : 'g')); const results = []; let m; re.lastIndex = 0; while ((m = re.exec(s)) !== null) { results.push({ match: m[0], index: m.index, groups: m.slice(1) }); } return results; }`,
|
|
508
|
+
regex_replace: `function regex_replace(s, pattern, replacement, flags) { return s.replace(__re(pattern, flags || 'g'), replacement); }`,
|
|
509
|
+
regex_split: `function regex_split(s, pattern, flags) { return s.split(__re(pattern, flags)); }`,
|
|
510
|
+
regex_capture: `function regex_capture(s, pattern, flags) { const m = s.match(__re(pattern, flags)); if (!m) return Err('No match'); if (!m.groups) return Err('No named groups'); return Ok(m.groups); }`,
|
|
342
511
|
|
|
343
512
|
// ── Validation ─────────────────────────────────────────
|
|
344
513
|
is_email: `function is_email(s) { return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(s); }`,
|
|
@@ -857,6 +1026,102 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
857
1026
|
collections: `const collections = Object.freeze({
|
|
858
1027
|
OrderedDict, DefaultDict, Counter, Deque
|
|
859
1028
|
});`,
|
|
1029
|
+
|
|
1030
|
+
// ─── Typed numeric array functions for @fast mode ───────────────
|
|
1031
|
+
|
|
1032
|
+
typed_zeros: `function typed_zeros(n, Type) {
|
|
1033
|
+
return new (Type || Float64Array)(n);
|
|
1034
|
+
}`,
|
|
1035
|
+
|
|
1036
|
+
typed_ones: `function typed_ones(n, Type) {
|
|
1037
|
+
const out = new (Type || Float64Array)(n);
|
|
1038
|
+
out.fill(1);
|
|
1039
|
+
return out;
|
|
1040
|
+
}`,
|
|
1041
|
+
|
|
1042
|
+
typed_fill: `function typed_fill(arr, value) {
|
|
1043
|
+
const out = new arr.constructor(arr.length);
|
|
1044
|
+
out.fill(value);
|
|
1045
|
+
return out;
|
|
1046
|
+
}`,
|
|
1047
|
+
|
|
1048
|
+
typed_range: `function typed_range(start, end, step) {
|
|
1049
|
+
step = step || 1;
|
|
1050
|
+
const n = Math.ceil((end - start) / step);
|
|
1051
|
+
const arr = new Float64Array(n);
|
|
1052
|
+
for (let i = 0; i < n; i++) arr[i] = start + i * step;
|
|
1053
|
+
return arr;
|
|
1054
|
+
}`,
|
|
1055
|
+
|
|
1056
|
+
typed_linspace: `function typed_linspace(start, end, n) {
|
|
1057
|
+
const out = new Float64Array(n);
|
|
1058
|
+
if (n <= 1) { if (n === 1) out[0] = start; return out; }
|
|
1059
|
+
const step = (end - start) / (n - 1);
|
|
1060
|
+
for (let i = 0; i < n; i++) out[i] = start + i * step;
|
|
1061
|
+
return out;
|
|
1062
|
+
}`,
|
|
1063
|
+
|
|
1064
|
+
typed_sum: `function typed_sum(arr) {
|
|
1065
|
+
let s = 0, c = 0;
|
|
1066
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1067
|
+
const y = arr[i] - c;
|
|
1068
|
+
const t = s + y;
|
|
1069
|
+
c = (t - s) - y;
|
|
1070
|
+
s = t;
|
|
1071
|
+
}
|
|
1072
|
+
return s;
|
|
1073
|
+
}`,
|
|
1074
|
+
|
|
1075
|
+
typed_dot: `function typed_dot(a, b) {
|
|
1076
|
+
const n = a.length;
|
|
1077
|
+
let s = 0;
|
|
1078
|
+
for (let i = 0; i < n; i++) s += a[i] * b[i];
|
|
1079
|
+
return s;
|
|
1080
|
+
}`,
|
|
1081
|
+
|
|
1082
|
+
typed_norm: `function typed_norm(arr) {
|
|
1083
|
+
let s = 0;
|
|
1084
|
+
for (let i = 0; i < arr.length; i++) s += arr[i] * arr[i];
|
|
1085
|
+
return Math.sqrt(s);
|
|
1086
|
+
}`,
|
|
1087
|
+
|
|
1088
|
+
typed_add: `function typed_add(a, b) {
|
|
1089
|
+
const n = a.length;
|
|
1090
|
+
const out = new a.constructor(n);
|
|
1091
|
+
for (let i = 0; i < n; i++) out[i] = a[i] + b[i];
|
|
1092
|
+
return out;
|
|
1093
|
+
}`,
|
|
1094
|
+
|
|
1095
|
+
typed_scale: `function typed_scale(arr, scalar) {
|
|
1096
|
+
const out = new arr.constructor(arr.length);
|
|
1097
|
+
for (let i = 0; i < arr.length; i++) out[i] = arr[i] * scalar;
|
|
1098
|
+
return out;
|
|
1099
|
+
}`,
|
|
1100
|
+
|
|
1101
|
+
typed_map: `function typed_map(arr, fn) {
|
|
1102
|
+
const out = new arr.constructor(arr.length);
|
|
1103
|
+
for (let i = 0; i < arr.length; i++) out[i] = fn(arr[i], i);
|
|
1104
|
+
return out;
|
|
1105
|
+
}`,
|
|
1106
|
+
|
|
1107
|
+
typed_reduce: `function typed_reduce(arr, fn, init) {
|
|
1108
|
+
let acc = init;
|
|
1109
|
+
for (let i = 0; i < arr.length; i++) acc = fn(acc, arr[i], i);
|
|
1110
|
+
return acc;
|
|
1111
|
+
}`,
|
|
1112
|
+
|
|
1113
|
+
typed_sort: `function typed_sort(arr) {
|
|
1114
|
+
if (arr instanceof Float64Array || arr instanceof Int32Array || arr instanceof Uint8Array ||
|
|
1115
|
+
arr instanceof Float32Array || arr instanceof Int16Array || arr instanceof Uint16Array ||
|
|
1116
|
+
arr instanceof Uint32Array || arr instanceof Int8Array) {
|
|
1117
|
+
const out = new arr.constructor(arr);
|
|
1118
|
+
out.sort();
|
|
1119
|
+
return out;
|
|
1120
|
+
}
|
|
1121
|
+
const out = [...arr];
|
|
1122
|
+
out.sort((a, b) => a - b);
|
|
1123
|
+
return out;
|
|
1124
|
+
}`,
|
|
860
1125
|
};
|
|
861
1126
|
|
|
862
1127
|
// All known builtin names for matching
|
|
@@ -881,13 +1146,21 @@ export const STDLIB_DEPS = {
|
|
|
881
1146
|
fs: ['Ok', 'Err'],
|
|
882
1147
|
url: ['Ok', 'Err'],
|
|
883
1148
|
parse_url: ['Ok', 'Err'],
|
|
884
|
-
|
|
885
|
-
|
|
1149
|
+
regex_test: ['__regex_cache'],
|
|
1150
|
+
regex_match: ['Ok', 'Err', '__regex_cache'],
|
|
1151
|
+
regex_find_all: ['__regex_cache'],
|
|
1152
|
+
regex_replace: ['__regex_cache'],
|
|
1153
|
+
regex_split: ['__regex_cache'],
|
|
1154
|
+
regex_capture: ['Ok', 'Err', '__regex_cache'],
|
|
886
1155
|
json_parse: ['Ok', 'Err'],
|
|
887
1156
|
date_parse: ['Ok', 'Err'],
|
|
888
1157
|
read_text: ['Ok', 'Err'],
|
|
889
1158
|
try_fn: ['Ok', 'Err'],
|
|
890
1159
|
try_async: ['Ok', 'Err'],
|
|
1160
|
+
// LazyTable requires Table and table_* functions
|
|
1161
|
+
lazy: ['LazyTable', 'Table'],
|
|
1162
|
+
collect: ['LazyTable'],
|
|
1163
|
+
LazyTable: ['Table', 'table_where', 'table_group_by'],
|
|
891
1164
|
// Seq uses Some/None
|
|
892
1165
|
Seq: ['Some', 'None'],
|
|
893
1166
|
// compare family
|
|
@@ -954,6 +1227,7 @@ const _LEGACY_NAMES = [
|
|
|
954
1227
|
'lines', 'capitalize', 'title_case', 'snake_case', 'camel_case',
|
|
955
1228
|
'assert_eq', 'assert_ne', 'assert', 'assert_throws',
|
|
956
1229
|
'create_spy', 'create_mock',
|
|
1230
|
+
'parallel_map',
|
|
957
1231
|
];
|
|
958
1232
|
export const BUILTINS = _LEGACY_NAMES.map(n => BUILTIN_FUNCTIONS[n]).join('\n');
|
|
959
1233
|
|
|
@@ -971,9 +1245,44 @@ export function buildSelectiveStdlib(usedNames) {
|
|
|
971
1245
|
return parts.join('\n');
|
|
972
1246
|
}
|
|
973
1247
|
|
|
974
|
-
//
|
|
1248
|
+
// Native FFI bridge initialization (server-side only, Bun runtime)
|
|
1249
|
+
// Lazily loads the Rust native library for high-performance stdlib operations
|
|
1250
|
+
// Async version for tova run (AsyncFunction context supports await)
|
|
1251
|
+
export const NATIVE_INIT = `var __tova_native = null;
|
|
1252
|
+
try {
|
|
1253
|
+
if (typeof Bun !== 'undefined') {
|
|
1254
|
+
const { dlopen: __dl, FFIType: __F } = await import('bun:ffi');
|
|
1255
|
+
const __path = await import('path');
|
|
1256
|
+
const __fs = await import('fs');
|
|
1257
|
+
const __searchDirs = [
|
|
1258
|
+
__path.join(__path.dirname(typeof __tova_filename !== 'undefined' ? __tova_filename : ''), 'native', 'target', 'release'),
|
|
1259
|
+
__path.join(process.cwd(), 'native', 'target', 'release'),
|
|
1260
|
+
__path.join(process.env.HOME || '', '.tova', 'lib'),
|
|
1261
|
+
];
|
|
1262
|
+
const __libName = process.platform === 'darwin' ? 'libtova_native.dylib' : process.platform === 'win32' ? 'tova_native.dll' : 'libtova_native.so';
|
|
1263
|
+
for (const __d of __searchDirs) {
|
|
1264
|
+
const __p = __path.join(__d, __libName);
|
|
1265
|
+
if (__fs.existsSync(__p)) {
|
|
1266
|
+
const __lib = __dl(__p, {
|
|
1267
|
+
tova_sort_f64: { args: [__F.ptr, __F.u64], returns: __F.void },
|
|
1268
|
+
tova_sort_i64: { args: [__F.ptr, __F.u64], returns: __F.void },
|
|
1269
|
+
tova_sum_f64: { args: [__F.ptr, __F.u64], returns: __F.f64 },
|
|
1270
|
+
tova_min_f64: { args: [__F.ptr, __F.u64], returns: __F.f64 },
|
|
1271
|
+
tova_max_f64: { args: [__F.ptr, __F.u64], returns: __F.f64 },
|
|
1272
|
+
});
|
|
1273
|
+
__tova_native = __lib.symbols;
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
} catch (__e) {}`;
|
|
1279
|
+
|
|
1280
|
+
// Sync-safe version without await (for non-async contexts like tests, REPL eval)
|
|
1281
|
+
export const NATIVE_INIT_SYNC = `var __tova_native = null;`;
|
|
1282
|
+
|
|
1283
|
+
// Full stdlib for runtime (REPL, run command) — sync-safe (no await)
|
|
975
1284
|
export function getFullStdlib() {
|
|
976
|
-
return `${buildSelectiveStdlib(BUILTIN_NAMES)}\n${RESULT_OPTION}\n${PROPAGATE}`;
|
|
1285
|
+
return `${NATIVE_INIT_SYNC}\n${buildSelectiveStdlib(BUILTIN_NAMES)}\n${RESULT_OPTION}\n${PROPAGATE}`;
|
|
977
1286
|
}
|
|
978
1287
|
|
|
979
1288
|
// Stdlib for client codegen (includes builtins + result/option + propagate)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Tova Native FFI Bridge
|
|
2
|
+
// Provides high-performance Rust-backed operations via Bun FFI
|
|
3
|
+
// Falls back gracefully to pure JS when native library is unavailable
|
|
4
|
+
|
|
5
|
+
let _lib = null;
|
|
6
|
+
let _available = false;
|
|
7
|
+
|
|
8
|
+
function _findLibrary() {
|
|
9
|
+
const { existsSync } = require('fs');
|
|
10
|
+
const { join, dirname } = require('path');
|
|
11
|
+
|
|
12
|
+
// Search paths for the native library
|
|
13
|
+
const names = process.platform === 'darwin'
|
|
14
|
+
? ['libtova_native.dylib']
|
|
15
|
+
: process.platform === 'win32'
|
|
16
|
+
? ['tova_native.dll']
|
|
17
|
+
: ['libtova_native.so'];
|
|
18
|
+
|
|
19
|
+
const searchDirs = [
|
|
20
|
+
// Relative to this file (src/stdlib/)
|
|
21
|
+
join(dirname(__filename), '..', '..', 'native', 'target', 'release'),
|
|
22
|
+
// Relative to bin/tova.js
|
|
23
|
+
join(dirname(__filename), '..', 'native', 'target', 'release'),
|
|
24
|
+
// System-wide install
|
|
25
|
+
join(process.env.HOME || '', '.tova', 'lib'),
|
|
26
|
+
// Next to the binary
|
|
27
|
+
dirname(process.argv[1] || ''),
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
for (const dir of searchDirs) {
|
|
31
|
+
for (const name of names) {
|
|
32
|
+
const path = join(dir, name);
|
|
33
|
+
if (existsSync(path)) return path;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _init() {
|
|
40
|
+
if (_lib !== null) return _available;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const libPath = _findLibrary();
|
|
44
|
+
if (!libPath) {
|
|
45
|
+
_lib = false;
|
|
46
|
+
_available = false;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { dlopen, FFIType } = require('bun:ffi');
|
|
51
|
+
_lib = dlopen(libPath, {
|
|
52
|
+
tova_sort_f64: {
|
|
53
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
54
|
+
returns: FFIType.void,
|
|
55
|
+
},
|
|
56
|
+
tova_sort_i64: {
|
|
57
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
58
|
+
returns: FFIType.void,
|
|
59
|
+
},
|
|
60
|
+
tova_unique_sorted_i64: {
|
|
61
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
62
|
+
returns: FFIType.u64,
|
|
63
|
+
},
|
|
64
|
+
tova_unique_sorted_f64: {
|
|
65
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
66
|
+
returns: FFIType.u64,
|
|
67
|
+
},
|
|
68
|
+
tova_sum_f64: {
|
|
69
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
70
|
+
returns: FFIType.f64,
|
|
71
|
+
},
|
|
72
|
+
tova_min_f64: {
|
|
73
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
74
|
+
returns: FFIType.f64,
|
|
75
|
+
},
|
|
76
|
+
tova_max_f64: {
|
|
77
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
78
|
+
returns: FFIType.f64,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
_available = true;
|
|
82
|
+
return true;
|
|
83
|
+
} catch (e) {
|
|
84
|
+
_lib = false;
|
|
85
|
+
_available = false;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sort an array of numbers using Rust radix sort.
|
|
92
|
+
* Returns a new sorted array. Falls back to JS sort if native unavailable.
|
|
93
|
+
* Only used for arrays above the threshold (overhead of copying to/from TypedArray).
|
|
94
|
+
*/
|
|
95
|
+
export function nativeSortNumbers(arr) {
|
|
96
|
+
if (!_init()) return null; // fallback signal
|
|
97
|
+
|
|
98
|
+
const len = arr.length;
|
|
99
|
+
const buf = new Float64Array(len);
|
|
100
|
+
for (let i = 0; i < len; i++) buf[i] = arr[i];
|
|
101
|
+
|
|
102
|
+
_lib.symbols.tova_sort_f64(buf, len);
|
|
103
|
+
|
|
104
|
+
const result = new Array(len);
|
|
105
|
+
for (let i = 0; i < len; i++) result[i] = buf[i];
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sort a Float64Array in-place using Rust radix sort.
|
|
111
|
+
*/
|
|
112
|
+
export function nativeSortF64(buf) {
|
|
113
|
+
if (!_init()) return false;
|
|
114
|
+
_lib.symbols.tova_sort_f64(buf, buf.length);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sum an array of numbers using Rust Kahan summation.
|
|
120
|
+
*/
|
|
121
|
+
export function nativeSum(arr) {
|
|
122
|
+
if (!_init()) return null;
|
|
123
|
+
const buf = new Float64Array(arr);
|
|
124
|
+
return _lib.symbols.tova_sum_f64(buf, buf.length);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Find min of numeric array using Rust.
|
|
129
|
+
*/
|
|
130
|
+
export function nativeMin(arr) {
|
|
131
|
+
if (!_init()) return null;
|
|
132
|
+
const buf = new Float64Array(arr);
|
|
133
|
+
return _lib.symbols.tova_min_f64(buf, buf.length);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Find max of numeric array using Rust.
|
|
138
|
+
*/
|
|
139
|
+
export function nativeMax(arr) {
|
|
140
|
+
if (!_init()) return null;
|
|
141
|
+
const buf = new Float64Array(arr);
|
|
142
|
+
return _lib.symbols.tova_max_f64(buf, buf.length);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if native library is available.
|
|
147
|
+
*/
|
|
148
|
+
export function isNativeAvailable() {
|
|
149
|
+
return _init();
|
|
150
|
+
}
|
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.6";
|