tova 0.1.1 → 0.2.2

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.
@@ -15,71 +15,6 @@ export const PROPAGATE = `function __propagate(val) {
15
15
  return val;
16
16
  }`;
17
17
 
18
- export const BUILTINS = `function print(...args) { console.log(...args); }
19
- 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; }
20
- 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; }
21
- function enumerate(a) { return a.map((v, i) => [i, v]); }
22
- function sum(a) { return a.reduce((x, y) => x + y, 0); }
23
- 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; }
24
- function reversed(a) { return [...a].reverse(); }
25
- 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; }
26
- function min(a) { return a.length === 0 ? null : Math.min(...a); }
27
- function max(a) { return a.length === 0 ? null : Math.max(...a); }
28
- function type_of(v) { if (v === null) return 'Nil'; if (Array.isArray(v)) return 'List'; if (v?.__tag) return v.__tag; const t = typeof v; switch(t) { case 'number': return Number.isInteger(v) ? 'Int' : 'Float'; case 'string': return 'String'; case 'boolean': return 'Bool'; case 'function': return 'Function'; case 'object': return 'Object'; default: return 'Unknown'; } }
29
- function filter(arr, fn) { return arr.filter(fn); }
30
- function map(arr, fn) { return arr.map(fn); }
31
- function find(arr, fn) { return arr.find(fn) ?? null; }
32
- function any(arr, fn) { return arr.some(fn); }
33
- function all(arr, fn) { return arr.every(fn); }
34
- function flat_map(arr, fn) { return arr.flatMap(fn); }
35
- function reduce(arr, fn, init) { return init === undefined ? arr.reduce(fn) : arr.reduce(fn, init); }
36
- function unique(arr) { return [...new Set(arr)]; }
37
- function group_by(arr, fn) { const r = {}; for (const v of arr) { const k = fn(v); if (!r[k]) r[k] = []; r[k].push(v); } return r; }
38
- function chunk(arr, n) { const r = []; for (let i = 0; i < arr.length; i += n) r.push(arr.slice(i, i + n)); return r; }
39
- function flatten(arr) { return arr.flat(); }
40
- function take(arr, n) { return arr.slice(0, n); }
41
- function drop(arr, n) { return arr.slice(n); }
42
- function first(arr) { return arr.length > 0 ? arr[0] : null; }
43
- function last(arr) { return arr.length > 0 ? arr[arr.length - 1] : null; }
44
- function count(arr, fn) { return arr.filter(fn).length; }
45
- function partition(arr, fn) { const y = [], n = []; for (const v of arr) { (fn(v) ? y : n).push(v); } return [y, n]; }
46
- function abs(n) { return Math.abs(n); }
47
- function floor(n) { return Math.floor(n); }
48
- function ceil(n) { return Math.ceil(n); }
49
- function round(n) { return Math.round(n); }
50
- function clamp(n, lo, hi) { return Math.min(Math.max(n, lo), hi); }
51
- function sqrt(n) { return Math.sqrt(n); }
52
- function pow(b, e) { return Math.pow(b, e); }
53
- function random() { return Math.random(); }
54
- function trim(s) { return s.trim(); }
55
- function split(s, sep) { return s.split(sep); }
56
- function join(arr, sep) { return arr.join(sep); }
57
- function replace(s, from, to) { return typeof from === 'string' ? s.replaceAll(from, to) : s.replace(from, to); }
58
- function repeat(s, n) { return s.repeat(n); }
59
- function keys(obj) { return Object.keys(obj); }
60
- function values(obj) { return Object.values(obj); }
61
- function entries(obj) { return Object.entries(obj); }
62
- function merge(...objs) { return Object.assign({}, ...objs); }
63
- function freeze(obj) { return Object.freeze(obj); }
64
- function clone(obj) { return structuredClone(obj); }
65
- function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
66
- function upper(s) { return s.toUpperCase(); }
67
- function lower(s) { return s.toLowerCase(); }
68
- function contains(s, sub) { return s.includes(sub); }
69
- function starts_with(s, prefix) { return s.startsWith(prefix); }
70
- function ends_with(s, suffix) { return s.endsWith(suffix); }
71
- function chars(s) { return [...s]; }
72
- function words(s) { return s.split(/\\s+/).filter(Boolean); }
73
- function lines(s) { return s.split('\\n'); }
74
- function capitalize(s) { return s.length ? s.charAt(0).toUpperCase() + s.slice(1) : s; }
75
- function title_case(s) { return s.replace(/\\b\\w/g, c => c.toUpperCase()); }
76
- function snake_case(s) { return s.replace(/[-\\s]+/g, '_').replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase().replace(/^_/, ''); }
77
- function camel_case(s) { return s.replace(/[-_\\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '').replace(/^[A-Z]/, c => c.toLowerCase()); }
78
- function assert_eq(a, b, msg) { if (a !== b) throw new Error(msg || \`Assertion failed: \${JSON.stringify(a)} !== \${JSON.stringify(b)}\`); }
79
- function assert_ne(a, b, msg) { if (a === b) throw new Error(msg || \`Assertion failed: values should not be equal: \${JSON.stringify(a)}\`); }
80
- function assert(cond, msg) { if (!cond) throw new Error(msg || "Assertion failed"); }`;
81
-
82
-
83
18
  // Individual builtin functions for tree-shaking
