agentlas 1.0.41 → 1.0.43

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 (72) hide show
  1. package/CHANGELOG.md +40 -8
  2. package/engine/agentlas-workforce.cjs +1 -1
  3. package/engine/commands/index.cjs +5 -5
  4. package/engine/hephaestus/runtime.cjs +1 -1
  5. package/engine/storm/storm.cjs +1 -1
  6. package/engine/storm/swarm.cjs +1 -1
  7. package/engine/ui/repl.cjs +4 -4
  8. package/engine/ui/{pitui-screens.cjs → screens.cjs} +143 -6
  9. package/engine/ui/{pitui-shell.cjs → shell.cjs} +42 -27
  10. package/engine/ui/width.cjs +1 -1
  11. package/engine/vendor/mermaid/LICENSE +205 -0
  12. package/engine/vendor/mermaid/ansi.js +23 -0
  13. package/engine/vendor/mermaid/canvas.js +366 -0
  14. package/engine/vendor/mermaid/graph.js +91 -0
  15. package/engine/vendor/mermaid/index.js +100 -0
  16. package/engine/vendor/mermaid/labels.js +324 -0
  17. package/engine/vendor/mermaid/layout-seq.js +194 -0
  18. package/engine/vendor/mermaid/layout.js +881 -0
  19. package/engine/vendor/mermaid/package.json +1 -0
  20. package/engine/vendor/mermaid/parse.js +1108 -0
  21. package/engine/vendor/mermaid/source-box.js +78 -0
  22. package/engine/vendor/mermaid/types.js +1 -0
  23. package/engine/vendor/mermaid/width-data.js +994 -0
  24. package/engine/vendor/mermaid/width.js +76 -0
  25. package/engine/vendor/tui/LICENSE +20 -0
  26. package/engine/vendor/tui/autocomplete.js +632 -0
  27. package/engine/vendor/tui/components/alt-screen-flash.js +37 -0
  28. package/engine/vendor/tui/components/box.js +104 -0
  29. package/engine/vendor/tui/components/cancellable-loader.js +35 -0
  30. package/engine/vendor/tui/components/editor.js +1961 -0
  31. package/engine/vendor/tui/components/h-stack.js +43 -0
  32. package/engine/vendor/tui/components/image.js +90 -0
  33. package/engine/vendor/tui/components/input.js +378 -0
  34. package/engine/vendor/tui/components/loader.js +69 -0
  35. package/engine/vendor/tui/components/markdown.js +806 -0
  36. package/engine/vendor/tui/components/scroll-view.js +173 -0
  37. package/engine/vendor/tui/components/select-list.js +159 -0
  38. package/engine/vendor/tui/components/settings-list.js +182 -0
  39. package/engine/vendor/tui/components/spacer.js +23 -0
  40. package/engine/vendor/tui/components/stack.js +111 -0
  41. package/engine/vendor/tui/components/text.js +89 -0
  42. package/engine/vendor/tui/components/truncated-text.js +51 -0
  43. package/engine/vendor/tui/components/v-stack.js +26 -0
  44. package/engine/vendor/tui/deps/east-asian-width/LICENSE +9 -0
  45. package/engine/vendor/tui/deps/east-asian-width/index.js +30 -0
  46. package/engine/vendor/tui/deps/east-asian-width/lookup-data.js +21 -0
  47. package/engine/vendor/tui/deps/east-asian-width/lookup.js +138 -0
  48. package/engine/vendor/tui/deps/east-asian-width/utilities.js +24 -0
  49. package/engine/vendor/tui/deps/marked/LICENSE +44 -0
  50. package/engine/vendor/tui/deps/marked/index.js +77 -0
  51. package/engine/vendor/tui/editor-component.js +2 -0
  52. package/engine/vendor/tui/fuzzy.js +110 -0
  53. package/engine/vendor/tui/index.js +42 -0
  54. package/engine/vendor/tui/keybindings.js +209 -0
  55. package/engine/vendor/tui/keys.js +1174 -0
  56. package/engine/vendor/tui/kill-ring.js +44 -0
  57. package/engine/vendor/tui/latex.js +1264 -0
  58. package/engine/vendor/tui/layout-node.js +6 -0
  59. package/engine/vendor/tui/layout.js +314 -0
  60. package/engine/vendor/tui/native-modifiers.js +60 -0
  61. package/engine/vendor/tui/package.json +1 -0
  62. package/engine/vendor/tui/stdin-buffer.js +361 -0
  63. package/engine/vendor/tui/terminal-colors.js +59 -0
  64. package/engine/vendor/tui/terminal-image.js +518 -0
  65. package/engine/vendor/tui/terminal.js +436 -0
  66. package/engine/vendor/tui/tui-alt-screen.js +902 -0
  67. package/engine/vendor/tui/tui-main-screen.js +533 -0
  68. package/engine/vendor/tui/tui.js +937 -0
  69. package/engine/vendor/tui/undo-stack.js +25 -0
  70. package/engine/vendor/tui/utils.js +1191 -0
  71. package/engine/vendor/tui/word-navigation.js +96 -0
  72. package/package.json +2 -6
