novac 2.0.1 → 2.2.0

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.
Files changed (161) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1574 -597
  3. package/bin/novac +468 -171
  4. package/bin/nvc +522 -0
  5. package/bin/nvml +78 -17
  6. package/demo.nv +0 -0
  7. package/demo_builtins.nv +0 -0
  8. package/demo_http.nv +0 -0
  9. package/examples/bf.nv +69 -0
  10. package/examples/math.nv +21 -0
  11. package/kits/birdAPI/kitdef.js +954 -0
  12. package/kits/kitRNG/kitdef.js +740 -0
  13. package/kits/kitSSH/kitdef.js +1272 -0
  14. package/kits/kitadb/kitdef.js +606 -0
  15. package/kits/kitai/kitdef.js +2185 -0
  16. package/kits/kitansi/kitdef.js +1402 -0
  17. package/kits/kitcanvas/kitdef.js +914 -0
  18. package/kits/kitclippy/kitdef.js +925 -0
  19. package/kits/kitformat/kitdef.js +1485 -0
  20. package/kits/kitgps/kitdef.js +1862 -0
  21. package/kits/kitlibproc/kitdef.js +3 -2
  22. package/kits/kitmatrix/ex.js +19 -0
  23. package/kits/kitmatrix/kitdef.js +960 -0
  24. package/kits/kitmorse/kitdef.js +229 -0
  25. package/kits/kitmpatch/kitdef.js +906 -0
  26. package/kits/kitnet/kitdef.js +1401 -0
  27. package/kits/kitnovacweb/README.md +1416 -143
  28. package/kits/kitnovacweb/kitdef.js +92 -2
  29. package/kits/kitnovacweb/nvml/executor.js +578 -176
  30. package/kits/kitnovacweb/nvml/index.js +2 -2
  31. package/kits/kitnovacweb/nvml/lexer.js +72 -69
  32. package/kits/kitnovacweb/nvml/parser.js +328 -159
  33. package/kits/kitnovacweb/nvml/renderer.js +770 -270
  34. package/kits/kitparse/kitdef.js +1688 -0
  35. package/kits/kitproto/kitdef.js +613 -0
  36. package/kits/kitqr/kitdef.js +637 -0
  37. package/kits/kitregex++/kitdef.js +1353 -0
  38. package/kits/kitrequire/kitdef.js +1599 -0
  39. package/kits/kitx11/kitdef.js +1 -0
  40. package/kits/kitx11/kitx11.js +2472 -0
  41. package/kits/kitx11/kitx11_conn.js +948 -0
  42. package/kits/kitx11/kitx11_worker.js +121 -0
  43. package/kits/libtea/kitdef.js +2691 -0
  44. package/kits/libterm/ex.js +285 -0
  45. package/kits/libterm/kitdef.js +1927 -0
  46. package/novac/LICENSE +21 -0
  47. package/novac/README.md +1823 -0
  48. package/novac/bin/novac +950 -0
  49. package/novac/bin/nvc +522 -0
  50. package/novac/bin/nvml +542 -0
  51. package/novac/demo.nv +245 -0
  52. package/novac/demo_builtins.nv +209 -0
  53. package/novac/demo_http.nv +62 -0
  54. package/novac/examples/bf.nv +69 -0
  55. package/novac/examples/math.nv +21 -0
  56. package/novac/kits/kitai/kitdef.js +2185 -0
  57. package/novac/kits/kitansi/kitdef.js +1402 -0
  58. package/novac/kits/kitformat/kitdef.js +1485 -0
  59. package/novac/kits/kitgps/kitdef.js +1862 -0
  60. package/novac/kits/kitlibfs/kitdef.js +231 -0
  61. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  62. package/novac/kits/kitmatrix/ex.js +19 -0
  63. package/novac/kits/kitmatrix/kitdef.js +960 -0
  64. package/novac/kits/kitmpatch/kitdef.js +906 -0
  65. package/novac/kits/kitnovacweb/README.md +1572 -0
  66. package/novac/kits/kitnovacweb/demo.nv +12 -0
  67. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  68. package/novac/kits/kitnovacweb/index.nova +12 -0
  69. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  70. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  71. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  72. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  73. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  74. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  75. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  76. package/novac/kits/kitparse/kitdef.js +1688 -0
  77. package/novac/kits/kitregex++/kitdef.js +1353 -0
  78. package/novac/kits/kitrequire/kitdef.js +1599 -0
  79. package/novac/kits/kitx11/kitdef.js +1 -0
  80. package/novac/kits/kitx11/kitx11.js +2472 -0
  81. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  82. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  83. package/novac/kits/libtea/tf.js +2691 -0
  84. package/novac/kits/libterm/ex.js +285 -0
  85. package/novac/kits/libterm/kitdef.js +1927 -0
  86. package/novac/node_modules/chalk/license +9 -0
  87. package/novac/node_modules/chalk/package.json +83 -0
  88. package/novac/node_modules/chalk/readme.md +297 -0
  89. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  90. package/novac/node_modules/chalk/source/index.js +225 -0
  91. package/novac/node_modules/chalk/source/utilities.js +33 -0
  92. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  93. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  94. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  95. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  96. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  97. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  98. package/novac/node_modules/commander/LICENSE +22 -0
  99. package/novac/node_modules/commander/Readme.md +1176 -0
  100. package/novac/node_modules/commander/esm.mjs +16 -0
  101. package/novac/node_modules/commander/index.js +24 -0
  102. package/novac/node_modules/commander/lib/argument.js +150 -0
  103. package/novac/node_modules/commander/lib/command.js +2777 -0
  104. package/novac/node_modules/commander/lib/error.js +39 -0
  105. package/novac/node_modules/commander/lib/help.js +747 -0
  106. package/novac/node_modules/commander/lib/option.js +380 -0
  107. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  108. package/novac/node_modules/commander/package-support.json +19 -0
  109. package/novac/node_modules/commander/package.json +82 -0
  110. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  111. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  112. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  113. package/novac/node_modules/node-addon-api/README.md +95 -0
  114. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  115. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  116. package/novac/node_modules/node-addon-api/index.js +14 -0
  117. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  118. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  119. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  120. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  121. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  122. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  123. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  124. package/novac/node_modules/node-addon-api/package.json +480 -0
  125. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  126. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  127. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  128. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  129. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  130. package/novac/node_modules/serialize-javascript/README.md +149 -0
  131. package/novac/node_modules/serialize-javascript/index.js +297 -0
  132. package/novac/node_modules/serialize-javascript/package.json +33 -0
  133. package/novac/package.json +27 -0
  134. package/novac/scripts/update-bin.js +24 -0
  135. package/novac/src/core/bstd.js +1035 -0
  136. package/novac/src/core/config.js +155 -0
  137. package/novac/src/core/describe.js +187 -0
  138. package/novac/src/core/emitter.js +499 -0
  139. package/novac/src/core/error.js +86 -0
  140. package/novac/src/core/executor.js +5606 -0
  141. package/novac/src/core/formatter.js +686 -0
  142. package/novac/src/core/lexer.js +1026 -0
  143. package/novac/src/core/nova_builtins.js +717 -0
  144. package/novac/src/core/nova_thread_worker.js +166 -0
  145. package/novac/src/core/parser.js +2181 -0
  146. package/novac/src/core/types.js +112 -0
  147. package/novac/src/index.js +28 -0
  148. package/novac/src/runtime/stdlib.js +244 -0
  149. package/package.json +6 -3
  150. package/scripts/update-bin.js +0 -0
  151. package/src/core/bstd.js +838 -362
  152. package/src/core/executor.js +2578 -170
  153. package/src/core/lexer.js +502 -54
  154. package/src/core/nova_builtins.js +21 -3
  155. package/src/core/parser.js +413 -72
  156. package/src/core/types.js +30 -2
  157. package/src/index.js +0 -0
  158. package/examples/example-project/README.md +0 -3
  159. package/examples/example-project/src/main.nova +0 -3
  160. package/src/core/environment.js +0 -0
  161. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -0,0 +1,1688 @@