84
19
  export const BUILTIN_FUNCTIONS = {
85
20
  print: `function print(...args) { console.log(...args); }`,
@@ -145,11 +80,343 @@ export const BUILTIN_FUNCTIONS = {
145
80
  assert_eq: `function assert_eq(a, b, msg) { if (a !== b) throw new Error(msg || \`Assertion failed: \${JSON.stringify(a)} !== \${JSON.stringify(b)}\`); }`,
146
81
  assert_ne: `function assert_ne(a, b, msg) { if (a === b) throw new Error(msg || \`Assertion failed: values should not be equal: \${JSON.stringify(a)}\`); }`,
147
82
  assert: `function assert(cond, msg) { if (!cond) throw new Error(msg || "Assertion failed"); }`,
83
+
84
+ // ── Missing from module files (synced to BUILTIN_FUNCTIONS) ──
85
+ find_index: `function find_index(arr, fn) { const i = arr.findIndex(fn); return i === -1 ? null : i; }`,
86
+ includes: `function includes(arr, value) { return arr.includes(value); }`,
87
+ replace_first: `function replace_first(s, from, to) { return s.replace(from, to); }`,
88
+ pad_start: `function pad_start(s, n, fill) { return s.padStart(n, fill || ' '); }`,
89
+ pad_end: `function pad_end(s, n, fill) { return s.padEnd(n, fill || ' '); }`,
90
+ char_at: `function char_at(s, i) { return i < s.length ? s[i] : null; }`,
91
+ trim_start: `function trim_start(s) { return s.trimStart(); }`,
92
+ trim_end: `function trim_end(s) { return s.trimEnd(); }`,
93
+
94
+ // ── Math constants ────────────────────────────────────
95
+ PI: `const PI = Math.PI;`,
96
+ E: `const E = Math.E;`,
97
+ INF: `const INF = Infinity;`,
98
+
99
+ // ── Trigonometric ─────────────────────────────────────
100
+ sin: `function sin(n) { return Math.sin(n); }`,
101
+ cos: `function cos(n) { return Math.cos(n); }`,
102
+ tan: `function tan(n) { return Math.tan(n); }`,
103
+ asin: `function asin(n) { return Math.asin(n); }`,
104
+ acos: `function acos(n) { return Math.acos(n); }`,
105
+ atan: `function atan(n) { return Math.atan(n); }`,
106
+ atan2: `function atan2(y, x) { return Math.atan2(y, x); }`,
107
+
108
+ // ── Logarithmic / Exponential ─────────────────────────
109
+ log: `function log(n) { return Math.log(n); }`,
110
+ log2: `function log2(n) { return Math.log2(n); }`,
111
+ log10: `function log10(n) { return Math.log10(n); }`,
112
+ exp: `function exp(n) { return Math.exp(n); }`,
113
+
114
+ // ── Numeric Utilities ─────────────────────────────────
115
+ sign: `function sign(n) { return Math.sign(n); }`,
116
+ trunc: `function trunc(n) { return Math.trunc(n); }`,
117
+ is_nan: `function is_nan(n) { return Number.isNaN(n); }`,
118
+ is_finite: `function is_finite(n) { return Number.isFinite(n); }`,
119
+ is_close: `function is_close(a, b, tol) { return Math.abs(a - b) <= (tol === undefined ? 1e-9 : tol); }`,
120
+ to_radians: `function to_radians(deg) { return deg * Math.PI / 180; }`,
121
+ to_degrees: `function to_degrees(rad) { return rad * 180 / Math.PI; }`,
122
+
123
+ // ── Integer Math ──────────────────────────────────────
124
+ gcd: `function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a; }`,
125
+ lcm: `function lcm(a, b) { if (a === 0 && b === 0) return 0; let x = Math.abs(a), y = Math.abs(b); while (y) { const t = y; y = x % y; x = t; } return Math.abs(a * b) / x; }`,
126
+ factorial: `function factorial(n) { if (n < 0) return null; if (n <= 1) return 1; let r = 1; for (let i = 2; i <= n; i++) r *= i; return r; }`,
127
+
128
+ // ── Randomness ────────────────────────────────────────
129
+ random_int: `function random_int(lo, hi) { return Math.floor(Math.random() * (hi - lo + 1)) + lo; }`,
130
+ random_float: `function random_float(lo, hi) { return Math.random() * (hi - lo) + lo; }`,
131
+ choice: `function choice(arr) { return arr.length === 0 ? null : arr[Math.floor(Math.random() * arr.length)]; }`,
132
+ sample: `function sample(arr, n) { const c = [...arr]; for (let i = c.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [c[i], c[j]] = [c[j], c[i]]; } return c.slice(0, n); }`,
133
+ shuffle: `function shuffle(arr) { const c = [...arr]; for (let i = c.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [c[i], c[j]] = [c[j], c[i]]; } return c; }`,
134
+
135
+ // ── Type Conversion ───────────────────────────────────
136
+ to_int: `function to_int(v) { if (typeof v === 'boolean') return v ? 1 : 0; const n = typeof v === 'string' ? parseInt(v, 10) : Math.trunc(Number(v)); return isNaN(n) ? null : n; }`,
137
+ to_float: `function to_float(v) { if (typeof v === 'boolean') return v ? 1.0 : 0.0; const n = Number(v); return isNaN(n) ? null : n; }`,
138
+ to_string: `function to_string(v) { if (v == null) return 'nil'; if (v && v.__tag) return v.__tag + (v.value !== undefined ? '(' + String(v.value) + ')' : ''); return String(v); }`,
139
+ to_bool: `function to_bool(v) { if (typeof v === 'string') return v !== '' && v !== '0' && v !== 'false'; return Boolean(v); }`,
140
+
141
+ // ── Table runtime ───────────────────────────────────
142
+ Table: `function Table(rows, columns) { if (rows instanceof Table) return rows; const t = Object.create(Table.prototype); t._rows = Array.isArray(rows) ? rows : []; t._columns = columns || (t._rows.length > 0 ? Object.keys(t._rows[0]) : []); return t; }
143
+ Table.prototype = { get rows() { return this._rows.length; }, get columns() { return [...this._columns]; }, get shape() { return [this._rows.length, this._columns.length]; }, get length() { return this._rows.length; }, [Symbol.iterator]() { return this._rows[Symbol.iterator](); }, at(i) { if (i < 0) i = this._rows.length + i; return this._rows[i] ?? null; }, slice(s, e) { return Table(this._rows.slice(s, e), this._columns); }, getColumn(n) { return this._rows.map(r => r[n]); }, toArray() { return [...this._rows]; }, toJSON() { return this._rows; }, toString() { if (this._rows.length === 0) return 'Table(0 rows, 0 columns)'; return 'Table(' + this._rows.length + ' rows, ' + this._columns.length + ' columns)'; }, _format(maxRows, title) { const lines = []; if (title) lines.push('── ' + title + ' ──'); const cols = this._columns; const dr = this._rows.slice(0, maxRows || 10); if (cols.length === 0 || dr.length === 0) { lines.push('(empty table)'); lines.push(this._rows.length + ' rows × ' + cols.length + ' columns'); return lines.join('\\n'); } const w = {}; for (const c of cols) { w[c] = c.length; for (const r of dr) { const s = r[c] == null ? 'nil' : String(r[c]); w[c] = Math.max(w[c], s.length); } w[c] = Math.min(w[c], 30); } lines.push(cols.map(c => c.padEnd(w[c])).join(' │ ')); lines.push(cols.map(c => '─'.repeat(w[c])).join('─┼─')); for (const r of dr) { lines.push(cols.map(c => { const s = r[c] == null ? 'nil' : String(r[c]); return s.slice(0, 30).padEnd(w[c]); }).join(' │ ')); } if (this._rows.length > (maxRows || 10)) lines.push('... ' + (this._rows.length - (maxRows || 10)) + ' more rows'); lines.push(this._rows.length + ' rows × ' + cols.length + ' columns'); return lines.join('\\n'); } };`,
144
+
145
+ // ── Table operations ────────────────────────────────
146
+ table_where: `function table_where(table, pred) { return Table(table._rows.filter(pred), table._columns); }`,
147
+ table_select: `function table_select(table, ...args) { let cols; if (args.length === 1 && args[0] && args[0].__exclude) { const ex = new Set(Array.isArray(args[0].__exclude) ? args[0].__exclude : [args[0].__exclude]); cols = table._columns.filter(c => !ex.has(c)); } else { cols = args.filter(a => typeof a === 'string'); } const rows = table._rows.map(r => { const row = {}; for (const c of cols) row[c] = r[c]; return row; }); return Table(rows, cols); }`,
148
+ table_derive: `function table_derive(table, derivations) { const nc = [...table._columns]; for (const k of Object.keys(derivations)) { if (!nc.includes(k)) nc.push(k); } const rows = table._rows.map(r => { const row = { ...r }; for (const [k, fn] of Object.entries(derivations)) { row[k] = typeof fn === 'function' ? fn(r) : fn; } return row; }); return Table(rows, nc); }`,
149
+ table_group_by: `function table_group_by(table, keyFn) { const groups = new Map(); for (const row of table._rows) { const key = typeof keyFn === 'function' ? keyFn(row) : row[keyFn]; const ks = String(key); if (!groups.has(ks)) groups.set(ks, { key, rows: [] }); groups.get(ks).rows.push(row); } return { __grouped: true, groups, columns: table._columns }; }`,
150
+ table_agg: `function table_agg(grouped, aggregations) { if (!grouped || !grouped.__grouped) throw new Error('agg() must be called after group_by()'); const rows = []; for (const [, { key, rows: gr }] of grouped.groups) { const row = typeof key === 'object' && key !== null ? { ...key } : { _group: key }; for (const [n, fn] of Object.entries(aggregations)) { row[n] = fn(gr); } rows.push(row); } return Table(rows, rows.length > 0 ? Object.keys(rows[0]) : []); }`,
151
+ table_sort_by: `function table_sort_by(table, keyFn, opts) { const desc = opts && opts.desc; const rows = [...table._rows].sort((a, b) => { const ka = typeof keyFn === 'function' ? keyFn(a) : a[keyFn]; const kb = typeof keyFn === 'function' ? keyFn(b) : b[keyFn]; let c = ka < kb ? -1 : ka > kb ? 1 : 0; return desc ? -c : c; }); return Table(rows, table._columns); }`,
152
+ table_limit: `function table_limit(table, n) { return Table(table._rows.slice(0, n), table._columns); }`,
153
+ table_join: `function table_join(table, other, opts) { const { left, right, how } = opts || {}; if (!left || !right) throw new Error('join() requires left and right key functions'); const rows = []; const ri = new Map(); for (const r of other._rows) { const k = typeof right === 'function' ? right(r) : r[right]; const ks = String(k); if (!ri.has(ks)) ri.set(ks, []); ri.get(ks).push(r); } const cc = [...new Set([...table._columns, ...other._columns])]; for (const lr of table._rows) { const k = typeof left === 'function' ? left(lr) : lr[left]; const ms = ri.get(String(k)) || []; if (ms.length > 0) { for (const rr of ms) rows.push({ ...lr, ...rr }); } else if (how === 'left' || how === 'outer') { const row = { ...lr }; for (const c of other._columns) { if (!(c in row)) row[c] = null; } rows.push(row); } } return Table(rows, cc); }`,
154
+ table_pivot: `function table_pivot(table, opts) { const { index, columns: colFn, values: valFn } = opts || {}; const pm = new Map(); const ac = new Set(); for (const row of table._rows) { const ik = typeof index === 'function' ? index(row) : row[index]; const col = typeof colFn === 'function' ? colFn(row) : row[colFn]; const val = typeof valFn === 'function' ? valFn(row) : row[valFn]; const ks = String(ik); if (!pm.has(ks)) pm.set(ks, { _index: ik }); pm.get(ks)[String(col)] = val; ac.add(String(col)); } return Table([...pm.values()], ['_index', ...ac]); }`,
155
+ table_unpivot: `function table_unpivot(table, opts) { const { id, columns: uc } = opts || {}; const cn = uc.filter(c => typeof c === 'string'); const rows = []; for (const row of table._rows) { const iv = typeof id === 'function' ? id(row) : row[id]; for (const col of cn) rows.push({ id: iv, variable: col, value: row[col] }); } return Table(rows, ['id', 'variable', 'value']); }`,
156
+ table_explode: `function table_explode(table, colFn) { const rows = []; for (const row of table._rows) { const arr = typeof colFn === 'function' ? colFn(row) : row[colFn]; if (Array.isArray(arr)) { const cn = typeof colFn === 'string' ? colFn : null; for (const val of arr) { const r = { ...row }; if (cn) r[cn] = val; rows.push(r); } } else { rows.push({ ...row }); } } return Table(rows, table._columns); }`,
157
+ table_union: `function table_union(table, other) { return Table([...table._rows, ...other._rows], [...new Set([...table._columns, ...other._columns])]); }`,
158
+ 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); }`,
159
+ 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); }`,
160
+
161
+ // ── Aggregation helpers ─────────────────────────────
162
+ agg_sum: `function agg_sum(fn) { return (rows) => rows.reduce((a, r) => a + (typeof fn === 'function' ? fn(r) : r[fn]), 0); }`,
163
+ agg_count: `function agg_count(fn) { if (!fn) return (rows) => rows.length; return (rows) => rows.filter(fn).length; }`,
164
+ agg_mean: `function agg_mean(fn) { return (rows) => { if (rows.length === 0) return 0; return rows.reduce((a, r) => a + (typeof fn === 'function' ? fn(r) : r[fn]), 0) / rows.length; }; }`,
165
+ agg_median: `function agg_median(fn) { return (rows) => { if (rows.length === 0) return 0; const vs = rows.map(r => typeof fn === 'function' ? fn(r) : r[fn]).sort((a, b) => a - b); const m = Math.floor(vs.length / 2); return vs.length % 2 !== 0 ? vs[m] : (vs[m - 1] + vs[m]) / 2; }; }`,
166
+ agg_min: `function agg_min(fn) { return (rows) => rows.length === 0 ? null : Math.min(...rows.map(r => typeof fn === 'function' ? fn(r) : r[fn])); }`,
167
+ agg_max: `function agg_max(fn) { return (rows) => rows.length === 0 ? null : Math.max(...rows.map(r => typeof fn === 'function' ? fn(r) : r[fn])); }`,
168
+
169
+ // ── Data exploration ────────────────────────────────
170
+ peek: `function peek(table, opts) { const o = typeof opts === 'object' ? opts : {}; console.log(table._format ? table._format(o.n || 10, o.title) : String(table)); return table; }`,
171
+ describe: `function describe(table) { const stats = []; for (const col of table._columns) { const vals = table._rows.map(r => r[col]).filter(v => v != null); const st = { Column: col, Type: 'Unknown', 'Non-Null': vals.length }; if (vals.length > 0) { const s = vals[0]; if (typeof s === 'number') { st.Type = Number.isInteger(s) ? 'Int' : 'Float'; st.Mean = vals.reduce((a, b) => a + b, 0) / vals.length; st.Min = Math.min(...vals); st.Max = Math.max(...vals); } else if (typeof s === 'string') { st.Type = 'String'; st.Unique = new Set(vals).size; } else if (typeof s === 'boolean') { st.Type = 'Bool'; } } stats.push(st); } const dt = Table(stats); console.log(dt._format(100, 'describe()')); return dt; }`,
172
+ schema_of: `function schema_of(table) { const sc = {}; if (table._rows.length === 0) { for (const c of table._columns) sc[c] = 'Unknown'; } else { const s = table._rows[0]; for (const c of table._columns) { const v = s[c]; if (v == null) sc[c] = 'Nil'; else if (typeof v === 'number') sc[c] = Number.isInteger(v) ? 'Int' : 'Float'; else if (typeof v === 'string') sc[c] = 'String'; else if (typeof v === 'boolean') sc[c] = 'Bool'; else if (Array.isArray(v)) sc[c] = 'Array'; else sc[c] = 'Object'; } } console.log('Schema:'); for (const [c, t] of Object.entries(sc)) console.log(' ' + c + ': ' + t); return sc; }`,
173
+
174
+ // ── Data cleaning ───────────────────────────────────
175
+ cast: `function cast(table, colFn, targetType) { const cn = typeof colFn === 'string' ? colFn : null; const rows = table._rows.map(r => { const row = { ...r }; const k = cn; if (k && k in row) { const v = row[k]; if (targetType === 'Int') row[k] = parseInt(v, 10) || 0; else if (targetType === 'Float') row[k] = parseFloat(v) || 0; else if (targetType === 'String') row[k] = String(v); else if (targetType === 'Bool') row[k] = Boolean(v); } return row; }); return Table(rows, table._columns); }`,
176
+ drop_nil: `function drop_nil(table, colFn) { const cn = typeof colFn === 'string' ? colFn : null; const rows = table._rows.filter(r => { const v = cn ? r[cn] : colFn(r); return v != null; }); return Table(rows, table._columns); }`,
177
+ fill_nil: `function fill_nil(table, colFn, defaultValue) { const cn = typeof colFn === 'string' ? colFn : null; const rows = table._rows.map(r => { const row = { ...r }; if (cn && (row[cn] == null)) row[cn] = defaultValue; return row; }); return Table(rows, table._columns); }`,
178
+ filter_ok: `function filter_ok(arr) { return arr.filter(r => r && r.__tag === 'Ok').map(r => r.value); }`,
179
+ filter_err: `function filter_err(arr) { return arr.filter(r => r && r.__tag === 'Err').map(r => r.error); }`,
180
+
181
+ // ── I/O functions ───────────────────────────────────
182
+ read: `async function read(sourceOrDb, queryOrOpts, opts) {
183
+ if (sourceOrDb && typeof sourceOrDb === 'object' && sourceOrDb.query) { const result = await sourceOrDb.query(queryOrOpts); return Table(result); }
184
+ const source = sourceOrDb;
185
+ if (typeof source !== 'string') throw new Error('read() expects a file path or URL string');
186
+ const options = typeof queryOrOpts === 'object' ? queryOrOpts : (opts || {});
187
+ if (source.startsWith('http://') || source.startsWith('https://')) {
188
+ const response = await fetch(source);
189
+ if (!response.ok) throw new Error('HTTP ' + response.status + ': ' + response.statusText);
190
+ const ct = response.headers.get('content-type') || '';
191
+ if (ct.includes('json')) { const data = await response.json(); if (Array.isArray(data)) return Table(data); return data; }
192
+ const text = await response.text();
193
+ if (source.endsWith('.csv')) return __parseCSV(text, options);
194
+ if (source.endsWith('.jsonl') || source.endsWith('.ndjson')) return __parseJSONL(text);
195
+ try { const data = JSON.parse(text); if (Array.isArray(data)) return Table(data); return data; } catch { return __parseCSV(text, options); }
196
+ }
197
+ const fs = await import('fs'); const path = await import('path');
198
+ const ext = path.extname(source).toLowerCase(); const text = fs.readFileSync(source, 'utf-8');
199
+ if (ext === '.csv') return __parseCSV(text, options);
200
+ if (ext === '.tsv') return __parseCSV(text, { ...options, delimiter: '\\t' });
201
+ if (ext === '.json') { const data = JSON.parse(text); if (Array.isArray(data)) return Table(data); return data; }
202
+ if (ext === '.jsonl' || ext === '.ndjson') return __parseJSONL(text);
203
+ try { const d = JSON.parse(text); if (Array.isArray(d)) return Table(d); return d; } catch { return __parseCSV(text, options); }
204
+ }`,
205
+ write: `async function write(data, destination, opts) {
206
+ const fs = await import('fs'); const path = await import('path');
207
+ const ext = path.extname(destination).toLowerCase();
208
+ const isTable = data && data._rows && data._columns;
209
+ const td = isTable ? data : (Array.isArray(data) ? Table(data) : null);
210
+ let content;
211
+ if (ext === '.csv' || ext === '.tsv') { if (!td) throw new Error('write() to CSV requires table/array data'); const delim = ext === '.tsv' ? '\\t' : ','; const cols = td._columns; const lines = [cols.join(delim)]; for (const row of td._rows) { lines.push(cols.map(c => { const v = row[c]; if (v == null) return ''; const s = String(v); return (s.includes(delim) || s.includes('"') || s.includes('\\n')) ? '"' + s.replace(/"/g, '""') + '"' : s; }).join(delim)); } content = lines.join('\\n'); }
212
+ else if (ext === '.jsonl' || ext === '.ndjson') { if (!td) throw new Error('write() to JSONL requires table/array data'); content = td._rows.map(r => JSON.stringify(r)).join('\\n'); }
213
+ else { content = JSON.stringify(isTable ? data._rows : data, null, 2); }
214
+ if (opts && opts.append) fs.appendFileSync(destination, content + '\\n', 'utf-8');
215
+ else fs.writeFileSync(destination, content, 'utf-8');
216
+ }`,
217
+
218
+ // ── CSV/JSONL parsing helpers ───────────────────────
219
+ __parseCSV: `function __parseCSV(text, opts) {
220
+ const delim = (opts && opts.delimiter) || ','; const hasHeader = !opts || opts.header !== false;
221
+ const lines = text.split('\\n').filter(l => l.trim());
222
+ if (lines.length === 0) return Table([]);
223
+ 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; };
224
+ 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; }
225
+ 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 (/^-?\\d+$/.test(v)) v = parseInt(v, 10); else if (/^-?\\d*\\.\\d+$/.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); }
226
+ return Table(rows, headers);
227
+ }`,
228
+ __parseJSONL: `function __parseJSONL(text) { return Table(text.split('\\n').filter(l => l.trim()).map(l => JSON.parse(l))); }`,
229
+
230
+ // ── Table operation aliases (short names for pipe-friendly use) ──
231
+ where: `function where(tableOrArr, pred) { if (tableOrArr && tableOrArr._rows) return table_where(tableOrArr, pred); return tableOrArr.filter(pred); }`,
232
+ select: `function select(table, ...args) { return table_select(table, ...args); }`,
233
+ derive: `function derive(table, derivations) { return table_derive(table, derivations); }`,
234
+ agg: `function agg(grouped, aggregations) { return table_agg(grouped, aggregations); }`,
235
+ sort_by: `function sort_by(table, keyFn, opts) { return table_sort_by(table, keyFn, opts); }`,
236
+ limit: `function limit(table, n) { return table_limit(table, n); }`,
237
+ pivot: `function pivot(table, opts) { return table_pivot(table, opts); }`,
238
+ unpivot: `function unpivot(table, opts) { return table_unpivot(table, opts); }`,
239
+ explode: `function explode(table, colFn) { return table_explode(table, colFn); }`,
240
+ union: `function union(a, b) { if (a && a._rows) return table_union(a, b); return [...new Set([...a, ...b])]; }`,
241
+ drop_duplicates: `function drop_duplicates(table, opts) { return table_drop_duplicates(table, opts); }`,
242
+ rename: `function rename(table, oldName, newName) { return table_rename(table, oldName, newName); }`,
243
+ 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); }`,
244
+ 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); }`,
245
+
246
+ // ── Strings (new) ──────────────────────────────────────
247
+ index_of: `function index_of(s, sub) { const i = s.indexOf(sub); return i === -1 ? null : i; }`,
248
+ last_index_of: `function last_index_of(s, sub) { const i = s.lastIndexOf(sub); return i === -1 ? null : i; }`,
249
+ count_of: `function count_of(s, sub) { if (!sub) return 0; let c = 0, i = 0; while ((i = s.indexOf(sub, i)) !== -1) { c++; i += sub.length; } return c; }`,
250
+ reverse_str: `function reverse_str(s) { return [...s].reverse().join(''); }`,
251
+ substr: `function substr(s, start, end) { return end === undefined ? s.slice(start) : s.slice(start, end); }`,
252
+ is_empty: `function is_empty(v) { if (v == null) return true; if (typeof v === 'string' || Array.isArray(v)) return v.length === 0; if (typeof v === 'object') return Object.keys(v).length === 0; return false; }`,
253
+ kebab_case: `function kebab_case(s) { return s.replace(/[-\\s]+/g, '-').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase().replace(/^-/, ''); }`,
254
+ center: `function center(s, n, fill) { if (s.length >= n) return s; const f = fill || ' '; const total = n - s.length; const left = Math.floor(total / 2); const right = total - left; return f.repeat(Math.ceil(left / f.length)).slice(0, left) + s + f.repeat(Math.ceil(right / f.length)).slice(0, right); }`,
255
+
256
+ // ── Collections (new) ──────────────────────────────────
257
+ zip_with: `function zip_with(a, b, fn) { const m = Math.min(a.length, b.length); const r = []; for (let i = 0; i < m; i++) r.push(fn(a[i], b[i])); return r; }`,
258
+ frequencies: `function frequencies(arr) { const r = {}; for (const v of arr) { const k = String(v); r[k] = (r[k] || 0) + 1; } return r; }`,
259
+ scan: `function scan(arr, fn, init) { const r = []; let acc = init; for (const v of arr) { acc = fn(acc, v); r.push(acc); } return r; }`,
260
+ min_by: `function min_by(arr, fn) { if (arr.length === 0) return null; let best = arr[0], bestK = fn(arr[0]); for (let i = 1; i < arr.length; i++) { const k = fn(arr[i]); if (k < bestK) { best = arr[i]; bestK = k; } } return best; }`,
261
+ max_by: `function max_by(arr, fn) { if (arr.length === 0) return null; let best = arr[0], bestK = fn(arr[0]); for (let i = 1; i < arr.length; i++) { const k = fn(arr[i]); if (k > bestK) { best = arr[i]; bestK = k; } } return best; }`,
262
+ sum_by: `function sum_by(arr, fn) { let s = 0; for (const v of arr) s += fn(v); return s; }`,
263
+ product: `function product(arr) { return arr.reduce((a, b) => a * b, 1); }`,
264
+ from_entries: `function from_entries(pairs) { return Object.fromEntries(pairs); }`,
265
+ has_key: `function has_key(obj, key) { return obj != null && Object.prototype.hasOwnProperty.call(obj, key); }`,
266
+ get: `function get(obj, path, def) { const keys = Array.isArray(path) ? path : String(path).split('.'); let cur = obj; for (const k of keys) { if (cur == null || typeof cur !== 'object') return def !== undefined ? def : null; cur = cur[k]; } return cur !== undefined ? cur : (def !== undefined ? def : null); }`,
267
+ pick: `function pick(obj, ks) { const r = {}; for (const k of ks) { if (k in obj) r[k] = obj[k]; } return r; }`,
268
+ omit: `function omit(obj, ks) { const s = new Set(ks); const r = {}; for (const k of Object.keys(obj)) { if (!s.has(k)) r[k] = obj[k]; } return r; }`,
269
+ map_values: `function map_values(obj, fn) { const r = {}; for (const [k, v] of Object.entries(obj)) r[k] = fn(v, k); return r; }`,
270
+ sliding_window: `function sliding_window(arr, n) { if (n <= 0 || n > arr.length) return []; const r = []; for (let i = 0; i <= arr.length - n; i++) r.push(arr.slice(i, i + n)); return r; }`,
271
+
272
+ // ── JSON (new) ─────────────────────────────────────────
273
+ json_parse: `function json_parse(s) { try { return Ok(JSON.parse(s)); } catch (e) { return Err(e.message); } }`,
274
+ json_stringify: `function json_stringify(v) { return JSON.stringify(v); }`,
275
+ json_pretty: `function json_pretty(v) { return JSON.stringify(v, null, 2); }`,
276
+
277
+ // ── Functional (new) ───────────────────────────────────
278
+ compose: `function compose(...fns) { return (x) => fns.reduceRight((v, fn) => fn(v), x); }`,
279
+ pipe_fn: `function pipe_fn(...fns) { return (x) => fns.reduce((v, fn) => fn(v), x); }`,
280
+ identity: `function identity(x) { return x; }`,
281
+ memoize: `function memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn.apply(this, args); cache.set(key, result); return result; }; }`,
282
+ debounce: `function debounce(fn, ms) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), ms); }; }`,
283
+ throttle: `function throttle(fn, ms) { let last = 0; return function(...args) { const now = Date.now(); if (now - last >= ms) { last = now; return fn.apply(this, args); } }; }`,
284
+ once: `function once(fn) { let called = false, result; return function(...args) { if (!called) { called = true; result = fn.apply(this, args); } return result; }; }`,
285
+ negate: `function negate(fn) { return function(...args) { return !fn.apply(this, args); }; }`,
286
+
287
+ // ── Error Handling (new) ───────────────────────────────
288
+ try_fn: `function try_fn(fn) { try { return Ok(fn()); } catch (e) { return Err(e instanceof Error ? e.message : String(e)); } }`,
289
+ try_async: `async function try_async(fn) { try { return Ok(await fn()); } catch (e) { return Err(e instanceof Error ? e.message : String(e)); } }`,
290
+
291
+ // ── Async (new) ────────────────────────────────────────
292
+ parallel: `function parallel(list) { return Promise.all(list); }`,
293
+ timeout: `function timeout(promise, ms) { return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout after ' + ms + 'ms')), ms))]); }`,
294
+ retry: `async function retry(fn, opts) { const o = opts || {}; const times = o.times || 3; const delay = o.delay || 100; const backoff = o.backoff || 1; let lastErr; for (let i = 0; i < times; i++) { try { return await fn(); } catch (e) { lastErr = e; if (i < times - 1) await new Promise(r => setTimeout(r, delay * Math.pow(backoff, i))); } } throw lastErr; }`,
295
+
296
+ // ── Encoding (new) ─────────────────────────────────────
297
+ base64_encode: `function base64_encode(s) { return typeof btoa === 'function' ? btoa(unescape(encodeURIComponent(s))) : Buffer.from(s, 'utf-8').toString('base64'); }`,
298
+ base64_decode: `function base64_decode(s) { return typeof atob === 'function' ? decodeURIComponent(escape(atob(s))) : Buffer.from(s, 'base64').toString('utf-8'); }`,
299
+ url_encode: `function url_encode(s) { return encodeURIComponent(s); }`,
300
+ url_decode: `function url_decode(s) { return decodeURIComponent(s); }`,
301
+
302
+ // ── Math (new) ─────────────────────────────────────────
303
+ hypot: `function hypot(a, b) { return Math.hypot(a, b); }`,
304
+ lerp: `function lerp(a, b, t) { return a + (b - a) * t; }`,
305
+ divmod: `function divmod(a, b) { return [Math.floor(a / b), a % b]; }`,
306
+ avg: `function avg(arr) { return arr.length === 0 ? 0 : arr.reduce((a, b) => a + b, 0) / arr.length; }`,
307
+
308
+ // ── Date/Time (new) ────────────────────────────────────
309
+ now: `function now() { return Date.now(); }`,
310
+ now_iso: `function now_iso() { return new Date().toISOString(); }`,
311
+ date_parse: `function date_parse(s) { const d = new Date(s); return isNaN(d.getTime()) ? Err('Invalid date: ' + s) : Ok(d); }`,
312
+ date_format: `function date_format(d, fmt) { if (typeof d === 'number') d = new Date(d); if (fmt === 'iso') return d.toISOString(); if (fmt === 'date') return d.toISOString().slice(0, 10); if (fmt === 'time') return d.toTimeString().slice(0, 8); if (fmt === 'datetime') return d.toISOString().slice(0, 10) + ' ' + d.toTimeString().slice(0, 8); return fmt.replace('YYYY', String(d.getFullYear())).replace('MM', String(d.getMonth() + 1).padStart(2, '0')).replace('DD', String(d.getDate()).padStart(2, '0')).replace('HH', String(d.getHours()).padStart(2, '0')).replace('mm', String(d.getMinutes()).padStart(2, '0')).replace('ss', String(d.getSeconds()).padStart(2, '0')); }`,
313
+ date_add: `function date_add(d, amount, unit) { if (typeof d === 'number') d = new Date(d); const r = new Date(d.getTime()); if (unit === 'years') r.setFullYear(r.getFullYear() + amount); else if (unit === 'months') r.setMonth(r.getMonth() + amount); else if (unit === 'days') r.setDate(r.getDate() + amount); else if (unit === 'hours') r.setHours(r.getHours() + amount); else if (unit === 'minutes') r.setMinutes(r.getMinutes() + amount); else if (unit === 'seconds') r.setSeconds(r.getSeconds() + amount); return r; }`,
314
+ date_diff: `function date_diff(d1, d2, unit) { if (typeof d1 === 'number') d1 = new Date(d1); if (typeof d2 === 'number') d2 = new Date(d2); const ms = d2.getTime() - d1.getTime(); if (unit === 'seconds') return Math.floor(ms / 1000); if (unit === 'minutes') return Math.floor(ms / 60000); if (unit === 'hours') return Math.floor(ms / 3600000); if (unit === 'days') return Math.floor(ms / 86400000); if (unit === 'months') return (d2.getFullYear() - d1.getFullYear()) * 12 + (d2.getMonth() - d1.getMonth()); if (unit === 'years') return d2.getFullYear() - d1.getFullYear(); return ms; }`,
315
+ date_from: `function date_from(parts) { return new Date(parts.year || 0, (parts.month || 1) - 1, parts.day || 1, parts.hour || 0, parts.minute || 0, parts.second || 0); }`,
316
+ 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; }`,
317
+ 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'); }`,
318
+
319
+ // ── Regex ──────────────────────────────────────────────
320
+ regex_test: `function regex_test(s, pattern, flags) { return new RegExp(pattern, flags).test(s); }`,
321
+ regex_match: `function regex_match(s, pattern, flags) { const m = s.match(new RegExp(pattern, flags)); if (!m) return Err('No match'); return Ok({ match: m[0], index: m.index, groups: m.slice(1) }); }`,
322
+ regex_find_all: `function regex_find_all(s, pattern, flags) { const re = new RegExp(pattern, (flags || '') + (flags && flags.includes('g') ? '' : 'g')); const results = []; let m; while ((m = re.exec(s)) !== null) { results.push({ match: m[0], index: m.index, groups: m.slice(1) }); } return results; }`,
323
+ regex_replace: `function regex_replace(s, pattern, replacement, flags) { return s.replace(new RegExp(pattern, flags || 'g'), replacement); }`,
324
+ regex_split: `function regex_split(s, pattern, flags) { return s.split(new RegExp(pattern, flags)); }`,
325
+ regex_capture: `function regex_capture(s, pattern, flags) { const m = s.match(new RegExp(pattern, flags)); if (!m) return Err('No match'); if (!m.groups) return Err('No named groups'); return Ok(m.groups); }`,
326
+
327
+ // ── Validation ─────────────────────────────────────────
328
+ is_email: `function is_email(s) { return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(s); }`,
329
+ is_url: `function is_url(s) { try { new URL(s); return true; } catch { return false; } }`,
330
+ is_numeric: `function is_numeric(s) { return typeof s === 'string' && s.length > 0 && !isNaN(Number(s)); }`,
331
+ is_alpha: `function is_alpha(s) { return /^[a-zA-Z]+$/.test(s); }`,
332
+ is_alphanumeric: `function is_alphanumeric(s) { return /^[a-zA-Z0-9]+$/.test(s); }`,
333
+ is_uuid: `function is_uuid(s) { return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s); }`,
334
+ is_hex: `function is_hex(s) { return /^[0-9a-fA-F]+$/.test(s); }`,
335
+
336
+ // ── URL & UUID ─────────────────────────────────────────
337
+ uuid: `function uuid() { return typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); }`,
338
+ parse_url: `function parse_url(s) { try { const u = new URL(s); return Ok({ protocol: u.protocol.replace(':', ''), host: u.host, pathname: u.pathname, search: u.search, hash: u.hash }); } catch (e) { return Err('Invalid URL: ' + s); } }`,
339
+ build_url: `function build_url(parts) { let url = (parts.protocol || 'https') + '://' + (parts.host || ''); url += parts.pathname || '/'; if (parts.search) url += (parts.search.startsWith('?') ? '' : '?') + parts.search; if (parts.hash) url += (parts.hash.startsWith('#') ? '' : '#') + parts.hash; return url; }`,
340
+ parse_query: `function parse_query(s) { const r = {}; const qs = s.startsWith('?') ? s.slice(1) : s; if (!qs) return r; for (const pair of qs.split('&')) { const [k, ...v] = pair.split('='); r[decodeURIComponent(k)] = decodeURIComponent(v.join('=')); } return r; }`,
341
+ build_query: `function build_query(obj) { return Object.entries(obj).map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&'); }`,
342
+
343
+ // ── Set Operations ─────────────────────────────────────
344
+ intersection: `function intersection(a, b) { const s = new Set(b); return a.filter(x => s.has(x)); }`,
345
+ difference: `function difference(a, b) { const s = new Set(b); return a.filter(x => !s.has(x)); }`,
346
+ symmetric_difference: `function symmetric_difference(a, b) { const sa = new Set(a); const sb = new Set(b); return [...a.filter(x => !sb.has(x)), ...b.filter(x => !sa.has(x))]; }`,
347
+ is_subset: `function is_subset(a, b) { const s = new Set(b); return a.every(x => s.has(x)); }`,
348
+ is_superset: `function is_superset(a, b) { const s = new Set(a); return b.every(x => s.has(x)); }`,
349
+
350
+ // ── Statistics ─────────────────────────────────────────
351
+ mode: `function mode(arr) { if (arr.length === 0) return null; const freq = {}; let maxF = 0, result = arr[0]; for (const v of arr) { const k = String(v); freq[k] = (freq[k] || 0) + 1; if (freq[k] > maxF) { maxF = freq[k]; result = v; } } return result; }`,
352
+ stdev: `function stdev(arr) { if (arr.length === 0) return 0; const m = arr.reduce((a, b) => a + b, 0) / arr.length; return Math.sqrt(arr.reduce((s, v) => s + (v - m) * (v - m), 0) / arr.length); }`,
353
+ variance: `function variance(arr) { if (arr.length === 0) return 0; const m = arr.reduce((a, b) => a + b, 0) / arr.length; return arr.reduce((s, v) => s + (v - m) * (v - m), 0) / arr.length; }`,
354
+ percentile: `function percentile(arr, p) { if (arr.length === 0) return null; const s = [...arr].sort((a, b) => a - b); const i = (p / 100) * (s.length - 1); const lo = Math.floor(i); const hi = Math.ceil(i); if (lo === hi) return s[lo]; return s[lo] + (s[hi] - s[lo]) * (i - lo); }`,
355
+
356
+ // ── Text Utilities ─────────────────────────────────────
357
+ truncate: `function truncate(s, n, suffix) { const sf = suffix !== undefined ? suffix : '...'; return s.length <= n ? s : s.slice(0, n - sf.length) + sf; }`,
358
+ word_wrap: `function word_wrap(s, width) { const ws = s.split(' '); const lines = []; let line = ''; for (const w of ws) { if (line && (line.length + 1 + w.length) > width) { lines.push(line); line = w; } else { line = line ? line + ' ' + w : w; } } if (line) lines.push(line); return lines.join('\\n'); }`,
359
+ dedent: `function dedent(s) { const lines = s.split('\\n'); const nonEmpty = lines.filter(l => l.trim().length > 0); if (nonEmpty.length === 0) return s; const indent = Math.min(...nonEmpty.map(l => l.match(/^(\\s*)/)[1].length)); return lines.map(l => l.slice(indent)).join('\\n'); }`,
360
+ indent_str: `function indent_str(s, n, ch) { const prefix = (ch || ' ').repeat(n); return s.split('\\n').map(l => prefix + l).join('\\n'); }`,
361
+ slugify: `function slugify(s) { return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); }`,
362
+ escape_html: `function escape_html(s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }`,
363
+ unescape_html: `function unescape_html(s) { return s.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'"); }`,
364
+
365
+ // ── Number Formatting ──────────────────────────────────
366
+ format_number: `function format_number(n, opts) { const o = opts || {}; const sep = o.separator || ','; const dec = o.decimals; let s = dec !== undefined ? n.toFixed(dec) : String(n); const parts = s.split('.'); parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, sep); return parts.join('.'); }`,
367
+ to_hex: `function to_hex(n) { return Math.trunc(n).toString(16); }`,
368
+ to_binary: `function to_binary(n) { return Math.trunc(n).toString(2); }`,
369
+ to_octal: `function to_octal(n) { return Math.trunc(n).toString(8); }`,
370
+ to_fixed: `function to_fixed(n, decimals) { return Number(n.toFixed(decimals)); }`,
371
+
372
+ // ── Itertools ──────────────────────────────────────────
373
+ pairwise: `function pairwise(arr) { const r = []; for (let i = 0; i < arr.length - 1; i++) r.push([arr[i], arr[i + 1]]); return r; }`,
374
+ combinations: `function combinations(arr, r) { const result = []; const combo = []; function gen(start, depth) { if (depth === r) { result.push([...combo]); return; } for (let i = start; i < arr.length; i++) { combo.push(arr[i]); gen(i + 1, depth + 1); combo.pop(); } } gen(0, 0); return result; }`,
375
+ permutations: `function permutations(arr, r) { const n = r === undefined ? arr.length : r; const result = []; const perm = []; const used = new Array(arr.length).fill(false); function gen() { if (perm.length === n) { result.push([...perm]); return; } for (let i = 0; i < arr.length; i++) { if (!used[i]) { used[i] = true; perm.push(arr[i]); gen(); perm.pop(); used[i] = false; } } } gen(); return result; }`,
376
+ intersperse: `function intersperse(arr, sep) { if (arr.length <= 1) return [...arr]; const r = [arr[0]]; for (let i = 1; i < arr.length; i++) { r.push(sep, arr[i]); } return r; }`,
377
+ interleave: `function interleave(...arrs) { const m = Math.max(...arrs.map(a => a.length)); const r = []; for (let i = 0; i < m; i++) { for (const a of arrs) { if (i < a.length) r.push(a[i]); } } return r; }`,
378
+ repeat_value: `function repeat_value(val, n) { return Array(n).fill(val); }`,
379
+
380
+ // ── Array Utilities ────────────────────────────────────
381
+ binary_search: `function binary_search(arr, target, keyFn) { let lo = 0, hi = arr.length - 1; while (lo <= hi) { const mid = (lo + hi) >> 1; const val = keyFn ? keyFn(arr[mid]) : arr[mid]; if (val === target) return mid; if (val < target) lo = mid + 1; else hi = mid - 1; } return -1; }`,
382
+ is_sorted: `function is_sorted(arr, keyFn) { for (let i = 1; i < arr.length; i++) { const a = keyFn ? keyFn(arr[i - 1]) : arr[i - 1]; const b = keyFn ? keyFn(arr[i]) : arr[i]; if (a > b) return false; } return true; }`,
383
+ compact: `function compact(arr) { return arr.filter(v => v != null); }`,
384
+ rotate: `function rotate(arr, n) { if (arr.length === 0) return []; const k = ((n % arr.length) + arr.length) % arr.length; return [...arr.slice(k), ...arr.slice(0, k)]; }`,
385
+ insert_at: `function insert_at(arr, idx, val) { const r = [...arr]; r.splice(idx, 0, val); return r; }`,
386
+ remove_at: `function remove_at(arr, idx) { const r = [...arr]; r.splice(idx, 1); return r; }`,
387
+ update_at: `function update_at(arr, idx, val) { const r = [...arr]; r[idx] = val; return r; }`,
388
+
389
+ // ── Functional (extended) ──────────────────────────────
390
+ partial: `function partial(fn, ...bound) { return function(...args) { return fn(...bound, ...args); }; }`,
391
+ curry: `function curry(fn, arity) { const n = arity || fn.length; return function curried(...args) { if (args.length >= n) return fn(...args); return function(...more) { return curried(...args, ...more); }; }; }`,
392
+ flip: `function flip(fn) { return function(a, b, ...rest) { return fn(b, a, ...rest); }; }`,
393
+
394
+ // ── Encoding (extended) ────────────────────────────────
395
+ hex_encode: `function hex_encode(s) { let r = ''; for (let i = 0; i < s.length; i++) r += s.charCodeAt(i).toString(16).padStart(2, '0'); return r; }`,
396
+ hex_decode: `function hex_decode(s) { let r = ''; for (let i = 0; i < s.length; i += 2) r += String.fromCharCode(parseInt(s.substr(i, 2), 16)); return r; }`,
397
+
398
+ // ── String (extended) ──────────────────────────────────
399
+ fmt: `function fmt(template, ...args) { let i = 0; return template.replace(/\\{\\}/g, () => i < args.length ? String(args[i++]) : '{}'); }`,
148
400
  };
