tova 0.2.9 → 0.3.1
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 +1404 -114
- package/package.json +3 -1
- package/src/analyzer/analyzer.js +882 -695
- package/src/analyzer/client-analyzer.js +191 -0
- package/src/analyzer/server-analyzer.js +467 -0
- package/src/analyzer/types.js +20 -4
- package/src/codegen/base-codegen.js +473 -111
- package/src/codegen/client-codegen.js +109 -46
- package/src/codegen/codegen.js +65 -5
- package/src/codegen/server-codegen.js +297 -38
- package/src/diagnostics/error-codes.js +255 -0
- package/src/diagnostics/formatter.js +150 -28
- package/src/docs/generator.js +390 -0
- package/src/lexer/lexer.js +306 -64
- package/src/lexer/tokens.js +19 -0
- package/src/lsp/server.js +935 -53
- package/src/parser/ast.js +81 -368
- package/src/parser/client-ast.js +138 -0
- package/src/parser/client-parser.js +504 -0
- package/src/parser/parser.js +492 -1056
- package/src/parser/server-ast.js +240 -0
- package/src/parser/server-parser.js +602 -0
- package/src/runtime/array-proto.js +32 -0
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +239 -42
- package/src/stdlib/advanced-collections.js +81 -0
- package/src/stdlib/inline.js +556 -13
- package/src/version.js +1 -1
package/src/stdlib/inline.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
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 = `function Ok(value) { return Object.freeze({ __tag: "Ok", value, map(fn) { return Ok(fn(value)); }, flatMap(fn) { const r = fn(value); if (r && r.__tag) return r; throw new Error("flatMap callback must return Ok/Err"); }, unwrap() { return value; }, unwrapOr(_) { return value; }, expect(_) { return value; }, isOk() { return true; }, isErr() { return false; }, mapErr(_) { return this; }, unwrapErr() { throw new Error("Called unwrapErr on Ok"); }, or(_) { return this; }, and(other) { return other; } }); }
|
|
6
|
-
function Err(error) { return Object.freeze({ __tag: "Err", error, map(_) { return this; }, flatMap(_) { return this; }, unwrap() { throw new Error("Called unwrap on Err: " + (typeof error === "object" ? JSON.stringify(error) : error)); }, unwrapOr(def) { return def; }, expect(msg) { throw new Error(msg); }, isOk() { return false; }, isErr() { return true; }, mapErr(fn) { return Err(fn(error)); }, unwrapErr() { return error; }, or(other) { return other; }, and(_) { return this; } }); }
|
|
7
|
-
function Some(value) { return Object.freeze({ __tag: "Some", value, map(fn) { return Some(fn(value)); }, flatMap(fn) { const r = fn(value); if (r && r.__tag) return r; throw new Error("flatMap callback must return Some/None"); }, unwrap() { return value; }, unwrapOr(_) { return value; }, expect(_) { return value; }, isSome() { return true; }, isNone() { return false; }, or(_) { return this; }, and(other) { return other; }, filter(pred) { return pred(value) ? this : None; } }); }
|
|
8
|
-
const None = Object.freeze({ __tag: "None", map(_) { return None; }, flatMap(_) { 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; } });`;
|
|
5
|
+
export const RESULT_OPTION = `function Ok(value) { return Object.freeze({ __tag: "Ok", value, map(fn) { return Ok(fn(value)); }, flatMap(fn) { const r = fn(value); if (r && r.__tag) return r; throw new Error("flatMap callback must return Ok/Err"); }, andThen(fn) { return this.flatMap(fn); }, unwrap() { return value; }, unwrapOr(_) { return value; }, expect(_) { return value; }, isOk() { return true; }, isErr() { return false; }, mapErr(_) { return this; }, unwrapErr() { throw new Error("Called unwrapErr on Ok"); }, or(_) { return this; }, and(other) { return other; }, context(_) { return this; } }); }
|
|
6
|
+
function Err(error) { return Object.freeze({ __tag: "Err", error, map(_) { return this; }, flatMap(_) { return this; }, andThen(_) { return this; }, unwrap() { throw new Error("Called unwrap on Err: " + (typeof error === "object" ? JSON.stringify(error) : error)); }, unwrapOr(def) { return def; }, expect(msg) { throw new Error(msg); }, isOk() { return false; }, isErr() { return true; }, mapErr(fn) { return Err(fn(error)); }, unwrapErr() { return error; }, or(other) { return other; }, and(_) { return this; }, context(msg) { const inner = typeof error === "object" ? JSON.stringify(error) : String(error); return Err(msg + " \\u2192 caused by: " + inner); } }); }
|
|
7
|
+
function Some(value) { return Object.freeze({ __tag: "Some", value, map(fn) { return Some(fn(value)); }, flatMap(fn) { const r = fn(value); if (r && r.__tag) return r; throw new Error("flatMap callback must return Some/None"); }, andThen(fn) { return this.flatMap(fn); }, unwrap() { return value; }, unwrapOr(_) { return value; }, expect(_) { return value; }, isSome() { return true; }, isNone() { return false; }, or(_) { return this; }, and(other) { return other; }, filter(pred) { return pred(value) ? this : None; } }); }
|
|
8
|
+
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
9
|
|
|
10
10
|
export const PROPAGATE = `function __propagate(val) {
|
|
11
11
|
if (val && val.__tag === "Err") throw { __tova_propagate: true, value: val };
|
|
@@ -25,8 +25,8 @@ export const BUILTIN_FUNCTIONS = {
|
|
|
25
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; }`,
|
|
26
26
|
reversed: `function reversed(a) { return [...a].reverse(); }`,
|
|
27
27
|
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
|
-
min: `function min(a) {
|
|
29
|
-
max: `function max(a) {
|
|
28
|
+
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; }`,
|
|
29
|
+
max: `function max(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; }`,
|
|
30
30
|
type_of: `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'; } }`,
|
|
31
31
|
filter: `function filter(arr, fn) { return arr.filter(fn); }`,
|
|
32
32
|
map: `function map(arr, fn) { return arr.map(fn); }`,
|
|
@@ -80,6 +80,21 @@ export const BUILTIN_FUNCTIONS = {
|
|
|
80
80
|
assert_eq: `function assert_eq(a, b, msg) { if (a !== b) throw new Error(msg || \`Assertion failed: \${JSON.stringify(a)} !== \${JSON.stringify(b)}\`); }`,
|
|
81
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)}\`); }`,
|
|
82
82
|
assert: `function assert(cond, msg) { if (!cond) throw new Error(msg || "Assertion failed"); }`,
|
|
83
|
+
assert_throws: `function assert_throws(fn, expected) {
|
|
84
|
+
try { fn(); } catch (e) {
|
|
85
|
+
if (expected !== undefined) {
|
|
86
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
87
|
+
if (typeof expected === 'string' && !msg.includes(expected)) {
|
|
88
|
+
throw new Error("Expected error containing \\"" + expected + "\\" but got \\"" + msg + "\\"");
|
|
89
|
+
}
|
|
90
|
+
if (expected instanceof RegExp && !expected.test(msg)) {
|
|
91
|
+
throw new Error("Expected error matching " + expected + " but got \\"" + msg + "\\"");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return e;
|
|
95
|
+
}
|
|
96
|
+
throw new Error("Expected function to throw" + (expected ? " \\"" + expected + "\\"" : "") + " but it did not");
|
|
97
|
+
}`,
|
|
83
98
|
|
|
84
99
|
// ── Missing from module files (synced to BUILTIN_FUNCTIONS) ──
|
|
85
100
|
find_index: `function find_index(arr, fn) { const i = arr.findIndex(fn); return i === -1 ? null : i; }`,
|
|
@@ -163,12 +178,12 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
163
178
|
agg_count: `function agg_count(fn) { if (!fn) return (rows) => rows.length; return (rows) => rows.filter(fn).length; }`,
|
|
164
179
|
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
180
|
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
|
|
167
|
-
agg_max: `function agg_max(fn) { return (rows) => rows.length === 0
|
|
181
|
+
agg_min: `function agg_min(fn) { return (rows) => { if (rows.length === 0) return null; let m = typeof fn === 'function' ? fn(rows[0]) : rows[0][fn]; for (let i = 1; i < rows.length; i++) { const v = typeof fn === 'function' ? fn(rows[i]) : rows[i][fn]; if (v < m) m = v; } return m; }; }`,
|
|
182
|
+
agg_max: `function agg_max(fn) { return (rows) => { if (rows.length === 0) return null; let m = typeof fn === 'function' ? fn(rows[0]) : rows[0][fn]; for (let i = 1; i < rows.length; i++) { const v = typeof fn === 'function' ? fn(rows[i]) : rows[i][fn]; if (v > m) m = v; } return m; }; }`,
|
|
168
183
|
|
|
169
184
|
// ── Data exploration ────────────────────────────────
|
|
170
185
|
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;
|
|
186
|
+
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; let mn = vals[0], mx = vals[0]; for (let i = 1; i < vals.length; i++) { if (vals[i] < mn) mn = vals[i]; if (vals[i] > mx) mx = vals[i]; } st.Min = mn; st.Max = mx; } 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
187
|
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
188
|
|
|
174
189
|
// ── Data cleaning ───────────────────────────────────
|
|
@@ -290,6 +305,7 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
290
305
|
|
|
291
306
|
// ── Async (new) ────────────────────────────────────────
|
|
292
307
|
parallel: `function parallel(list) { return Promise.all(list); }`,
|
|
308
|
+
race: `function race(promises) { return Promise.race(promises); }`,
|
|
293
309
|
timeout: `function timeout(promise, ms) { return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout after ' + ms + 'ms')), ms))]); }`,
|
|
294
310
|
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
311
|
|
|
@@ -302,7 +318,7 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
302
318
|
// ── Math (new) ─────────────────────────────────────────
|
|
303
319
|
hypot: `function hypot(a, b) { return Math.hypot(a, b); }`,
|
|
304
320
|
lerp: `function lerp(a, b, t) { return a + (b - a) * t; }`,
|
|
305
|
-
divmod: `function divmod(a, b) {
|
|
321
|
+
divmod: `function divmod(a, b) { const q = Math.floor(a / b); return [q, a - q * b]; }`,
|
|
306
322
|
avg: `function avg(arr) { return arr.length === 0 ? 0 : arr.reduce((a, b) => a + b, 0) / arr.length; }`,
|
|
307
323
|
|
|
308
324
|
// ── Date/Time (new) ────────────────────────────────────
|
|
@@ -374,7 +390,7 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
374
390
|
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
391
|
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
392
|
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; }`,
|
|
393
|
+
interleave: `function interleave(...arrs) { if (arrs.length === 0) return []; 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
394
|
repeat_value: `function repeat_value(val, n) { return Array(n).fill(val); }`,
|
|
379
395
|
|
|
380
396
|
// ── Array Utilities ────────────────────────────────────
|
|
@@ -397,11 +413,534 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
397
413
|
|
|
398
414
|
// ── String (extended) ──────────────────────────────────
|
|
399
415
|
fmt: `function fmt(template, ...args) { let i = 0; return template.replace(/\\{\\}/g, () => i < args.length ? String(args[i++]) : '{}'); }`,
|
|
416
|
+
|
|
417
|
+
// ── Scripting: Environment & CLI ──────────────────────
|
|
418
|
+
env: `function env(key, fallback) { if (key === undefined) return { ...process.env }; const v = process.env[key]; return v !== undefined ? v : (fallback !== undefined ? fallback : null); }`,
|
|
419
|
+
set_env: `function set_env(key, value) { process.env[key] = String(value); }`,
|
|
420
|
+
args: `function args() { return typeof __tova_args !== 'undefined' ? __tova_args : process.argv.slice(2); }`,
|
|
421
|
+
exit: `function exit(code) { process.exit(code !== undefined ? code : 0); }`,
|
|
422
|
+
|
|
423
|
+
// ── Scripting: Filesystem ─────────────────────────────
|
|
424
|
+
exists: `function exists(path) { const fs = require('fs'); return fs.existsSync(path); }`,
|
|
425
|
+
is_dir: `function is_dir(path) { try { return require('fs').statSync(path).isDirectory(); } catch { return false; } }`,
|
|
426
|
+
is_file: `function is_file(path) { try { return require('fs').statSync(path).isFile(); } catch { return false; } }`,
|
|
427
|
+
ls: `function ls(dir, opts) { const fs = require('fs'); const p = require('path'); const d = dir || '.'; const entries = fs.readdirSync(d); if (opts && opts.full) return entries.map(e => p.join(d, e)); return entries; }`,
|
|
428
|
+
glob_files: `function glob_files(pattern, opts) { if (typeof Bun !== 'undefined' && Bun.Glob) { const glob = new Bun.Glob(pattern); const results = [...glob.scanSync(opts && opts.cwd || '.')]; return results; } const fs = require('fs'); if (fs.globSync) return fs.globSync(pattern, opts); return []; }`,
|
|
429
|
+
mkdir: `function mkdir(dir) { try { require('fs').mkdirSync(dir, { recursive: true }); return Ok(dir); } catch (e) { return Err(e.message); } }`,
|
|
430
|
+
rm: `function rm(path, opts) { try { require('fs').rmSync(path, { recursive: !!(opts && opts.recursive), force: !!(opts && opts.force) }); return Ok(path); } catch (e) { return Err(e.message); } }`,
|
|
431
|
+
cp: `function cp(src, dest, opts) { try { const fs = require('fs'); if (opts && opts.recursive) { fs.cpSync(src, dest, { recursive: true }); } else { fs.copyFileSync(src, dest); } return Ok(dest); } catch (e) { return Err(e.message); } }`,
|
|
432
|
+
mv: `function mv(src, dest) { try { require('fs').renameSync(src, dest); return Ok(dest); } catch (e) { return Err(e.message); } }`,
|
|
433
|
+
cwd: `function cwd() { return process.cwd(); }`,
|
|
434
|
+
chdir: `function chdir(dir) { try { process.chdir(dir); return Ok(dir); } catch (e) { return Err(e.message); } }`,
|
|
435
|
+
read_text: `function read_text(path, enc) { try { return Ok(require('fs').readFileSync(path, enc || 'utf-8')); } catch (e) { return Err(e.message); } }`,
|
|
436
|
+
read_bytes: `function read_bytes(path) { try { return Ok(require('fs').readFileSync(path)); } catch (e) { return Err(e.message); } }`,
|
|
437
|
+
write_text: `function write_text(path, content, opts) { try { const fs = require('fs'); if (opts && opts.append) fs.appendFileSync(path, content); else fs.writeFileSync(path, content); return Ok(path); } catch (e) { return Err(e.message); } }`,
|
|
438
|
+
|
|
439
|
+
// ── Scripting: Shell ──────────────────────────────────
|
|
440
|
+
// sh() uses shell:true for convenience (pipes, redirects). For trusted commands only.
|
|
441
|
+
// exec() uses shell:false — safe from injection by default (array args).
|
|
442
|
+
sh: `function sh(cmd, opts) { try { const cp = require('child_process'); const o = opts || {}; const result = cp.spawnSync(cmd, { shell: true, cwd: o.cwd, env: o.env ? { ...process.env, ...o.env } : undefined, timeout: o.timeout, stdio: o.inherit ? 'inherit' : 'pipe', encoding: 'utf-8' }); if (result.error) return Err(result.error.message); return Ok({ stdout: (result.stdout || '').trimEnd(), stderr: (result.stderr || '').trimEnd(), exitCode: result.status }); } catch (e) { return Err(e.message); } }`,
|
|
443
|
+
exec: `function exec(cmd, cmdArgs, opts) { try { const cp = require('child_process'); if (cmdArgs && typeof cmdArgs === 'object' && !Array.isArray(cmdArgs)) { opts = cmdArgs; cmdArgs = []; } const o = opts || {}; const a = cmdArgs || []; const result = cp.spawnSync(cmd, a, { shell: false, cwd: o.cwd, env: o.env ? { ...process.env, ...o.env } : undefined, timeout: o.timeout, stdio: o.inherit ? 'inherit' : 'pipe', encoding: 'utf-8' }); if (result.error) return Err(result.error.message); return Ok({ stdout: (result.stdout || '').trimEnd(), stderr: (result.stderr || '').trimEnd(), exitCode: result.status }); } catch (e) { return Err(e.message); } }`,
|
|
444
|
+
|
|
445
|
+
// ── Scripting: stdin ─────────────────────────────────
|
|
446
|
+
read_stdin: `function read_stdin() { try { return require('fs').readFileSync(0, 'utf-8'); } catch { return ''; } }`,
|
|
447
|
+
read_lines: `function read_lines() { try { return require('fs').readFileSync(0, 'utf-8').split('\\n').filter(l => l.length > 0); } catch { return []; } }`,
|
|
448
|
+
|
|
449
|
+
// ── Scripting: Script path ──────────────────────────
|
|
450
|
+
script_path: `function script_path() { return typeof __tova_filename !== 'undefined' ? __tova_filename : null; }`,
|
|
451
|
+
script_dir: `function script_dir() { return typeof __tova_dirname !== 'undefined' ? __tova_dirname : null; }`,
|
|
452
|
+
|
|
453
|
+
// ── Scripting: Argument parsing ──────────────────────
|
|
454
|
+
parse_args: `function parse_args(argv) { const flags = {}; const positional = []; let i = 0; while (i < argv.length) { const arg = argv[i]; if (arg === '--') { positional.push(...argv.slice(i + 1)); break; } if (arg.startsWith('--')) { const eq = arg.indexOf('='); if (eq !== -1) { flags[arg.slice(2, eq)] = arg.slice(eq + 1); } else if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) { flags[arg.slice(2)] = argv[i + 1]; i++; } else { flags[arg.slice(2)] = true; } } else if (arg.startsWith('-') && arg.length > 1) { for (let j = 1; j < arg.length; j++) flags[arg[j]] = true; } else { positional.push(arg); } i++; } return { flags, positional }; }`,
|
|
455
|
+
|
|
456
|
+
// ── Lazy Iterators / Sequences ──────────────────────
|
|
457
|
+
iter: `function iter(source) { return new Seq(function*() { for (const x of source) yield x; }); }`,
|
|
458
|
+
Seq: `class Seq {
|
|
459
|
+
constructor(gen) { this._gen = gen; }
|
|
460
|
+
filter(fn) { const g = this._gen; return new Seq(function*() { for (const x of g()) if (fn(x)) yield x; }); }
|
|
461
|
+
map(fn) { const g = this._gen; return new Seq(function*() { for (const x of g()) yield fn(x); }); }
|
|
462
|
+
take(n) { const g = this._gen; return new Seq(function*() { let i = 0; for (const x of g()) { if (i++ >= n) return; yield x; } }); }
|
|
463
|
+
drop(n) { const g = this._gen; return new Seq(function*() { let i = 0; for (const x of g()) { if (i++ < n) continue; yield x; } }); }
|
|
464
|
+
zip(other) { const g1 = this._gen; const g2 = other._gen; return new Seq(function*() { const i1 = g1(), i2 = g2(); while (true) { const a = i1.next(), b = i2.next(); if (a.done || b.done) return; yield [a.value, b.value]; } }); }
|
|
465
|
+
flat_map(fn) { const g = this._gen; return new Seq(function*() { for (const x of g()) { const result = fn(x); if (result && result._gen) { for (const y of result._gen()) yield y; } else if (result && result[Symbol.iterator]) { for (const y of result) yield y; } else { yield result; } } }); }
|
|
466
|
+
enumerate() { const g = this._gen; return new Seq(function*() { let i = 0; for (const x of g()) yield [i++, x]; }); }
|
|
467
|
+
collect() { return [...this._gen()]; }
|
|
468
|
+
toArray() { return this.collect(); }
|
|
469
|
+
reduce(fn, init) { let acc = init; for (const x of this._gen()) acc = fn(acc, x); return acc; }
|
|
470
|
+
first() { for (const x of this._gen()) return Some(x); return None; }
|
|
471
|
+
count() { let n = 0; for (const x of this._gen()) n++; return n; }
|
|
472
|
+
forEach(fn) { for (const x of this._gen()) fn(x); }
|
|
473
|
+
any(fn) { for (const x of this._gen()) if (fn(x)) return true; return false; }
|
|
474
|
+
all(fn) { for (const x of this._gen()) if (!fn(x)) return false; return true; }
|
|
475
|
+
find(fn) { for (const x of this._gen()) if (fn(x)) return Some(x); return None; }
|
|
476
|
+
[Symbol.iterator]() { return this._gen(); }
|
|
477
|
+
}`,
|
|
478
|
+
|
|
479
|
+
// ── Scripting: Terminal colors ──────────────────────
|
|
480
|
+
color: `function color(text, name) { if (typeof process !== 'undefined' && (process.env.NO_COLOR || (process.stdout && !process.stdout.isTTY))) return String(text); const codes = { red: '31', green: '32', yellow: '33', blue: '34', magenta: '35', cyan: '36', white: '37', gray: '90' }; const c = codes[name]; return c ? '\\x1b[' + c + 'm' + text + '\\x1b[0m' : String(text); }`,
|
|
481
|
+
bold: `function bold(text) { if (typeof process !== 'undefined' && (process.env.NO_COLOR || (process.stdout && !process.stdout.isTTY))) return String(text); return '\\x1b[1m' + text + '\\x1b[0m'; }`,
|
|
482
|
+
dim: `function dim(text) { if (typeof process !== 'undefined' && (process.env.NO_COLOR || (process.stdout && !process.stdout.isTTY))) return String(text); return '\\x1b[2m' + text + '\\x1b[0m'; }`,
|
|
483
|
+
|
|
484
|
+
// ── Scripting: Signal handling ────────────────────────
|
|
485
|
+
on_signal: `function on_signal(name, callback) { process.on(name, callback); }`,
|
|
486
|
+
|
|
487
|
+
// ── Scripting: File stat ──────────────────────────────
|
|
488
|
+
file_stat: `function file_stat(path) { try { const s = require('fs').statSync(path); return Ok({ size: s.size, mode: s.mode, mtime: s.mtime.toISOString(), atime: s.atime.toISOString(), isDir: s.isDirectory(), isFile: s.isFile(), isSymlink: s.isSymbolicLink() }); } catch (e) { return Err(e.message); } }`,
|
|
489
|
+
file_size: `function file_size(path) { try { return Ok(require('fs').statSync(path).size); } catch (e) { return Err(e.message); } }`,
|
|
490
|
+
|
|
491
|
+
// ── Scripting: Path utilities ─────────────────────────
|
|
492
|
+
path_join: `function path_join(...parts) { return require('path').join(...parts); }`,
|
|
493
|
+
path_dirname: `function path_dirname(p) { return require('path').dirname(p); }`,
|
|
494
|
+
path_basename: `function path_basename(p, ext) { return ext ? require('path').basename(p, ext) : require('path').basename(p); }`,
|
|
495
|
+
path_resolve: `function path_resolve(p) { return require('path').resolve(p); }`,
|
|
496
|
+
path_ext: `function path_ext(p) { return require('path').extname(p); }`,
|
|
497
|
+
path_relative: `function path_relative(from, to) { return require('path').relative(from, to); }`,
|
|
498
|
+
|
|
499
|
+
// ── Scripting: Symlinks ───────────────────────────────
|
|
500
|
+
symlink: `function symlink(target, path) { try { require('fs').symlinkSync(target, path); return Ok(null); } catch (e) { return Err(e.message); } }`,
|
|
501
|
+
readlink: `function readlink(path) { try { return Ok(require('fs').readlinkSync(path)); } catch (e) { return Err(e.message); } }`,
|
|
502
|
+
is_symlink: `function is_symlink(path) { try { return require('fs').lstatSync(path).isSymbolicLink(); } catch { return false; } }`,
|
|
503
|
+
|
|
504
|
+
// ── Scripting: Async shell ────────────────────────────
|
|
505
|
+
spawn: `function spawn(cmd, cmdArgs, opts) { if (cmdArgs && typeof cmdArgs === 'object' && !Array.isArray(cmdArgs)) { opts = cmdArgs; cmdArgs = []; } const o = opts || {}; const a = cmdArgs || []; return new Promise(function(resolve) { try { const cp = require('child_process'); const child = cp.spawn(cmd, a, { shell: !!o.shell, cwd: o.cwd, env: o.env ? Object.assign({}, process.env, o.env) : undefined, stdio: 'pipe' }); let stdout = ''; let stderr = ''; child.stdout.on('data', function(d) { stdout += d; }); child.stderr.on('data', function(d) { stderr += d; }); child.on('error', function(e) { resolve(Err(e.message)); }); child.on('close', function(code) { resolve(Ok({ stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), exitCode: code })); }); } catch (e) { resolve(Err(e.message)); } }); }`,
|
|
506
|
+
|
|
507
|
+
// ── Ordering type ─────────────────────────────────────
|
|
508
|
+
Less: `const Less = Object.freeze({ __tag: "Less", value: -1 });`,
|
|
509
|
+
Equal: `const Equal = Object.freeze({ __tag: "Equal", value: 0 });`,
|
|
510
|
+
Greater: `const Greater = Object.freeze({ __tag: "Greater", value: 1 });`,
|
|
511
|
+
compare: `function compare(a, b) { if (a < b) return Less; if (a > b) return Greater; return Equal; }`,
|
|
512
|
+
compare_by: `function compare_by(arr, fn) { return [...arr].sort(function(a, b) { const ord = fn(a, b); return ord.value; }); }`,
|
|
513
|
+
|
|
514
|
+
// ── Regex Builder ─────────────────────────────────────
|
|
515
|
+
RegexBuilder: `class RegexBuilder {
|
|
516
|
+
constructor() { this._parts = []; this._flags = ''; }
|
|
517
|
+
literal(s) { this._parts.push(s.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')); return this; }
|
|
518
|
+
digits(n) { this._parts.push(n ? '\\\\d{' + n + '}' : '\\\\d+'); return this; }
|
|
519
|
+
word() { this._parts.push('\\\\w+'); return this; }
|
|
520
|
+
space() { this._parts.push('\\\\s+'); return this; }
|
|
521
|
+
any() { this._parts.push('.'); return this; }
|
|
522
|
+
oneOf(chars) { this._parts.push('[' + chars.replace(/[\\]\\\\]/g, '\\\\$&') + ']'); return this; }
|
|
523
|
+
group(name) { this._parts.push(name ? '(?<' + name + '>' : '('); return this; }
|
|
524
|
+
endGroup() { this._parts.push(')'); return this; }
|
|
525
|
+
optional() { this._parts.push('?'); return this; }
|
|
526
|
+
oneOrMore() { this._parts.push('+'); return this; }
|
|
527
|
+
zeroOrMore() { this._parts.push('*'); return this; }
|
|
528
|
+
startOfLine() { this._parts.push('^'); return this; }
|
|
529
|
+
endOfLine() { this._parts.push('$'); return this; }
|
|
530
|
+
flags(f) { this._flags = f; return this; }
|
|
531
|
+
build() { return new RegExp(this._parts.join(''), this._flags); }
|
|
532
|
+
test(s) { return this.build().test(s); }
|
|
533
|
+
match(s) { return s.match(this.build()); }
|
|
534
|
+
}`,
|
|
535
|
+
regex_builder: `function regex_builder() { return new RegexBuilder(); }`,
|
|
536
|
+
|
|
537
|
+
// ── Namespace modules ──────────────────────────────────
|
|
538
|
+
math: `const math = Object.freeze({
|
|
539
|
+
sin(n) { return Math.sin(n); },
|
|
540
|
+
cos(n) { return Math.cos(n); },
|
|
541
|
+
tan(n) { return Math.tan(n); },
|
|
542
|
+
asin(n) { return Math.asin(n); },
|
|
543
|
+
acos(n) { return Math.acos(n); },
|
|
544
|
+
atan(n) { return Math.atan(n); },
|
|
545
|
+
atan2(y, x) { return Math.atan2(y, x); },
|
|
546
|
+
log(n) { return Math.log(n); },
|
|
547
|
+
log2(n) { return Math.log2(n); },
|
|
548
|
+
log10(n) { return Math.log10(n); },
|
|
549
|
+
exp(n) { return Math.exp(n); },
|
|
550
|
+
abs(n) { return Math.abs(n); },
|
|
551
|
+
floor(n) { return Math.floor(n); },
|
|
552
|
+
ceil(n) { return Math.ceil(n); },
|
|
553
|
+
round(n) { return Math.round(n); },
|
|
554
|
+
sqrt(n) { return Math.sqrt(n); },
|
|
555
|
+
pow(b, e) { return Math.pow(b, e); },
|
|
556
|
+
clamp(n, lo, hi) { return Math.min(Math.max(n, lo), hi); },
|
|
557
|
+
random() { return Math.random(); },
|
|
558
|
+
sign(n) { return Math.sign(n); },
|
|
559
|
+
trunc(n) { return Math.trunc(n); },
|
|
560
|
+
hypot(a, b) { return Math.hypot(a, b); },
|
|
561
|
+
lerp(a, b, t) { return a + (b - a) * t; },
|
|
562
|
+
gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a; },
|
|
563
|
+
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; },
|
|
564
|
+
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; },
|
|
565
|
+
PI: Math.PI,
|
|
566
|
+
E: Math.E,
|
|
567
|
+
INF: Infinity
|
|
568
|
+
});`,
|
|
569
|
+
|
|
570
|
+
str: `const str = Object.freeze({
|
|
571
|
+
upper(s) { return s.toUpperCase(); },
|
|
572
|
+
lower(s) { return s.toLowerCase(); },
|
|
573
|
+
trim(s) { return s.trim(); },
|
|
574
|
+
trim_start(s) { return s.trimStart(); },
|
|
575
|
+
trim_end(s) { return s.trimEnd(); },
|
|
576
|
+
split(s, sep) { return s.split(sep); },
|
|
577
|
+
join(arr, sep) { return arr.join(sep); },
|
|
578
|
+
replace(s, from, to) { return typeof from === 'string' ? s.replaceAll(from, to) : s.replace(from, to); },
|
|
579
|
+
repeat(s, n) { return s.repeat(n); },
|
|
580
|
+
contains(s, sub) { return s.includes(sub); },
|
|
581
|
+
starts_with(s, prefix) { return s.startsWith(prefix); },
|
|
582
|
+
ends_with(s, suffix) { return s.endsWith(suffix); },
|
|
583
|
+
chars(s) { return [...s]; },
|
|
584
|
+
words(s) { return s.split(/\\s+/).filter(Boolean); },
|
|
585
|
+
lines(s) { return s.split('\\n'); },
|
|
586
|
+
capitalize(s) { return s.length ? s.charAt(0).toUpperCase() + s.slice(1) : s; },
|
|
587
|
+
title_case(s) { return s.replace(/\\b\\w/g, c => c.toUpperCase()); },
|
|
588
|
+
snake_case(s) { return s.replace(/[-\\s]+/g, '_').replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase().replace(/^_/, ''); },
|
|
589
|
+
camel_case(s) { return s.replace(/[-_\\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '').replace(/^[A-Z]/, c => c.toLowerCase()); },
|
|
590
|
+
kebab_case(s) { return s.replace(/[-\\s]+/g, '-').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase().replace(/^-/, ''); },
|
|
591
|
+
index_of(s, sub) { const i = s.indexOf(sub); return i === -1 ? null : i; },
|
|
592
|
+
last_index_of(s, sub) { const i = s.lastIndexOf(sub); return i === -1 ? null : i; },
|
|
593
|
+
pad_start(s, n, fill) { return s.padStart(n, fill || ' '); },
|
|
594
|
+
pad_end(s, n, fill) { return s.padEnd(n, fill || ' '); },
|
|
595
|
+
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); },
|
|
596
|
+
slugify(s) { return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); },
|
|
597
|
+
escape_html(s) { return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); },
|
|
598
|
+
unescape_html(s) { return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'"); }
|
|
599
|
+
});`,
|
|
600
|
+
|
|
601
|
+
arr: `const arr = Object.freeze({
|
|
602
|
+
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; },
|
|
603
|
+
reversed(a) { return [...a].reverse(); },
|
|
604
|
+
unique(a) { return [...new Set(a)]; },
|
|
605
|
+
chunk(a, n) { const r = []; for (let i = 0; i < a.length; i += n) r.push(a.slice(i, i + n)); return r; },
|
|
606
|
+
flatten(a) { return a.flat(); },
|
|
607
|
+
take(a, n) { return a.slice(0, n); },
|
|
608
|
+
drop(a, n) { return a.slice(n); },
|
|
609
|
+
first(a) { return a.length > 0 ? a[0] : null; },
|
|
610
|
+
last(a) { return a.length > 0 ? a[a.length - 1] : null; },
|
|
611
|
+
count(a, fn) { return a.filter(fn).length; },
|
|
612
|
+
partition(a, fn) { const y = [], n = []; for (const v of a) { (fn(v) ? y : n).push(v); } return [y, n]; },
|
|
613
|
+
group_by(a, fn) { const r = {}; for (const v of a) { const k = fn(v); if (!r[k]) r[k] = []; r[k].push(v); } return r; },
|
|
614
|
+
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 ? fn(a[i], b[i]) : [a[i], b[i]]); return r; },
|
|
615
|
+
frequencies(a) { const r = {}; for (const v of a) { const k = String(v); r[k] = (r[k] || 0) + 1; } return r; },
|
|
616
|
+
scan(a, fn, init) { const r = []; let acc = init; for (const v of a) { acc = fn(acc, v); r.push(acc); } return r; },
|
|
617
|
+
min_by(a, fn) { if (a.length === 0) return null; let best = a[0], bestK = fn(a[0]); for (let i = 1; i < a.length; i++) { const k = fn(a[i]); if (k < bestK) { best = a[i]; bestK = k; } } return best; },
|
|
618
|
+
max_by(a, fn) { if (a.length === 0) return null; let best = a[0], bestK = fn(a[0]); for (let i = 1; i < a.length; i++) { const k = fn(a[i]); if (k > bestK) { best = a[i]; bestK = k; } } return best; },
|
|
619
|
+
sum_by(a, fn) { let s = 0; for (const v of a) s += fn(v); return s; },
|
|
620
|
+
compact(a) { return a.filter(v => v != null); },
|
|
621
|
+
rotate(a, n) { if (a.length === 0) return []; const k = ((n % a.length) + a.length) % a.length; return [...a.slice(k), ...a.slice(0, k)]; },
|
|
622
|
+
insert_at(a, idx, val) { const r = [...a]; r.splice(idx, 0, val); return r; },
|
|
623
|
+
remove_at(a, idx) { const r = [...a]; r.splice(idx, 1); return r; },
|
|
624
|
+
binary_search(a, target, keyFn) { let lo = 0, hi = a.length - 1; while (lo <= hi) { const mid = (lo + hi) >> 1; const val = keyFn ? keyFn(a[mid]) : a[mid]; if (val === target) return mid; if (val < target) lo = mid + 1; else hi = mid - 1; } return -1; },
|
|
625
|
+
is_sorted(a, keyFn) { for (let i = 1; i < a.length; i++) { const x = keyFn ? keyFn(a[i - 1]) : a[i - 1]; const y = keyFn ? keyFn(a[i]) : a[i]; if (x > y) return false; } return true; }
|
|
626
|
+
});`,
|
|
627
|
+
|
|
628
|
+
dt: `const dt = Object.freeze({
|
|
629
|
+
now() { return Date.now(); },
|
|
630
|
+
now_iso() { return new Date().toISOString(); },
|
|
631
|
+
parse(s) { const d = new Date(s); return isNaN(d.getTime()) ? Err('Invalid date: ' + s) : Ok(d); },
|
|
632
|
+
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')); },
|
|
633
|
+
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; },
|
|
634
|
+
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; },
|
|
635
|
+
from(parts) { return new Date(parts.year || 0, (parts.month || 1) - 1, parts.day || 1, parts.hour || 0, parts.minute || 0, parts.second || 0); },
|
|
636
|
+
part(d, p) { if (typeof d === 'number') d = new Date(d); if (p === 'year') return d.getFullYear(); if (p === 'month') return d.getMonth() + 1; if (p === 'day') return d.getDate(); if (p === 'hour') return d.getHours(); if (p === 'minute') return d.getMinutes(); if (p === 'second') return d.getSeconds(); if (p === 'weekday') return d.getDay(); return null; },
|
|
637
|
+
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'); }
|
|
638
|
+
});`,
|
|
639
|
+
|
|
640
|
+
re: `const re = Object.freeze({
|
|
641
|
+
test(s, pattern, flags) { return new RegExp(pattern, flags).test(s); },
|
|
642
|
+
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) }); },
|
|
643
|
+
find_all(s, pattern, flags) { const r = new RegExp(pattern, (flags || '') + (flags && flags.includes('g') ? '' : 'g')); const results = []; let m; while ((m = r.exec(s)) !== null) { results.push({ match: m[0], index: m.index, groups: m.slice(1) }); } return results; },
|
|
644
|
+
replace(s, pattern, replacement, flags) { return s.replace(new RegExp(pattern, flags || 'g'), replacement); },
|
|
645
|
+
split(s, pattern, flags) { return s.split(new RegExp(pattern, flags)); },
|
|
646
|
+
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); }
|
|
647
|
+
});`,
|
|
648
|
+
|
|
649
|
+
json: `const json = Object.freeze({
|
|
650
|
+
parse(s) { try { return Ok(JSON.parse(s)); } catch (e) { return Err(e.message); } },
|
|
651
|
+
stringify(v) { return JSON.stringify(v); },
|
|
652
|
+
pretty(v) { return JSON.stringify(v, null, 2); }
|
|
653
|
+
});`,
|
|
654
|
+
|
|
655
|
+
fs: `const fs = Object.freeze({
|
|
656
|
+
read_text(path, enc) { try { return Ok(require('fs').readFileSync(path, enc || 'utf-8')); } catch (e) { return Err(e.message); } },
|
|
657
|
+
write_text(path, content, opts) { try { const f = require('fs'); if (opts && opts.append) f.appendFileSync(path, content); else f.writeFileSync(path, content); return Ok(path); } catch (e) { return Err(e.message); } },
|
|
658
|
+
exists(path) { return require('fs').existsSync(path); },
|
|
659
|
+
is_dir(path) { try { return require('fs').statSync(path).isDirectory(); } catch { return false; } },
|
|
660
|
+
is_file(path) { try { return require('fs').statSync(path).isFile(); } catch { return false; } },
|
|
661
|
+
ls(dir, opts) { const f = require('fs'); const p = require('path'); const d = dir || '.'; const entries = f.readdirSync(d); if (opts && opts.full) return entries.map(e => p.join(d, e)); return entries; },
|
|
662
|
+
mkdir(dir) { try { require('fs').mkdirSync(dir, { recursive: true }); return Ok(dir); } catch (e) { return Err(e.message); } },
|
|
663
|
+
rm(path, opts) { try { require('fs').rmSync(path, { recursive: !!(opts && opts.recursive), force: !!(opts && opts.force) }); return Ok(path); } catch (e) { return Err(e.message); } },
|
|
664
|
+
cp(src, dest, opts) { try { const f = require('fs'); if (opts && opts.recursive) { f.cpSync(src, dest, { recursive: true }); } else { f.copyFileSync(src, dest); } return Ok(dest); } catch (e) { return Err(e.message); } },
|
|
665
|
+
mv(src, dest) { try { require('fs').renameSync(src, dest); return Ok(dest); } catch (e) { return Err(e.message); } },
|
|
666
|
+
glob_files(pattern, opts) { if (typeof Bun !== 'undefined' && Bun.Glob) { const glob = new Bun.Glob(pattern); return [...glob.scanSync(opts && opts.cwd || '.')]; } const f = require('fs'); if (f.globSync) return f.globSync(pattern, opts); return []; }
|
|
667
|
+
});`,
|
|
668
|
+
|
|
669
|
+
url: `const url = Object.freeze({
|
|
670
|
+
parse(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); } },
|
|
671
|
+
build(parts) { let u = (parts.protocol || 'https') + '://' + (parts.host || ''); u += parts.pathname || '/'; if (parts.search) u += (parts.search.startsWith('?') ? '' : '?') + parts.search; if (parts.hash) u += (parts.hash.startsWith('#') ? '' : '#') + parts.hash; return u; },
|
|
672
|
+
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; },
|
|
673
|
+
build_query(obj) { return Object.entries(obj).map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&'); }
|
|
674
|
+
});`,
|
|
675
|
+
|
|
676
|
+
// ── Channel-based async ───────────────────────────────
|
|
677
|
+
Channel: `class Channel {
|
|
678
|
+
constructor(capacity) {
|
|
679
|
+
this._capacity = capacity || 0;
|
|
680
|
+
this._buffer = [];
|
|
681
|
+
this._closed = false;
|
|
682
|
+
this._sendWaiters = [];
|
|
683
|
+
this._recvWaiters = [];
|
|
684
|
+
}
|
|
685
|
+
async send(value) {
|
|
686
|
+
if (this._closed) throw new Error('Cannot send on closed channel');
|
|
687
|
+
if (this._recvWaiters.length > 0) {
|
|
688
|
+
const waiter = this._recvWaiters.shift();
|
|
689
|
+
waiter(Some(value));
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (this._capacity > 0 && this._buffer.length < this._capacity) {
|
|
693
|
+
this._buffer.push(value);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
return new Promise(function(resolve) {
|
|
697
|
+
this._sendWaiters.push({ value: value, resolve: resolve });
|
|
698
|
+
}.bind(this));
|
|
699
|
+
}
|
|
700
|
+
async receive() {
|
|
701
|
+
if (this._buffer.length > 0) {
|
|
702
|
+
const value = this._buffer.shift();
|
|
703
|
+
if (this._sendWaiters.length > 0) {
|
|
704
|
+
const waiter = this._sendWaiters.shift();
|
|
705
|
+
this._buffer.push(waiter.value);
|
|
706
|
+
waiter.resolve();
|
|
707
|
+
}
|
|
708
|
+
return Some(value);
|
|
709
|
+
}
|
|
710
|
+
if (this._closed) return None;
|
|
711
|
+
if (this._sendWaiters.length > 0) {
|
|
712
|
+
const waiter = this._sendWaiters.shift();
|
|
713
|
+
waiter.resolve();
|
|
714
|
+
return Some(waiter.value);
|
|
715
|
+
}
|
|
716
|
+
return new Promise(function(resolve) {
|
|
717
|
+
this._recvWaiters.push(resolve);
|
|
718
|
+
}.bind(this));
|
|
719
|
+
}
|
|
720
|
+
close() {
|
|
721
|
+
this._closed = true;
|
|
722
|
+
for (const waiter of this._recvWaiters) waiter(None);
|
|
723
|
+
this._recvWaiters = [];
|
|
724
|
+
}
|
|
725
|
+
[Symbol.asyncIterator]() {
|
|
726
|
+
const ch = this;
|
|
727
|
+
return {
|
|
728
|
+
async next() {
|
|
729
|
+
const val = await ch.receive();
|
|
730
|
+
if (val.__tag === 'None') return { done: true, value: undefined };
|
|
731
|
+
return { done: false, value: val.value };
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
}`,
|
|
736
|
+
|
|
737
|
+
// ── Snapshot testing ──────────────────────────────────
|
|
738
|
+
assert_snapshot: `function assert_snapshot(value, name) {
|
|
739
|
+
const snap = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
740
|
+
const updateMode = typeof process !== 'undefined' && process.env.TOVA_UPDATE_SNAPSHOTS === '1';
|
|
741
|
+
if (typeof __tova_snapshots === 'undefined') { globalThis.__tova_snapshots = {}; }
|
|
742
|
+
const key = name || ('snapshot_' + Object.keys(__tova_snapshots).length);
|
|
743
|
+
if (updateMode || !__tova_snapshots[key]) {
|
|
744
|
+
__tova_snapshots[key] = snap;
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (__tova_snapshots[key] !== snap) {
|
|
748
|
+
throw new Error('Snapshot mismatch for "' + key + '":\\nExpected:\\n' + __tova_snapshots[key] + '\\nActual:\\n' + snap);
|
|
749
|
+
}
|
|
750
|
+
}`,
|
|
751
|
+
|
|
752
|
+
// ── Property-based testing ────────────────────────────
|
|
753
|
+
Gen: `const Gen = {
|
|
754
|
+
int: function(min, max) { return function() { const lo = min !== undefined ? min : -1000; const hi = max !== undefined ? max : 1000; return Math.floor(Math.random() * (hi - lo + 1)) + lo; }; },
|
|
755
|
+
float: function(min, max) { return function() { const lo = min !== undefined ? min : -1000; const hi = max !== undefined ? max : 1000; return Math.random() * (hi - lo) + lo; }; },
|
|
756
|
+
bool: function() { return function() { return Math.random() < 0.5; }; },
|
|
757
|
+
string: function(maxLen) { return function() { const len = Math.floor(Math.random() * (maxLen || 20)); const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; let s = ''; for (let i = 0; i < len; i++) s += chars[Math.floor(Math.random() * chars.length)]; return s; }; },
|
|
758
|
+
array: function(gen, maxLen) { return function() { const len = Math.floor(Math.random() * (maxLen || 10)); const arr = []; for (let i = 0; i < len; i++) arr.push(gen()); return arr; }; },
|
|
759
|
+
oneOf: function(values) { return function() { return values[Math.floor(Math.random() * values.length)]; }; }
|
|
760
|
+
};`,
|
|
761
|
+
forAll: `function forAll(generators, property, opts) {
|
|
762
|
+
const runs = (opts && opts.runs) || 100;
|
|
763
|
+
for (let i = 0; i < runs; i++) {
|
|
764
|
+
const args = generators.map(function(g) { return g(); });
|
|
765
|
+
let result;
|
|
766
|
+
try { result = property.apply(null, args); } catch (e) { throw new Error('Property failed on input ' + JSON.stringify(args) + ': ' + e.message); }
|
|
767
|
+
if (result === false) { throw new Error('Property failed on input: ' + JSON.stringify(args)); }
|
|
768
|
+
}
|
|
769
|
+
}`,
|
|
770
|
+
|
|
771
|
+
// ── Mock / Spy Utilities ────────────────────────────────
|
|
772
|
+
create_spy: `function create_spy(impl) {
|
|
773
|
+
const spy = function(...args) {
|
|
774
|
+
spy.calls.push(args);
|
|
775
|
+
spy.call_count++;
|
|
776
|
+
spy.called = true;
|
|
777
|
+
spy.last_args = args;
|
|
778
|
+
if (spy._impl) return spy._impl(...args);
|
|
779
|
+
return spy._return_value;
|
|
780
|
+
};
|
|
781
|
+
spy.calls = [];
|
|
782
|
+
spy.call_count = 0;
|
|
783
|
+
spy.called = false;
|
|
784
|
+
spy.last_args = null;
|
|
785
|
+
spy._impl = impl || null;
|
|
786
|
+
spy._return_value = undefined;
|
|
787
|
+
spy.returns = function(val) { spy._return_value = val; spy._impl = null; return spy; };
|
|
788
|
+
spy.reset = function() { spy.calls = []; spy.call_count = 0; spy.called = false; spy.last_args = null; };
|
|
789
|
+
spy.called_with = function(...expected) {
|
|
790
|
+
return spy.calls.some(function(call) {
|
|
791
|
+
return expected.length === call.length && expected.every(function(v, i) { return v === call[i]; });
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
return spy;
|
|
795
|
+
}`,
|
|
796
|
+
create_mock: `function create_mock(return_value) {
|
|
797
|
+
return create_spy(typeof return_value === 'function' ? return_value : function() { return return_value; });
|
|
798
|
+
}`,
|
|
799
|
+
|
|
800
|
+
// ── Advanced Collections ────────────────────────────────
|
|
801
|
+
OrderedDict: `class OrderedDict {
|
|
802
|
+
constructor(entries) { this._map = new Map(entries || []); }
|
|
803
|
+
get(key) { return this._map.has(key) ? this._map.get(key) : null; }
|
|
804
|
+
set(key, value) { const m = new Map(this._map); m.set(key, value); return new OrderedDict([...m]); }
|
|
805
|
+
delete(key) { const m = new Map(this._map); m.delete(key); return new OrderedDict([...m]); }
|
|
806
|
+
has(key) { return this._map.has(key); }
|
|
807
|
+
keys() { return [...this._map.keys()]; }
|
|
808
|
+
values() { return [...this._map.values()]; }
|
|
809
|
+
entries() { return [...this._map.entries()]; }
|
|
810
|
+
get length() { return this._map.size; }
|
|
811
|
+
[Symbol.iterator]() { return this._map[Symbol.iterator](); }
|
|
812
|
+
toString() { return 'OrderedDict(' + this._map.size + ' entries)'; }
|
|
813
|
+
}`,
|
|
814
|
+
|
|
815
|
+
DefaultDict: `class DefaultDict {
|
|
816
|
+
constructor(defaultFn) { this._map = new Map(); this._default = defaultFn; }
|
|
817
|
+
get(key) { if (!this._map.has(key)) { this._map.set(key, this._default()); } return this._map.get(key); }
|
|
818
|
+
set(key, value) { this._map.set(key, value); return this; }
|
|
819
|
+
has(key) { return this._map.has(key); }
|
|
820
|
+
delete(key) { this._map.delete(key); return this; }
|
|
821
|
+
keys() { return [...this._map.keys()]; }
|
|
822
|
+
values() { return [...this._map.values()]; }
|
|
823
|
+
entries() { return [...this._map.entries()]; }
|
|
824
|
+
get length() { return this._map.size; }
|
|
825
|
+
[Symbol.iterator]() { return this._map[Symbol.iterator](); }
|
|
826
|
+
toString() { return 'DefaultDict(' + this._map.size + ' entries)'; }
|
|
827
|
+
}`,
|
|
828
|
+
|
|
829
|
+
Counter: `class Counter {
|
|
830
|
+
constructor(items) { this._counts = new Map(); if (items) { for (const item of items) { this._counts.set(item, (this._counts.get(item) || 0) + 1); } } }
|
|
831
|
+
count(item) { return this._counts.get(item) || 0; }
|
|
832
|
+
total() { let s = 0; for (const v of this._counts.values()) s += v; return s; }
|
|
833
|
+
most_common(n) { const sorted = [...this._counts.entries()].sort((a, b) => b[1] - a[1]); return n !== undefined ? sorted.slice(0, n) : sorted; }
|
|
834
|
+
keys() { return [...this._counts.keys()]; }
|
|
835
|
+
values() { return [...this._counts.values()]; }
|
|
836
|
+
entries() { return [...this._counts.entries()]; }
|
|
837
|
+
has(item) { return this._counts.has(item); }
|
|
838
|
+
get length() { return this._counts.size; }
|
|
839
|
+
[Symbol.iterator]() { return this._counts[Symbol.iterator](); }
|
|
840
|
+
toString() { return 'Counter(' + this._counts.size + ' items)'; }
|
|
841
|
+
}`,
|
|
842
|
+
|
|
843
|
+
Deque: `class Deque {
|
|
844
|
+
constructor(items) { this._items = items ? [...items] : []; }
|
|
845
|
+
push_back(val) { return new Deque([...this._items, val]); }
|
|
846
|
+
push_front(val) { return new Deque([val, ...this._items]); }
|
|
847
|
+
pop_back() { if (this._items.length === 0) return [null, this]; return [this._items[this._items.length - 1], new Deque(this._items.slice(0, -1))]; }
|
|
848
|
+
pop_front() { if (this._items.length === 0) return [null, this]; return [this._items[0], new Deque(this._items.slice(1))]; }
|
|
849
|
+
peek_front() { return this._items.length > 0 ? this._items[0] : null; }
|
|
850
|
+
peek_back() { return this._items.length > 0 ? this._items[this._items.length - 1] : null; }
|
|
851
|
+
get length() { return this._items.length; }
|
|
852
|
+
toArray() { return [...this._items]; }
|
|
853
|
+
[Symbol.iterator]() { return this._items[Symbol.iterator](); }
|
|
854
|
+
toString() { return 'Deque(' + this._items.length + ' items)'; }
|
|
855
|
+
}`,
|
|
856
|
+
|
|
857
|
+
collections: `const collections = Object.freeze({
|
|
858
|
+
OrderedDict, DefaultDict, Counter, Deque
|
|
859
|
+
});`,
|
|
400
860
|
};
|
|
401
861
|
|
|
402
862
|
// All known builtin names for matching
|
|
403
863
|
export const BUILTIN_NAMES = new Set(Object.keys(BUILTIN_FUNCTIONS));
|
|
404
864
|
|
|
865
|
+
// ─── Stdlib Dependency Graph ──────────────────────────────────
|
|
866
|
+
// Maps each builtin to the builtins it depends on (must be emitted first).
|
|
867
|
+
// This replaces scattered ad-hoc dependency checks throughout the codebase.
|
|
868
|
+
export const STDLIB_DEPS = {
|
|
869
|
+
// iter() requires the Seq class
|
|
870
|
+
iter: ['Seq'],
|
|
871
|
+
// collections namespace requires all collection classes
|
|
872
|
+
collections: ['OrderedDict', 'DefaultDict', 'Counter', 'Deque'],
|
|
873
|
+
// Table operations may use Table
|
|
874
|
+
describe: ['Table'],
|
|
875
|
+
// Some builtins reference Result/Option types (Ok, Err, Some, None)
|
|
876
|
+
// These are provided by RESULT_OPTION, not the builtin map, so no dep here
|
|
877
|
+
// Namespace modules that use builtins internally
|
|
878
|
+
json: ['Ok', 'Err'],
|
|
879
|
+
re: ['Ok', 'Err'],
|
|
880
|
+
dt: ['Ok', 'Err'],
|
|
881
|
+
fs: ['Ok', 'Err'],
|
|
882
|
+
url: ['Ok', 'Err'],
|
|
883
|
+
parse_url: ['Ok', 'Err'],
|
|
884
|
+
regex_match: ['Ok', 'Err'],
|
|
885
|
+
regex_capture: ['Ok', 'Err'],
|
|
886
|
+
json_parse: ['Ok', 'Err'],
|
|
887
|
+
date_parse: ['Ok', 'Err'],
|
|
888
|
+
read_text: ['Ok', 'Err'],
|
|
889
|
+
try_fn: ['Ok', 'Err'],
|
|
890
|
+
try_async: ['Ok', 'Err'],
|
|
891
|
+
// Seq uses Some/None
|
|
892
|
+
Seq: ['Some', 'None'],
|
|
893
|
+
// compare family
|
|
894
|
+
compare: ['Less', 'Equal', 'Greater'],
|
|
895
|
+
compare_by: ['Less', 'Equal', 'Greater'],
|
|
896
|
+
// mock/spy
|
|
897
|
+
create_mock: ['create_spy'],
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
// Resolve all transitive dependencies for a set of used names
|
|
901
|
+
export function resolveStdlibDeps(usedNames) {
|
|
902
|
+
const resolved = new Set(usedNames);
|
|
903
|
+
const queue = [...usedNames];
|
|
904
|
+
while (queue.length > 0) {
|
|
905
|
+
const name = queue.pop();
|
|
906
|
+
const deps = STDLIB_DEPS[name];
|
|
907
|
+
if (deps) {
|
|
908
|
+
for (const dep of deps) {
|
|
909
|
+
if (!resolved.has(dep)) {
|
|
910
|
+
resolved.add(dep);
|
|
911
|
+
queue.push(dep);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return resolved;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Topological sort: emit dependencies before dependents
|
|
920
|
+
function _topoSort(names) {
|
|
921
|
+
const result = [];
|
|
922
|
+
const visited = new Set();
|
|
923
|
+
const visiting = new Set();
|
|
924
|
+
|
|
925
|
+
function visit(name) {
|
|
926
|
+
if (visited.has(name)) return;
|
|
927
|
+
if (visiting.has(name)) return; // circular — break
|
|
928
|
+
visiting.add(name);
|
|
929
|
+
const deps = STDLIB_DEPS[name];
|
|
930
|
+
if (deps) {
|
|
931
|
+
for (const dep of deps) {
|
|
932
|
+
if (names.has(dep)) visit(dep);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
visiting.delete(name);
|
|
936
|
+
visited.add(name);
|
|
937
|
+
result.push(name);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
for (const name of names) visit(name);
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
943
|
+
|
|
405
944
|
// Legacy compat: full stdlib as a single string (derived from BUILTIN_FUNCTIONS)
|
|
406
945
|
// Only includes non-internal, non-table functions for backward compat with tests/playground
|
|
407
946
|
const _LEGACY_NAMES = [
|
|
@@ -413,14 +952,18 @@ const _LEGACY_NAMES = [
|
|
|
413
952
|
'keys', 'values', 'entries', 'merge', 'freeze', 'clone', 'sleep',
|
|
414
953
|
'upper', 'lower', 'contains', 'starts_with', 'ends_with', 'chars', 'words',
|
|
415
954
|
'lines', 'capitalize', 'title_case', 'snake_case', 'camel_case',
|
|
416
|
-
'assert_eq', 'assert_ne', 'assert',
|
|
955
|
+
'assert_eq', 'assert_ne', 'assert', 'assert_throws',
|
|
956
|
+
'create_spy', 'create_mock',
|
|
417
957
|
];
|
|
418
958
|
export const BUILTINS = _LEGACY_NAMES.map(n => BUILTIN_FUNCTIONS[n]).join('\n');
|
|
419
959
|
|
|
420
960
|
// Build stdlib containing only the functions that are actually used
|
|
421
961
|
export function buildSelectiveStdlib(usedNames) {
|
|
962
|
+
// Resolve transitive dependencies and topologically sort
|
|
963
|
+
const withDeps = resolveStdlibDeps(usedNames);
|
|
964
|
+
const ordered = _topoSort(withDeps);
|
|
422
965
|
const parts = [];
|
|
423
|
-
for (const name of
|
|
966
|
+
for (const name of ordered) {
|
|
424
967
|
if (BUILTIN_FUNCTIONS[name]) {
|
|
425
968
|
parts.push(BUILTIN_FUNCTIONS[name]);
|
|
426
969
|
}
|