1
+ // kitparse — novac standard parsing kit
2
+ // Covers: CSV, TOML, INI, query strings, XML, JSON, YAML, HJSON, NDJSON,
3
+ // dotenv, simple tokenizer/lexer helpers, custom expression resolvers
4
+
5
+ // ─── CSV ─────────────────────────────────────────────────────────────────────
6
+
7
+ function csvParse(input, opts = {}) {
8
+ const sep = opts.separator ?? ',';
9
+ const quote = opts.quote ?? '"';
10
+ const headers = opts.headers ?? true;
11
+ const skipEmpty = opts.skipEmpty ?? true;
12
+ const trim = opts.trim ?? false;
13
+
14
+ const lines = splitLines(input);
15
+ const rows = [];
16
+
17
+ for (const raw of lines) {
18
+ if (skipEmpty && raw.trim() === '') continue;
19
+ const row = [];
20
+ let i = 0, cur = '';
21
+ let inQ = false;
22
+
23
+ while (i < raw.length) {
24
+ const ch = raw[i];
25
+ if (inQ) {
26
+ if (ch === quote) {
27
+ if (raw[i + 1] === quote) { cur += quote; i += 2; }
28
+ else { inQ = false; i++; }
29
+ } else { cur += ch; i++; }
30
+ } else {
31
+ if (ch === quote) { inQ = true; i++; }
32
+ else if (ch === sep) { row.push(trim ? cur.trim() : cur); cur = ''; i++; }
33
+ else { cur += ch; i++; }
34
+ }
35
+ }
36
+ row.push(trim ? cur.trim() : cur);
37
+ rows.push(row);
38
+ }
39
+
40
+ if (!headers || rows.length === 0) return rows;
41
+
42
+ const keys = rows[0];
43
+ const result = [];
44
+ for (let r = 1; r < rows.length; r++) {
45
+ const obj = {};
46
+ for (let c = 0; c < keys.length; c++) obj[keys[c]] = rows[r][c] ?? '';
47
+ result.push(obj);
48
+ }
49
+ return result;
50
+ }
51
+
52
+ function csvStringify(data, opts = {}) {
53
+ const sep = opts.separator ?? ',';
54
+ const quote = opts.quote ?? '"';
55
+ const crlf = opts.crlf ?? false;
56
+ const nl = crlf ? '\r\n' : '\n';
57
+
58
+ const needsQuote = (v) => {
59
+ const s = String(v);
60
+ let i = 0;
61
+ while (i < s.length) {
62
+ const c = s[i];
63
+ if (c === sep[0] || c === quote || c === '\n') return true;
64
+ i++;
65
+ }
66
+ return false;
67
+ };
68
+ const cell = (v) => {
69
+ const s = v == null ? '' : String(v);
70
+ if (needsQuote(s)) {
71
+ let escaped = '';
72
+ for (let i = 0; i < s.length; i++) {
73
+ escaped += s[i] === quote ? quote + quote : s[i];
74
+ }
75
+ return quote + escaped + quote;
76
+ }
77
+ return s;
78
+ };
79
+
80
+ if (data.length === 0) return '';
81
+
82
+ if (Array.isArray(data[0])) {
83
+ return data.map(row => row.map(cell).join(sep)).join(nl);
84
+ }
85
+
86
+ const keys = Object.keys(data[0]);
87
+ const header = keys.map(cell).join(sep);
88
+ const body = data.map(row => keys.map(k => cell(row[k])).join(sep));
89
+ return [header, ...body].join(nl);
90
+ }
91
+
92
+ // ─── TOML ────────────────────────────────────────────────────────────────────
93
+
94
+ function tomlParse(input) {
95
+ const lines = splitLines(input);
96
+ const root = {};
97
+ let current = root;
98
+
99
+ // manual integer check: optional leading minus, then all digits
100
+ function isInt(s) {
101
+ let i = 0;
102
+ if (s[i] === '-') i++;
103
+ if (i === s.length) return false;
104
+ while (i < s.length) { if (s[i] < '0' || s[i] > '9') return false; i++; }
105
+ return true;
106
+ }
107
+
108
+ // manual float check: optional minus, digits, dot, digits
109
+ function isFloat(s) {
110
+ let i = 0;
111
+ if (s[i] === '-') i++;
112
+ let hasDot = false;
113
+ let hasDigit = false;
114
+ while (i < s.length) {
115
+ const c = s[i];
116
+ if (c >= '0' && c <= '9') { hasDigit = true; i++; }
117
+ else if (c === '.' && !hasDot) { hasDot = true; i++; }
118
+ else return false;
119
+ }
120
+ return hasDot && hasDigit;
121
+ }
122
+
123
+ function coerce(s) {
124
+ s = s.trim();
125
+ if (s === 'true') return true;
126
+ if (s === 'false') return false;
127
+ if (isInt(s)) return parseInt(s, 10);
128
+ if (isFloat(s)) return parseFloat(s);
129
+ const q = s[0];
130
+ if ((q === '"' || q === "'") && s[s.length - 1] === q) return s.slice(1, -1);
131
+ if (s[0] === '[') {
132
+ // simple inline array — split by comma, trim, coerce each
133
+ const inner = s.slice(1, s.length - 1);
134
+ return splitCommaRespectingQuotes(inner).map(v => coerce(v.trim())).filter(v => v !== '');
135
+ }
136
+ return s;
137
+ }
138
+
139
+ function splitCommaRespectingQuotes(s) {
140
+ const parts = [];
141
+ let cur = '', depth = 0;
142
+ let inQ = false, q = '';
143
+ for (let i = 0; i < s.length; i++) {
144
+ const c = s[i];
145
+ if (inQ) {
146
+ cur += c;
147
+ if (c === q) inQ = false;
148
+ } else if (c === '"' || c === "'") {
149
+ inQ = true; q = c; cur += c;
150
+ } else if (c === '[') { depth++; cur += c; }
151
+ else if (c === ']') { depth--; cur += c; }
152
+ else if (c === ',' && depth === 0) { parts.push(cur); cur = ''; }
153
+ else cur += c;
154
+ }
155
+ if (cur.trim()) parts.push(cur);
156
+ return parts;
157
+ }
158
+
159
+ const nav = (obj, parts) => {
160
+ let cur = obj;
161
+ for (let i = 0; i < parts.length - 1; i++) {
162
+ if (!(parts[i] in cur)) cur[parts[i]] = {};
163
+ cur = cur[parts[i]];
164
+ }
165
+ return cur;
166
+ };
167
+
168
+ let i = 0;
169
+ while (i < lines.length) {
170
+ // strip comment: find first # not inside a quoted string
171
+ let line = stripTomlComment(lines[i]).trim();
172
+ i++;
173
+ if (!line) continue;
174
+
175
+ // [[array table]]
176
+ if (line[0] === '[' && line[1] === '[' && line[line.length - 1] === ']' && line[line.length - 2] === ']') {
177
+ const key = line.slice(2, -2).trim();
178
+ const parts = key.split('.');
179
+ const last = parts.pop();
180
+ const p = nav(root, parts.length ? [...parts, last] : [last]);
181
+ const k = parts.length ? last : key.split('.')[0];
182
+ const arr = nav(root, parts.length ? parts : []);
183
+ const target = parts.length ? arr[last] : root[key];
184
+ if (!Array.isArray(target)) {
185
+ const container = parts.length ? nav(root, parts) : root;
186
+ if (!Array.isArray(container[last])) container[last] = [];
187
+ container[last].push({});
188
+ current = container[last][container[last].length - 1];
189
+ }
190
+ continue;
191
+ }
192
+
193
+ // [table]
194
+ if (line[0] === '[' && line[line.length - 1] === ']') {
195
+ const key = line.slice(1, -1).trim();
196
+ const parts = key.split('.');
197
+ current = root;
198
+ for (const part of parts) {
199
+ if (!(part in current)) current[part] = {};
200
+ current = current[part];
201
+ }
202
+ continue;
203
+ }
204
+
205
+ // key = value
206
+ const eq = line.indexOf('=');
207
+ if (eq === -1) continue;
208
+ const key = line.slice(0, eq).trim();
209
+ const val = line.slice(eq + 1).trim();
210
+ current[key] = coerce(val);
211
+ }
212
+
213
+ return root;
214
+ }
215
+
216
+ function stripTomlComment(line) {
217
+ let inQ = false, q = '';
218
+ for (let i = 0; i < line.length; i++) {
219
+ const c = line[i];
220
+ if (inQ) { if (c === q) inQ = false; }
221
+ else if (c === '"' || c === "'") { inQ = true; q = c; }
222
+ else if (c === '#') return line.slice(0, i);
223
+ }
224
+ return line;
225
+ }
226
+
227
+ function tomlStringify(obj, indent = '') {
228
+ const lines = [];
229
+ const nested = [];
230
+
231
+ for (const [k, v] of Object.entries(obj)) {
232
+ if (v === null || v === undefined) continue;
233
+ if (typeof v === 'object' && !Array.isArray(v)) {
234
+ nested.push([k, v]);
235
+ } else if (Array.isArray(v) && v.length > 0 && typeof v[0] === 'object') {
236
+ for (const item of v) {
237
+ lines.push(`[[${k}]]`);
238
+ lines.push(tomlStringify(item, ''));
239
+ }
240
+ } else if (typeof v === 'string') {
241
+ lines.push(`${k} = "${escapeTomlString(v)}"`);
242
+ } else if (Array.isArray(v)) {
243
+ const items = v.map(x => typeof x === 'string' ? `"${x}"` : String(x));
244
+ lines.push(`${k} = [${items.join(', ')}]`);
245
+ } else {
246
+ lines.push(`${k} = ${v}`);
247
+ }
248
+ }
249
+
250
+ for (const [k, v] of nested) {
251
+ lines.push(`\n[${indent ? indent + '.' : ''}${k}]`);
252
+ lines.push(tomlStringify(v, indent ? indent + '.' + k : k));
253
+ }
254
+
255
+ return lines.join('\n');
256
+ }
257
+
258
+ function escapeTomlString(s) {
259
+ let out = '';
260
+ for (let i = 0; i < s.length; i++) {
261
+ const c = s[i];
262
+ if (c === '"') out += '\\"';
263
+ else if (c === '\\') out += '\\\\';
264
+ else out += c;
265
+ }
266
+ return out;
267
+ }
268
+
269
+ // ─── INI ─────────────────────────────────────────────────────────────────────
270
+
271
+ function iniParse(input, opts = {}) {
272
+ const comment = opts.comment ?? [';', '#'];
273
+ const delim = opts.delim ?? '=';
274
+ const lowercase = opts.lowercase ?? false;
275
+
276
+ const isComment = (line) => {
277
+ for (const c of comment) if (line.startsWith(c)) return true;
278
+ return false;
279
+ };
280
+
281
+ const lines = splitLines(input);
282
+ const result = { __global__: {} };
283
+ let section = '__global__';
284
+
285
+ for (let raw of lines) {
286
+ raw = raw.trim();
287
+ if (!raw || isComment(raw)) continue;
288
+
289
+ if (raw[0] === '[' && raw[raw.length - 1] === ']') {
290
+ section = raw.slice(1, -1).trim();
291
+ if (lowercase) section = section.toLowerCase();
292
+ if (!(section in result)) result[section] = {};
293
+ continue;
294
+ }
295
+
296
+ const idx = raw.indexOf(delim);
297
+ if (idx === -1) continue;
298
+
299
+ let key = raw.slice(0, idx).trim();
300
+ let val = raw.slice(idx + 1).trim();
301
+ if (lowercase) key = key.toLowerCase();
302
+
303
+ // strip inline comments
304
+ for (const c of comment) {
305
+ const ci = val.indexOf(' ' + c);
306
+ if (ci !== -1) val = val.slice(0, ci).trim();
307
+ }
308
+
309
+ // strip surrounding quotes
310
+ const q = val[0];
311
+ if ((q === '"' || q === "'") && val[val.length - 1] === q) val = val.slice(1, -1);
312
+
313
+ result[section][key] = val;
314
+ }
315
+
316
+ return result;
317
+ }
318
+
319
+ function iniStringify(obj, opts = {}) {
320
+ const delim = opts.delim ?? '=';
321
+ const lines = [];
322
+
323
+ const global = obj['__global__'] ?? {};
324
+ for (const [k, v] of Object.entries(global)) lines.push(`${k} ${delim} ${v}`);
325
+ if (lines.length) lines.push('');
326
+
327
+ for (const [section, entries] of Object.entries(obj)) {
328
+ if (section === '__global__') continue;
329
+ lines.push(`[${section}]`);
330
+ for (const [k, v] of Object.entries(entries)) lines.push(`${k} ${delim} ${v}`);
331
+ lines.push('');
332
+ }
333
+
334
+ return lines.join('\n').trimEnd();
335
+ }
336
+
337
+ // ─── QUERY STRING ────────────────────────────────────────────────────────────
338
+
339
+ function qsParse(input, opts = {}) {
340
+ const sep = opts.separator ?? '&';
341
+ const eq = opts.eq ?? '=';
342
+ const decode = opts.decode ?? true;
343
+ const arrays = opts.arrays ?? true;
344
+
345
+ const str = input[0] === '?' ? input.slice(1) : input;
346
+ if (!str) return {};
347
+
348
+ const result = {};
349
+ for (const pair of str.split(sep)) {
350
+ const idx = pair.indexOf(eq);
351
+ let key = idx === -1 ? pair : pair.slice(0, idx);
352
+ let val = idx === -1 ? '' : pair.slice(idx + 1);
353
+ if (decode) { try { key = decodeURIComponent(key.split('+').join(' ')); } catch {} }
354
+ if (decode) { try { val = decodeURIComponent(val.split('+').join(' ')); } catch {} }
355
+
356
+ if (arrays && key in result) {
357
+ if (!Array.isArray(result[key])) result[key] = [result[key]];
358
+ result[key].push(val);
359
+ } else {
360
+ result[key] = val;
361
+ }
362
+ }
363
+ return result;
364
+ }
365
+
366
+ function qsStringify(obj, opts = {}) {
367
+ const sep = opts.separator ?? '&';
368
+ const eq = opts.eq ?? '=';
369
+ const encode = opts.encode ?? true;
370
+
371
+ const enc = encode ? encodeURIComponent : (x) => String(x);
372
+ const pairs = [];
373
+
374
+ for (const [k, v] of Object.entries(obj)) {
375
+ if (Array.isArray(v)) {
376
+ for (const item of v) pairs.push(`${enc(k)}${eq}${enc(item)}`);
377
+ } else if (v !== null && v !== undefined) {
378
+ pairs.push(`${enc(k)}${eq}${enc(v)}`);
379
+ }
380
+ }
381
+
382
+ return pairs.join(sep);
383
+ }
384
+
385
+ // ─── TOKENIZER / LEXER HELPER ─────────────────────────────────────────────────
386
+
387
+ class TokenStream {
388
+ constructor(tokens) {
389
+ this._tokens = tokens;
390
+ this._pos = 0;
391
+ }
392
+ get pos() { return this._pos; }
393
+ get length() { return this._tokens.length; }
394
+ peek(offset = 0) { return this._tokens[this._pos + offset] ?? null; }
395
+ next() { return this._tokens[this._pos++] ?? null; }
396
+ back() { if (this._pos > 0) this._pos--; }
397
+ done() { return this._pos >= this._tokens.length; }
398
+ expect(type) {
399
+ const t = this.next();
400
+ if (!t) throw new Error(`Expected token type '${type}' but reached end of stream`);
401
+ if (t.type !== type) throw new Error(`Expected '${type}' but got '${t.type}' ("${t.value}") at pos ${t.pos ?? this._pos}`);
402
+ return t;
403
+ }
404
+ consume(type) {
405
+ if (this.peek()?.type === type) return this.next();
406
+ return null;
407
+ }
408
+ takeWhile(pred) {
409
+ const out = [];
410
+ while (!this.done() && pred(this.peek())) out.push(this.next());
411
+ return out;
412
+ }
413
+ rest() { return this._tokens.slice(this._pos); }
414
+ reset() { this._pos = 0; }
415
+ clone() { const s = new TokenStream(this._tokens); s._pos = this._pos; return s; }
416
+ }
417
+
418
+ function tokenize(input, rules, opts = {}) {
419
+ // rules: [ { type: string, pattern: RegExp, skip?: bool, transform?: fn } ]
420
+ // opts.onUnknown: 'throw' | 'skip' | 'raw' (default 'throw')
421
+ const onUnknown = opts.onUnknown ?? 'throw';
422
+ const tokens = [];
423
+ let pos = 0;
424
+
425
+ outer: while (pos < input.length) {
426
+ for (const rule of rules) {
427
+ const re = new RegExp(rule.pattern.source, 'y');
428
+ re.lastIndex = pos;
429
+ const match = re.exec(input);
430
+ if (!match) continue;
431
+
432
+ const raw = match[0];
433
+ if (!rule.skip) {
434
+ const value = rule.transform ? rule.transform(raw, match) : raw;
435
+ tokens.push({ type: rule.type, value, pos });
436
+ }
437
+ pos += raw.length;
438
+ continue outer;
439
+ }
440
+
441
+ if (onUnknown === 'throw') {
442
+ throw new Error(`Unexpected character '${input[pos]}' at position ${pos}`);
443
+ } else if (onUnknown === 'raw') {
444
+ tokens.push({ type: 'UNKNOWN', value: input[pos], pos });
445
+ }
446
+ pos++;
447
+ }
448
+
449
+ return new TokenStream(tokens);
450
+ }
451
+
452
+ // ─── EXPRESSION RESOLVER ─────────────────────────────────────────────────────
453
+ // Parses and evaluates infix expressions with user-defined operators.
454
+ //
455
+ // Built-in ops: + - * / % ** ( )
456
+ // Custom op definition:
457
+ // ExprResolver.define(symbol, precedence, fn(left, right))
458
+ // ExprResolver.defineUnary(symbol, fn(operand))
459
+ //
460
+ // Usage:
461
+ // const r = new ExprResolver()
462
+ // r.define('|>', 0, (a, b) => b(a)) // pipe
463
+ // r.define('??', 1, (a, b) => a ?? b) // nullish coalesce
464
+ // r.defineUnary('!', (a) => !a)
465
+ // r.eval('3 + 4 * 2') // → 11
466
+ // r.eval('(1 + 2) ** 3') // → 27
467
+
468
+ class ExprResolver {
469
+ constructor() {
470
+ // ops: Map<symbol, { prec: number, assoc: 'left'|'right', fn: (l,r)=>any }>
471
+ this._ops = new Map();
472
+ // unary: Map<symbol, fn>
473
+ this._unary = new Map();
474
+
475
+ // built-in binary ops
476
+ this.define('+', 10, (a, b) => a + b);
477
+ this.define('-', 10, (a, b) => a - b);
478
+ this.define('*', 20, (a, b) => a * b);
479
+ this.define('/', 20, (a, b) => a / b);
480
+ this.define('%', 20, (a, b) => a % b);
481
+ this.define('**', 30, (a, b) => a ** b, 'right');
482
+
483
+ // built-in unary
484
+ this.defineUnary('-', (a) => -a);
485
+ this.defineUnary('!', (a) => !a);
486
+ this.defineUnary('~', (a) => ~a);
487
+ }
488
+
489
+ // define or override a binary infix operator
490
+ define(sym, prec, fn, assoc = 'left') {
491
+ this._ops.set(sym, { prec, assoc, fn });
492
+ return this;
493
+ }
494
+
495
+ // define or override a prefix unary operator
496
+ defineUnary(sym, fn) {
497
+ this._unary.set(sym, fn);
498
+ return this;
499
+ }
500
+
501
+ // remove an operator
502
+ undefine(sym) { this._ops.delete(sym); this._unary.delete(sym); return this; }
503
+
504
+ // list all defined binary ops
505
+ ops() { return [...this._ops.entries()].map(([sym, def]) => ({ sym, ...def })); }
506
+
507
+ // evaluate an expression string, optionally with a variable scope object
508
+ eval(input, scope = {}) {
509
+ const tokens = this._lex(input.trim());
510
+ const stream = new TokenStream(tokens);
511
+ const result = this._parseInfix(stream, 0, scope);
512
+ if (!stream.done()) {
513
+ const t = stream.peek();
514
+ throw new Error(`Unexpected token '${t.value}' at pos ${t.pos}`);
515
+ }
516
+ return result;
517
+ }
518
+
519
+ // parse an expression and return the AST without evaluating
520
+ parse(input) {
521
+ const tokens = this._lex(input.trim());
522
+ const stream = new TokenStream(tokens);
523
+ return this._parseASTInfix(stream, 0);
524
+ }
525
+
526
+ _lex(input) {
527
+ // collect all operator symbols (longest first to handle ** before *)
528
+ const symsSorted = [...this._ops.keys(), ...this._unary.keys()]
529
+ .filter((v, i, a) => a.indexOf(v) === i)
530
+ .sort((a, b) => b.length - a.length);
531
+
532
+ const tokens = [];
533
+ let pos = 0;
534
+
535
+ while (pos < input.length) {
536
+ // skip whitespace
537
+ if (isWS(input[pos])) { pos++; continue; }
538
+
539
+ // number literal: digits, optional dot, optional e/E exponent
540
+ if (isDigit(input[pos]) || (input[pos] === '.' && isDigit(input[pos + 1] ?? ''))) {
541
+ let raw = '';
542
+ while (pos < input.length && (isDigit(input[pos]) || input[pos] === '.')) raw += input[pos++];
543
+ if ((input[pos] === 'e' || input[pos] === 'E') && pos < input.length) {
544
+ raw += input[pos++];
545
+ if (input[pos] === '+' || input[pos] === '-') raw += input[pos++];
546
+ while (pos < input.length && isDigit(input[pos])) raw += input[pos++];
547
+ }
548
+ tokens.push({ type: 'NUM', value: parseFloat(raw), pos: pos - raw.length });
549
+ continue;
550
+ }
551
+
552
+ // string literal
553
+ if (input[pos] === '"' || input[pos] === "'") {
554
+ const q = input[pos++];
555
+ let s = '';
556
+ while (pos < input.length && input[pos] !== q) {
557
+ if (input[pos] === '\\') { pos++; s += escSeq(input[pos++]); }
558
+ else s += input[pos++];
559
+ }
560
+ pos++; // closing quote
561
+ tokens.push({ type: 'STR', value: s, pos: pos - s.length - 2 });
562
+ continue;
563
+ }
564
+
565
+ // parens
566
+ if (input[pos] === '(') { tokens.push({ type: 'LPAREN', value: '(', pos }); pos++; continue; }
567
+ if (input[pos] === ')') { tokens.push({ type: 'RPAREN', value: ')', pos }); pos++; continue; }
568
+
569
+ // booleans / null / identifiers
570
+ if (isAlpha(input[pos]) || input[pos] === '_') {
571
+ let name = '';
572
+ while (pos < input.length && (isAlpha(input[pos]) || isDigit(input[pos]) || input[pos] === '_')) name += input[pos++];
573
+ if (name === 'true') { tokens.push({ type: 'BOOL', value: true, pos: pos - name.length }); continue; }
574
+ if (name === 'false') { tokens.push({ type: 'BOOL', value: false, pos: pos - name.length }); continue; }
575
+ if (name === 'null' || name === 'nil') { tokens.push({ type: 'NULL', value: null, pos: pos - name.length }); continue; }
576
+ tokens.push({ type: 'IDENT', value: name, pos: pos - name.length });
577
+ continue;
578
+ }
579
+
580
+ // operators (longest match first)
581
+ let matched = false;
582
+ for (const sym of symsSorted) {
583
+ if (input.startsWith(sym, pos)) {
584
+ tokens.push({ type: 'OP', value: sym, pos });
585
+ pos += sym.length;
586
+ matched = true;
587
+ break;
588
+ }
589
+ }
590
+ if (!matched) throw new Error(`Unexpected character '${input[pos]}' at position ${pos}`);
591
+ }
592
+
593
+ return tokens;
594
+ }
595
+
596
+ _parseAtom(stream, scope) {
597
+ const t = stream.next();
598
+ if (!t) throw new Error('Unexpected end of expression');
599
+
600
+ if (t.type === 'NUM' || t.type === 'STR' || t.type === 'BOOL' || t.type === 'NULL') return t.value;
601
+
602
+ if (t.type === 'IDENT') {
603
+ const val = scope[t.value];
604
+ if (val === undefined && !(t.value in scope)) throw new Error(`Undefined variable '${t.value}'`);
605
+ return val;
606
+ }
607
+
608
+ if (t.type === 'LPAREN') {
609
+ const val = this._parseInfix(stream, 0, scope);
610
+ stream.expect('RPAREN');
611
+ return val;
612
+ }
613
+
614
+ if (t.type === 'OP') {
615
+ const unary = this._unary.get(t.value);
616
+ if (unary) {
617
+ // unary binds tighter than any binary; use max prec
618
+ const operand = this._parseAtom(stream, scope);
619
+ return unary(operand);
620
+ }
621
+ throw new Error(`Operator '${t.value}' used in prefix position but not defined as unary`);
622
+ }
623
+
624
+ throw new Error(`Unexpected token '${t.value}' (type: ${t.type})`);
625
+ }
626
+
627
+ _parseInfix(stream, minPrec, scope) {
628
+ let left = this._parseAtom(stream, scope);
629
+
630
+ while (true) {
631
+ const op = stream.peek();
632
+ if (!op || op.type !== 'OP') break;
633
+ const def = this._ops.get(op.value);
634
+ if (!def) break;
635
+ const { prec, assoc, fn } = def;
636
+ if (prec < minPrec) break;
637
+ if (prec === minPrec && assoc === 'left') break;
638
+ stream.next();
639
+ const nextMinPrec = assoc === 'right' ? prec : prec + 1;
640
+ const right = this._parseInfix(stream, nextMinPrec, scope);
641
+ left = fn(left, right);
642
+ }
643
+
644
+ return left;
645
+ }
646
+
647
+ // AST-only parse (no eval) — returns node objects
648
+ _parseASTAtom(stream) {
649
+ const t = stream.next();
650
+ if (!t) throw new Error('Unexpected end of expression');
651
+
652
+ if (t.type === 'NUM') return { type: 'Literal', kind: 'number', value: t.value };
653
+ if (t.type === 'STR') return { type: 'Literal', kind: 'string', value: t.value };
654
+ if (t.type === 'BOOL') return { type: 'Literal', kind: 'boolean', value: t.value };
655
+ if (t.type === 'NULL') return { type: 'Literal', kind: 'null', value: null };
656
+ if (t.type === 'IDENT') return { type: 'Identifier', name: t.value };
657
+
658
+ if (t.type === 'LPAREN') {
659
+ const expr = this._parseASTInfix(stream, 0);
660
+ stream.expect('RPAREN');
661
+ return { type: 'Group', expr };
662
+ }
663
+
664
+ if (t.type === 'OP' && this._unary.has(t.value)) {
665
+ const operand = this._parseASTAtom(stream);
666
+ return { type: 'UnaryExpr', op: t.value, operand };
667
+ }
668
+
669
+ throw new Error(`Unexpected token '${t.value}'`);
670
+ }
671
+
672
+ _parseASTInfix(stream, minPrec) {
673
+ let left = this._parseASTAtom(stream);
674
+
675
+ while (true) {
676
+ const op = stream.peek();
677
+ if (!op || op.type !== 'OP') break;
678
+ const def = this._ops.get(op.value);
679
+ if (!def) break;
680
+ const { prec, assoc } = def;
681
+ if (prec < minPrec) break;
682
+ if (prec === minPrec && assoc === 'left') break;
683
+ stream.next();
684
+ const nextMinPrec = assoc === 'right' ? prec : prec + 1;
685
+ const right = this._parseASTInfix(stream, nextMinPrec);
686
+ left = { type: 'BinaryExpr', op: op.value, left, right };
687
+ }
688
+
689
+ return left;
690
+ }
691
+ }
692
+
693
+ // legacy single-function wrapper (built-in arithmetic only)
694
+ function parseExpr(input) {
695
+ return new ExprResolver().eval(input);
696
+ }
697
+
698
+ // ─── XML ─────────────────────────────────────────────────────────────────────
699
+
700
+ function xmlParse(input) {
701
+ const s = input.trim();
702
+ let pos = 0;
703
+
704
+ function skipWS() { while (pos < s.length && isWS(s[pos])) pos++; }
705
+
706
+ function parseAttr() {
707
+ const attrs = {};
708
+ while (pos < s.length && s[pos] !== '>' && s[pos] !== '/') {
709
+ skipWS();
710
+ if (s[pos] === '>' || s[pos] === '/') break;
711
+ let name = '';
712
+ while (pos < s.length && !isWS(s[pos]) && s[pos] !== '=' && s[pos] !== '>' && s[pos] !== '/') name += s[pos++];
713
+ if (!name) { pos++; continue; }
714
+ skipWS();
715
+ if (s[pos] === '=') {
716
+ pos++;
717
+ skipWS();
718
+ const q = s[pos++];
719
+ let val = '';
720
+ while (pos < s.length && s[pos] !== q) val += s[pos++];
721
+ pos++; // closing quote
722
+ attrs[name] = xmlDecodeEntities(val);
723
+ } else {
724
+ attrs[name] = true;
725
+ }
726
+ skipWS();
727
+ }
728
+ return attrs;
729
+ }
730
+
731
+ function parseNode() {
732
+ skipWS();
733
+ // processing instruction / doctype — skip
734
+ if (s[pos] === '<' && (s[pos + 1] === '?' || s[pos + 1] === '!')) {
735
+ while (pos < s.length && s[pos] !== '>') pos++;
736
+ pos++;
737
+ return parseNode();
738
+ }
739
+ if (s[pos] !== '<') throw new Error(`Expected '<' at pos ${pos}, got '${s[pos]}'`);
740
+ pos++;
741
+ let tag = '';
742
+ while (pos < s.length && !isWS(s[pos]) && s[pos] !== '>' && s[pos] !== '/') tag += s[pos++];
743
+ skipWS();
744
+ const attrs = parseAttr();
745
+ skipWS();
746
+ // self-closing
747
+ if (s[pos] === '/') { pos += 2; return { tag, attrs, children: [], text: '' }; }
748
+ pos++; // skip >
749
+ const children = [];
750
+ let text = '';
751
+ while (pos < s.length) {
752
+ if (s[pos] === '<' && s[pos + 1] === '/') { // closing tag
753
+ pos += 2;
754
+ while (pos < s.length && s[pos] !== '>') pos++;
755
+ pos++;
756
+ break;
757
+ }
758
+ if (s[pos] === '<') {
759
+ if (s[pos + 1] === '!' && s[pos + 2] === '-' && s[pos + 3] === '-') {
760
+ // comment
761
+ const end = indexOfStr(s, '-->', pos);
762
+ pos = end === -1 ? s.length : end + 3;
763
+ continue;
764
+ }
765
+ const child = parseNode();
766
+ if (child) children.push(child);
767
+ } else {
768
+ let raw = '';
769
+ while (pos < s.length && s[pos] !== '<') raw += s[pos++];
770
+ text += xmlDecodeEntities(raw);
771
+ }
772
+ }
773
+ return { tag, attrs, children, text: text.trim() };
774
+ }
775
+
776
+ return parseNode();
777
+ }
778
+
779
+ function xmlDecodeEntities(s) {
780
+ let out = '';
781
+ let i = 0;
782
+ while (i < s.length) {
783
+ if (s[i] === '&') {
784
+ let entity = '';
785
+ i++;
786
+ while (i < s.length && s[i] !== ';') entity += s[i++];
787
+ i++; // skip ;
788
+ if (entity === 'amp') out += '&';
789
+ else if (entity === 'lt') out += '<';
790
+ else if (entity === 'gt') out += '>';
791
+ else if (entity === 'quot') out += '"';
792
+ else if (entity === 'apos') out += "'";
793
+ else out += '&' + entity + ';';
794
+ } else {
795
+ out += s[i++];
796
+ }
797
+ }
798
+ return out;
799
+ }
800
+
801
+ function xmlStringify(node, indent = '', opts = {}) {
802
+ const pretty = opts.pretty ?? true;
803
+ const nl = pretty ? '\n' : '';
804
+ const step = opts.indent ?? ' ';
805
+
806
+ const escape = (s) => {
807
+ let out = '';
808
+ for (let i = 0; i < String(s).length; i++) {
809
+ const c = String(s)[i];
810
+ if (c === '&') out += '&amp;';
811
+ else if (c === '<') out += '&lt;';
812
+ else if (c === '>') out += '&gt;';
813
+ else if (c === '"') out += '&quot;';
814
+ else out += c;
815
+ }
816
+ return out;
817
+ };
818
+
819
+ const attrStr = Object.entries(node.attrs ?? {})
820
+ .map(([k, v]) => v === true ? ` ${k}` : ` ${k}="${escape(v)}"`)
821
+ .join('');
822
+
823
+ const next = indent + step;
824
+
825
+ if (!node.children?.length && !node.text) return `${indent}<${node.tag}${attrStr}/>${nl}`;
826
+ if (!node.children?.length) return `${indent}<${node.tag}${attrStr}>${escape(node.text)}</${node.tag}>${nl}`;
827
+
828
+ const body = node.children.map(c => xmlStringify(c, next, opts)).join('');
829
+ return `${indent}<${node.tag}${attrStr}>${nl}${body}${indent}</${node.tag}>${nl}`;
830
+ }
831
+
832
+ // xmlQuery: CSS-like selector on the parsed tree.
833
+ // Supports: tag, tag > child, descendant, [attr], [attr=val]
834
+ function xmlQuery(node, selector) {
835
+ // manual selector tokenizer: split on ' > ' for child combinator, ' ' for descendant
836
+ const results = [];
837
+ const parts = splitSelectorParts(selector.trim());
838
+
839
+ function matchSingle(n, part) {
840
+ // parse tag name (up to '[' or end)
841
+ let i = 0;
842
+ let tag = '';
843
+ while (i < part.length && part[i] !== '[') tag += part[i++];
844
+ tag = tag.trim() || '*';
845
+ if (tag !== '*' && n.tag !== tag) return false;
846
+
847
+ // parse attribute selectors [key] or [key=val]
848
+ while (i < part.length && part[i] === '[') {
849
+ i++; // skip [
850
+ let key = '';
851
+ while (i < part.length && part[i] !== '=' && part[i] !== ']') key += part[i++];
852
+ key = key.trim();
853
+ if (part[i] === '=') {
854
+ i++; // skip =
855
+ let val = '';
856
+ // optional quotes
857
+ const q = (part[i] === '"' || part[i] === "'") ? part[i++] : null;
858
+ while (i < part.length && part[i] !== (q ?? ']') && part[i] !== ']') val += part[i++];
859
+ if (q && part[i] === q) i++;
860
+ if (part[i] === ']') i++;
861
+ if (!(key in (n.attrs ?? {}))) return false;
862
+ if (n.attrs[key] !== val) return false;
863
+ } else {
864
+ if (part[i] === ']') i++;
865
+ if (!(key in (n.attrs ?? {}))) return false;
866
+ }
867
+ }
868
+ return true;
869
+ }
870
+
871
+ function walk(n, sels, isChild) {
872
+ if (!sels.length) { results.push(n); return; }
873
+ const { sel, combinator } = sels[0];
874
+ const rest = sels.slice(1);
875
+ if (matchSingle(n, sel)) {
876
+ if (!rest.length) results.push(n);
877
+ else for (const c of (n.children ?? [])) walk(c, rest, rest[0]?.combinator === '>');
878
+ }
879
+ // descendant: recurse into children even if this node didn't match
880
+ if (!isChild) {
881
+ for (const c of (n.children ?? [])) walk(c, sels, false);
882
+ }
883
+ }
884
+
885
+ walk(node, parts, false);
886
+ return results;
887
+ }
888
+
889
+ function splitSelectorParts(selector) {
890
+ // returns [{ sel: string, combinator: '>'|' ' }]
891
+ const parts = [];
892
+ let i = 0;
893
+ let current = '';
894
+ while (i <= selector.length) {
895
+ const c = selector[i];
896
+ if (i === selector.length || (isWS(c) && selector[i - 1] !== ']')) {
897
+ // look ahead for >
898
+ while (i < selector.length && isWS(selector[i])) i++;
899
+ const comb = selector[i] === '>' ? '>' : ' ';
900
+ if (comb === '>') i++;
901
+ while (i < selector.length && isWS(selector[i])) i++;
902
+ if (current.trim()) parts.push({ sel: current.trim(), combinator: comb });
903
+ current = '';
904
+ } else {
905
+ current += c;
906
+ i++;
907
+ }
908
+ }
909
+ return parts;
910
+ }
911
+
912
+ // ─── JSON (extended) ──────────────────────────────────────────────────────────
913
+
914
+ function jsonParse(input, opts = {}) {
915
+ const { dates = false, bigints = false } = opts;
916
+ return JSON.parse(input, (key, val) => {
917
+ if (dates && typeof val === 'string' && isDateString(val)) return new Date(val);
918
+ if (bigints && typeof val === 'string' && isBigIntString(val)) { try { return BigInt(val); } catch {} }
919
+ return val;
920
+ });
921
+ }
922
+
923
+ function jsonStringify(val, opts = {}) {
924
+ const { pretty = false, sorted = false, indent = 2 } = opts;
925
+ const replacer = sorted
926
+ ? (key, v) => (v && typeof v === 'object' && !Array.isArray(v)
927
+ ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b)))
928
+ : v)
929
+ : undefined;
930
+ return JSON.stringify(val, replacer, pretty ? indent : undefined);
931
+ }
932
+
933
+ function jsonMerge(base, ...patches) {
934
+ const out = Array.isArray(base) ? [...base] : { ...base };
935
+ for (const patch of patches) {
936
+ for (const [k, v] of Object.entries(patch ?? {})) {
937
+ if (v && typeof v === 'object' && !Array.isArray(v) && out[k] && typeof out[k] === 'object') {
938
+ out[k] = jsonMerge(out[k], v);
939
+ } else {
940
+ out[k] = v;
941
+ }
942
+ }
943
+ }
944
+ return out;
945
+ }
946
+
947
+ function jsonDiff(a, b, path = '') {
948
+ const added = {}, removed = {}, changed = {};
949
+ const allKeys = new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);
950
+ for (const k of allKeys) {
951
+ const full = path ? `${path}.${k}` : k;
952
+ if (!(k in (a ?? {}))) { added[full] = b[k]; continue; }
953
+ if (!(k in (b ?? {}))) { removed[full] = a[k]; continue; }
954
+ if (typeof a[k] === 'object' && typeof b[k] === 'object' && a[k] !== null && b[k] !== null) {
955
+ const sub = jsonDiff(a[k], b[k], full);
956
+ Object.assign(added, sub.added); Object.assign(removed, sub.removed); Object.assign(changed, sub.changed);
957
+ } else if (a[k] !== b[k]) {
958
+ changed[full] = { from: a[k], to: b[k] };
959
+ }
960
+ }
961
+ return { added, removed, changed };
962
+ }
963
+
964
+ // jsonPath: manual JSONPath-lite query ($.a.b, $[0], $.*, $..key)
965
+ function jsonPath(obj, expr) {
966
+ // tokenize the path manually instead of regex replace
967
+ const tokens = tokenizeJsonPath(expr);
968
+
969
+ function traverse(node, toks) {
970
+ if (!toks.length) return [node];
971
+ const head = toks[0];
972
+ const rest = toks.slice(1);
973
+
974
+ if (head === '$') return traverse(node, rest);
975
+
976
+ if (head === '*') {
977
+ const vals = Array.isArray(node) ? node : Object.values(node ?? {});
978
+ return vals.flatMap(v => traverse(v, rest));
979
+ }
980
+
981
+ if (head === '..') {
982
+ const all = [];
983
+ function dig(n) {
984
+ all.push(n);
985
+ if (Array.isArray(n)) n.forEach(dig);
986
+ else if (n && typeof n === 'object') Object.values(n).forEach(dig);
987
+ }
988
+ dig(node);
989
+ return all.flatMap(n => traverse(n, rest));
990
+ }
991
+
992
+ const child = Array.isArray(node) ? node[parseInt(head, 10)] : (node ?? {})[head];
993
+ return child !== undefined ? traverse(child, rest) : [];
994
+ }
995
+
996
+ const result = traverse(obj, tokens);
997
+ return result.length === 1 ? result[0] : result;
998
+ }
999
+
1000
+ function tokenizeJsonPath(expr) {
1001
+ // parse $.a.b[0].c or $..key into tokens without regex
1002
+ const tokens = [];
1003
+ let i = 0;
1004
+ while (i < expr.length) {
1005
+ if (expr[i] === '$') { tokens.push('$'); i++; }
1006
+ else if (expr[i] === '.' && expr[i + 1] === '.') { tokens.push('..'); i += 2; }
1007
+ else if (expr[i] === '.') {
1008
+ i++;
1009
+ // collect identifier
1010
+ let name = '';
1011
+ while (i < expr.length && expr[i] !== '.' && expr[i] !== '[') name += expr[i++];
1012
+ if (name) tokens.push(name);
1013
+ }
1014
+ else if (expr[i] === '[') {
1015
+ i++;
1016
+ let content = '';
1017
+ while (i < expr.length && expr[i] !== ']') content += expr[i++];
1018
+ i++; // skip ]
1019
+ // strip quotes if any
1020
+ const q = content[0];
1021
+ if ((q === '"' || q === "'") && content[content.length - 1] === q) content = content.slice(1, -1);
1022
+ tokens.push(content);
1023
+ }
1024
+ else {
1025
+ i++; // skip unknown
1026
+ }
1027
+ }
1028
+ return tokens;
1029
+ }
1030
+
1031
+ // ─── YAML ─────────────────────────────────────────────────────────────────────
1032
+
1033
+ function yamlParse(input) {
1034
+ const lines = splitLines(input);
1035
+ const anchors = {};
1036
+
1037
+ function getIndent(line) {
1038
+ let i = 0;
1039
+ while (i < line.length && line[i] === ' ') i++;
1040
+ return i;
1041
+ }
1042
+
1043
+ function coerce(s) {
1044
+ s = s.trim();
1045
+ if (s === '' || s === 'null' || s === '~') return null;
1046
+ if (s === 'true' || s === 'yes' || s === 'on') return true;
1047
+ if (s === 'false' || s === 'no' || s === 'off') return false;
1048
+
1049
+ // hex: 0x...
1050
+ if (s.length > 2 && s[0] === '0' && (s[1] === 'x' || s[1] === 'X')) {
1051
+ let valid = true;
1052
+ for (let i = 2; i < s.length; i++) {
1053
+ const c = s[i].toLowerCase();
1054
+ if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) { valid = false; break; }
1055
+ }
1056
+ if (valid) return parseInt(s, 16);
1057
+ }
1058
+
1059
+ // octal: 0o...
1060
+ if (s.length > 2 && s[0] === '0' && (s[1] === 'o' || s[1] === 'O')) {
1061
+ let valid = true;
1062
+ for (let i = 2; i < s.length; i++) {
1063
+ if (s[i] < '0' || s[i] > '7') { valid = false; break; }
1064
+ }
1065
+ if (valid) return parseInt(s.slice(2), 8);
1066
+ }
1067
+
1068
+ // integer
1069
+ {
1070
+ let i = 0;
1071
+ if (s[i] === '-') i++;
1072
+ let allDigits = i < s.length;
1073
+ while (i < s.length) { if (s[i] < '0' || s[i] > '9') { allDigits = false; break; } i++; }
1074
+ if (allDigits && s.length > (s[0] === '-' ? 1 : 0)) return parseInt(s, 10);
1075
+ }
1076
+
1077
+ // float / scientific
1078
+ {
1079
+ const n = parseFloat(s);
1080
+ if (!isNaN(n) && isNumericString(s)) return n;
1081
+ }
1082
+
1083
+ // quoted strings
1084
+ const q = s[0];
1085
+ if ((q === '"' || q === "'") && s[s.length - 1] === q) {
1086
+ const inner = s.slice(1, -1);
1087
+ if (q === '"') return unescapeYaml(inner);
1088
+ return inner;
1089
+ }
1090
+
1091
+ return s;
1092
+ }
1093
+
1094
+ function unescapeYaml(s) {
1095
+ let out = '';
1096
+ let i = 0;
1097
+ while (i < s.length) {
1098
+ if (s[i] === '\\') {
1099
+ i++;
1100
+ out += escSeq(s[i++]);
1101
+ } else out += s[i++];
1102
+ }
1103
+ return out;
1104
+ }
1105
+
1106
+ function isNumericString(s) {
1107
+ // check if string looks like a float/sci notation without regex
1108
+ let i = 0;
1109
+ if (s[i] === '-') i++;
1110
+ let hasDot = false, hasE = false, hasDigit = false;
1111
+ while (i < s.length) {
1112
+ const c = s[i];
1113
+ if (c >= '0' && c <= '9') { hasDigit = true; i++; }
1114
+ else if (c === '.' && !hasDot && !hasE) { hasDot = true; i++; }
1115
+ else if ((c === 'e' || c === 'E') && !hasE && hasDigit) {
1116
+ hasE = true; i++;
1117
+ if (s[i] === '+' || s[i] === '-') i++;
1118
+ }
1119
+ else return false;
1120
+ }
1121
+ return hasDigit;
1122
+ }
1123
+
1124
+ function parseBlock(startLine, baseIndent) {
1125
+ let i = startLine;
1126
+
1127
+ function peekLine() {
1128
+ while (i < lines.length) {
1129
+ const l = lines[i];
1130
+ const t = l.trim();
1131
+ if (t === '' || t[0] === '#') { i++; continue; }
1132
+ return l;
1133
+ }
1134
+ return null;
1135
+ }
1136
+
1137
+ const first = peekLine();
1138
+ if (first === null) return [null, i];
1139
+ const ind = getIndent(first);
1140
+ if (ind < baseIndent) return [null, i];
1141
+
1142
+ const trimFirst = first.trim();
1143
+
1144
+ // sequence
1145
+ if (trimFirst[0] === '-' && (trimFirst[1] === ' ' || trimFirst.length === 1)) {
1146
+ const arr = [];
1147
+ while (i < lines.length) {
1148
+ const l = peekLine();
1149
+ if (l === null || getIndent(l) < ind) break;
1150
+ if (l.trim()[0] !== '-') break;
1151
+ i++;
1152
+ const content = l.trim().slice(1).trim();
1153
+ if (content === '' || content[0] === '#') {
1154
+ const [val, ni] = parseBlock(i, ind + 2);
1155
+ arr.push(val); i = ni;
1156
+ } else if (content.includes(': ') || content[content.length - 1] === ':') {
1157
+ const [val, ni] = parseBlock(i - 1, ind + 2);
1158
+ arr.push(val); i = ni;
1159
+ } else {
1160
+ arr.push(coerce(content));
1161
+ }
1162
+ }
1163
+ return [arr, i];
1164
+ }
1165
+
1166
+ // mapping: line contains ': ' or ends with ':'
1167
+ if (trimFirst.includes(': ') || trimFirst[trimFirst.length - 1] === ':') {
1168
+ const obj = {};
1169
+ while (i < lines.length) {
1170
+ const l = peekLine();
1171
+ if (l === null || getIndent(l) < ind) break;
1172
+ const lt = l.trim();
1173
+ // check if it looks like a key
1174
+ const col = findMappingColon(lt);
1175
+ if (col === -1) break;
1176
+ i++;
1177
+ let key = lt.slice(0, col).trim();
1178
+ let val = lt.slice(col + 1).trim();
1179
+
1180
+ // anchor definition
1181
+ let anchorName = null;
1182
+ if (val[0] === '&') {
1183
+ let ai = 1;
1184
+ while (ai < val.length && !isWS(val[ai])) ai++;
1185
+ anchorName = val.slice(1, ai);
1186
+ val = val.slice(ai).trim();
1187
+ }
1188
+
1189
+ // alias
1190
+ if (val[0] === '*') {
1191
+ const alias = val.slice(1).trim();
1192
+ obj[key] = anchors[alias] ?? null;
1193
+ continue;
1194
+ }
1195
+
1196
+ // block scalar
1197
+ if (val === '|' || val === '>') {
1198
+ const fold = val === '>';
1199
+ const blockLines = [];
1200
+ const blockInd = ind + 2;
1201
+ while (i < lines.length && (lines[i].trim() === '' || getIndent(lines[i]) >= blockInd)) {
1202
+ blockLines.push(lines[i].slice(blockInd));
1203
+ i++;
1204
+ }
1205
+ const text = fold
1206
+ ? blockLines.join(' ').trimEnd()
1207
+ : blockLines.join('\n').trimEnd() + '\n';
1208
+ obj[key] = text;
1209
+ if (anchorName) anchors[anchorName] = obj[key];
1210
+ continue;
1211
+ }
1212
+
1213
+ if (val === '' || val[0] === '#') {
1214
+ const [child, ni] = parseBlock(i, ind + 2);
1215
+ obj[key] = child; i = ni;
1216
+ } else {
1217
+ obj[key] = coerce(val);
1218
+ }
1219
+
1220
+ if (anchorName) anchors[anchorName] = obj[key];
1221
+ }
1222
+ return [obj, i];
1223
+ }
1224
+
1225
+ // scalar fallback
1226
+ i++;
1227
+ return [coerce(trimFirst), i];
1228
+ }
1229
+
1230
+ function findMappingColon(line) {
1231
+ // find first ':' that is followed by space or end-of-line, not inside quotes
1232
+ let inQ = false, q = '';
1233
+ for (let i = 0; i < line.length; i++) {
1234
+ const c = line[i];
1235
+ if (inQ) { if (c === q) inQ = false; }
1236
+ else if (c === '"' || c === "'") { inQ = true; q = c; }
1237
+ else if (c === ':' && (i + 1 >= line.length || isWS(line[i + 1]))) return i;
1238
+ }
1239
+ return -1;
1240
+ }
1241
+
1242
+ const [result] = parseBlock(0, 0);
1243
+ return result;
1244
+ }
1245
+
1246
+ function yamlStringify(val, indent = 0, opts = {}) {
1247
+ const step = opts.indent ?? 2;
1248
+ const pad = ' '.repeat(indent);
1249
+ const next = ' '.repeat(indent + step);
1250
+
1251
+ if (val === null || val === undefined) return 'null';
1252
+ if (typeof val === 'boolean') return val ? 'true' : 'false';
1253
+ if (typeof val === 'number') return String(val);
1254
+ if (typeof val === 'string') {
1255
+ if (needsYamlQuote(val)) return `"${val.split('\n').join('\\n').split('"').join('\\"')}"`;
1256
+ return val;
1257
+ }
1258
+ if (Array.isArray(val)) {
1259
+ if (!val.length) return '[]';
1260
+ return val.map(v => {
1261
+ const s = yamlStringify(v, indent + step, opts);
1262
+ return `${pad}- ${s.trimStart()}`;
1263
+ }).join('\n');
1264
+ }
1265
+ if (typeof val === 'object') {
1266
+ const entries = Object.entries(val);
1267
+ if (!entries.length) return '{}';
1268
+ return entries.map(([k, v]) => {
1269
+ const s = yamlStringify(v, indent + step, opts);
1270
+ if (typeof v === 'object' && v !== null && !Array.isArray(v) && Object.keys(v).length) {
1271
+ return `${pad}${k}:\n${s}`;
1272
+ }
1273
+ if (Array.isArray(v) && v.length) return `${pad}${k}:\n${s}`;
1274
+ return `${pad}${k}: ${s.trimStart()}`;
1275
+ }).join('\n');
1276
+ }
1277
+ return String(val);
1278
+ }
1279
+
1280
+ function needsYamlQuote(s) {
1281
+ // check for chars that require quoting in YAML
1282
+ const special = ':#[]{}*&!|>\'",';
1283
+ if (s !== s.trim()) return true;
1284
+ for (let i = 0; i < s.length; i++) {
1285
+ if (special.includes(s[i]) || s[i] === '\n') return true;
1286
+ }
1287
+ return false;
1288
+ }
1289
+
1290
+ // ─── HJSON ────────────────────────────────────────────────────────────────────
1291
+ // Manual AST parse — no regex strip. Handles comments, trailing commas,
1292
+ // unquoted keys, multiline ''', bare string values.
1293
+
1294
+ function hjsonParse(input) {
1295
+ let pos = 0;
1296
+ const s = input;
1297
+
1298
+ function skipWS() {
1299
+ while (pos < s.length && isWS(s[pos])) pos++;
1300
+ }
1301
+
1302
+ function skipLineComment() {
1303
+ // skip to end of line
1304
+ while (pos < s.length && s[pos] !== '\n') pos++;
1305
+ }
1306
+
1307
+ function skipBlockComment() {
1308
+ // already consumed /*
1309
+ while (pos < s.length) {
1310
+ if (s[pos] === '*' && s[pos + 1] === '/') { pos += 2; return; }
1311
+ pos++;
1312
+ }
1313
+ }
1314
+
1315
+ function skipWsAndComments() {
1316
+ while (pos < s.length) {
1317
+ if (isWS(s[pos])) { pos++; continue; }
1318
+ if (s[pos] === '/' && s[pos + 1] === '/') { pos += 2; skipLineComment(); continue; }
1319
+ if (s[pos] === '/' && s[pos + 1] === '*') { pos += 2; skipBlockComment(); continue; }
1320
+ break;
1321
+ }
1322
+ }
1323
+
1324
+ function parseString() {
1325
+ const q = s[pos++]; // " or '
1326
+ let out = '';
1327
+ while (pos < s.length && s[pos] !== q) {
1328
+ if (s[pos] === '\\') { pos++; out += escSeq(s[pos++]); }
1329
+ else out += s[pos++];
1330
+ }
1331
+ pos++; // closing quote
1332
+ return out;
1333
+ }
1334
+
1335
+ function parseMultilineString() {
1336
+ // consume '''
1337
+ pos += 3;
1338
+ let raw = '';
1339
+ while (pos < s.length) {
1340
+ if (s[pos] === "'" && s[pos + 1] === "'" && s[pos + 2] === "'") { pos += 3; break; }
1341
+ raw += s[pos++];
1342
+ }
1343
+ // strip leading newline per HJSON spec
1344
+ if (raw[0] === '\n') raw = raw.slice(1);
1345
+ return raw;
1346
+ }
1347
+
1348
+ function parseKey() {
1349
+ skipWsAndComments();
1350
+ // quoted key
1351
+ if (s[pos] === '"' || s[pos] === "'") return parseString();
1352
+ // unquoted key: read until colon or whitespace
1353
+ let key = '';
1354
+ while (pos < s.length && s[pos] !== ':' && s[pos] !== '\n' && !isWS(s[pos])) key += s[pos++];
1355
+ return key;
1356
+ }
1357
+
1358
+ function parseBareString() {
1359
+ // read to end of line (minus inline comments)
1360
+ let raw = '';
1361
+ while (pos < s.length && s[pos] !== '\n' && s[pos] !== ',' && s[pos] !== '}' && s[pos] !== ']') {
1362
+ if (s[pos] === '/' && (s[pos + 1] === '/' || s[pos + 1] === '*')) break;
1363
+ raw += s[pos++];
1364
+ }
1365
+ return raw.trim();
1366
+ }
1367
+
1368
+ function parseValue() {
1369
+ skipWsAndComments();
1370
+ const c = s[pos];
1371
+
1372
+ // multiline string
1373
+ if (c === "'" && s[pos + 1] === "'" && s[pos + 2] === "'") return parseMultilineString();
1374
+
1375
+ // quoted string
1376
+ if (c === '"' || c === "'") return parseString();
1377
+
1378
+ // array
1379
+ if (c === '[') return parseArray();
1380
+
1381
+ // object
1382
+ if (c === '{') return parseObject();
1383
+
1384
+ // true / false / null / number
1385
+ // read a "word" or number
1386
+ let word = '';
1387
+ const start = pos;
1388
+ while (pos < s.length && s[pos] !== ',' && s[pos] !== '\n' && s[pos] !== '}' && s[pos] !== ']') {
1389
+ if (s[pos] === '/' && (s[pos + 1] === '/' || s[pos + 1] === '*')) break;
1390
+ word += s[pos++];
1391
+ }
1392
+ word = word.trim();
1393
+
1394
+ if (word === 'true') return true;
1395
+ if (word === 'false') return false;
1396
+ if (word === 'null') return null;
1397
+ const n = Number(word);
1398
+ if (word !== '' && !isNaN(n)) return n;
1399
+
1400
+ // bare string
1401
+ return word;
1402
+ }
1403
+
1404
+ function parseArray() {
1405
+ pos++; // skip [
1406
+ const arr = [];
1407
+ while (true) {
1408
+ skipWsAndComments();
1409
+ if (pos >= s.length || s[pos] === ']') { pos++; break; }
1410
+ if (s[pos] === ',') { pos++; continue; } // skip comma / trailing comma
1411
+ arr.push(parseValue());
1412
+ }
1413
+ return arr;
1414
+ }
1415
+
1416
+ function parseObject() {
1417
+ pos++; // skip {
1418
+ const obj = {};
1419
+ while (true) {
1420
+ skipWsAndComments();
1421
+ if (pos >= s.length || s[pos] === '}') { pos++; break; }
1422
+ if (s[pos] === ',') { pos++; continue; }
1423
+ const key = parseKey();
1424
+ skipWsAndComments();
1425
+ if (s[pos] === ':') pos++; // skip :
1426
+ skipWsAndComments();
1427
+ obj[key] = parseValue();
1428
+ }
1429
+ return obj;
1430
+ }
1431
+
1432
+ skipWsAndComments();
1433
+ return parseValue();
1434
+ }
1435
+
1436
+ function hjsonStringify(val, indent = 2) {
1437
+ return JSON.stringify(val, null, indent);
1438
+ }
1439
+
1440
+ // ─── NDJSON ───────────────────────────────────────────────────────────────────
1441
+
1442
+ function ndjsonParse(input) {
1443
+ return splitLines(input)
1444
+ .map(l => l.trim())
1445
+ .filter(Boolean)
1446
+ .map((l, i) => {
1447
+ try { return JSON.parse(l); }
1448
+ catch (e) { throw new Error(`NDJSON line ${i + 1}: ${e.message}`); }
1449
+ });
1450
+ }
1451
+
1452
+ function ndjsonStringify(arr) {
1453
+ return arr.map(v => JSON.stringify(v)).join('\n');
1454
+ }
1455
+
1456
+ // ─── DOTENV ───────────────────────────────────────────────────────────────────
1457
+
1458
+ function dotenvParse(input, opts = {}) {
1459
+ const expand = opts.expand ?? true;
1460
+ const lines = splitLines(input);
1461
+ const env = {};
1462
+
1463
+ for (let raw of lines) {
1464
+ raw = raw.trim();
1465
+ if (!raw || raw[0] === '#') continue;
1466
+ if (raw.startsWith('export ')) raw = raw.slice(7).trim();
1467
+
1468
+ const eq = raw.indexOf('=');
1469
+ if (eq === -1) continue;
1470
+
1471
+ const key = raw.slice(0, eq).trim();
1472
+ let val = raw.slice(eq + 1).trim();
1473
+
1474
+ const q = val[0];
1475
+ if ((q === '"' || q === "'") && val[val.length - 1] === q) {
1476
+ val = val.slice(1, -1);
1477
+ if (q === '"') {
1478
+ // manual escape expansion
1479
+ let out = '';
1480
+ let i = 0;
1481
+ while (i < val.length) {
1482
+ if (val[i] === '\\') { i++; out += escSeq(val[i++]); }
1483
+ else out += val[i++];
1484
+ }
1485
+ val = out;
1486
+ }
1487
+ } else {
1488
+ // strip inline comment
1489
+ const ci = val.indexOf(' #');
1490
+ if (ci !== -1) val = val.slice(0, ci).trim();
1491
+ }
1492
+
1493
+ env[key] = val;
1494
+ }
1495
+
1496
+ if (expand) {
1497
+ for (const key of Object.keys(env)) {
1498
+ env[key] = expandDotenvVars(env[key], env);
1499
+ }
1500
+ }
1501
+
1502
+ return env;
1503
+ }
1504
+
1505
+ function expandDotenvVars(val, env) {
1506
+ let out = '';
1507
+ let i = 0;
1508
+ while (i < val.length) {
1509
+ if (val[i] === '$') {
1510
+ i++;
1511
+ if (val[i] === '{') {
1512
+ i++; // skip {
1513
+ let name = '';
1514
+ while (i < val.length && val[i] !== '}') name += val[i++];
1515
+ i++; // skip }
1516
+ out += env[name] ?? (typeof process !== 'undefined' ? process?.env?.[name] : undefined) ?? '';
1517
+ } else {
1518
+ let name = '';
1519
+ while (i < val.length && (isAlpha(val[i]) || isDigit(val[i]) || val[i] === '_')) name += val[i++];
1520
+ out += env[name] ?? (typeof process !== 'undefined' ? process?.env?.[name] : undefined) ?? '';
1521
+ }
1522
+ } else {
1523
+ out += val[i++];
1524
+ }
1525
+ }
1526
+ return out;
1527
+ }
1528
+
1529
+ function dotenvStringify(obj) {
1530
+ return Object.entries(obj).map(([k, v]) => {
1531
+ const val = String(v);
1532
+ if (needsDotenvQuote(val)) {
1533
+ let escaped = '';
1534
+ for (let i = 0; i < val.length; i++) {
1535
+ if (val[i] === '\n') escaped += '\\n';
1536
+ else if (val[i] === '"') escaped += '\\"';
1537
+ else escaped += val[i];
1538
+ }
1539
+ return `${k}="${escaped}"`;
1540
+ }
1541
+ return `${k}=${val}`;
1542
+ }).join('\n');
1543
+ }
1544
+
1545
+ function needsDotenvQuote(s) {
1546
+ for (let i = 0; i < s.length; i++) {
1547
+ const c = s[i];
1548
+ if (isWS(c) || c === '#' || c === '"' || c === "'" || c === '\\' || c === '\n') return true;
1549
+ }
1550
+ return false;
1551
+ }
1552
+
1553
+ // ─── SHARED UTILITIES ─────────────────────────────────────────────────────────
1554
+
1555
+ function splitLines(input) {
1556
+ // normalize \r\n and \r to \n then split
1557
+ const out = [];
1558
+ let i = 0, start = 0;
1559
+ while (i < input.length) {
1560
+ if (input[i] === '\r') {
1561
+ out.push(input.slice(start, i));
1562
+ if (input[i + 1] === '\n') i++;
1563
+ i++;
1564
+ start = i;
1565
+ } else if (input[i] === '\n') {
1566
+ out.push(input.slice(start, i));
1567
+ i++;
1568
+ start = i;
1569
+ } else {
1570
+ i++;
1571
+ }
1572
+ }
1573
+ out.push(input.slice(start));
1574
+ return out;
1575
+ }
1576
+
1577
+ function isWS(c) { return c === ' ' || c === '\t' || c === '\r' || c === '\n'; }
1578
+ function isDigit(c) { return c >= '0' && c <= '9'; }
1579
+ function isAlpha(c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
1580
+
1581
+ function indexOfStr(haystack, needle, start = 0) {
1582
+ for (let i = start; i <= haystack.length - needle.length; i++) {
1583
+ let match = true;
1584
+ for (let j = 0; j < needle.length; j++) {
1585
+ if (haystack[i + j] !== needle[j]) { match = false; break; }
1586
+ }
1587
+ if (match) return i;
1588
+ }
1589
+ return -1;
1590
+ }
1591
+
1592
+ function escSeq(c) {
1593
+ if (c === 'n') return '\n';
1594
+ if (c === 't') return '\t';
1595
+ if (c === 'r') return '\r';
1596
+ if (c === '"') return '"';
1597
+ if (c === "'") return "'";
1598
+ if (c === '\\') return '\\';
1599
+ if (c === '0') return '\0';
1600
+ return c;
1601
+ }
1602
+
1603
+ function isDateString(s) {
1604
+ // YYYY-MM-DDTHH:MM check without regex
1605
+ if (s.length < 10) return false;
1606
+ return s[4] === '-' && s[7] === '-' && s[10] === 'T';
1607
+ }
1608
+
1609
+ function isBigIntString(s) {
1610
+ if (s.length < 16) return false;
1611
+ for (let i = 0; i < s.length; i++) if (!isDigit(s[i])) return false;
1612
+ return true;
1613
+ }
1614
+
1615
+ // ─── EXPORTS ──────────────────────────────────────────────────────────────────
1616
+
1617
+ module.exports = {
1618
+ // ── CSV
1619
+ csvParse,
1620
+ csvStringify,
1621
+
1622
+ // ── TOML
1623
+ tomlParse,
1624
+ tomlStringify,
1625
+
1626
+ // ── INI
1627
+ iniParse,
1628
+ iniStringify,
1629
+
1630
+ // ── Query strings
1631
+ qsParse,
1632
+ qsStringify,
1633
+
1634
+ // ── XML
1635
+ xmlParse,
1636
+ xmlStringify,
1637
+ xmlQuery,
1638
+
1639
+ // ── JSON extended
1640
+ jsonParse,
1641
+ jsonStringify,
1642
+ jsonMerge,
1643
+ jsonDiff,
1644
+ jsonPath,
1645
+
1646
+ // ── YAML
1647
+ yamlParse,
1648
+ yamlStringify,
1649
+
1650
+ // ── HJSON
1651
+ hjsonParse,
1652
+ hjsonStringify,
1653
+
1654
+ // ── NDJSON
1655
+ ndjsonParse,
1656
+ ndjsonStringify,
1657
+
1658
+ // ── dotenv
1659
+ dotenvParse,
1660
+ dotenvStringify,
1661
+
1662
+ // ── Tokenizer / lexer helpers
1663
+ tokenize,
1664
+ TokenStream,
1665
+
1666
+ // ── Expression resolver (extensible)
1667
+ ExprResolver,
1668
+ parseExpr,
1669
+
1670
+ // ── kitdef namespace (backward compat)
1671
+ kitdef: {
1672
+ csv: { parse: csvParse, stringify: csvStringify },
1673
+ toml: { parse: tomlParse, stringify: tomlStringify },
1674
+ ini: { parse: iniParse, stringify: iniStringify },
1675
+ qs: { parse: qsParse, stringify: qsStringify },
1676
+ xml: { parse: xmlParse, stringify: xmlStringify, query: xmlQuery },
1677
+ json: { parse: jsonParse, stringify: jsonStringify,
1678
+ merge: jsonMerge, diff: jsonDiff, path: jsonPath },
1679
+ yaml: { parse: yamlParse, stringify: yamlStringify },
1680
+ hjson: { parse: hjsonParse, stringify: hjsonStringify },
1681
+ ndjson:{ parse: ndjsonParse,stringify: ndjsonStringify},
1682
+ dotenv:{ parse: dotenvParse,stringify: dotenvStringify},
1683
+ tokenize,
1684
+ TokenStream,
1685
+ ExprResolver,
1686
+ parseExpr,
1687
+ }
1688
+ };