bireactive 0.2.3 → 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/dist/animation/anim.js +4 -0
- package/dist/coll.d.ts +7 -7
- package/dist/coll.js +3 -1
- package/dist/core/cell.d.ts +89 -66
- package/dist/core/cell.js +642 -401
- package/dist/core/index.d.ts +4 -14
- package/dist/core/index.js +4 -14
- package/dist/core/lenses/aggregates.d.ts +1 -1
- package/dist/core/lenses/aggregates.js +4 -3
- package/dist/core/lenses/closed-form-policies.js +6 -6
- package/dist/core/lenses/decompositions.js +3 -3
- package/dist/core/lenses/domain-aggregates.js +5 -5
- package/dist/core/lenses/geometry.d.ts +1 -1
- package/dist/core/lenses/geometry.js +6 -7
- package/dist/core/lenses/memory.d.ts +2 -2
- package/dist/core/lenses/memory.js +3 -3
- package/dist/core/lenses/typed-factor.js +4 -3
- package/dist/core/traits.d.ts +1 -0
- package/dist/core/values/box.js +7 -7
- package/dist/core/values/color.js +5 -5
- package/dist/core/values/field.d.ts +70 -0
- package/dist/core/values/field.js +230 -0
- package/dist/core/values/gpu.d.ts +4 -2
- package/dist/core/values/gpu.js +11 -4
- package/dist/core/values/matrix.js +7 -7
- package/dist/core/values/num.d.ts +1 -1
- package/dist/core/values/num.js +1 -1
- package/dist/core/values/pose.js +4 -4
- package/dist/core/values/range.js +6 -6
- package/dist/core/values/template.d.ts +1 -1
- package/dist/core/values/template.js +2 -1
- package/dist/core/values/transform.js +7 -7
- package/dist/core/values/tri.js +3 -3
- package/dist/core/values/vec.js +8 -12
- package/dist/ext/timeline.js +2 -2
- package/dist/formats/cst.d.ts +127 -0
- package/dist/formats/cst.js +280 -0
- package/dist/formats/edn.d.ts +2 -0
- package/dist/formats/edn.js +301 -0
- package/dist/formats/index.d.ts +6 -0
- package/dist/formats/index.js +8 -0
- package/dist/formats/json.d.ts +2 -0
- package/dist/formats/json.js +332 -0
- package/dist/formats/lens.d.ts +8 -0
- package/dist/formats/lens.js +54 -0
- package/dist/formats/toml.d.ts +2 -0
- package/dist/formats/toml.js +526 -0
- package/dist/formats/yaml.d.ts +2 -0
- package/dist/formats/yaml.js +661 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/learn/data.d.ts +49 -0
- package/dist/learn/data.js +181 -0
- package/dist/learn/index.d.ts +3 -0
- package/dist/learn/index.js +6 -0
- package/dist/learn/lens-net.d.ts +63 -0
- package/dist/learn/lens-net.js +219 -0
- package/dist/learn/mlp.d.ts +77 -0
- package/dist/learn/mlp.js +292 -0
- package/dist/propagators/csp.d.ts +13 -0
- package/dist/propagators/csp.js +52 -0
- package/dist/propagators/flex.d.ts +31 -0
- package/dist/propagators/flex.js +189 -0
- package/dist/propagators/graph.d.ts +73 -0
- package/dist/propagators/graph.js +543 -0
- package/dist/propagators/index.d.ts +8 -6
- package/dist/propagators/index.js +15 -6
- package/dist/propagators/lattice.d.ts +45 -0
- package/dist/propagators/lattice.js +113 -0
- package/dist/propagators/layout.d.ts +1 -27
- package/dist/propagators/layout.js +6 -175
- package/dist/propagators/numeric.d.ts +17 -0
- package/dist/propagators/numeric.js +93 -0
- package/dist/propagators/solver.d.ts +51 -0
- package/dist/propagators/solver.js +175 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/lens.d.ts +121 -0
- package/dist/schema/lens.js +429 -0
- package/dist/shapes/annular-sector.js +4 -4
- package/dist/shapes/button.js +1 -1
- package/dist/shapes/circle.js +1 -1
- package/dist/shapes/handle.js +2 -2
- package/dist/shapes/label.js +1 -1
- package/dist/shapes/layout.js +2 -2
- package/dist/shapes/rect.js +7 -7
- package/dist/shapes/shape.js +8 -8
- package/dist/tex/tex.js +9 -2
- package/dist/web/diagram.js +2 -2
- package/package.json +9 -19
- package/dist/propagators/network.d.ts +0 -52
- package/dist/propagators/network.js +0 -185
- package/dist/propagators/propagator.d.ts +0 -12
- package/dist/propagators/propagator.js +0 -16
- package/dist/propagators/range.d.ts +0 -45
- package/dist/propagators/range.js +0 -147
- package/dist/propagators/relations.d.ts +0 -60
- package/dist/propagators/relations.js +0 -343
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// lens.ts — hub-and-spoke wiring: one abstract-value hub cell, one
|
|
2
|
+
// stateful text lens per concrete syntax.
|
|
3
|
+
//
|
|
4
|
+
// Each spoke's complement is { text, tree, errors, synced }: the
|
|
5
|
+
// concrete text, its tolerant CST, current error regions, and the hub
|
|
6
|
+
// value this text last agreed with (by identity — the hub only changes
|
|
7
|
+
// identity on a real change). The spoke:
|
|
8
|
+
//
|
|
9
|
+
// bwd — parse the written text. Clean ⇒ push the recovered value to
|
|
10
|
+
// the hub. Errors ⇒ `updates: [SKIP]` (hub untouched),
|
|
11
|
+
// but the complement keeps the broken text so the view echoes
|
|
12
|
+
// it back instead of trampling the editor.
|
|
13
|
+
// step — when the hub moved away from `synced`, absorb it by
|
|
14
|
+
// three-way surgical merge around any error regions.
|
|
15
|
+
// fwd — the complement's text.
|
|
16
|
+
import { cell, lens, SKIP } from "../core/cell.js";
|
|
17
|
+
import { deepEqual, mergeText, valueOf, } from "./cst.js";
|
|
18
|
+
function fromValue(adapter, v) {
|
|
19
|
+
const text = adapter.print(v);
|
|
20
|
+
const { tree, errors } = adapter.parse(text);
|
|
21
|
+
return { text, tree, errors, synced: v };
|
|
22
|
+
}
|
|
23
|
+
function absorb(adapter, c, theirs) {
|
|
24
|
+
const { text } = mergeText(adapter, c.text, c.tree, c.errors, theirs, c.synced);
|
|
25
|
+
if (text === c.text)
|
|
26
|
+
return { ...c, synced: theirs };
|
|
27
|
+
const { tree, errors } = adapter.parse(text);
|
|
28
|
+
return { text, tree, errors, synced: theirs };
|
|
29
|
+
}
|
|
30
|
+
/** Writable hub for a shared abstract value (deep-equality pruned). */
|
|
31
|
+
export function valueHub(initial) {
|
|
32
|
+
return cell(initial, { equals: deepEqual });
|
|
33
|
+
}
|
|
34
|
+
/** Writable text view of `hub` in `adapter`'s syntax. Valid edits push
|
|
35
|
+
* through; broken edits hold the hub and merge external changes around
|
|
36
|
+
* the error regions. */
|
|
37
|
+
export function formatSpoke(hub, adapter) {
|
|
38
|
+
return lens(hub, {
|
|
39
|
+
init: ([v]) => fromValue(adapter, v),
|
|
40
|
+
step: ([v], c) => (v === c.synced ? c : absorb(adapter, c, v)),
|
|
41
|
+
fwd: (_vals, c) => c.text,
|
|
42
|
+
bwd: (target, _vals, c) => {
|
|
43
|
+
const { tree, errors } = adapter.parse(target);
|
|
44
|
+
if (errors.length === 0) {
|
|
45
|
+
const v = valueOf(tree);
|
|
46
|
+
return { updates: [v], complement: { text: target, tree, errors, synced: v } };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
updates: [SKIP],
|
|
50
|
+
complement: { text: target, tree, errors, synced: c.synced },
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
// toml.ts — tolerant TOML adapter (line subset).
|
|
2
|
+
//
|
|
3
|
+
// Supports `key = value` lines, `[section]` headers (dotted paths),
|
|
4
|
+
// comments, basic/literal strings, numbers, booleans, inline arrays
|
|
5
|
+
// (multi-line) and inline tables. The canonical printer emits root
|
|
6
|
+
// scalars first, then one [section] per top-level object; deeper
|
|
7
|
+
// objects render as inline tables. TOML has no null: ops carrying null
|
|
8
|
+
// are reported as inexpressible (full-reprint fallback on clean docs).
|
|
9
|
+
// `[[arrays-of-tables]]`, dotted keys, and datetimes parse as error
|
|
10
|
+
// regions rather than failing the document.
|
|
11
|
+
import { isObject, } from "./cst.js";
|
|
12
|
+
const BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
13
|
+
const NUMRE = /^[-+]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/;
|
|
14
|
+
const HEADER = /^\[\s*([^\]]+?)\s*\]\s*(?:#.*)?$/;
|
|
15
|
+
class P {
|
|
16
|
+
text;
|
|
17
|
+
pos = 0;
|
|
18
|
+
errors = [];
|
|
19
|
+
root;
|
|
20
|
+
current;
|
|
21
|
+
currentEntry = null;
|
|
22
|
+
lastContentEnd = -1;
|
|
23
|
+
sawSection = false;
|
|
24
|
+
constructor(text) {
|
|
25
|
+
this.text = text;
|
|
26
|
+
this.root = {
|
|
27
|
+
kind: "object",
|
|
28
|
+
start: 0,
|
|
29
|
+
end: text.length,
|
|
30
|
+
entries: [],
|
|
31
|
+
meta: { kind: "root", preambleEnd: 0 },
|
|
32
|
+
};
|
|
33
|
+
this.current = this.root;
|
|
34
|
+
}
|
|
35
|
+
err(start, end, message) {
|
|
36
|
+
this.errors.push({ start, end, message });
|
|
37
|
+
}
|
|
38
|
+
run() {
|
|
39
|
+
const t = this.text;
|
|
40
|
+
while (this.pos < t.length) {
|
|
41
|
+
const lineStart = this.pos;
|
|
42
|
+
let lineEnd = t.indexOf("\n", this.pos);
|
|
43
|
+
if (lineEnd === -1)
|
|
44
|
+
lineEnd = t.length;
|
|
45
|
+
let j = lineStart;
|
|
46
|
+
while (j < lineEnd && (t[j] === " " || t[j] === "\t"))
|
|
47
|
+
j++;
|
|
48
|
+
if (j >= lineEnd || t[j] === "#" || t[j] === "\r") {
|
|
49
|
+
this.pos = lineEnd + 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (t[j] === "[") {
|
|
53
|
+
this.header(j, lineStart, lineEnd);
|
|
54
|
+
this.pos = lineEnd + 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
this.keyValue(j, lineStart, lineEnd);
|
|
58
|
+
}
|
|
59
|
+
this.finalizeSection();
|
|
60
|
+
}
|
|
61
|
+
finalizeSection() {
|
|
62
|
+
if (this.current !== this.root && this.lastContentEnd >= 0) {
|
|
63
|
+
this.current.end = Math.max(this.current.end, this.lastContentEnd);
|
|
64
|
+
if (this.currentEntry !== null) {
|
|
65
|
+
this.currentEntry.end = this.current.end;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
errorLine(lineStart, contentStart, lineEnd, message) {
|
|
70
|
+
this.err(contentStart, lineEnd, message);
|
|
71
|
+
const node = { kind: "error", start: contentStart, end: lineEnd };
|
|
72
|
+
this.current.entries.push({
|
|
73
|
+
key: undefined,
|
|
74
|
+
start: lineStart,
|
|
75
|
+
end: lineEnd,
|
|
76
|
+
node,
|
|
77
|
+
meta: { lineStart },
|
|
78
|
+
});
|
|
79
|
+
this.lastContentEnd = lineEnd;
|
|
80
|
+
}
|
|
81
|
+
header(j, lineStart, lineEnd) {
|
|
82
|
+
const raw = this.text.slice(j, lineEnd);
|
|
83
|
+
if (raw.startsWith("[[")) {
|
|
84
|
+
this.errorLine(lineStart, j, lineEnd, "arrays of tables are not supported");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const m = HEADER.exec(raw);
|
|
88
|
+
if (m === null) {
|
|
89
|
+
this.errorLine(lineStart, j, lineEnd, "malformed table header");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const segs = m[1].split(".").map(s => s.trim().replace(/^["']|["']$/g, ""));
|
|
93
|
+
if (segs.some(s => s.length === 0)) {
|
|
94
|
+
this.errorLine(lineStart, j, lineEnd, "malformed table path");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.finalizeSection();
|
|
98
|
+
let node = this.root;
|
|
99
|
+
for (let i = 0; i < segs.length; i++) {
|
|
100
|
+
const seg = segs[i];
|
|
101
|
+
const existing = node.entries.find(e => e.key === seg);
|
|
102
|
+
if (existing !== undefined) {
|
|
103
|
+
if (existing.node.kind !== "object") {
|
|
104
|
+
this.errorLine(lineStart, j, lineEnd, `'${seg}' is already a value`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
node = existing.node;
|
|
108
|
+
if (i === segs.length - 1) {
|
|
109
|
+
node.meta.kind = "section";
|
|
110
|
+
this.currentEntry = existing;
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const child = {
|
|
115
|
+
kind: "object",
|
|
116
|
+
start: lineStart,
|
|
117
|
+
end: lineEnd,
|
|
118
|
+
entries: [],
|
|
119
|
+
meta: { kind: i === segs.length - 1 ? "section" : "implicit" },
|
|
120
|
+
};
|
|
121
|
+
const entry = {
|
|
122
|
+
key: seg,
|
|
123
|
+
start: lineStart,
|
|
124
|
+
end: lineEnd,
|
|
125
|
+
node: child,
|
|
126
|
+
meta: { lineStart },
|
|
127
|
+
};
|
|
128
|
+
node.entries.push(entry);
|
|
129
|
+
node = child;
|
|
130
|
+
if (i === segs.length - 1)
|
|
131
|
+
this.currentEntry = entry;
|
|
132
|
+
}
|
|
133
|
+
this.current = node;
|
|
134
|
+
this.lastContentEnd = lineEnd;
|
|
135
|
+
this.sawSection = true;
|
|
136
|
+
}
|
|
137
|
+
keyValue(j, lineStart, lineEnd) {
|
|
138
|
+
const t = this.text;
|
|
139
|
+
let key = null;
|
|
140
|
+
let p = j;
|
|
141
|
+
if (t[p] === '"' || t[p] === "'") {
|
|
142
|
+
const s = this.scanString(p);
|
|
143
|
+
if (s !== null) {
|
|
144
|
+
key = s.value;
|
|
145
|
+
p = s.end;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
let e = p;
|
|
150
|
+
while (e < lineEnd && BARE_KEY.test(t[e]))
|
|
151
|
+
e++;
|
|
152
|
+
if (e > p) {
|
|
153
|
+
key = t.slice(p, e);
|
|
154
|
+
p = e;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
while (p < lineEnd && (t[p] === " " || t[p] === "\t"))
|
|
158
|
+
p++;
|
|
159
|
+
if (key === null || t[p] !== "=") {
|
|
160
|
+
this.errorLine(lineStart, j, lineEnd, "expected 'key = value'");
|
|
161
|
+
this.pos = lineEnd + 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
p++;
|
|
165
|
+
while (p < lineEnd && (t[p] === " " || t[p] === "\t"))
|
|
166
|
+
p++;
|
|
167
|
+
let node = this.parseValue(p);
|
|
168
|
+
// Past the value: only whitespace or a comment may remain on its line.
|
|
169
|
+
let q = node.end;
|
|
170
|
+
const vLineEnd = endOfLine(t, node.end);
|
|
171
|
+
while (q < vLineEnd && (t[q] === " " || t[q] === "\t"))
|
|
172
|
+
q++;
|
|
173
|
+
if (q < vLineEnd && t[q] !== "#" && node.kind !== "error") {
|
|
174
|
+
this.err(node.start, vLineEnd, "unexpected content after value");
|
|
175
|
+
node = { kind: "error", start: node.start, end: vLineEnd };
|
|
176
|
+
}
|
|
177
|
+
const entry = {
|
|
178
|
+
key: node.kind === "error" ? undefined : key,
|
|
179
|
+
start: lineStart,
|
|
180
|
+
end: node.end,
|
|
181
|
+
node,
|
|
182
|
+
meta: { lineStart },
|
|
183
|
+
};
|
|
184
|
+
this.current.entries.push(entry);
|
|
185
|
+
if (this.current === this.root && !this.sawSection) {
|
|
186
|
+
this.root.meta.preambleEnd = Math.min(vLineEnd + 1, t.length);
|
|
187
|
+
}
|
|
188
|
+
this.lastContentEnd = node.end;
|
|
189
|
+
this.pos = vLineEnd + 1;
|
|
190
|
+
}
|
|
191
|
+
scanString(start) {
|
|
192
|
+
const t = this.text;
|
|
193
|
+
const q = t[start];
|
|
194
|
+
let out = "";
|
|
195
|
+
let p = start + 1;
|
|
196
|
+
while (p < t.length && t[p] !== "\n") {
|
|
197
|
+
const c = t[p];
|
|
198
|
+
if (q === '"' && c === "\\") {
|
|
199
|
+
const esc = t[p + 1];
|
|
200
|
+
p += 2;
|
|
201
|
+
if (esc === "n")
|
|
202
|
+
out += "\n";
|
|
203
|
+
else if (esc === "t")
|
|
204
|
+
out += "\t";
|
|
205
|
+
else
|
|
206
|
+
out += esc ?? "";
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (c === q)
|
|
210
|
+
return { value: out, end: p + 1 };
|
|
211
|
+
out += c;
|
|
212
|
+
p++;
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
skipValueWs(p) {
|
|
217
|
+
const t = this.text;
|
|
218
|
+
for (;;) {
|
|
219
|
+
while (p < t.length && (t[p] === " " || t[p] === "\t" || t[p] === "\n" || t[p] === "\r"))
|
|
220
|
+
p++;
|
|
221
|
+
if (t[p] === "#") {
|
|
222
|
+
while (p < t.length && t[p] !== "\n")
|
|
223
|
+
p++;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
return p;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
garbage(start, message) {
|
|
230
|
+
const t = this.text;
|
|
231
|
+
let p = start;
|
|
232
|
+
let depth = 0;
|
|
233
|
+
while (p < t.length) {
|
|
234
|
+
const c = t[p];
|
|
235
|
+
if (c === "[" || c === "{")
|
|
236
|
+
depth++;
|
|
237
|
+
else if (c === "]" || c === "}") {
|
|
238
|
+
if (depth === 0)
|
|
239
|
+
break;
|
|
240
|
+
depth--;
|
|
241
|
+
}
|
|
242
|
+
else if (depth === 0 && (c === "," || c === "\n" || c === "#"))
|
|
243
|
+
break;
|
|
244
|
+
p++;
|
|
245
|
+
}
|
|
246
|
+
let end = p;
|
|
247
|
+
while (end > start && (t[end - 1] === " " || t[end - 1] === "\t"))
|
|
248
|
+
end--;
|
|
249
|
+
if (end === start)
|
|
250
|
+
end = Math.min(start + 1, t.length);
|
|
251
|
+
this.err(start, end, message);
|
|
252
|
+
return { kind: "error", start, end };
|
|
253
|
+
}
|
|
254
|
+
parseValue(start) {
|
|
255
|
+
const t = this.text;
|
|
256
|
+
const c = t[start];
|
|
257
|
+
if (c === undefined || c === "\n") {
|
|
258
|
+
this.err(start, start + 1, "missing value");
|
|
259
|
+
return { kind: "error", start, end: Math.min(start + 1, t.length) };
|
|
260
|
+
}
|
|
261
|
+
if (c === '"' || c === "'") {
|
|
262
|
+
const s = this.scanString(start);
|
|
263
|
+
if (s === null)
|
|
264
|
+
return this.garbage(start, "unterminated string");
|
|
265
|
+
return { kind: "scalar", start, end: s.end, value: s.value };
|
|
266
|
+
}
|
|
267
|
+
if (c === "[") {
|
|
268
|
+
const items = [];
|
|
269
|
+
let p = start + 1;
|
|
270
|
+
for (;;) {
|
|
271
|
+
p = this.skipValueWs(p);
|
|
272
|
+
if (p >= t.length) {
|
|
273
|
+
this.err(start, start + 1, "unclosed array");
|
|
274
|
+
return { kind: "array", start, end: p, items };
|
|
275
|
+
}
|
|
276
|
+
if (t[p] === "]")
|
|
277
|
+
return { kind: "array", start, end: p + 1, items };
|
|
278
|
+
if (t[p] === ",") {
|
|
279
|
+
p++;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const item = this.parseValue(p);
|
|
283
|
+
items.push(item);
|
|
284
|
+
p = item.end;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (c === "{") {
|
|
288
|
+
const entries = [];
|
|
289
|
+
let p = start + 1;
|
|
290
|
+
for (;;) {
|
|
291
|
+
p = this.skipValueWs(p);
|
|
292
|
+
if (p >= t.length || t[p] === "\n") {
|
|
293
|
+
this.err(start, start + 1, "unclosed inline table");
|
|
294
|
+
return {
|
|
295
|
+
kind: "object",
|
|
296
|
+
start,
|
|
297
|
+
end: p,
|
|
298
|
+
entries,
|
|
299
|
+
meta: { kind: "inline" },
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (t[p] === "}") {
|
|
303
|
+
return {
|
|
304
|
+
kind: "object",
|
|
305
|
+
start,
|
|
306
|
+
end: p + 1,
|
|
307
|
+
entries,
|
|
308
|
+
meta: { kind: "inline" },
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (t[p] === ",") {
|
|
312
|
+
p++;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const entryStart = p;
|
|
316
|
+
let key = null;
|
|
317
|
+
if (t[p] === '"' || t[p] === "'") {
|
|
318
|
+
const s = this.scanString(p);
|
|
319
|
+
if (s !== null) {
|
|
320
|
+
key = s.value;
|
|
321
|
+
p = s.end;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
let e = p;
|
|
326
|
+
while (e < t.length && BARE_KEY.test(t[e]))
|
|
327
|
+
e++;
|
|
328
|
+
if (e > p) {
|
|
329
|
+
key = t.slice(p, e);
|
|
330
|
+
p = e;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
while (p < t.length && (t[p] === " " || t[p] === "\t"))
|
|
334
|
+
p++;
|
|
335
|
+
if (key === null || t[p] !== "=") {
|
|
336
|
+
const g = this.garbage(entryStart, "expected 'key = value' in inline table");
|
|
337
|
+
entries.push({ key: undefined, start: entryStart, end: g.end, node: g });
|
|
338
|
+
p = Math.max(g.end, entryStart + 1);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
p++;
|
|
342
|
+
while (p < t.length && (t[p] === " " || t[p] === "\t"))
|
|
343
|
+
p++;
|
|
344
|
+
const v = this.parseValue(p);
|
|
345
|
+
entries.push({
|
|
346
|
+
key: v.kind === "error" ? undefined : key,
|
|
347
|
+
start: entryStart,
|
|
348
|
+
end: v.end,
|
|
349
|
+
node: v,
|
|
350
|
+
});
|
|
351
|
+
p = v.end;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const rest = t.slice(start, endOfLine(t, start));
|
|
355
|
+
if (rest.startsWith("true"))
|
|
356
|
+
return this.literal(start, 4, true);
|
|
357
|
+
if (rest.startsWith("false"))
|
|
358
|
+
return this.literal(start, 5, false);
|
|
359
|
+
const num = NUMRE.exec(rest);
|
|
360
|
+
if (num !== null && num[0].length > 0 && /^[-+]?\d/.test(rest)) {
|
|
361
|
+
const end = start + num[0].length;
|
|
362
|
+
const after = t[end];
|
|
363
|
+
if (after === undefined || " \t\n,]}#".includes(after)) {
|
|
364
|
+
return { kind: "scalar", start, end, value: Number(num[0]) };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return this.garbage(start, "expected a value");
|
|
368
|
+
}
|
|
369
|
+
literal(start, len, value) {
|
|
370
|
+
const after = this.text[start + len];
|
|
371
|
+
if (after === undefined || " \t\n,]}#".includes(after)) {
|
|
372
|
+
return { kind: "scalar", start, end: start + len, value };
|
|
373
|
+
}
|
|
374
|
+
return this.garbage(start, "expected a value");
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function endOfLine(text, pos) {
|
|
378
|
+
const nl = text.indexOf("\n", pos);
|
|
379
|
+
return nl === -1 ? text.length : nl;
|
|
380
|
+
}
|
|
381
|
+
function parse(text) {
|
|
382
|
+
const p = new P(text);
|
|
383
|
+
p.run();
|
|
384
|
+
if (p.root.entries.length === 0 && p.errors.length === 0) {
|
|
385
|
+
p.err(0, 0, "empty document");
|
|
386
|
+
return { tree: { kind: "error", start: 0, end: 0 }, errors: p.errors };
|
|
387
|
+
}
|
|
388
|
+
return { tree: p.root, errors: p.errors };
|
|
389
|
+
}
|
|
390
|
+
// -- printing ----------------------------------------------------------------
|
|
391
|
+
function containsNull(v) {
|
|
392
|
+
if (v === null)
|
|
393
|
+
return true;
|
|
394
|
+
if (Array.isArray(v))
|
|
395
|
+
return v.some(containsNull);
|
|
396
|
+
if (isObject(v))
|
|
397
|
+
return Object.values(v).some(containsNull);
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
function printKey(k) {
|
|
401
|
+
return BARE_KEY.test(k) ? k : JSON.stringify(k);
|
|
402
|
+
}
|
|
403
|
+
function printInline(v) {
|
|
404
|
+
if (v === null)
|
|
405
|
+
return '""'; // unreachable: null ops are rejected upstream
|
|
406
|
+
if (typeof v === "string")
|
|
407
|
+
return JSON.stringify(v);
|
|
408
|
+
if (typeof v !== "object")
|
|
409
|
+
return String(v);
|
|
410
|
+
if (Array.isArray(v))
|
|
411
|
+
return `[${v.map(printInline).join(", ")}]`;
|
|
412
|
+
const keys = Object.keys(v);
|
|
413
|
+
if (keys.length === 0)
|
|
414
|
+
return "{}";
|
|
415
|
+
return `{ ${keys.map(k => `${printKey(k)} = ${printInline(v[k])}`).join(", ")} }`;
|
|
416
|
+
}
|
|
417
|
+
function sectionBody(obj) {
|
|
418
|
+
return Object.keys(obj)
|
|
419
|
+
.map(k => `${printKey(k)} = ${printInline(obj[k])}`)
|
|
420
|
+
.join("\n");
|
|
421
|
+
}
|
|
422
|
+
function printSection(key, obj) {
|
|
423
|
+
const body = sectionBody(obj);
|
|
424
|
+
return body.length > 0 ? `[${printKey(key)}]\n${body}` : `[${printKey(key)}]`;
|
|
425
|
+
}
|
|
426
|
+
function print(value) {
|
|
427
|
+
if (!isObject(value))
|
|
428
|
+
return "# TOML requires a table at the root\n";
|
|
429
|
+
const keys = Object.keys(value);
|
|
430
|
+
const scalars = keys.filter(k => !isObject(value[k]));
|
|
431
|
+
const tables = keys.filter(k => isObject(value[k]));
|
|
432
|
+
const parts = [];
|
|
433
|
+
if (scalars.length > 0) {
|
|
434
|
+
parts.push(scalars.map(k => `${printKey(k)} = ${printInline(value[k])}`).join("\n"));
|
|
435
|
+
}
|
|
436
|
+
for (const k of tables)
|
|
437
|
+
parts.push(printSection(k, value[k]));
|
|
438
|
+
return `${parts.join("\n\n")}\n`;
|
|
439
|
+
}
|
|
440
|
+
// -- ops → edits ---------------------------------------------------------------
|
|
441
|
+
function tableKind(node) {
|
|
442
|
+
if (node.kind !== "object")
|
|
443
|
+
return null;
|
|
444
|
+
return node.meta.kind;
|
|
445
|
+
}
|
|
446
|
+
function opToEdit(op, text) {
|
|
447
|
+
switch (op.type) {
|
|
448
|
+
case "replace": {
|
|
449
|
+
const { node, value } = op;
|
|
450
|
+
if (containsNull(value))
|
|
451
|
+
return null;
|
|
452
|
+
const kind = tableKind(node);
|
|
453
|
+
if (kind === "root") {
|
|
454
|
+
if (!isObject(value))
|
|
455
|
+
return null;
|
|
456
|
+
return [{ start: 0, end: text.length, text: print(value) }];
|
|
457
|
+
}
|
|
458
|
+
if (kind === "section") {
|
|
459
|
+
if (!isObject(value) || op.entry?.key === undefined)
|
|
460
|
+
return null;
|
|
461
|
+
return [{ start: node.start, end: node.end, text: printSection(op.entry.key, value) }];
|
|
462
|
+
}
|
|
463
|
+
if (kind === "implicit")
|
|
464
|
+
return null;
|
|
465
|
+
// Inline value (scalar, array, inline table, or entry value line).
|
|
466
|
+
return [{ start: node.start, end: node.end, text: printInline(value) }];
|
|
467
|
+
}
|
|
468
|
+
case "insert": {
|
|
469
|
+
const { obj, key, value } = op;
|
|
470
|
+
if (containsNull(value))
|
|
471
|
+
return null;
|
|
472
|
+
const kind = tableKind(obj);
|
|
473
|
+
if (kind === "root") {
|
|
474
|
+
const meta = obj.meta;
|
|
475
|
+
if (isObject(value)) {
|
|
476
|
+
const prefix = text.length === 0 || text.endsWith("\n") ? "\n" : "\n\n";
|
|
477
|
+
return [
|
|
478
|
+
{
|
|
479
|
+
start: text.length,
|
|
480
|
+
end: text.length,
|
|
481
|
+
text: `${prefix}${printSection(key, value)}\n`,
|
|
482
|
+
},
|
|
483
|
+
];
|
|
484
|
+
}
|
|
485
|
+
const at = Math.min(meta.preambleEnd ?? 0, text.length);
|
|
486
|
+
const pad = at > 0 && text[at - 1] !== "\n" ? "\n" : "";
|
|
487
|
+
return [{ start: at, end: at, text: `${pad}${printKey(key)} = ${printInline(value)}\n` }];
|
|
488
|
+
}
|
|
489
|
+
if (kind === "section" || kind === "implicit") {
|
|
490
|
+
// Anchor after the surviving line entry, never after a child
|
|
491
|
+
// section (the key would land in the wrong table); with no safe
|
|
492
|
+
// anchor, insert right below the header.
|
|
493
|
+
const afterKind = op.after !== undefined ? tableKind(op.after.node) : null;
|
|
494
|
+
const anchor = op.after !== undefined && (afterKind === null || afterKind === "inline")
|
|
495
|
+
? op.after.end
|
|
496
|
+
: endOfLine(text, obj.start);
|
|
497
|
+
return [{ start: anchor, end: anchor, text: `\n${printKey(key)} = ${printInline(value)}` }];
|
|
498
|
+
}
|
|
499
|
+
// Inline table.
|
|
500
|
+
if (obj.entries.length === 0)
|
|
501
|
+
return null; // diff replaces empty objects
|
|
502
|
+
const anchor = (op.after ?? obj.entries[obj.entries.length - 1]).end;
|
|
503
|
+
return [{ start: anchor, end: anchor, text: `, ${printKey(key)} = ${printInline(value)}` }];
|
|
504
|
+
}
|
|
505
|
+
case "delete": {
|
|
506
|
+
const { obj, entry } = op;
|
|
507
|
+
const kind = tableKind(obj);
|
|
508
|
+
if (kind === "inline") {
|
|
509
|
+
const idx = obj.entries.indexOf(entry);
|
|
510
|
+
const next = obj.entries[idx + 1];
|
|
511
|
+
if (next !== undefined)
|
|
512
|
+
return [{ start: entry.start, end: next.start, text: "" }];
|
|
513
|
+
const prev = obj.entries[idx - 1];
|
|
514
|
+
const start = prev !== undefined ? prev.end : obj.start + 1;
|
|
515
|
+
return [{ start, end: entry.end, text: "" }];
|
|
516
|
+
}
|
|
517
|
+
// Line-level entry (scalar line or whole section).
|
|
518
|
+
const eol = endOfLine(text, entry.end);
|
|
519
|
+
const end = eol < text.length ? eol + 1 : eol;
|
|
520
|
+
const meta = entry.meta;
|
|
521
|
+
const start = meta?.lineStart ?? entry.start;
|
|
522
|
+
return [{ start, end, text: "" }];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
export const tomlFormat = { name: "TOML", parse, print, opToEdit };
|