flowfig 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -23
- package/dist/check.js +68 -6
- package/dist/cli.js +24 -13
- package/dist/code.d.ts +26 -0
- package/dist/code.js +568 -0
- package/dist/diff.js +12 -5
- package/dist/draw.js +1 -1
- package/dist/edges.d.ts +7 -0
- package/dist/edges.js +557 -0
- package/dist/geometry.d.ts +11 -2
- package/dist/geometry.js +255 -67
- package/dist/guide.d.ts +22 -2
- package/dist/guide.js +282 -219
- package/dist/imports.d.ts +11 -0
- package/dist/imports.js +235 -0
- package/dist/index.js +36 -28
- package/dist/load.d.ts +2 -0
- package/dist/load.js +24 -1
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +22 -9
- package/dist/model.d.ts +13 -2
- package/dist/model.js +54 -11
- package/dist/scene.d.ts +2 -0
- package/dist/source.d.ts +1 -1
- package/dist/source.js +3 -3
- package/dist/svg.js +49 -32
- package/dist/type-index.d.ts +55 -0
- package/dist/type-index.js +383 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +733 -0
- package/dist/verify.d.ts +25 -3
- package/dist/verify.js +67 -22
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/code.js
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
const LANGS = {
|
|
4
|
+
'.ts': 'ts',
|
|
5
|
+
'.tsx': 'ts',
|
|
6
|
+
'.mts': 'ts',
|
|
7
|
+
'.cts': 'ts',
|
|
8
|
+
'.js': 'ts',
|
|
9
|
+
'.jsx': 'ts',
|
|
10
|
+
'.mjs': 'ts',
|
|
11
|
+
'.cjs': 'ts',
|
|
12
|
+
'.py': 'py',
|
|
13
|
+
'.go': 'go',
|
|
14
|
+
'.java': 'java',
|
|
15
|
+
'.cs': 'cs',
|
|
16
|
+
'.rs': 'rs',
|
|
17
|
+
};
|
|
18
|
+
export const langOf = (path) => LANGS[extname(path)] ?? null;
|
|
19
|
+
export const readFile = (full) => {
|
|
20
|
+
try {
|
|
21
|
+
return statSync(full).isFile() ? readFileSync(full, 'utf8') : null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
readFile.files = (root) => {
|
|
28
|
+
const out = [];
|
|
29
|
+
const walk = (dir) => {
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const e of entries) {
|
|
38
|
+
if (e.name.startsWith('.') || e.name === 'node_modules')
|
|
39
|
+
continue;
|
|
40
|
+
const full = resolve(dir, e.name);
|
|
41
|
+
if (e.isDirectory())
|
|
42
|
+
walk(full);
|
|
43
|
+
else
|
|
44
|
+
out.push(relative(root, full).split(sep).join('/'));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
walk(root);
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
50
|
+
const blank = (s) => s.replace(/[^\n]/g, ' ');
|
|
51
|
+
const wordBefore = (s, end) => /(\w+)$/.exec(s.slice(Math.max(0, end - 12), end))?.[1] ?? '';
|
|
52
|
+
function mask(src, lang) {
|
|
53
|
+
let code = '';
|
|
54
|
+
let keep = '';
|
|
55
|
+
const n = src.length;
|
|
56
|
+
let i = 0;
|
|
57
|
+
const push = (txt, kind) => {
|
|
58
|
+
code += kind === 'code' ? txt : blank(txt);
|
|
59
|
+
keep += kind === 'comment' ? blank(txt) : txt;
|
|
60
|
+
};
|
|
61
|
+
if (lang === 'py') {
|
|
62
|
+
while (i < n) {
|
|
63
|
+
const c = src[i];
|
|
64
|
+
if (c === '#') {
|
|
65
|
+
const e = src.indexOf('\n', i);
|
|
66
|
+
const end = e < 0 ? n : e;
|
|
67
|
+
push(src.slice(i, end), 'comment');
|
|
68
|
+
i = end;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const m = /^([rRbBuUfF]{0,2})('''|"""|'|")/.exec(src.slice(i, i + 5));
|
|
72
|
+
if (m && (m[1] === '' || !/[\w]/.test(src[i - 1] ?? ''))) {
|
|
73
|
+
const q = m[2];
|
|
74
|
+
let j = i + m[0].length;
|
|
75
|
+
while (j < n) {
|
|
76
|
+
if (src[j] === '\\')
|
|
77
|
+
j += 2;
|
|
78
|
+
else if (src.startsWith(q, j)) {
|
|
79
|
+
j += q.length;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
else if (q.length === 1 && src[j] === '\n')
|
|
83
|
+
break;
|
|
84
|
+
else
|
|
85
|
+
j++;
|
|
86
|
+
}
|
|
87
|
+
const bodyAt = i + m[0].length;
|
|
88
|
+
const closeAt = Math.max(bodyAt, j - q.length);
|
|
89
|
+
push(src.slice(i, bodyAt), 'code');
|
|
90
|
+
push(src.slice(bodyAt, closeAt), 'string');
|
|
91
|
+
push(src.slice(closeAt, j), 'code');
|
|
92
|
+
i = j;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
push(c, 'code');
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
return { code, keep };
|
|
99
|
+
}
|
|
100
|
+
const stack = [];
|
|
101
|
+
let depth = 0;
|
|
102
|
+
let prevSig = '';
|
|
103
|
+
while (i < n) {
|
|
104
|
+
const c = src[i];
|
|
105
|
+
const d = src[i + 1];
|
|
106
|
+
if (c === '/' && d === '/') {
|
|
107
|
+
const e = src.indexOf('\n', i);
|
|
108
|
+
const end = e < 0 ? n : e;
|
|
109
|
+
push(src.slice(i, end), 'comment');
|
|
110
|
+
i = end;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (c === '/' && d === '*') {
|
|
114
|
+
const e = src.indexOf('*/', i + 2);
|
|
115
|
+
const end = e < 0 ? n : e + 2;
|
|
116
|
+
push(src.slice(i, end), 'comment');
|
|
117
|
+
i = end;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (lang === 'ts' && c === '/' && /^$|[(,=:[!&|?{};]$/.test(prevSig)) {
|
|
121
|
+
let j = i + 1;
|
|
122
|
+
let cls = false;
|
|
123
|
+
while (j < n && src[j] !== '\n') {
|
|
124
|
+
if (src[j] === '\\')
|
|
125
|
+
j += 2;
|
|
126
|
+
else if (src[j] === '/' && !cls)
|
|
127
|
+
break;
|
|
128
|
+
else {
|
|
129
|
+
if (src[j] === '[')
|
|
130
|
+
cls = true;
|
|
131
|
+
else if (src[j] === ']')
|
|
132
|
+
cls = false;
|
|
133
|
+
j++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (src[j] === '/') {
|
|
137
|
+
push('/', 'code');
|
|
138
|
+
push(src.slice(i + 1, j), 'string');
|
|
139
|
+
push('/', 'code');
|
|
140
|
+
i = j + 1;
|
|
141
|
+
prevSig = 'x';
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (lang === 'rs' && c === "'" && /[A-Za-z_]/.test(d ?? '') && src[i + 2] !== "'") {
|
|
146
|
+
push(c, 'code');
|
|
147
|
+
i++;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const tpl = lang === 'ts' && c === '`';
|
|
151
|
+
const raw = (lang === 'go' && c === '`') || (lang === 'cs' && c === '@' && d === '"');
|
|
152
|
+
if (c === '"' || c === "'" || tpl || raw || (c === '}' && stack.length && stack.at(-1) === depth)) {
|
|
153
|
+
let open = c;
|
|
154
|
+
if (c === '}') {
|
|
155
|
+
stack.pop();
|
|
156
|
+
depth--;
|
|
157
|
+
}
|
|
158
|
+
if (raw && c === '@')
|
|
159
|
+
open = '"';
|
|
160
|
+
const startQ = c === '@' ? i + 2 : i + 1;
|
|
161
|
+
const q = c === '}' ? '`' : open;
|
|
162
|
+
const triple = (lang === 'java' || lang === 'cs') && src.startsWith('"""', i);
|
|
163
|
+
let j = triple ? i + 3 : startQ;
|
|
164
|
+
let enterTpl = false;
|
|
165
|
+
while (j < n) {
|
|
166
|
+
if (triple) {
|
|
167
|
+
if (src.startsWith('"""', j))
|
|
168
|
+
break;
|
|
169
|
+
j++;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (src[j] === '\\' && !raw)
|
|
173
|
+
j += 2;
|
|
174
|
+
else if (raw && c === '@' && src[j] === '"' && src[j + 1] === '"')
|
|
175
|
+
j += 2;
|
|
176
|
+
else if (src[j] === q)
|
|
177
|
+
break;
|
|
178
|
+
else if (q === '`' && lang === 'ts' && src[j] === '$' && src[j + 1] === '{') {
|
|
179
|
+
enterTpl = true;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
else if (!raw && q !== '`' && src[j] === '\n')
|
|
183
|
+
break;
|
|
184
|
+
else
|
|
185
|
+
j++;
|
|
186
|
+
}
|
|
187
|
+
const head = c === '}' ? 1 : startQ - i;
|
|
188
|
+
push(src.slice(i, i + head), 'code');
|
|
189
|
+
push(src.slice(i + head, j), 'string');
|
|
190
|
+
if (enterTpl) {
|
|
191
|
+
push('${', 'code');
|
|
192
|
+
depth++;
|
|
193
|
+
stack.push(depth);
|
|
194
|
+
i = j + 2;
|
|
195
|
+
prevSig = '{';
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const tail = triple ? 3 : 1;
|
|
199
|
+
push(src.slice(j, j + tail), 'code');
|
|
200
|
+
i = j + tail;
|
|
201
|
+
prevSig = 'x';
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (c === '{')
|
|
205
|
+
depth++;
|
|
206
|
+
else if (c === '}')
|
|
207
|
+
depth--;
|
|
208
|
+
push(c, 'code');
|
|
209
|
+
if (!/\s/.test(c))
|
|
210
|
+
prevSig = /\w/.test(c)
|
|
211
|
+
? !/\w/.test(src[i + 1] ?? '') && /^(return|typeof|case|in|of)$/.test(wordBefore(src, i + 1))
|
|
212
|
+
? '('
|
|
213
|
+
: 'x'
|
|
214
|
+
: c;
|
|
215
|
+
i++;
|
|
216
|
+
}
|
|
217
|
+
return { code, keep };
|
|
218
|
+
}
|
|
219
|
+
export const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
220
|
+
export function matchClose(code, open) {
|
|
221
|
+
const pair = { '{': '}', '(': ')', '[': ']' }[code[open]];
|
|
222
|
+
let d = 0;
|
|
223
|
+
for (let i = open; i < code.length; i++) {
|
|
224
|
+
if (code[i] === code[open])
|
|
225
|
+
d++;
|
|
226
|
+
else if (code[i] === pair && --d === 0)
|
|
227
|
+
return i;
|
|
228
|
+
}
|
|
229
|
+
return code.length - 1;
|
|
230
|
+
}
|
|
231
|
+
const indentAt = (code, pos) => {
|
|
232
|
+
const ls = code.lastIndexOf('\n', pos - 1) + 1;
|
|
233
|
+
return /^[ \t]*/.exec(code.slice(ls))[0].length;
|
|
234
|
+
};
|
|
235
|
+
function braceBody(code, from, start, lang) {
|
|
236
|
+
let i = from;
|
|
237
|
+
let pd = 0;
|
|
238
|
+
const defIndent = indentAt(code, start);
|
|
239
|
+
while (i < code.length) {
|
|
240
|
+
const c = code[i];
|
|
241
|
+
if (c === '(' || c === '[')
|
|
242
|
+
pd++;
|
|
243
|
+
else if (c === ')' || c === ']') {
|
|
244
|
+
if (--pd < 0)
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
else if (pd === 0) {
|
|
248
|
+
if (c === ';')
|
|
249
|
+
return [start, i + 1];
|
|
250
|
+
if (c === '\n' && lang === 'go' && !/[,({[=+\-*/|&.:]\s*$/.test(code.slice(Math.max(start, i - 40), i)))
|
|
251
|
+
return [start, i];
|
|
252
|
+
if (c === '}')
|
|
253
|
+
return null;
|
|
254
|
+
if (c === '=' && code[i + 1] === '>' && lang === 'ts') {
|
|
255
|
+
let k = i + 2;
|
|
256
|
+
while (/\s/.test(code[k]))
|
|
257
|
+
k++;
|
|
258
|
+
if (code[k] !== '{') {
|
|
259
|
+
let pd2 = 0;
|
|
260
|
+
for (let j = k; j < code.length; j++) {
|
|
261
|
+
const ch = code[j];
|
|
262
|
+
if ('([{'.includes(ch))
|
|
263
|
+
pd2++;
|
|
264
|
+
else if (')]}'.includes(ch)) {
|
|
265
|
+
if (--pd2 < 0)
|
|
266
|
+
return [start, j];
|
|
267
|
+
}
|
|
268
|
+
else if (pd2 === 0 && (ch === ';' || ch === ','))
|
|
269
|
+
return [start, j];
|
|
270
|
+
else if (pd2 === 0 && ch === '\n') {
|
|
271
|
+
const next = code.slice(j + 1).match(/^[ \t]*(\S)/);
|
|
272
|
+
if (next && indentAt(code, j + 1) <= defIndent && !/[.?:|&+]/.test(next[1]))
|
|
273
|
+
return [start, j];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return [start, code.length];
|
|
277
|
+
}
|
|
278
|
+
i = k;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (c === '{') {
|
|
282
|
+
const before = code.slice(Math.max(0, i - 12), i).trimEnd();
|
|
283
|
+
const goTypeDecl = lang === 'go' && /^type\s+\w+(\[[^\]]*\])?\s+(struct|interface)\s*$/.test(code.slice(start, i));
|
|
284
|
+
if (!goTypeDecl && (/[:|&<,]$/.test(before) || /\b(interface|struct)$/.test(before))) {
|
|
285
|
+
i = matchClose(code, i) + 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
return [start, matchClose(code, i) + 1];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
i++;
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
export function pyBody(code, start) {
|
|
296
|
+
const ind = indentAt(code, start);
|
|
297
|
+
let i = start;
|
|
298
|
+
let pd = 0;
|
|
299
|
+
for (; i < code.length; i++) {
|
|
300
|
+
const c = code[i];
|
|
301
|
+
if ('([{'.includes(c))
|
|
302
|
+
pd++;
|
|
303
|
+
else if (')]}'.includes(c))
|
|
304
|
+
pd--;
|
|
305
|
+
else if (c === ':' && pd === 0 && /^[ \t]*(\n|$)/.test(code.slice(i + 1, i + 200)))
|
|
306
|
+
break;
|
|
307
|
+
else if (c === '\n' && pd === 0 && !code.slice(start, i).endsWith('\\')) {
|
|
308
|
+
if (!/^\s*(async\s+)?(def|class)\b/.test(code.slice(code.lastIndexOf('\n', start - 1) + 1)))
|
|
309
|
+
return [start, i];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
let j = code.indexOf('\n', i);
|
|
313
|
+
if (j < 0)
|
|
314
|
+
return [start, code.length];
|
|
315
|
+
let end = j;
|
|
316
|
+
while (j < code.length) {
|
|
317
|
+
const ls = j + 1;
|
|
318
|
+
const le = code.indexOf('\n', ls) < 0 ? code.length : code.indexOf('\n', ls);
|
|
319
|
+
const line = code.slice(ls, le);
|
|
320
|
+
if (line.trim() && indentAt(code, ls) <= ind)
|
|
321
|
+
break;
|
|
322
|
+
if (line.trim())
|
|
323
|
+
end = le;
|
|
324
|
+
j = le;
|
|
325
|
+
if (le >= code.length)
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
return [start, end];
|
|
329
|
+
}
|
|
330
|
+
const E = '(?![\\p{L}\\p{N}_$])';
|
|
331
|
+
function defPatterns(lang, name, inContainer) {
|
|
332
|
+
const N = esc(name);
|
|
333
|
+
const re = (src) => new RegExp(src, 'gu');
|
|
334
|
+
switch (lang) {
|
|
335
|
+
case 'ts':
|
|
336
|
+
return [
|
|
337
|
+
re(`\\bfunction\\s*\\*?\\s*${N}\\s*[<(]`),
|
|
338
|
+
re(`\\b(class|interface|enum|namespace|module)\\s+${N}${E}`),
|
|
339
|
+
re(`\\btype\\s+${N}${E}\\s*[<=]`),
|
|
340
|
+
re(`\\b(const|let|var)\\s+${N}${E}\\s*[:=]`),
|
|
341
|
+
re(`(^|[\\n;{}])[ \\t]*((public|private|protected|static|async|readonly|override|abstract|get|set|declare)\\s+)*\\*?${N}\\s*[<(]`),
|
|
342
|
+
re(`(^|[\\n;{}])[ \\t]*((public|private|protected|static|readonly|override|declare)\\s+)*${N}\\s*[?!]?\\s*(:(?:[^=;\\n]|=>)*)?=(?![=>])`),
|
|
343
|
+
re(`(^|[\\n;{}])[ \\t]*(module\\.)?exports\\.${N}\\s*=(?!=)`),
|
|
344
|
+
...(inContainer ? [re(`(^|[\\n{,])[ \\t]*${N}\\s*[?]?:`)] : []),
|
|
345
|
+
];
|
|
346
|
+
case 'py':
|
|
347
|
+
return [
|
|
348
|
+
re(`(^|\\n)[ \\t]*(async\\s+)?def\\s+${N}\\s*\\(`),
|
|
349
|
+
re(`(^|\\n)[ \\t]*class\\s+${N}${E}`),
|
|
350
|
+
re(`(^|\\n)${inContainer ? '[ \\t]+' : ''}${N}\\s*(:[^=\\n]+)?=(?!=)`),
|
|
351
|
+
];
|
|
352
|
+
case 'go':
|
|
353
|
+
return [
|
|
354
|
+
re(`\\bfunc\\s+${N}\\s*[\\[(]`),
|
|
355
|
+
re(`\\bfunc\\s*\\([^)]*\\)\\s*${N}\\s*[\\[(]`),
|
|
356
|
+
re(`\\btype\\s+${N}${E}`),
|
|
357
|
+
re(`\\b(var|const)\\s+${N}${E}`),
|
|
358
|
+
re(`(^|\\n)[ \\t]+${N}${E}(\\s*,\\s*[\\p{L}\\p{N}_]+)*\\s*(=|[\\w\\[*]+[^\\n]*=)`),
|
|
359
|
+
re(`(^|\\n)[ \\t]+${N}\\s+(struct|interface)${E}`),
|
|
360
|
+
...(inContainer ? [re(`(^|\\n)[ \\t]+${N}\\s*(\\(|\\s+\\S)`)] : []),
|
|
361
|
+
];
|
|
362
|
+
case 'java':
|
|
363
|
+
case 'cs':
|
|
364
|
+
return [
|
|
365
|
+
re(`\\b(class|interface|enum|record|struct)\\s+${N}${E}`),
|
|
366
|
+
re(`(^|[\\n;{}])[ \\t]*([\\w<>\\[\\],.?]+\\s+){1,12}${N}\\s*(<[^>]*>)?\\s*\\(`),
|
|
367
|
+
re(`(^|[\\n;{}\\]])[ \\t]*([\\w<>\\[\\],.?]+\\s+){1,12}${N}\\s*(=(?![=>])|;|\\{\\s*(get|set|init)\\b|=>)`),
|
|
368
|
+
];
|
|
369
|
+
case 'rs':
|
|
370
|
+
return [re(`\\bfn\\s+${N}${E}`), re(`\\b(struct|enum|trait|type|mod|const|static|union)\\s+${N}${E}`)];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const KEYWORDS = new Set(['if', 'for', 'while', 'switch', 'catch', 'return', 'new', 'await', 'typeof', 'else', 'do', 'throw']);
|
|
374
|
+
function depthAt(code, pos, lo) {
|
|
375
|
+
let d = 0;
|
|
376
|
+
for (let i = lo; i < pos; i++) {
|
|
377
|
+
if (code[i] === '{')
|
|
378
|
+
d++;
|
|
379
|
+
else if (code[i] === '}')
|
|
380
|
+
d--;
|
|
381
|
+
}
|
|
382
|
+
return d;
|
|
383
|
+
}
|
|
384
|
+
function inTypeBody(code, pos) {
|
|
385
|
+
for (let i = pos, d = 0; i >= 0; i--) {
|
|
386
|
+
if (code[i] === '}')
|
|
387
|
+
d++;
|
|
388
|
+
else if (code[i] === '{' && d-- === 0)
|
|
389
|
+
return /(?<![:,]\s*)\b(class|interface|enum|record|struct)\s+\w[^;{}]*$/.test(code.slice(Math.max(0, i - 300), i));
|
|
390
|
+
}
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
function pyMemberIndent(code, lo, hi) {
|
|
394
|
+
const base = indentAt(code, lo);
|
|
395
|
+
let min = Infinity;
|
|
396
|
+
for (const line of code.slice(lo, hi).split('\n').slice(1)) {
|
|
397
|
+
const ind = /^[ \t]*/.exec(line)[0].length;
|
|
398
|
+
if (line.trim() && ind > base)
|
|
399
|
+
min = Math.min(min, ind);
|
|
400
|
+
}
|
|
401
|
+
return min;
|
|
402
|
+
}
|
|
403
|
+
function findIn(code, lang, name, lo, hi, member, receiver) {
|
|
404
|
+
if (KEYWORDS.has(name) && !/^(rs|py|go)$/.test(lang))
|
|
405
|
+
return null;
|
|
406
|
+
const inContainer = member != null;
|
|
407
|
+
const memberIndent = inContainer && lang === 'py' ? pyMemberIndent(code, lo, hi) : -1;
|
|
408
|
+
const hits = [];
|
|
409
|
+
const pats = defPatterns(lang, name, inContainer);
|
|
410
|
+
for (const re of pats) {
|
|
411
|
+
re.lastIndex = lo;
|
|
412
|
+
let m;
|
|
413
|
+
while ((m = re.exec(code)) && m.index < hi) {
|
|
414
|
+
const nameAt = m.index + m[0].lastIndexOf(name);
|
|
415
|
+
const start = m.index + /^[;{}\]]?\s*/.exec(m[0])[0].length;
|
|
416
|
+
if (lang === 'go' && receiver != null && !/^\s*func\s*\(/.test(m[0]))
|
|
417
|
+
continue;
|
|
418
|
+
if (lang === 'go' && receiver != null && /^func\s*\(/.test(m[0])) {
|
|
419
|
+
const recv = /^func\s*\(([^)]*)\)/
|
|
420
|
+
.exec(m[0])[1]
|
|
421
|
+
.trim()
|
|
422
|
+
.replace(/^\w+\s+/, '');
|
|
423
|
+
if (!new RegExp(`^\\*?\\s*${esc(receiver)}(\\[[^\\]]*\\])?$`).test(recv))
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (code[nameAt - 1] === '.' && !/\bexports\.$/.test(code.slice(nameAt - 8, nameAt)))
|
|
427
|
+
continue;
|
|
428
|
+
if (lang === 'ts') {
|
|
429
|
+
const before = code.slice(0, start);
|
|
430
|
+
if (/[?:=&|]\s*$/.test(before) && !/\b(case\b[^:\n]*|default)\s*:\s*$/.test(before))
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (lang === 'go' && !inContainer && m[0].includes('=') && depthAt(code, start, 0) > 0)
|
|
434
|
+
continue;
|
|
435
|
+
if ((lang === 'java' || lang === 'cs') && re === pats.at(-1) && !inTypeBody(code, start))
|
|
436
|
+
continue;
|
|
437
|
+
const ext = lang === 'py' ? pyBody(code, start) : braceBody(code, nameAt + name.length, start, lang);
|
|
438
|
+
if (!ext)
|
|
439
|
+
continue;
|
|
440
|
+
const isMethodish = /\(\s*$/.test(m[0]) && !/\b(function|func|fn|def)\b/.test(m[0]);
|
|
441
|
+
if (isMethodish && lang !== 'py' && !(lang === 'go' && inContainer)) {
|
|
442
|
+
const p = code.indexOf('(', nameAt);
|
|
443
|
+
const close = matchClose(code, p);
|
|
444
|
+
const after = code.slice(close + 1).match(/^\s*(\S)/)?.[1];
|
|
445
|
+
if (!after ||
|
|
446
|
+
!(after === '{' ||
|
|
447
|
+
after === ':' ||
|
|
448
|
+
(lang !== 'ts' && /[\w=]/.test(after)) ||
|
|
449
|
+
(after === ';' && (lang === 'java' || lang === 'cs'))))
|
|
450
|
+
continue;
|
|
451
|
+
if (lang === 'java' || lang === 'cs') {
|
|
452
|
+
const pre = code.slice(start, nameAt);
|
|
453
|
+
if (/\b(new|return|else|throw)\s*$/.test(pre) || /[=.]\s*$/.test(pre))
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (lang === 'java' || lang === 'cs') {
|
|
458
|
+
const pre = code.slice(start, nameAt);
|
|
459
|
+
if (/\b(new|return|else|throw|await|yield|case|in|is|as|out|ref|goto|var|using|do)\s*$/.test(pre) || /[=.]\s*$/.test(pre))
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const depth = depthAt(code, start, lo);
|
|
463
|
+
if (inContainer && lang === 'py' ? indentAt(code, start) !== memberIndent : member != null && depth !== member)
|
|
464
|
+
continue;
|
|
465
|
+
hits.push({ start, end: ext[1], depth });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!hits.length)
|
|
469
|
+
return null;
|
|
470
|
+
hits.sort((a, b) => a.depth - b.depth || a.start - b.start);
|
|
471
|
+
return [hits[0].start, hits[0].end];
|
|
472
|
+
}
|
|
473
|
+
function rustImpls(code, type) {
|
|
474
|
+
const out = [];
|
|
475
|
+
const self = new RegExp(`^(&\\s*('\\w+\\s+)?(mut\\s+)?)?(\\w+::)*${esc(type)}\\s*(<.*>)?$`, 's');
|
|
476
|
+
const re = /\bimpl\b([^{;]*)\{/g;
|
|
477
|
+
let m;
|
|
478
|
+
while ((m = re.exec(code))) {
|
|
479
|
+
let head = m[1].replace(/\bwhere\b[^]*$/, '').trim();
|
|
480
|
+
if (head.startsWith('<')) {
|
|
481
|
+
let d = 0;
|
|
482
|
+
let k = 0;
|
|
483
|
+
do {
|
|
484
|
+
if (head[k] === '<')
|
|
485
|
+
d++;
|
|
486
|
+
else if (head[k] === '>' && head[k - 1] !== '-')
|
|
487
|
+
d--;
|
|
488
|
+
} while (d > 0 && ++k < head.length);
|
|
489
|
+
head = head.slice(k + 1).trim();
|
|
490
|
+
}
|
|
491
|
+
const forAt = head.search(/\bfor\s/);
|
|
492
|
+
if (!self.test(forAt < 0 ? head : head.slice(forAt + 3).trim()))
|
|
493
|
+
continue;
|
|
494
|
+
const open = m.index + m[0].length - 1;
|
|
495
|
+
out.push([open, matchClose(code, open)]);
|
|
496
|
+
}
|
|
497
|
+
return out;
|
|
498
|
+
}
|
|
499
|
+
export function outside(root, path) {
|
|
500
|
+
const rel = relative(root, resolve(root, path));
|
|
501
|
+
return isAbsolute(path) || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
|
|
502
|
+
}
|
|
503
|
+
export function codeFile(root, path, read = readFile, cache = new Map()) {
|
|
504
|
+
const full = resolve(root, path);
|
|
505
|
+
if (cache.has(full))
|
|
506
|
+
return cache.get(full);
|
|
507
|
+
const lang = outside(root, path) ? null : langOf(path);
|
|
508
|
+
const raw = lang ? read(full) : null;
|
|
509
|
+
const text = raw?.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
|
|
510
|
+
const file = lang && text != null ? { path, lang, text, ...mask(text, lang) } : null;
|
|
511
|
+
cache.set(full, file);
|
|
512
|
+
return file;
|
|
513
|
+
}
|
|
514
|
+
export function locate(file, symbol) {
|
|
515
|
+
const { code, lang } = file;
|
|
516
|
+
const parts = symbol.split('.');
|
|
517
|
+
if (!parts.every((p) => /^[\p{L}_$][\p{L}\p{N}_$]*$/u.test(p)))
|
|
518
|
+
return null;
|
|
519
|
+
const hit = (r) => {
|
|
520
|
+
if (!r)
|
|
521
|
+
return null;
|
|
522
|
+
let start = r[0];
|
|
523
|
+
for (;;) {
|
|
524
|
+
const prevEnd = code.lastIndexOf('\n', start - 1);
|
|
525
|
+
if (prevEnd < 0)
|
|
526
|
+
break;
|
|
527
|
+
const prevStart = code.lastIndexOf('\n', prevEnd - 1) + 1;
|
|
528
|
+
if (!/^\s*@/.test(code.slice(prevStart, prevEnd)))
|
|
529
|
+
break;
|
|
530
|
+
start = prevStart;
|
|
531
|
+
}
|
|
532
|
+
return { start, end: r[1] };
|
|
533
|
+
};
|
|
534
|
+
if (parts.length === 1 && parts[0] === 'default' && lang === 'ts') {
|
|
535
|
+
const m = /\bexport\s+default\b/.exec(code);
|
|
536
|
+
return m && { start: m.index, end: code.length };
|
|
537
|
+
}
|
|
538
|
+
if (parts.length === 1)
|
|
539
|
+
return hit(findIn(code, lang, parts[0], 0, code.length, null));
|
|
540
|
+
if (lang === 'go' && parts.length === 2) {
|
|
541
|
+
const r = findIn(code, lang, parts[1], 0, code.length, null, parts[0]);
|
|
542
|
+
if (r && /^func\s*\(/.test(code.slice(r[0])))
|
|
543
|
+
return hit(r);
|
|
544
|
+
const t = findIn(code, lang, parts[0], 0, code.length, null);
|
|
545
|
+
return t ? hit(findIn(code, lang, parts[1], t[0] + 1, t[1], 1)) : null;
|
|
546
|
+
}
|
|
547
|
+
if (lang === 'rs' && parts.length === 2) {
|
|
548
|
+
for (const [a, b] of rustImpls(code, parts[0])) {
|
|
549
|
+
const r = findIn(code, lang, parts[1], a + 1, b, 0);
|
|
550
|
+
if (r)
|
|
551
|
+
return hit(r);
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
let lo = 0;
|
|
556
|
+
let hi = code.length;
|
|
557
|
+
for (let k = 0; k < parts.length; k++) {
|
|
558
|
+
const r = findIn(code, lang, parts[k], lo, hi, k > 0 ? 1 : null);
|
|
559
|
+
if (!r)
|
|
560
|
+
return null;
|
|
561
|
+
if (k === parts.length - 1)
|
|
562
|
+
return hit(r);
|
|
563
|
+
lo = r[0] + 1;
|
|
564
|
+
hi = r[1];
|
|
565
|
+
}
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
export const isDefined = (file, symbol) => locate(file, symbol) != null;
|
package/dist/diff.js
CHANGED
|
@@ -19,9 +19,16 @@ function compare(kind, before, after, fieldsOf) {
|
|
|
19
19
|
out.push({ kind, op: 'added', id });
|
|
20
20
|
return out;
|
|
21
21
|
}
|
|
22
|
-
const messages = (fig) =>
|
|
23
|
-
|
|
24
|
-
.
|
|
22
|
+
const messages = (fig) => {
|
|
23
|
+
const seen = new Map();
|
|
24
|
+
const ids = (fig.steps ?? []).flatMap((s) => s.flow.flatMap((b) => toBeat(b).hops.map((h) => {
|
|
25
|
+
const key = `${str(s.label)}: ${h.edge}${h.back ? ' back' : ''}${h.data != null ? ` "${str(h.data)}"` : ''}${h.tone ? ` [${h.tone}]` : ''}${h.source ? ` <${h.source}>` : ''}${h.via ? ` via ${h.via}` : ''}`;
|
|
26
|
+
const n = (seen.get(key) ?? 0) + 1;
|
|
27
|
+
seen.set(key, n);
|
|
28
|
+
return n > 1 ? `${key} #${n}` : key;
|
|
29
|
+
})));
|
|
30
|
+
return new Map(ids.map((id) => [id, id]));
|
|
31
|
+
};
|
|
25
32
|
export function diff(before, after) {
|
|
26
33
|
const boxes = (f) => {
|
|
27
34
|
const m = new Map();
|
|
@@ -33,8 +40,8 @@ export function diff(before, after) {
|
|
|
33
40
|
const steps = (f) => new Map((f.steps ?? []).map((s) => [str(s.label), s]));
|
|
34
41
|
const out = [
|
|
35
42
|
...compare('box', boxes(before), boxes(after), (a, b) => fields(a, b, ['label', 'sub', 'shape', 'tone', 'mark', 'from', 'to', 'source', 'group'])),
|
|
36
|
-
...compare('edge', edges(before), edges(after), (a, b) => fields(a, b, ['from', 'to', 'label', 'source'])),
|
|
37
|
-
...compare('step', steps(before), steps(after), () => []),
|
|
43
|
+
...compare('edge', edges(before), edges(after), (a, b) => fields(a, b, ['from', 'to', 'label', 'source', 'via'])),
|
|
44
|
+
...compare('step', steps(before), steps(after), (a, b) => fields(a, b, ['caption'])),
|
|
38
45
|
...compare('message', messages(before), messages(after), () => []),
|
|
39
46
|
];
|
|
40
47
|
// The rail is a boolean or 'only', so str() would hide it.
|
package/dist/draw.js
CHANGED
|
@@ -22,7 +22,7 @@ export function slug(question) {
|
|
|
22
22
|
const specPath = (out) => out.replace(/\.svg$/, '.json');
|
|
23
23
|
export function agentArgs(o) {
|
|
24
24
|
const spec = specPath(o.out);
|
|
25
|
-
const system = `${AGENT_TEXT.trimEnd()}\n\nWrite the spec to ${spec} with the Write tool. Then run exactly \`npx flowfig ${spec} ${o.out}\` as one command, alone on its line: no heredoc, no \`;\`, no \`&&\`, no pipe (the permission rule matches one plain command only).
|
|
25
|
+
const system = `${AGENT_TEXT.trimEnd()}\n\nWrite the spec to ${spec} with the Write tool. Then run exactly \`npx flowfig ${spec} ${o.out}\` as one command, alone on its line: no heredoc, no \`;\`, no \`&&\`, no pipe (the permission rule matches one plain command only). Run every command from this folder, ${o.cwd}.`;
|
|
26
26
|
const args = [
|
|
27
27
|
'-p',
|
|
28
28
|
o.question,
|
package/dist/edges.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CodeFile, type Lang, type Read } from './code.ts';
|
|
2
|
+
export type EdgeResult = 'found' | 'not-found' | 'unsure' | 'not-checked';
|
|
3
|
+
export declare const EDGE_LANGS: ReadonlySet<Lang>;
|
|
4
|
+
export declare function edgeResult(root: string, caller: string, callee: string | undefined, via: string | undefined, read?: Read, cache?: Map<string, CodeFile | null>): {
|
|
5
|
+
result: EdgeResult;
|
|
6
|
+
reason: string;
|
|
7
|
+
};
|