149
401
 
150
402
  // All known builtin names for matching
151
403
  export const BUILTIN_NAMES = new Set(Object.keys(BUILTIN_FUNCTIONS));
152
404
 
405
+ // Legacy compat: full stdlib as a single string (derived from BUILTIN_FUNCTIONS)
406
+ // Only includes non-internal, non-table functions for backward compat with tests/playground
407
+ const _LEGACY_NAMES = [
408
+ 'print', 'len', 'range', 'enumerate', 'sum', 'sorted', 'reversed', 'zip',
409
+ 'min', 'max', 'type_of', 'filter', 'map', 'find', 'any', 'all', 'flat_map',
410
+ 'reduce', 'unique', 'group_by', 'chunk', 'flatten', 'take', 'drop', 'first',
411
+ 'last', 'count', 'partition', 'abs', 'floor', 'ceil', 'round', 'clamp',
412
+ 'sqrt', 'pow', 'random', 'trim', 'split', 'join', 'replace', 'repeat',
413
+ 'keys', 'values', 'entries', 'merge', 'freeze', 'clone', 'sleep',
414
+ 'upper', 'lower', 'contains', 'starts_with', 'ends_with', 'chars', 'words',
415
+ 'lines', 'capitalize', 'title_case', 'snake_case', 'camel_case',
416
+ 'assert_eq', 'assert_ne', 'assert',
417
+ ];
418
+ export const BUILTINS = _LEGACY_NAMES.map(n => BUILTIN_FUNCTIONS[n]).join('\n');
419
+
153
420
  // Build stdlib containing only the functions that are actually used