@@ -0,0 +1,1108 @@
1
+ /**
2
+ * Source text to diagram model.
3
+ *
4
+ * Every `parseX` returns `null` when the source is not that kind of diagram,
5
+ * or when it exceeds a cap — `render` tries each in turn and falls back to a
6
+ * framed copy of the source when they all decline.
7
+ */
8
+ import { emptyClassInfo, Graph, MAX_EDGES, MAX_GROUP_DEPTH, MAX_GROUPS, MAX_MEMBERS, MAX_NODES, parseDir, } from './graph.js';
9
+ import { asciiLower, cleanLabel, decodeHtmlEntities, isIdChar, srcLines } from './labels.js';
10
+ // ---------------------------------------------------------------- statements
11
+ function flushStatement(cur, out) {
12
+ const trimmed = cur.trim();
13
+ if (trimmed !== '')
14
+ out.push(trimmed);
15
+ return '';
16
+ }
17
+ /**
18
+ * Split one source line into statements on `;`, stopping at a `%%` comment.
19
+ *
20
+ * Quoted spans are opaque, so a label may contain `;` and `%%`.
21
+ */
22
+ export function splitStatements(line, out) {
23
+ const chars = [...line];
24
+ let cur = '';
25
+ let inQuotes = false;
26
+ for (let i = 0; i < chars.length; i++) {
27
+ const c = chars[i];
28
+ if (inQuotes) {
29
+ if (c === '"')
30
+ inQuotes = false;
31
+ cur += c;
32
+ }
33
+ else if (c === '"') {
34
+ inQuotes = true;
35
+ cur += c;
36
+ }
37
+ else if (c === '%' && chars[i + 1] === '%') {
38
+ break;
39
+ }
40
+ else if (c === ';') {
41
+ cur = flushStatement(cur, out);
42
+ }
43
+ else {
44
+ cur += c;
45
+ }
46
+ }
47
+ flushStatement(cur, out);
48
+ }
49
+ /** All statements in a source block, in order. */
50
+ export function statementsOf(src) {
51
+ const out = [];
52
+ for (const line of srcLines(src))
53
+ splitStatements(line, out);
54
+ return out;
55
+ }
56
+ const firstWord = (s) => s.split(/\s+/).filter((w) => w !== '')[0] ?? '';
57
+ const words = (s) => s.split(/\s+/).filter((w) => w !== '');
58
+ /** Split on the first occurrence of `sep`, Rust's `split_once`. */
59
+ function splitOnce(s, sep) {
60
+ const i = s.indexOf(sep);
61
+ return i === -1 ? null : [s.slice(0, i), s.slice(i + sep.length)];
62
+ }
63
+ const nonEmpty = (s) => (s === '' ? null : s);
64
+ /** Diagram kind from the header statement, lowercased. */
65
+ function headerKind(statements) {
66
+ const header = statements[0];
67
+ if (header === undefined)
68
+ return null;
69
+ const kind = firstWord(header);
70
+ return kind === '' ? null : asciiLower(kind);
71
+ }
72
+ /**
73
+ * The kind of diagram `src` declares, or `null` if its header names no type
74
+ * this renderer draws.
75
+ *
76
+ * Reads the header only — it says nothing about whether the body parses. Pair
77
+ * it with `render` to tell a source this renderer will never draw from one that
78
+ * is merely malformed:
79
+ *
80
+ * ```ts
81
+ * render(src) === null && diagramKind(src) !== null // syntax error
82
+ * ```
83
+ *
84
+ * Each branch mirrors the header test in the matching `parseX`, so the two
85
+ * always agree on what they recognise.
86
+ */
87
+ export function diagramKind(src) {
88
+ const kind = headerKind(statementsOf(src));
89
+ if (kind === null)
90
+ return null;
91
+ if (kind === 'graph' || kind === 'flowchart')
92
+ return 'flowchart';
93
+ if (kind.startsWith('statediagram'))
94
+ return 'state';
95
+ if (kind.startsWith('classdiagram'))
96
+ return 'class';
97
+ if (kind === 'erdiagram')
98
+ return 'er';
99
+ if (kind === 'sequencediagram')
100
+ return 'sequence';
101
+ return null;
102
+ }
103
+ // ----------------------------------------------------------------- flowchart
104
+ export function parseGraph(src) {
105
+ const statements = statementsOf(src);
106
+ const kind = headerKind(statements);
107
+ if (kind !== 'graph' && kind !== 'flowchart')
108
+ return null;
109
+ const graph = new Graph(parseDir(words(statements[0])[1] ?? 'TB'));
110
+ const stack = [];
111
+ for (const st of statements.slice(1)) {
112
+ switch (asciiLower(firstWord(st))) {
113
+ case 'subgraph': {
114
+ if (graph.groups.length >= MAX_GROUPS || stack.length >= MAX_GROUP_DEPTH)
115
+ return null;
116
+ const [id, label] = parseSubgraphDecl(st.slice('subgraph'.length).trim());
117
+ graph.groups.push({ id, label, parent: stack.at(-1) ?? null });
118
+ stack.push(graph.groups.length - 1);
119
+ graph.curGroup = stack.at(-1) ?? null;
120
+ continue;
121
+ }
122
+ case 'end':
123
+ stack.pop();
124
+ graph.curGroup = stack.at(-1) ?? null;
125
+ continue;
126
+ case 'classdef':
127
+ case 'class':
128
+ case 'style':
129
+ case 'linkstyle':
130
+ case 'click':
131
+ case 'direction':
132
+ continue;
133
+ default:
134
+ break;
135
+ }
136
+ parseStatement(st, graph);
137
+ if (graph.overCap)
138
+ return null;
139
+ }
140
+ return graph.nodes.length === 0 ? null : graph;
141
+ }
142
+ /** `subgraph id[Title]`, `subgraph "Title"`, or a bare title. */
143
+ function parseSubgraphDecl(rest) {
144
+ if (rest.startsWith('"')) {
145
+ const close = rest.indexOf('"', 1);
146
+ if (close !== -1) {
147
+ const label = rest.slice(1, close);
148
+ return [label, decodeHtmlEntities(label)];
149
+ }
150
+ }
151
+ const open = rest.indexOf('[');
152
+ if (open !== -1) {
153
+ const id = rest.slice(0, open).trim();
154
+ const label = cleanLabel(rest
155
+ .slice(open + 1)
156
+ .replace(/\]+$/, '')
157
+ .trim());
158
+ if (id !== '' && label !== '')
159
+ return [id, label];
160
+ }
161
+ return [rest, rest];
162
+ }
163
+ /**
164
+ * A chain of `node link node link node ...`, each link fanning out over `&`.
165
+ *
166
+ * Parses as far as it can and keeps the prefix, matching upstream and
167
+ * mermaid.js. Whatever it could not read is recorded in `graph.warnings` rather
168
+ * than failing the diagram — see the note on that field.
169
+ */
170
+ function parseStatement(st, graph) {
171
+ const chars = [...st];
172
+ let i = 0;
173
+ const head = parseNodeGroup(chars, i, graph);
174
+ if (!head) {
175
+ graph.warnings.push(`dropped, does not start with a node: "${st}"`);
176
+ return;
177
+ }
178
+ let prev = head.group;
179
+ i = head.next;
180
+ for (;;) {
181
+ i = skipSpaces(chars, i);
182
+ if (i >= chars.length)
183
+ break;
184
+ const link = parseLink(chars, i);
185
+ if (!link) {
186
+ graph.warnings.push(`dropped, expected a link: "${chars.slice(i).join('')}"`);
187
+ break;
188
+ }
189
+ i = skipSpaces(chars, link.next);
190
+ const target = parseNodeGroup(chars, i, graph);
191
+ if (!target) {
192
+ graph.warnings.push(`dropped, link has no target: "${st}"`);
193
+ break;
194
+ }
195
+ i = target.next;
196
+ for (const f of prev) {
197
+ for (const t of target.group) {
198
+ // `A <-- B` reads right-to-left: swap the endpoints so the arrow that
199
+ // was written on the left becomes a normal forward head.
200
+ const reversed = link.left === 'arrow' && link.right !== 'arrow';
201
+ const pushed = graph.pushEdge({
202
+ from: reversed ? t : f,
203
+ to: reversed ? f : t,
204
+ label: link.label,
205
+ headTo: reversed ? 'arrow' : link.right,
206
+ headFrom: reversed ? link.right : link.left,
207
+ line: link.line,
208
+ });
209
+ if (!pushed)
210
+ return;
211
+ }
212
+ }
213
+ prev = target.group;
214
+ }
215
+ }
216
+ /** One or more nodes joined by `&`, which fan out into a cross product. */
217
+ function parseNodeGroup(chars, start, graph) {
218
+ const first = parseNode(chars, start, graph);
219
+ if (!first)
220
+ return null;
221
+ const group = [first.index];
222
+ let i = first.next;
223
+ for (;;) {
224
+ const j = skipSpaces(chars, i);
225
+ if (chars[j] !== '&')
226
+ break;
227
+ const next = parseNode(chars, j + 1, graph);
228
+ if (!next)
229
+ return null;
230
+ group.push(next.index);
231
+ i = next.next;
232
+ }
233
+ return { group, next: i };
234
+ }
235
+ function skipSpaces(chars, i) {
236
+ while (i < chars.length && (chars[i] === ' ' || chars[i] === '\t'))
237
+ i++;
238
+ return i;
239
+ }
240
+ function parseNode(chars, start, graph) {
241
+ let i = skipSpaces(chars, start);
242
+ const idStart = i;
243
+ while (i < chars.length && isIdChar(chars[i]))
244
+ i++;
245
+ if (i === idStart)
246
+ return null;
247
+ const id = chars.slice(idStart, i).join('');
248
+ const shaped = readShapeAt(chars, i);
249
+ if (shaped.unclosed !== undefined) {
250
+ graph.warnings.push(`node "${id}": label is missing its closing \`${shaped.unclosed}\``);
251
+ }
252
+ const index = graph.nodeIndex(id, shaped.label, shaped.shape);
253
+ return index === null ? null : { index, next: shaped.after };
254
+ }
255
+ /** Dispatch on the bracket following an id to pick shape and closing token. */
256
+ function readShapeAt(chars, i) {
257
+ const c = chars[i];
258
+ const n = chars[i + 1];
259
+ if (c === '[') {
260
+ if (n === '[')
261
+ return readShape(chars, i + 2, ']]', 'rect');
262
+ if (n === '(')
263
+ return readShape(chars, i + 2, ')]', 'round');
264
+ return readShape(chars, i + 1, ']', 'rect');
265
+ }
266
+ if (c === '(') {
267
+ if (n === '(')
268
+ return readShape(chars, i + 2, '))', 'round');
269
+ if (n === '[')
270
+ return readShape(chars, i + 2, '])', 'round');
271
+ return readShape(chars, i + 1, ')', 'round');
272
+ }
273
+ if (c === '{') {
274
+ if (n === '{')
275
+ return readShape(chars, i + 2, '}}', 'diamond');
276
+ return readShape(chars, i + 1, '}', 'diamond');
277
+ }
278
+ if (c === '>')
279
+ return readShape(chars, i + 1, ']', 'rect');
280
+ return { shape: 'rect', label: null, after: i };
281
+ }
282
+ /**
283
+ * Read label text up to `closer`.
284
+ *
285
+ * Quoting is decided by the first non-space character: inside a quoted label
286
+ * the closer is ignored until the quote closes, so `A["a] b"]` is one node.
287
+ * An unquoted label ends at the first closer, so `A[5" pipe]` keeps its quote.
288
+ */
289
+ function readShape(chars, start, closer, shape) {
290
+ let j = start;
291
+ while (chars[j] === ' ' || chars[j] === '\t')
292
+ j++;
293
+ const quoted = chars[j] === '"';
294
+ let i = start;
295
+ let text = '';
296
+ let inQuotes = false;
297
+ while (i < chars.length) {
298
+ const c = chars[i];
299
+ if (quoted && c === '"') {
300
+ inQuotes = !inQuotes;
301
+ text += c;
302
+ i++;
303
+ continue;
304
+ }
305
+ if (!inQuotes && chars.slice(i, i + closer.length).join('') === closer) {
306
+ return { shape, label: cleanLabel(text), after: i + closer.length };
307
+ }
308
+ text += c;
309
+ i++;
310
+ }
311
+ // Ran off the end still looking for the closer: everything after the opening
312
+ // bracket became label text, so any link operator in it was swallowed.
313
+ return { shape, label: cleanLabel(text), after: chars.length, unclosed: closer };
314
+ }
315
+ const isLinkChar = (c) => c === '-' || c === '.' || c === '=' || c === '<' || c === '>';
316
+ /**
317
+ * Read a link operator and its label.
318
+ *
319
+ * Labels come in two forms: `-->|text|` and the inline `-- text -->`, the
320
+ * latter only when the first operator carried no head.
321
+ */
322
+ function parseLink(chars, start) {
323
+ let i = skipSpaces(chars, start);
324
+ let left = 'none';
325
+ // A leading `o`/`x` decorates the tail, but only directly before an operator.
326
+ if ((chars[i] === 'o' || chars[i] === 'x') &&
327
+ (chars[i + 1] === '-' || chars[i + 1] === '.' || chars[i + 1] === '=')) {
328
+ left = chars[i] === 'o' ? 'circle' : 'cross';
329
+ i++;
330
+ }
331
+ const opStart = i;
332
+ while (i < chars.length && isLinkChar(chars[i]))
333
+ i++;
334
+ if (i === opStart)
335
+ return null;
336
+ const op1 = chars.slice(opStart, i).join('');
337
+ if (left === 'none' && op1.startsWith('<'))
338
+ left = 'arrow';
339
+ let line = lineKind(op1);
340
+ let right = op1.includes('>') ? 'arrow' : 'none';
341
+ if (right === 'none') {
342
+ const trailing = trailingHead(chars, i);
343
+ if (trailing) {
344
+ right = trailing.head;
345
+ i = trailing.next;
346
+ }
347
+ }
348
+ if (chars[i] === '|') {
349
+ i++;
350
+ const lStart = i;
351
+ while (i < chars.length && chars[i] !== '|')
352
+ i++;
353
+ const label = cleanLabel(chars.slice(lStart, i).join(''));
354
+ if (chars[i] === '|')
355
+ i++;
356
+ return { left, right, line, label: nonEmpty(label), next: i };
357
+ }
358
+ if (right === 'none') {
359
+ const textStart = skipSpaces(chars, i);
360
+ let j = textStart;
361
+ while (j < chars.length && !isLinkChar(chars[j]))
362
+ j++;
363
+ if (j < chars.length && j > textStart && chars[j] !== '<') {
364
+ const text = chars.slice(textStart, j).join('');
365
+ const op2Start = j;
366
+ while (j < chars.length && isLinkChar(chars[j]))
367
+ j++;
368
+ const op2 = chars.slice(op2Start, j).join('');
369
+ if (op2.includes('>')) {
370
+ right = 'arrow';
371
+ }
372
+ else {
373
+ const trailing = trailingHead(chars, j);
374
+ if (trailing) {
375
+ right = trailing.head;
376
+ j = trailing.next;
377
+ }
378
+ }
379
+ if (line === 'solid')
380
+ line = lineKind(op2);
381
+ return { left, right, line, label: nonEmpty(cleanLabel(text)), next: j };
382
+ }
383
+ }
384
+ return { left, right, line, label: null, next: i };
385
+ }
386
+ function lineKind(op) {
387
+ if (op.includes('='))
388
+ return 'thick';
389
+ if (op.includes('.'))
390
+ return 'dotted';
391
+ return 'solid';
392
+ }
393
+ /** A trailing `o`/`x` head, only when followed by a statement boundary. */
394
+ function trailingHead(chars, i) {
395
+ const head = chars[i] === 'o' ? 'circle' : chars[i] === 'x' ? 'cross' : null;
396
+ if (head === null)
397
+ return null;
398
+ const after = chars[i + 1];
399
+ const boundary = after === undefined ||
400
+ after === ' ' ||
401
+ after === '\t' ||
402
+ after === '|' ||
403
+ after === '&' ||
404
+ after === ';';
405
+ return boundary ? { head, next: i + 1 } : null;
406
+ }
407
+ // --------------------------------------------------------------------- state
408
+ export function parseState(src) {
409
+ const statements = statementsOf(src);
410
+ const kind = headerKind(statements);
411
+ if (kind === null || !kind.startsWith('statediagram'))
412
+ return null;
413
+ const graph = new Graph();
414
+ let inNote = false;
415
+ for (const st of statements.slice(1)) {
416
+ if (inNote) {
417
+ if (asciiLower(st) === 'end note')
418
+ inNote = false;
419
+ continue;
420
+ }
421
+ const first = asciiLower(firstWord(st));
422
+ if (first === 'direction') {
423
+ graph.dir = parseDir(words(st)[1] ?? '');
424
+ }
425
+ else if (first === 'note') {
426
+ // A single-line `note ... : text` needs no terminator.
427
+ if (!st.includes(':'))
428
+ inNote = true;
429
+ }
430
+ else if (first === 'state') {
431
+ if (parseStateDecl(st, graph) === null)
432
+ return null;
433
+ }
434
+ else if (['classdef', 'class', 'hide', 'scale', '}', '--'].includes(first)) {
435
+ // Styling and composite-state punctuation carry no layout meaning.
436
+ }
437
+ else if (st.includes('-->')) {
438
+ if (parseTransition(st, graph) === null)
439
+ return null;
440
+ }
441
+ else if (parseStateDesc(st, graph) === null) {
442
+ return null;
443
+ }
444
+ if (graph.overCap)
445
+ return null;
446
+ }
447
+ return graph.nodes.length === 0 ? null : graph;
448
+ }
449
+ /** `state "Label" as id`, `state id <<choice>>`, or `state id {`. */
450
+ function parseStateDecl(st, graph) {
451
+ const rest = st.slice('state'.length).trim().replace(/\{$/, '').trim();
452
+ if (rest === '')
453
+ return true;
454
+ if (rest.startsWith('"')) {
455
+ const close = rest.indexOf('"', 1);
456
+ if (close === -1)
457
+ return null;
458
+ const label = rest.slice(1, close);
459
+ const after = rest.slice(close + 1).trim();
460
+ const id = after.startsWith('as') ? after.slice(2).trim() : label;
461
+ return graph.nodeLabel(id, decodeHtmlEntities(label)) === null ? null : true;
462
+ }
463
+ let shape = 'round';
464
+ let id = rest;
465
+ let stereotyped = false;
466
+ const pos = rest.indexOf('<<');
467
+ if (pos !== -1) {
468
+ const stereo = rest
469
+ .slice(pos + 2)
470
+ .replace(/>>$/, '')
471
+ .trim();
472
+ if (stereo === 'choice')
473
+ shape = 'diamond';
474
+ id = rest.slice(0, pos).trim();
475
+ stereotyped = true;
476
+ }
477
+ if (id === '' || /\s/.test(id))
478
+ return null;
479
+ return graph.nodeIndex(id, stereotyped ? id : null, shape) === null ? null : true;
480
+ }
481
+ /** `A --> B: label`, including chains `A --> B --> C`. */
482
+ function parseTransition(st, graph) {
483
+ let rest = st;
484
+ let prev = null;
485
+ for (;;) {
486
+ const split = splitOnce(rest, '-->');
487
+ if (!split)
488
+ break;
489
+ const [lhs, rhs] = split;
490
+ const fromId = lhs.trimEnd().replace(/-+$/, '').trim();
491
+ let from;
492
+ if (prev !== null) {
493
+ // Mid-chain: the source is the previous target, so nothing may precede.
494
+ if (fromId !== '')
495
+ return null;
496
+ from = prev;
497
+ }
498
+ else {
499
+ if (fromId === '')
500
+ return null;
501
+ const f = stateEndpoint(graph, fromId, true);
502
+ if (f === null)
503
+ return null;
504
+ from = f;
505
+ }
506
+ const nextArrow = rhs.indexOf('-->');
507
+ const toPartRaw = nextArrow === -1 ? rhs : rhs.slice(0, nextArrow);
508
+ const tail = nextArrow === -1 ? '' : rhs.slice(nextArrow);
509
+ const colon = splitOnce(toPartRaw, ':');
510
+ const toPart = colon ? colon[0] : toPartRaw;
511
+ const label = colon ? nonEmpty(decodeHtmlEntities(colon[1].trim())) : null;
512
+ const toId = toPart.trimStart().replace(/^>+/, '').trimEnd().replace(/-+$/, '').trim();
513
+ if (toId === '')
514
+ return null;
515
+ const to = stateEndpoint(graph, toId, false);
516
+ if (to === null)
517
+ return null;
518
+ if (!graph.pushEdge({ from, to, label, headTo: 'arrow', headFrom: 'none', line: 'solid' })) {
519
+ return true;
520
+ }
521
+ prev = to;
522
+ rest = tail;
523
+ }
524
+ return true;
525
+ }
526
+ /** `[*]` is start or end depending on which side of the arrow it sits. */
527
+ function stateEndpoint(graph, id, isSource) {
528
+ if (id === '[*]')
529
+ return graph.nodeIndex(isSource ? '[*]start' : '[*]end', '●', 'round');
530
+ return graph.nodeIndex(id, null, 'round');
531
+ }
532
+ /** `id: description`, or a bare state name. */
533
+ function parseStateDesc(st, graph) {
534
+ const split = splitOnce(st, ':');
535
+ if (split) {
536
+ const id = split[0].trim();
537
+ const desc = split[1].trim();
538
+ if (id === '' || /\s/.test(id) || desc === '')
539
+ return null;
540
+ return graph.nodeLabel(id, decodeHtmlEntities(desc)) === null ? null : true;
541
+ }
542
+ if (/\s/.test(st))
543
+ return null;
544
+ return graph.nodeIndex(st, null, 'round') === null ? null : true;
545
+ }
546
+ // --------------------------------------------------------------------- class
547
+ /** Relation operators, longest-first so `--|>` wins over `--`. */
548
+ const CLASS_OPS = [
549
+ ['<|--', 'triangle', 'none', 'solid'],
550
+ ['--|>', 'none', 'triangle', 'solid'],
551
+ ['<|..', 'triangle', 'none', 'dotted'],
552
+ ['..|>', 'none', 'triangle', 'dotted'],
553
+ ['*--', 'diamondFill', 'none', 'solid'],
554
+ ['--*', 'none', 'diamondFill', 'solid'],
555
+ ['o--', 'diamondOpen', 'none', 'solid'],
556
+ ['--o', 'none', 'diamondOpen', 'solid'],
557
+ ['<--', 'arrow', 'none', 'solid'],
558
+ ['-->', 'none', 'arrow', 'solid'],
559
+ ['<..', 'arrow', 'none', 'dotted'],
560
+ ['..>', 'none', 'arrow', 'dotted'],
561
+ ['--', 'none', 'none', 'solid'],
562
+ ['..', 'none', 'none', 'dotted'],
563
+ ];
564
+ const MAX_CLASS_OP = 4;
565
+ export function parseClass(src) {
566
+ const statements = statementsOf(src);
567
+ const kind = headerKind(statements);
568
+ if (kind === null || !kind.startsWith('classdiagram'))
569
+ return null;
570
+ const graph = new Graph();
571
+ const infos = [];
572
+ const sync = () => {
573
+ while (infos.length < graph.nodes.length)
574
+ infos.push(emptyClassInfo());
575
+ };
576
+ /** Declare a class, keeping `infos` aligned with `graph.nodes`. */
577
+ const declare = (name) => {
578
+ const idx = graph.nodeIndex(name, null, 'rect');
579
+ sync();
580
+ return idx;
581
+ };
582
+ let curClass = null;
583
+ for (const st of statements.slice(1)) {
584
+ if (curClass !== null) {
585
+ if (st === '}')
586
+ curClass = null;
587
+ else
588
+ pushMember(infos[curClass], st);
589
+ continue;
590
+ }
591
+ const first = asciiLower(firstWord(st));
592
+ if (first === 'direction') {
593
+ graph.dir = parseDir(words(st)[1] ?? '');
594
+ continue;
595
+ }
596
+ if ([
597
+ 'note',
598
+ 'callback',
599
+ 'click',
600
+ 'link',
601
+ 'style',
602
+ 'cssclass',
603
+ 'classdef',
604
+ 'namespace',
605
+ '}',
606
+ ].includes(first)) {
607
+ continue;
608
+ }
609
+ if (first === 'class') {
610
+ const rest = st.slice('class'.length).trim();
611
+ const open = rest.endsWith('{');
612
+ const name = open ? rest.slice(0, -1).trim() : rest;
613
+ if (name === '' || /\s/.test(name))
614
+ return null;
615
+ const idx = declare(name);
616
+ if (idx === null)
617
+ return null;
618
+ if (open)
619
+ curClass = idx;
620
+ continue;
621
+ }
622
+ if (st.startsWith('<<')) {
623
+ const split = splitOnce(st.slice(2), '>>');
624
+ if (!split)
625
+ return null;
626
+ const name = split[1].trim();
627
+ if (name === '' || /\s/.test(name))
628
+ return null;
629
+ const idx = declare(name);
630
+ if (idx === null)
631
+ return null;
632
+ infos[idx].annotation = split[0].trim();
633
+ continue;
634
+ }
635
+ const rel = parseClassRelation(st);
636
+ if (rel) {
637
+ const f = declare(rel.from);
638
+ if (f === null)
639
+ return null;
640
+ const t = declare(rel.to);
641
+ if (t === null)
642
+ return null;
643
+ if (graph.edges.length >= MAX_EDGES)
644
+ return null;
645
+ graph.edges.push({
646
+ from: f,
647
+ to: t,
648
+ label: rel.label,
649
+ headTo: rel.headTo,
650
+ headFrom: rel.headFrom,
651
+ line: rel.line,
652
+ });
653
+ continue;
654
+ }
655
+ const member = splitOnce(st, ':');
656
+ if (member) {
657
+ const id = member[0].trim();
658
+ const text = member[1].trim();
659
+ if (id === '' || /\s/.test(id) || text === '')
660
+ return null;
661
+ const idx = declare(id);
662
+ if (idx === null)
663
+ return null;
664
+ pushMember(infos[idx], text);
665
+ continue;
666
+ }
667
+ return null;
668
+ }
669
+ if (graph.nodes.length === 0)
670
+ return null;
671
+ sync();
672
+ return { graph, infos };
673
+ }
674
+ /** Add a member to the attribute or method compartment, eliding past the cap. */
675
+ export function pushMember(info, raw) {
676
+ if (raw.startsWith('<<')) {
677
+ const split = splitOnce(raw.slice(2), '>>');
678
+ if (split)
679
+ info.annotation = split[0].trim();
680
+ return;
681
+ }
682
+ const member = decodeHtmlEntities(displayGenerics(raw.trim()));
683
+ const list = member.includes('(') ? info.methods : info.attrs;
684
+ if (list.length < MAX_MEMBERS)
685
+ list.push(member);
686
+ else if (list.length === MAX_MEMBERS)
687
+ list.push('…');
688
+ }
689
+ function parseClassRelation(st) {
690
+ const chars = [...st];
691
+ let found = null;
692
+ outer: for (let pos = 0; pos < chars.length; pos++) {
693
+ const tail = chars.slice(pos, pos + MAX_CLASS_OP).join('');
694
+ for (const [op, headFrom, headTo, line] of CLASS_OPS) {
695
+ if (!tail.startsWith(op))
696
+ continue;
697
+ // `o` is also an identifier character: skip a match glued to a name.
698
+ if (op.startsWith('o') && pos > 0 && isIdChar(chars[pos - 1]))
699
+ continue;
700
+ const after = chars[pos + [...op].length];
701
+ if (op.endsWith('o') && after !== undefined && isIdChar(after))
702
+ continue;
703
+ found = { pos, op, headFrom, headTo, line };
704
+ break outer;
705
+ }
706
+ }
707
+ if (!found)
708
+ return null;
709
+ const lhsRaw = chars.slice(0, found.pos).join('').trim();
710
+ const rhsRaw = chars
711
+ .slice(found.pos + [...found.op].length)
712
+ .join('')
713
+ .trim();
714
+ const [lhs, cardFrom] = stripCardinalitySuffix(lhsRaw);
715
+ const [rhs, cardTo] = stripCardinalityPrefix(rhsRaw);
716
+ const split = splitOnce(rhs, ':');
717
+ const toId = (split ? split[0] : rhs).trim();
718
+ const relLabel = split ? nonEmpty(decodeHtmlEntities(split[1].trim())) : null;
719
+ if (lhs === '' || toId === '' || /\s/.test(lhs) || /\s/.test(toId))
720
+ return null;
721
+ const label = nonEmpty([cardFrom, relLabel ?? '', cardTo].filter((s) => s !== '').join(' '));
722
+ return {
723
+ from: lhs,
724
+ to: toId,
725
+ headFrom: found.headFrom,
726
+ headTo: found.headTo,
727
+ line: found.line,
728
+ label,
729
+ };
730
+ }
731
+ /** `Class "1"` — a quoted cardinality trailing the left-hand name. */
732
+ function stripCardinalitySuffix(s) {
733
+ const t = s.trimEnd();
734
+ if (t.endsWith('"')) {
735
+ const rest = t.slice(0, -1);
736
+ const q = rest.lastIndexOf('"');
737
+ if (q !== -1)
738
+ return [rest.slice(0, q).trimEnd(), rest.slice(q + 1)];
739
+ }
740
+ return [t, ''];
741
+ }
742
+ /** `"0..*" Class` — a quoted cardinality leading the right-hand name. */
743
+ function stripCardinalityPrefix(s) {
744
+ const t = s.trimStart();
745
+ if (t.startsWith('"')) {
746
+ const rest = t.slice(1);
747
+ const q = rest.indexOf('"');
748
+ if (q !== -1)
749
+ return [rest.slice(q + 1).trimStart(), rest.slice(0, q)];
750
+ }
751
+ return [t, ''];
752
+ }
753
+ /** Mermaid writes generics as `List~T~`; show them as `List<T>`. */
754
+ function displayGenerics(s) {
755
+ let out = '';
756
+ let open = false;
757
+ for (const c of s) {
758
+ if (c === '~') {
759
+ out += open ? '>' : '<';
760
+ open = !open;
761
+ }
762
+ else {
763
+ out += c;
764
+ }
765
+ }
766
+ return out;
767
+ }
768
+ // ------------------------------------------------------------------------ ER
769
+ export function parseEr(src) {
770
+ const statements = statementsOf(src);
771
+ if (headerKind(statements) !== 'erdiagram')
772
+ return null;
773
+ const graph = new Graph();
774
+ const infos = [];
775
+ let curEntity = null;
776
+ for (const st of statements.slice(1)) {
777
+ if (curEntity !== null) {
778
+ if (st === '}')
779
+ curEntity = null;
780
+ else
781
+ pushErAttribute(infos[curEntity], st);
782
+ continue;
783
+ }
784
+ const rel = splitErRelationship(st);
785
+ if (rel) {
786
+ const tokens = words(rel.rel);
787
+ if (tokens.length !== 3)
788
+ return null;
789
+ const op = parseErOp(tokens[1]);
790
+ if (!op)
791
+ return null;
792
+ const f = erEntity(graph, infos, tokens[0]);
793
+ if (f === null)
794
+ return null;
795
+ const t = erEntity(graph, infos, tokens[2]);
796
+ if (t === null)
797
+ return null;
798
+ if (graph.edges.length >= MAX_EDGES)
799
+ return null;
800
+ const relLabel = rel.label === null ? '' : cleanLabel(rel.label);
801
+ graph.edges.push({
802
+ from: f,
803
+ to: t,
804
+ label: nonEmpty([op.cardL, relLabel, op.cardR].filter((s) => s !== '').join(' ')),
805
+ headTo: 'none',
806
+ headFrom: 'none',
807
+ line: op.line,
808
+ });
809
+ continue;
810
+ }
811
+ const open = st.endsWith('{');
812
+ const decl = open ? st.slice(0, -1).trim() : st;
813
+ if (decl === '' || words(decl).length !== 1)
814
+ return null;
815
+ const idx = erEntity(graph, infos, decl);
816
+ if (idx === null)
817
+ return null;
818
+ if (open)
819
+ curEntity = idx;
820
+ }
821
+ if (graph.nodes.length === 0)
822
+ return null;
823
+ while (infos.length < graph.nodes.length)
824
+ infos.push(emptyClassInfo());
825
+ return { graph, infos };
826
+ }
827
+ function erEntity(graph, infos, token) {
828
+ const open = token.indexOf('[');
829
+ let idx;
830
+ if (open !== -1) {
831
+ const id = token.slice(0, open);
832
+ const label = cleanLabel(token.slice(open + 1).replace(/\]+$/, ''));
833
+ if (id === '' || label === '')
834
+ return null;
835
+ idx = graph.nodeLabel(id, label);
836
+ }
837
+ else {
838
+ idx = graph.nodeIndex(token, null, 'rect');
839
+ }
840
+ if (idx === null)
841
+ return null;
842
+ while (infos.length < graph.nodes.length)
843
+ infos.push(emptyClassInfo());
844
+ return idx;
845
+ }
846
+ function splitErRelationship(st) {
847
+ const split = splitOnce(st, ':');
848
+ const rel = split ? split[0] : st;
849
+ const label = split ? split[1].trim() : null;
850
+ return words(rel).some((t) => parseErOp(t) !== null) ? { rel, label } : null;
851
+ }
852
+ const isAscii = (s) => {
853
+ for (let i = 0; i < s.length; i++)
854
+ if (s.charCodeAt(i) > 0x7f)
855
+ return false;
856
+ return true;
857
+ };
858
+ /** A crow's-foot operator: two cardinality glyphs around `--` or `..`. */
859
+ function parseErOp(tok) {
860
+ if (tok.length !== 6 || !isAscii(tok))
861
+ return null;
862
+ const mid = tok.slice(2, 4);
863
+ const line = mid === '--' ? 'solid' : mid === '..' ? 'dotted' : null;
864
+ if (line === null)
865
+ return null;
866
+ const cardL = erCard(tok.slice(0, 2));
867
+ const cardR = erCard(tok.slice(4, 6));
868
+ return cardL === null || cardR === null ? null : { cardL, cardR, line };
869
+ }
870
+ function erCard(tok) {
871
+ switch (tok) {
872
+ case '|o':
873
+ case 'o|':
874
+ return '0..1';
875
+ case '||':
876
+ return '1';
877
+ case '}o':
878
+ case 'o{':
879
+ return '0..*';
880
+ case '}|':
881
+ case '|{':
882
+ return '1..*';
883
+ default:
884
+ return null;
885
+ }
886
+ }
887
+ /** ER attributes are `type name`; a trailing quoted comment is dropped. */
888
+ export function pushErAttribute(info, raw) {
889
+ const parts = [];
890
+ for (const tok of words(raw)) {
891
+ if (tok.startsWith('"'))
892
+ break;
893
+ parts.push(tok);
894
+ }
895
+ if (parts.length === 0)
896
+ return;
897
+ const line = decodeHtmlEntities(parts.join(' '));
898
+ if (info.attrs.length < MAX_MEMBERS)
899
+ info.attrs.push(line);
900
+ else if (info.attrs.length === MAX_MEMBERS)
901
+ info.attrs.push('…');
902
+ }
903
+ /** Message operators, longest-first so `-->>` wins over `-->`. */
904
+ const SEQ_OPS = [
905
+ ['-->>', true, 'arrow'],
906
+ ['->>', false, 'arrow'],
907
+ ['--x', true, 'cross'],
908
+ ['-x', false, 'cross'],
909
+ ['--)', true, 'arrow'],
910
+ ['-)', false, 'arrow'],
911
+ ['-->', true, 'arrow'],
912
+ ['->', false, 'arrow'],
913
+ ];
914
+ const MAX_SEQ_OP = 4;
915
+ export class Sequence {
916
+ labels = [];
917
+ index = new Map();
918
+ items = [];
919
+ participant(id, label) {
920
+ const existing = this.index.get(id);
921
+ if (existing !== undefined) {
922
+ if (label !== null)
923
+ this.labels[existing] = label;
924
+ return existing;
925
+ }
926
+ if (this.labels.length >= MAX_NODES)
927
+ return null;
928
+ this.index.set(id, this.labels.length);
929
+ this.labels.push(label ?? id);
930
+ return this.labels.length - 1;
931
+ }
932
+ }
933
+ export function parseSequence(src) {
934
+ const statements = statementsOf(src);
935
+ if (headerKind(statements) !== 'sequencediagram')
936
+ return null;
937
+ const seq = new Sequence();
938
+ let autonumber = false;
939
+ let msgCount = 0;
940
+ /** One entry per open block; `true` when it draws a divider on `end`. */
941
+ const blocks = [];
942
+ for (const st of statements.slice(1)) {
943
+ const first = firstWord(st);
944
+ const lower = asciiLower(first);
945
+ if (lower === 'participant' || lower === 'actor') {
946
+ const rest = st.slice(first.length).trim();
947
+ if (rest === '')
948
+ return null;
949
+ const as = splitOnce(rest, ' as ');
950
+ if (seq.participant(as ? as[0].trim() : rest, as ? cleanLabel(as[1]) : null) === null) {
951
+ return null;
952
+ }
953
+ continue;
954
+ }
955
+ if (lower === 'autonumber') {
956
+ autonumber = true;
957
+ continue;
958
+ }
959
+ if ([
960
+ 'activate',
961
+ 'deactivate',
962
+ 'create',
963
+ 'destroy',
964
+ 'title',
965
+ 'acctitle',
966
+ 'accdescr',
967
+ 'links',
968
+ 'link',
969
+ 'properties',
970
+ ].includes(lower)) {
971
+ continue;
972
+ }
973
+ if (lower === 'note') {
974
+ const note = parseNoteAnchor(st.slice(first.length).trim(), seq);
975
+ if (!note)
976
+ return null;
977
+ if (seq.items.length >= MAX_EDGES)
978
+ return null;
979
+ seq.items.push({ kind: 'note', anchor: note.anchor, text: note.text });
980
+ continue;
981
+ }
982
+ if (['loop', 'alt', 'opt', 'par', 'critical', 'break', 'else', 'and', 'option'].includes(lower)) {
983
+ if (['else', 'and', 'option'].includes(lower)) {
984
+ // A continuation only divides a block that opened one.
985
+ if (blocks.at(-1) !== true)
986
+ continue;
987
+ }
988
+ else {
989
+ blocks.push(true);
990
+ }
991
+ if (seq.items.length >= MAX_EDGES)
992
+ return null;
993
+ seq.items.push({ kind: 'divider', text: decodeHtmlEntities(st) });
994
+ continue;
995
+ }
996
+ if (lower === 'rect' || lower === 'box') {
997
+ blocks.push(false);
998
+ continue;
999
+ }
1000
+ if (lower === 'end') {
1001
+ if (blocks.pop() === true) {
1002
+ if (seq.items.length >= MAX_EDGES)
1003
+ return null;
1004
+ seq.items.push({ kind: 'divider', text: 'end' });
1005
+ }
1006
+ continue;
1007
+ }
1008
+ const msg = parseSeqMessage(st, seq);
1009
+ if (!msg)
1010
+ return null;
1011
+ let text = msg.text;
1012
+ if (autonumber) {
1013
+ msgCount++;
1014
+ text = text === null ? `${msgCount}.` : `${msgCount}. ${text}`;
1015
+ }
1016
+ if (seq.items.length >= MAX_EDGES)
1017
+ return null;
1018
+ seq.items.push({
1019
+ kind: 'message',
1020
+ from: msg.from,
1021
+ to: msg.to,
1022
+ text,
1023
+ dashed: msg.dashed,
1024
+ head: msg.head,
1025
+ });
1026
+ }
1027
+ return seq.labels.length === 0 ? null : seq;
1028
+ }
1029
+ function parseNoteAnchor(rest, seq) {
1030
+ const lower = asciiLower(rest);
1031
+ let kind;
1032
+ let idsAndText;
1033
+ if (lower.startsWith('over ')) {
1034
+ kind = 'over';
1035
+ idsAndText = rest.slice('over '.length);
1036
+ }
1037
+ else if (lower.startsWith('left of ')) {
1038
+ kind = 'left';
1039
+ idsAndText = rest.slice('left of '.length);
1040
+ }
1041
+ else if (lower.startsWith('right of ')) {
1042
+ kind = 'right';
1043
+ idsAndText = rest.slice('right of '.length);
1044
+ }
1045
+ else {
1046
+ return null;
1047
+ }
1048
+ const split = splitOnce(idsAndText, ':');
1049
+ if (!split)
1050
+ return null;
1051
+ const text = decodeHtmlEntities(split[1].trim());
1052
+ const parts = split[0]
1053
+ .split(',')
1054
+ .map((s) => s.trim())
1055
+ .filter((s) => s !== '');
1056
+ if (parts.length === 0)
1057
+ return null;
1058
+ const a = seq.participant(parts[0], null);
1059
+ if (a === null)
1060
+ return null;
1061
+ if (kind !== 'over')
1062
+ return { text, anchor: { kind, at: a } };
1063
+ let b = a;
1064
+ if (parts[1] !== undefined) {
1065
+ const second = seq.participant(parts[1], null);
1066
+ if (second === null)
1067
+ return null;
1068
+ b = second;
1069
+ }
1070
+ return { text, anchor: { kind: 'over', from: Math.min(a, b), to: Math.max(a, b) } };
1071
+ }
1072
+ function parseSeqMessage(st, seq) {
1073
+ const chars = [...st];
1074
+ let found = null;
1075
+ outer: for (let pos = 0; pos < chars.length; pos++) {
1076
+ const tail = chars.slice(pos, pos + MAX_SEQ_OP).join('');
1077
+ for (const [op, dashed, head] of SEQ_OPS) {
1078
+ if (tail.startsWith(op)) {
1079
+ found = { pos, op, dashed, head };
1080
+ break outer;
1081
+ }
1082
+ }
1083
+ }
1084
+ if (!found)
1085
+ return null;
1086
+ const fromId = chars.slice(0, found.pos).join('').trim();
1087
+ if (fromId === '')
1088
+ return null;
1089
+ // `+`/`-` activate and deactivate the target; they carry no layout meaning.
1090
+ const rest = chars
1091
+ .slice(found.pos + [...found.op].length)
1092
+ .join('')
1093
+ .trimStart()
1094
+ .replace(/^[+-]+/, '');
1095
+ const split = splitOnce(rest, ':');
1096
+ const toId = (split ? split[0] : rest).trim();
1097
+ const text = split ? nonEmpty(decodeHtmlEntities(split[1].trim())) : null;
1098
+ if (toId === '')
1099
+ return null;
1100
+ const from = seq.participant(fromId, null);
1101
+ if (from === null)
1102
+ return null;
1103
+ const to = seq.participant(toId, null);
1104
+ if (to === null)
1105
+ return null;
1106
+ return { from, to, text, dashed: found.dashed, head: found.head };
1107
+ }
1108
+ //# sourceMappingURL=parse.js.map