tova 0.2.8 → 0.3.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.
- package/bin/tova.js +49 -18
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +134 -62
- package/src/codegen/base-codegen.js +15 -5
- package/src/codegen/client-codegen.js +17 -4
- package/src/codegen/server-codegen.js +7 -2
- package/src/lexer/lexer.js +2 -1
- package/src/lsp/server.js +43 -23
- package/src/parser/parser.js +47 -19
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +49 -33
- package/src/stdlib/inline.js +7 -7
- package/src/version.js +1 -1
|
@@ -363,9 +363,9 @@ export function watch(getter, callback, options = {}) {
|
|
|
363
363
|
const effect = createEffect(() => {
|
|
364
364
|
const newValue = getter();
|
|
365
365
|
if (initialized) {
|
|
366
|
-
callback(newValue, oldValue);
|
|
366
|
+
untrack(() => callback(newValue, oldValue));
|
|
367
367
|
} else if (options.immediate) {
|
|
368
|
-
callback(newValue, undefined);
|
|
368
|
+
untrack(() => callback(newValue, undefined));
|
|
369
369
|
}
|
|
370
370
|
oldValue = newValue;
|
|
371
371
|
initialized = true;
|
|
@@ -463,13 +463,9 @@ export function ErrorBoundary({ fallback, children, onError, onReset, retry = 0
|
|
|
463
463
|
if (onError) onError({ error: e, componentStack: stack });
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
pushErrorHandler(handleError);
|
|
467
|
-
|
|
468
466
|
// Return a reactive wrapper that switches between children and fallback
|
|
469
467
|
const childContent = children && children.length === 1 ? children[0] : tova_fragment(children || []);
|
|
470
468
|
|
|
471
|
-
popErrorHandler();
|
|
472
|
-
|
|
473
469
|
const vnode = {
|
|
474
470
|
__tova: true,
|
|
475
471
|
tag: '__dynamic',
|
|
@@ -477,6 +473,7 @@ export function ErrorBoundary({ fallback, children, onError, onReset, retry = 0
|
|
|
477
473
|
children: [],
|
|
478
474
|
_fallback: fallback,
|
|
479
475
|
_componentName: 'ErrorBoundary',
|
|
476
|
+
_errorHandler: handleError, // Active during __dynamic effect render cycle
|
|
480
477
|
compute: () => {
|
|
481
478
|
const err = error();
|
|
482
479
|
if (err) {
|
|
@@ -547,6 +544,7 @@ export function Portal({ target, children }) {
|
|
|
547
544
|
|
|
548
545
|
export function lazy(loader) {
|
|
549
546
|
let resolved = null;
|
|
547
|
+
let loadError = null;
|
|
550
548
|
let promise = null;
|
|
551
549
|
|
|
552
550
|
return function LazyWrapper(props) {
|
|
@@ -554,28 +552,28 @@ export function lazy(loader) {
|
|
|
554
552
|
return resolved(props);
|
|
555
553
|
}
|
|
556
554
|
|
|
557
|
-
const [comp, setComp] = createSignal(null);
|
|
558
|
-
const [err, setErr] = createSignal(null);
|
|
559
|
-
|
|
560
555
|
if (!promise) {
|
|
561
556
|
promise = loader()
|
|
562
557
|
.then(mod => {
|
|
563
558
|
resolved = mod.default || mod;
|
|
564
|
-
setComp(() => resolved);
|
|
565
559
|
})
|
|
566
|
-
.catch(e =>
|
|
560
|
+
.catch(e => { loadError = e; });
|
|
567
561
|
}
|
|
568
562
|
|
|
563
|
+
const [tick, setTick] = createSignal(0);
|
|
564
|
+
|
|
565
|
+
// Trigger re-render when promise settles
|
|
566
|
+
promise.then(() => setTick(1)).catch(() => setTick(1));
|
|
567
|
+
|
|
569
568
|
return {
|
|
570
569
|
__tova: true,
|
|
571
570
|
tag: '__dynamic',
|
|
572
571
|
props: {},
|
|
573
572
|
children: [],
|
|
574
573
|
compute: () => {
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
if (c) return c(props);
|
|
574
|
+
tick(); // Track for reactivity
|
|
575
|
+
if (loadError) return tova_el('span', { className: 'tova-error' }, [String(loadError)]);
|
|
576
|
+
if (resolved) return resolved(props);
|
|
579
577
|
// Fallback while loading
|
|
580
578
|
return props && props.fallback ? props.fallback : null;
|
|
581
579
|
},
|
|
@@ -861,22 +859,36 @@ export function render(vnode) {
|
|
|
861
859
|
frag.appendChild(marker);
|
|
862
860
|
|
|
863
861
|
let prevDispose = null;
|
|
862
|
+
const errHandler = vnode._errorHandler || null;
|
|
864
863
|
createEffect(() => {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
864
|
+
if (errHandler) pushErrorHandler(errHandler);
|
|
865
|
+
try {
|
|
866
|
+
const inner = vnode.compute();
|
|
867
|
+
const parent = marker.parentNode;
|
|
868
|
+
const ref = nextSiblingAfterMarker(marker);
|
|
869
|
+
|
|
870
|
+
if (prevDispose) {
|
|
871
|
+
prevDispose();
|
|
872
|
+
prevDispose = null;
|
|
873
|
+
}
|
|
874
|
+
clearMarkerContent(marker);
|
|
868
875
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
876
|
+
createRoot((dispose) => {
|
|
877
|
+
prevDispose = dispose;
|
|
878
|
+
const rendered = render(inner);
|
|
879
|
+
marker.__tovaNodes = insertRendered(parent, rendered, ref, marker);
|
|
880
|
+
});
|
|
881
|
+
} catch (e) {
|
|
882
|
+
if (errHandler) {
|
|
883
|
+
errHandler(e);
|
|
884
|
+
} else if (currentErrorHandler) {
|
|
885
|
+
currentErrorHandler(e);
|
|
886
|
+
} else {
|
|
887
|
+
console.error('Uncaught error during render:', e);
|
|
888
|
+
}
|
|
889
|
+
} finally {
|
|
890
|
+
if (errHandler) popErrorHandler();
|
|
872
891
|
}
|
|
873
|
-
clearMarkerContent(marker);
|
|
874
|
-
|
|
875
|
-
createRoot((dispose) => {
|
|
876
|
-
prevDispose = dispose;
|
|
877
|
-
const rendered = render(inner);
|
|
878
|
-
marker.__tovaNodes = insertRendered(parent, rendered, ref, marker);
|
|
879
|
-
});
|
|
880
892
|
});
|
|
881
893
|
|
|
882
894
|
return frag;
|
|
@@ -965,6 +977,14 @@ function applyPropValue(el, key, val) {
|
|
|
965
977
|
} else if (key === 'disabled' || key === 'readOnly' || key === 'hidden') {
|
|
966
978
|
el[key] = !!val;
|
|
967
979
|
} else if (key === 'style' && typeof val === 'object') {
|
|
980
|
+
// Clear old properties not present in new style object
|
|
981
|
+
for (let i = el.style.length - 1; i >= 0; i--) {
|
|
982
|
+
const prop = el.style[i];
|
|
983
|
+
const camel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
984
|
+
if (!(prop in val) && !(camel in val)) {
|
|
985
|
+
el.style.removeProperty(prop);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
968
988
|
Object.assign(el.style, val);
|
|
969
989
|
} else {
|
|
970
990
|
const s = val == null ? '' : String(val);
|
|
@@ -1150,11 +1170,7 @@ function patchPositionalInMarker(marker, newChildren) {
|
|
|
1150
1170
|
if (oldNodes[i].parentNode === parent) parent.removeChild(oldNodes[i]);
|
|
1151
1171
|
}
|
|
1152
1172
|
|
|
1153
|
-
marker.__tovaNodes = oldNodes.slice(0,
|
|
1154
|
-
// Simplify: rebuild __tovaNodes from what should remain
|
|
1155
|
-
if (newCount <= oldCount) {
|
|
1156
|
-
marker.__tovaNodes = oldNodes.slice(0, newCount);
|
|
1157
|
-
}
|
|
1173
|
+
marker.__tovaNodes = oldNodes.slice(0, newCount);
|
|
1158
1174
|
}
|
|
1159
1175
|
|
|
1160
1176
|
// Keyed reconciliation for children of an element (not marker-based)
|
package/src/stdlib/inline.js
CHANGED
|
@@ -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); }`,
|
|
@@ -163,12 +163,12 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
163
163
|
agg_count: `function agg_count(fn) { if (!fn) return (rows) => rows.length; return (rows) => rows.filter(fn).length; }`,
|
|
164
164
|
agg_mean: `function agg_mean(fn) { return (rows) => { if (rows.length === 0) return 0; return rows.reduce((a, r) => a + (typeof fn === 'function' ? fn(r) : r[fn]), 0) / rows.length; }; }`,
|
|
165
165
|
agg_median: `function agg_median(fn) { return (rows) => { if (rows.length === 0) return 0; const vs = rows.map(r => typeof fn === 'function' ? fn(r) : r[fn]).sort((a, b) => a - b); const m = Math.floor(vs.length / 2); return vs.length % 2 !== 0 ? vs[m] : (vs[m - 1] + vs[m]) / 2; }; }`,
|
|
166
|
-
agg_min: `function agg_min(fn) { return (rows) => rows.length === 0
|
|
167
|
-
agg_max: `function agg_max(fn) { return (rows) => rows.length === 0
|
|
166
|
+
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; }; }`,
|
|
167
|
+
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
168
|
|
|
169
169
|
// ── Data exploration ────────────────────────────────
|
|
170
170
|
peek: `function peek(table, opts) { const o = typeof opts === 'object' ? opts : {}; console.log(table._format ? table._format(o.n || 10, o.title) : String(table)); return table; }`,
|
|
171
|
-
describe: `function describe(table) { const stats = []; for (const col of table._columns) { const vals = table._rows.map(r => r[col]).filter(v => v != null); const st = { Column: col, Type: 'Unknown', 'Non-Null': vals.length }; if (vals.length > 0) { const s = vals[0]; if (typeof s === 'number') { st.Type = Number.isInteger(s) ? 'Int' : 'Float'; st.Mean = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
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; 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
172
|
schema_of: `function schema_of(table) { const sc = {}; if (table._rows.length === 0) { for (const c of table._columns) sc[c] = 'Unknown'; } else { const s = table._rows[0]; for (const c of table._columns) { const v = s[c]; if (v == null) sc[c] = 'Nil'; else if (typeof v === 'number') sc[c] = Number.isInteger(v) ? 'Int' : 'Float'; else if (typeof v === 'string') sc[c] = 'String'; else if (typeof v === 'boolean') sc[c] = 'Bool'; else if (Array.isArray(v)) sc[c] = 'Array'; else sc[c] = 'Object'; } } console.log('Schema:'); for (const [c, t] of Object.entries(sc)) console.log(' ' + c + ': ' + t); return sc; }`,
|
|
173
173
|
|
|
174
174
|
// ── Data cleaning ───────────────────────────────────
|
|
@@ -302,7 +302,7 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
302
302
|
// ── Math (new) ─────────────────────────────────────────
|
|
303
303
|
hypot: `function hypot(a, b) { return Math.hypot(a, b); }`,
|
|
304
304
|
lerp: `function lerp(a, b, t) { return a + (b - a) * t; }`,
|
|
305
|
-
divmod: `function divmod(a, b) {
|
|
305
|
+
divmod: `function divmod(a, b) { const q = Math.floor(a / b); return [q, a - q * b]; }`,
|
|
306
306
|
avg: `function avg(arr) { return arr.length === 0 ? 0 : arr.reduce((a, b) => a + b, 0) / arr.length; }`,
|
|
307
307
|
|
|
308
308
|
// ── Date/Time (new) ────────────────────────────────────
|
|
@@ -374,7 +374,7 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
|
|
|
374
374
|
combinations: `function combinations(arr, r) { const result = []; const combo = []; function gen(start, depth) { if (depth === r) { result.push([...combo]); return; } for (let i = start; i < arr.length; i++) { combo.push(arr[i]); gen(i + 1, depth + 1); combo.pop(); } } gen(0, 0); return result; }`,
|
|
375
375
|
permutations: `function permutations(arr, r) { const n = r === undefined ? arr.length : r; const result = []; const perm = []; const used = new Array(arr.length).fill(false); function gen() { if (perm.length === n) { result.push([...perm]); return; } for (let i = 0; i < arr.length; i++) { if (!used[i]) { used[i] = true; perm.push(arr[i]); gen(); perm.pop(); used[i] = false; } } } gen(); return result; }`,
|
|
376
376
|
intersperse: `function intersperse(arr, sep) { if (arr.length <= 1) return [...arr]; const r = [arr[0]]; for (let i = 1; i < arr.length; i++) { r.push(sep, arr[i]); } return r; }`,
|
|
377
|
-
interleave: `function interleave(...arrs) { const m = Math.max(...arrs.map(a => a.length)); const r = []; for (let i = 0; i < m; i++) { for (const a of arrs) { if (i < a.length) r.push(a[i]); } } return r; }`,
|
|
377
|
+
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
378
|
repeat_value: `function repeat_value(val, n) { return Array(n).fill(val); }`,
|
|
379
379
|
|
|
380
380
|
// ── Array Utilities ────────────────────────────────────
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/embed-runtime.js — do not edit
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.3.0";
|