154
421
  export function buildSelectiveStdlib(usedNames) {
155
422
  const parts = [];
@@ -163,10 +430,10 @@ export function buildSelectiveStdlib(usedNames) {
163
430
 
164
431
  // Full stdlib for runtime (REPL, run command)
165
432
  export function getFullStdlib() {
166
- return `${BUILTINS}\n${RESULT_OPTION}\n${PROPAGATE}`;
433
+ return `${buildSelectiveStdlib(BUILTIN_NAMES)}\n${RESULT_OPTION}\n${PROPAGATE}`;
167
434
  }
168
435
 
169
436
  // Stdlib for client codegen (includes builtins + result/option + propagate)
170
437
  export function getClientStdlib() {
171
- return `${BUILTINS}\n${RESULT_OPTION}\n${PROPAGATE}`;
438
+ return `${buildSelectiveStdlib(BUILTIN_NAMES)}\n${RESULT_OPTION}\n${PROPAGATE}`;
172
439
  }
@@ -0,0 +1,93 @@
1
+ // Tova standard library — math functions
2
+
3
+ export const PI = Math.PI;
4
+ export const E = Math.E;
5
+ export const INF = Infinity;
6
+
7
+ export function sin(n) { return Math.sin(n); }
8
+ export function cos(n) { return Math.cos(n); }
9
+ export function tan(n) { return Math.tan(n); }
10
+ export function asin(n) { return Math.asin(n); }
11
+ export function acos(n) { return Math.acos(n); }
12
+ export function atan(n) { return Math.atan(n); }
13
+ export function atan2(y, x) { return Math.atan2(y, x); }
14
+
15
+ export function log(n) { return Math.log(n); }
16
+ export function log2(n) { return Math.log2(n); }
17
+ export function log10(n) { return Math.log10(n); }
18
+ export function exp(n) { return Math.exp(n); }
19
+
20
+ export function sign(n) { return Math.sign(n); }
21
+ export function trunc(n) { return Math.trunc(n); }
22
+ export function is_nan(n) { return Number.isNaN(n); }
23
+ export function is_finite(n) { return Number.isFinite(n); }
24
+ export function is_close(a, b, tol) { return Math.abs(a - b) <= (tol === undefined ? 1e-9 : tol); }
25
+ export function to_radians(deg) { return deg * Math.PI / 180; }
26
+ export function to_degrees(rad) { return rad * 180 / Math.PI; }
27
+
28
+ export function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a; }
29
+ export function lcm(a, b) {
30
+ if (a === 0 && b === 0) return 0;
31
+ let x = Math.abs(a), y = Math.abs(b);
32
+ while (y) { const t = y; y = x % y; x = t; }
33
+ return Math.abs(a * b) / x;
34
+ }
35
+ export function factorial(n) { if (n < 0) return null; if (n <= 1) return 1; let r = 1; for (let i = 2; i <= n; i++) r *= i; return r; }
36
+
37
+ export function hypot(a, b) { return Math.hypot(a, b); }
38
+ export function lerp(a, b, t) { return a + (b - a) * t; }
39
+ export function divmod(a, b) { return [Math.floor(a / b), a % b]; }
40
+ export function avg(arr) { return arr.length === 0 ? 0 : arr.reduce((a, b) => a + b, 0) / arr.length; }
41
+
42
+ // ── Statistics ────────────────────────────────────────────
43
+
44
+ export function mode(arr) {
45
+ if (arr.length === 0) return null;
46
+ const freq = {};
47
+ let maxF = 0, result = arr[0];
48
+ for (const v of arr) {
49
+ const k = String(v);
50
+ freq[k] = (freq[k] || 0) + 1;
51
+ if (freq[k] > maxF) { maxF = freq[k]; result = v; }
52
+ }
53
+ return result;
54
+ }
55
+
56
+ export function stdev(arr) {
57
+ if (arr.length === 0) return 0;
58
+ const m = arr.reduce((a, b) => a + b, 0) / arr.length;
59
+ return Math.sqrt(arr.reduce((s, v) => s + (v - m) * (v - m), 0) / arr.length);
60
+ }
61
+
62
+ export function variance(arr) {
63
+ if (arr.length === 0) return 0;
64
+ const m = arr.reduce((a, b) => a + b, 0) / arr.length;
65
+ return arr.reduce((s, v) => s + (v - m) * (v - m), 0) / arr.length;
66
+ }
67
+
68
+ export function percentile(arr, p) {
69
+ if (arr.length === 0) return null;
70
+ const s = [...arr].sort((a, b) => a - b);
71
+ const i = (p / 100) * (s.length - 1);
72
+ const lo = Math.floor(i);
73
+ const hi = Math.ceil(i);
74
+ if (lo === hi) return s[lo];
75
+ return s[lo] + (s[hi] - s[lo]) * (i - lo);
76
+ }
77
+
78
+ // ── Number Formatting ─────────────────────────────────────
79
+
80
+ export function format_number(n, opts) {
81
+ const o = opts || {};
82
+ const sep = o.separator || ',';
83
+ const dec = o.decimals;
84
+ let s = dec !== undefined ? n.toFixed(dec) : String(n);
85
+ const parts = s.split('.');
86
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, sep);
87
+ return parts.join('.');
88
+ }
89
+
90
+ export function to_hex(n) { return Math.trunc(n).toString(16); }
91
+ export function to_binary(n) { return Math.trunc(n).toString(2); }
92
+ export function to_octal(n) { return Math.trunc(n).toString(8); }
93
+ export function to_fixed(n, decimals) { return Number(n.toFixed(decimals)); }