@hncode/hncode 0.5.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/package.json +37 -38
  2. package/src/agent.js +67 -2
  3. package/src/image-row.js +111 -0
  4. package/src/latex.js +1557 -0
  5. package/src/llm.js +19 -4
  6. package/src/mermaid/ansi.js +55 -0
  7. package/src/mermaid/canvas.js +449 -0
  8. package/src/mermaid/class-style.js +57 -0
  9. package/src/mermaid/css-colors.js +153 -0
  10. package/src/mermaid/diagrams/class.js +289 -0
  11. package/src/mermaid/diagrams/er.js +201 -0
  12. package/src/mermaid/diagrams/flowchart.js +488 -0
  13. package/src/mermaid/diagrams/gitgraph.js +205 -0
  14. package/src/mermaid/diagrams/mindmap.js +101 -0
  15. package/src/mermaid/diagrams/pie.js +104 -0
  16. package/src/mermaid/diagrams/sequence.js +282 -0
  17. package/src/mermaid/diagrams/state.js +292 -0
  18. package/src/mermaid/diagrams/timeline.js +99 -0
  19. package/src/mermaid/graph.js +138 -0
  20. package/src/mermaid/index.js +59 -0
  21. package/src/mermaid/labels.js +339 -0
  22. package/src/mermaid/layout-seq.js +225 -0
  23. package/src/mermaid/layout.js +1454 -0
  24. package/src/mermaid/registry.js +54 -0
  25. package/src/mermaid/source-box.js +102 -0
  26. package/src/mermaid/statements.js +235 -0
  27. package/src/mermaid/types.js +1 -0
  28. package/src/mermaid/width.js +29 -0
  29. package/src/screen-diff.js +512 -0
  30. package/src/screen-paint.js +128 -0
  31. package/src/screen.js +277 -0
  32. package/src/subagent-types.js +136 -147
  33. package/src/term-caps.js +10 -4
  34. package/src/tex/font.js +292 -0
  35. package/src/tex/fonts/LICENSE.txt +11 -0
  36. package/src/tex/fonts/STIX2Math.ttf +0 -0
  37. package/src/tex/index.js +293 -0
  38. package/src/tex/kitty.js +186 -0
  39. package/src/tex/layout.js +446 -0
  40. package/src/tex/parse.js +331 -0
  41. package/src/tex/raster.js +216 -0
  42. package/src/tex/render.js +167 -0
  43. package/src/tex/sixel.js +326 -0
  44. package/src/tex/svg.js +237 -0
  45. package/src/tool-result.js +2 -2
  46. package/src/tools/agent-swarm.js +3 -15
  47. package/src/tools/agent.js +454 -464
  48. package/src/tools/ask-user-question.js +195 -206
  49. package/src/tools/bash.js +39 -3
  50. package/src/tools/edit.js +10 -112
  51. package/src/tools/fetch-url.js +130 -120
  52. package/src/tools/memory.js +80 -88
  53. package/src/tools/read.js +27 -23
  54. package/src/tools/todo.js +8 -13
  55. package/src/tools/validate-args.js +78 -0
  56. package/src/tui.js +1346 -502
@@ -0,0 +1,153 @@
1
+ // Generated by scripts/gen-css-colors.ts from https://drafts.csswg.org/css-color-4/ — do not edit.
2
+ /** The CSS Color 4 named-color table: name → `#rrggbb`. */
3
+ export const NAMED_COLORS = {
4
+ aliceblue: '#f0f8ff',
5
+ antiquewhite: '#faebd7',
6
+ aqua: '#00ffff',
7
+ aquamarine: '#7fffd4',
8
+ azure: '#f0ffff',
9
+ beige: '#f5f5dc',
10
+ bisque: '#ffe4c4',
11
+ black: '#000000',
12
+ blanchedalmond: '#ffebcd',
13
+ blue: '#0000ff',
14
+ blueviolet: '#8a2be2',
15
+ brown: '#a52a2a',
16
+ burlywood: '#deb887',
17
+ cadetblue: '#5f9ea0',
18
+ chartreuse: '#7fff00',
19
+ chocolate: '#d2691e',
20
+ coral: '#ff7f50',
21
+ cornflowerblue: '#6495ed',
22
+ cornsilk: '#fff8dc',
23
+ crimson: '#dc143c',
24
+ cyan: '#00ffff',
25
+ darkblue: '#00008b',
26
+ darkcyan: '#008b8b',
27
+ darkgoldenrod: '#b8860b',
28
+ darkgray: '#a9a9a9',
29
+ darkgreen: '#006400',
30
+ darkgrey: '#a9a9a9',
31
+ darkkhaki: '#bdb76b',
32
+ darkmagenta: '#8b008b',
33
+ darkolivegreen: '#556b2f',
34
+ darkorange: '#ff8c00',
35
+ darkorchid: '#9932cc',
36
+ darkred: '#8b0000',
37
+ darksalmon: '#e9967a',
38
+ darkseagreen: '#8fbc8f',
39
+ darkslateblue: '#483d8b',
40
+ darkslategray: '#2f4f4f',
41
+ darkslategrey: '#2f4f4f',
42
+ darkturquoise: '#00ced1',
43
+ darkviolet: '#9400d3',
44
+ deeppink: '#ff1493',
45
+ deepskyblue: '#00bfff',
46
+ dimgray: '#696969',
47
+ dimgrey: '#696969',
48
+ dodgerblue: '#1e90ff',
49
+ firebrick: '#b22222',
50
+ floralwhite: '#fffaf0',
51
+ forestgreen: '#228b22',
52
+ fuchsia: '#ff00ff',
53
+ gainsboro: '#dcdcdc',
54
+ ghostwhite: '#f8f8ff',
55
+ gold: '#ffd700',
56
+ goldenrod: '#daa520',
57
+ gray: '#808080',
58
+ green: '#008000',
59
+ greenyellow: '#adff2f',
60
+ grey: '#808080',
61
+ honeydew: '#f0fff0',
62
+ hotpink: '#ff69b4',
63
+ indianred: '#cd5c5c',
64
+ indigo: '#4b0082',
65
+ ivory: '#fffff0',
66
+ khaki: '#f0e68c',
67
+ lavender: '#e6e6fa',
68
+ lavenderblush: '#fff0f5',
69
+ lawngreen: '#7cfc00',
70
+ lemonchiffon: '#fffacd',
71
+ lightblue: '#add8e6',
72
+ lightcoral: '#f08080',
73
+ lightcyan: '#e0ffff',
74
+ lightgoldenrodyellow: '#fafad2',
75
+ lightgray: '#d3d3d3',
76
+ lightgreen: '#90ee90',
77
+ lightgrey: '#d3d3d3',
78
+ lightpink: '#ffb6c1',
79
+ lightsalmon: '#ffa07a',
80
+ lightseagreen: '#20b2aa',
81
+ lightskyblue: '#87cefa',
82
+ lightslategray: '#778899',
83
+ lightslategrey: '#778899',
84
+ lightsteelblue: '#b0c4de',
85
+ lightyellow: '#ffffe0',
86
+ lime: '#00ff00',
87
+ limegreen: '#32cd32',
88
+ linen: '#faf0e6',
89
+ magenta: '#ff00ff',
90
+ maroon: '#800000',
91
+ mediumaquamarine: '#66cdaa',
92
+ mediumblue: '#0000cd',
93
+ mediumorchid: '#ba55d3',
94
+ mediumpurple: '#9370db',
95
+ mediumseagreen: '#3cb371',
96
+ mediumslateblue: '#7b68ee',
97
+ mediumspringgreen: '#00fa9a',
98
+ mediumturquoise: '#48d1cc',
99
+ mediumvioletred: '#c71585',
100
+ midnightblue: '#191970',
101
+ mintcream: '#f5fffa',
102
+ mistyrose: '#ffe4e1',
103
+ moccasin: '#ffe4b5',
104
+ navajowhite: '#ffdead',
105
+ navy: '#000080',
106
+ oldlace: '#fdf5e6',
107
+ olive: '#808000',
108
+ olivedrab: '#6b8e23',
109
+ orange: '#ffa500',
110
+ orangered: '#ff4500',
111
+ orchid: '#da70d6',
112
+ palegoldenrod: '#eee8aa',
113
+ palegreen: '#98fb98',
114
+ paleturquoise: '#afeeee',
115
+ palevioletred: '#db7093',
116
+ papayawhip: '#ffefd5',
117
+ peachpuff: '#ffdab9',
118
+ peru: '#cd853f',
119
+ pink: '#ffc0cb',
120
+ plum: '#dda0dd',
121
+ powderblue: '#b0e0e6',
122
+ purple: '#800080',
123
+ rebeccapurple: '#663399',
124
+ red: '#ff0000',
125
+ rosybrown: '#bc8f8f',
126
+ royalblue: '#4169e1',
127
+ saddlebrown: '#8b4513',
128
+ salmon: '#fa8072',
129
+ sandybrown: '#f4a460',
130
+ seagreen: '#2e8b57',
131
+ seashell: '#fff5ee',
132
+ sienna: '#a0522d',
133
+ silver: '#c0c0c0',
134
+ skyblue: '#87ceeb',
135
+ slateblue: '#6a5acd',
136
+ slategray: '#708090',
137
+ slategrey: '#708090',
138
+ snow: '#fffafa',
139
+ springgreen: '#00ff7f',
140
+ steelblue: '#4682b4',
141
+ tan: '#d2b48c',
142
+ teal: '#008080',
143
+ thistle: '#d8bfd8',
144
+ tomato: '#ff6347',
145
+ turquoise: '#40e0d0',
146
+ violet: '#ee82ee',
147
+ wheat: '#f5deb3',
148
+ white: '#ffffff',
149
+ whitesmoke: '#f5f5f5',
150
+ yellow: '#ffff00',
151
+ yellowgreen: '#9acd32',
152
+ };
153
+ //# sourceMappingURL=css-colors.js.map
@@ -0,0 +1,289 @@
1
+ /**
2
+ * `classDiagram`: classes with member compartments and UML relations.
3
+ * Lenient: an unreadable statement is dropped and recorded in `warnings`.
4
+ *
5
+ * Compartments live on `Node.sections`: `[title, attrs, methods]`, where the
6
+ * title is the optional `«annotation»` line followed by the class name.
7
+ */
8
+ import { Graph, MAX_MEMBERS, parseDir } from '../graph.js';
9
+ import { asciiLower, cleanLabel, decodeHtmlEntities, displayGenerics, isIdChar } from '../labels.js';
10
+ import { layoutClass } from '../layout.js';
11
+ import { firstWord, headerKind, nonEmpty, parseClassAssign, parseClassDef, parseHref, quoteMask, splitColon, splitOnce, statementsOf, takeTags, words, } from '../statements.js';
12
+ export const classDiagram = {
13
+ kind: 'class',
14
+ headers: ['classdiagram', 'classdiagram-v2'],
15
+ render(src) {
16
+ const graph = parseClass(src);
17
+ if (graph === null)
18
+ return null;
19
+ const canvas = layoutClass(graph);
20
+ if (canvas === null)
21
+ return null;
22
+ return { canvas, warnings: graph.warnings, classDefs: graph.classDefs };
23
+ },
24
+ };
25
+ /** Relation operators, longest-first so `--|>` wins over `--`. */
26
+ const CLASS_OPS = [
27
+ ['<|--', 'triangle', 'none', 'solid'],
28
+ ['--|>', 'none', 'triangle', 'solid'],
29
+ ['<|..', 'triangle', 'none', 'dotted'],
30
+ ['..|>', 'none', 'triangle', 'dotted'],
31
+ ['*--', 'diamondFill', 'none', 'solid'],
32
+ ['--*', 'none', 'diamondFill', 'solid'],
33
+ ['o--', 'diamondOpen', 'none', 'solid'],
34
+ ['--o', 'none', 'diamondOpen', 'solid'],
35
+ ['<--', 'arrow', 'none', 'solid'],
36
+ ['-->', 'none', 'arrow', 'solid'],
37
+ ['<..', 'arrow', 'none', 'dotted'],
38
+ ['..>', 'none', 'arrow', 'dotted'],
39
+ ['--', 'none', 'none', 'solid'],
40
+ ['..', 'none', 'none', 'dotted'],
41
+ ];
42
+ const MAX_CLASS_OP = 4;
43
+ export function parseClass(src) {
44
+ const statements = statementsOf(src);
45
+ const kind = headerKind(statements);
46
+ if (kind === null || !classDiagram.headers.includes(kind))
47
+ return null;
48
+ const graph = new Graph();
49
+ /** Declare a class from an id token, attaching any `:::` tags it carries. */
50
+ const declare = (token) => {
51
+ const { id, classes } = takeTags(token);
52
+ const idx = graph.nodeIndex(id, null, 'rect');
53
+ if (idx !== null) {
54
+ graph.nodes[idx].sections ??= [[displayGenerics(id)], [], []];
55
+ for (const cls of classes)
56
+ graph.addClass(idx, cls);
57
+ }
58
+ return idx;
59
+ };
60
+ /** The open `{` body: a class index, `'skip'` for a dropped class, or none. */
61
+ let curClass = null;
62
+ /** `class A,B name` / `cssClass "A,B" name` assignments, applied post-walk. */
63
+ const classAssignments = [];
64
+ /** `link A "url"` / `click A href "url"` targets, applied post-walk. */
65
+ const hrefs = [];
66
+ for (const st of statements.slice(1)) {
67
+ if (curClass !== null) {
68
+ if (st === '}')
69
+ curClass = null;
70
+ else if (curClass !== 'skip')
71
+ pushMember(graph.nodes[curClass], st);
72
+ continue;
73
+ }
74
+ const first = asciiLower(firstWord(st));
75
+ if (first === 'direction') {
76
+ graph.dir = parseDir(words(st)[1] ?? '');
77
+ continue;
78
+ }
79
+ if (first === 'classdef') {
80
+ const def = parseClassDef(st.slice(firstWord(st).length));
81
+ if (def)
82
+ for (const name of def.names)
83
+ graph.classDefs[name] = def.props;
84
+ continue;
85
+ }
86
+ if (['note', 'callback', 'style', 'namespace', '}'].includes(first)) {
87
+ continue;
88
+ }
89
+ if (first === 'link' || first === 'click') {
90
+ const target = parseHref(st.slice(firstWord(st).length));
91
+ if (target)
92
+ hrefs.push(target);
93
+ continue;
94
+ }
95
+ if (first === 'cssclass') {
96
+ const assign = parseClassAssign(st.slice(firstWord(st).length).replace(/"/g, ''));
97
+ if (assign)
98
+ classAssignments.push(assign);
99
+ else
100
+ graph.drop(st);
101
+ continue;
102
+ }
103
+ if (first === 'class') {
104
+ const rest = st.slice('class'.length).trim();
105
+ const open = rest.endsWith('{');
106
+ let name = open ? rest.slice(0, -1).trim() : rest;
107
+ // `class A["Label"]` (mermaid ≥10.1): the label titles the box, the id
108
+ // keys relations. Peel it before the space test — labels carry spaces.
109
+ let label = null;
110
+ const labeled = /^(\S+?)\[(.+)\](:::\S+)?$/.exec(name);
111
+ if (labeled) {
112
+ name = labeled[1] + (labeled[3] ?? '');
113
+ label = nonEmpty(cleanLabel(labeled[2]));
114
+ }
115
+ if (!open && /\s/.test(name)) {
116
+ // Class names carry no spaces, so `class Agent focus` is the
117
+ // assignment form, as in flowcharts.
118
+ const assign = parseClassAssign(name);
119
+ if (assign)
120
+ classAssignments.push(assign);
121
+ else
122
+ graph.drop(st);
123
+ }
124
+ else if (name === '' || /\s/.test(name)) {
125
+ // A bad declaration that opened a body swallows it whole; reading the
126
+ // members as top-level statements would misparse everything inside.
127
+ graph.drop(st);
128
+ if (open)
129
+ curClass = 'skip';
130
+ }
131
+ else {
132
+ const idx = declare(name);
133
+ if (idx !== null && label !== null) {
134
+ const node = graph.nodes[idx];
135
+ node.label = label;
136
+ // The name is the last title line (an annotation may precede it).
137
+ const title = node.sections?.[0];
138
+ if (title)
139
+ title[title.length - 1] = label;
140
+ }
141
+ if (open)
142
+ curClass = idx ?? 'skip';
143
+ }
144
+ }
145
+ else if (st.startsWith('<<')) {
146
+ const split = splitOnce(st.slice(2), '>>');
147
+ const name = split ? split[1].trim() : '';
148
+ if (split === null || name === '' || /\s/.test(name)) {
149
+ graph.drop(st);
150
+ }
151
+ else {
152
+ const idx = declare(name);
153
+ if (idx !== null)
154
+ setAnnotation(graph.nodes[idx], split[0].trim());
155
+ }
156
+ }
157
+ else {
158
+ const rel = parseClassRelation(st);
159
+ if (rel !== null) {
160
+ const f = declare(rel.from);
161
+ const t = f === null ? null : declare(rel.to);
162
+ if (f !== null && t !== null) {
163
+ graph.pushEdge({
164
+ from: f,
165
+ to: t,
166
+ label: rel.label,
167
+ cardFrom: rel.cardFrom,
168
+ cardTo: rel.cardTo,
169
+ headTo: rel.headTo,
170
+ headFrom: rel.headFrom,
171
+ line: rel.line,
172
+ });
173
+ }
174
+ }
175
+ else {
176
+ const member = splitColon(st);
177
+ const id = member ? member[0].trim() : '';
178
+ const text = member ? member[1].trim() : '';
179
+ if (member === null || id === '' || /\s/.test(id) || text === '') {
180
+ graph.drop(st);
181
+ }
182
+ else {
183
+ const idx = declare(id);
184
+ if (idx !== null)
185
+ pushMember(graph.nodes[idx], text);
186
+ }
187
+ }
188
+ }
189
+ if (graph.truncated !== null) {
190
+ graph.warnings.push(`diagram truncated: ${graph.truncated}`);
191
+ break;
192
+ }
193
+ }
194
+ graph.applyClasses(classAssignments);
195
+ graph.applyHrefs(hrefs);
196
+ return graph.nodes.length === 0 ? null : graph;
197
+ }
198
+ /** Rewrite the title compartment as `«annotation»` over the class name. */
199
+ function setAnnotation(node, annotation) {
200
+ ;
201
+ node.sections[0] = [`«${annotation}»`, displayGenerics(node.label)];
202
+ }
203
+ /** Add a member to the attribute or method compartment, eliding past the cap. */
204
+ export function pushMember(node, raw) {
205
+ const sections = node.sections;
206
+ if (raw.startsWith('<<')) {
207
+ const split = splitOnce(raw.slice(2), '>>');
208
+ if (split)
209
+ setAnnotation(node, split[0].trim());
210
+ return;
211
+ }
212
+ const member = decodeHtmlEntities(displayGenerics(raw.trim()));
213
+ const list = member.includes('(') ? sections[2] : sections[1];
214
+ if (list.length < MAX_MEMBERS)
215
+ list.push(member);
216
+ else if (list.length === MAX_MEMBERS)
217
+ list.push('…');
218
+ }
219
+ function parseClassRelation(st) {
220
+ const chars = [...st];
221
+ // Skip quoted spans, or the `..` inside a cardinality like `"0..*"` would
222
+ // match the dotted-link operator.
223
+ const quoted = quoteMask(chars);
224
+ let found = null;
225
+ outer: for (let pos = 0; pos < chars.length; pos++) {
226
+ if (quoted[pos])
227
+ continue;
228
+ const tail = chars.slice(pos, pos + MAX_CLASS_OP).join('');
229
+ for (const [op, headFrom, headTo, line] of CLASS_OPS) {
230
+ if (!tail.startsWith(op))
231
+ continue;
232
+ // `o` is also an identifier character: skip a match glued to a name.
233
+ if (op.startsWith('o') && pos > 0 && isIdChar(chars[pos - 1]))
234
+ continue;
235
+ const after = chars[pos + [...op].length];
236
+ if (op.endsWith('o') && after !== undefined && isIdChar(after))
237
+ continue;
238
+ found = { pos, op, headFrom, headTo, line };
239
+ break outer;
240
+ }
241
+ }
242
+ if (!found)
243
+ return null;
244
+ const lhsRaw = chars.slice(0, found.pos).join('').trim();
245
+ const rhsRaw = chars
246
+ .slice(found.pos + [...found.op].length)
247
+ .join('')
248
+ .trim();
249
+ const [lhs, cardFrom] = stripCardinalitySuffix(lhsRaw);
250
+ const [rhs, cardTo] = stripCardinalityPrefix(rhsRaw);
251
+ const split = splitColon(rhs);
252
+ const toId = (split ? split[0] : rhs).trim();
253
+ const relLabel = split ? nonEmpty(decodeHtmlEntities(split[1].trim())) : null;
254
+ if (lhs === '' || toId === '' || /\s/.test(lhs) || /\s/.test(toId))
255
+ return null;
256
+ return {
257
+ from: lhs,
258
+ to: toId,
259
+ headFrom: found.headFrom,
260
+ headTo: found.headTo,
261
+ line: found.line,
262
+ label: relLabel,
263
+ cardFrom: cardFrom === '' ? undefined : cardFrom,
264
+ cardTo: cardTo === '' ? undefined : cardTo,
265
+ };
266
+ }
267
+ /** `Class "1"` — a quoted cardinality trailing the left-hand name. */
268
+ function stripCardinalitySuffix(s) {
269
+ const t = s.trimEnd();
270
+ if (t.endsWith('"')) {
271
+ const rest = t.slice(0, -1);
272
+ const q = rest.lastIndexOf('"');
273
+ if (q !== -1)
274
+ return [rest.slice(0, q).trimEnd(), rest.slice(q + 1)];
275
+ }
276
+ return [t, ''];
277
+ }
278
+ /** `"0..*" Class` — a quoted cardinality leading the right-hand name. */
279
+ function stripCardinalityPrefix(s) {
280
+ const t = s.trimStart();
281
+ if (t.startsWith('"')) {
282
+ const rest = t.slice(1);
283
+ const q = rest.indexOf('"');
284
+ if (q !== -1)
285
+ return [rest.slice(q + 1).trimStart(), rest.slice(0, q)];
286
+ }
287
+ return [t, ''];
288
+ }
289
+ //# sourceMappingURL=class.js.map
@@ -0,0 +1,201 @@
1
+ /**
2
+ * `erDiagram`: entities with attribute compartments and crow's-foot
3
+ * relationships. Lenient: an unreadable statement is dropped and recorded in
4
+ * `warnings`.
5
+ *
6
+ * Entities use `Node.sections` as `[title, attrs]`.
7
+ */
8
+ import { Graph, MAX_MEMBERS } from '../graph.js';
9
+ import { cleanLabel, decodeHtmlEntities, displayGenerics } from '../labels.js';
10
+ import { layoutClass } from '../layout.js';
11
+ import { headerKind, nonEmpty, splitOnce, statementsOf, words } from '../statements.js';
12
+ export const er = {
13
+ kind: 'er',
14
+ headers: ['erdiagram'],
15
+ render(src) {
16
+ const graph = parseEr(src);
17
+ if (graph === null)
18
+ return null;
19
+ const canvas = layoutClass(graph);
20
+ if (canvas === null)
21
+ return null;
22
+ return { canvas, warnings: graph.warnings, classDefs: graph.classDefs };
23
+ },
24
+ };
25
+ export function parseEr(src) {
26
+ const statements = statementsOf(src);
27
+ const kind = headerKind(statements);
28
+ if (kind === null || !er.headers.includes(kind))
29
+ return null;
30
+ const graph = new Graph();
31
+ /** The open `{` body: an entity index, `'skip'` for a dropped one, or none. */
32
+ let curEntity = null;
33
+ for (const st of statements.slice(1)) {
34
+ if (curEntity !== null) {
35
+ if (st === '}')
36
+ curEntity = null;
37
+ else if (curEntity !== 'skip')
38
+ pushErAttribute(graph.nodes[curEntity], st);
39
+ continue;
40
+ }
41
+ const rel = splitErRelationship(st);
42
+ if (rel) {
43
+ const tokens = erTokens(rel.rel);
44
+ const op = tokens.length === 3 ? parseErOp(tokens[1]) : null;
45
+ const f = op === null ? null : erEntity(graph, tokens[0]);
46
+ const t = f === null ? null : erEntity(graph, tokens[2]);
47
+ if (op === null || f === null || t === null) {
48
+ graph.drop(st);
49
+ }
50
+ else {
51
+ const relLabel = rel.label === null ? '' : cleanLabel(rel.label);
52
+ graph.pushEdge({
53
+ from: f,
54
+ to: t,
55
+ label: nonEmpty(relLabel),
56
+ cardFrom: op.cardL,
57
+ cardTo: op.cardR,
58
+ headTo: 'none',
59
+ headFrom: 'none',
60
+ line: op.line,
61
+ });
62
+ }
63
+ }
64
+ else {
65
+ const open = st.endsWith('{');
66
+ const decl = open ? st.slice(0, -1).trim() : st;
67
+ if (decl === '' || erTokens(decl).length !== 1) {
68
+ // A bad declaration that opened a body swallows it whole; reading the
69
+ // attributes as top-level statements would misparse everything inside.
70
+ graph.drop(st);
71
+ if (open)
72
+ curEntity = 'skip';
73
+ }
74
+ else {
75
+ const idx = erEntity(graph, decl);
76
+ if (open)
77
+ curEntity = idx ?? 'skip';
78
+ }
79
+ }
80
+ if (graph.truncated !== null) {
81
+ graph.warnings.push(`diagram truncated: ${graph.truncated}`);
82
+ break;
83
+ }
84
+ }
85
+ return graph.nodes.length === 0 ? null : graph;
86
+ }
87
+ /** Resolve an entity token (`NAME`, `"Quoted Name"` or `id[Label]`), keeping
88
+ * its title row fresh. A quoted name is its own identity; the quotes are not
89
+ * part of the title. */
90
+ function erEntity(graph, token) {
91
+ const open = token.startsWith('"') ? -1 : token.indexOf('[');
92
+ let idx;
93
+ if (open !== -1) {
94
+ const id = token.slice(0, open);
95
+ if (!token.endsWith(']')) {
96
+ graph.warnings.push(`entity "${id}": alias is missing its closing \`]\``);
97
+ }
98
+ const label = cleanLabel(token.slice(open + 1).replace(/\]+$/, ''));
99
+ if (id === '' || label === '')
100
+ return null;
101
+ idx = graph.nodeLabel(id, label);
102
+ }
103
+ else if (token.startsWith('"')) {
104
+ const label = cleanLabel(token);
105
+ if (label === '')
106
+ return null;
107
+ idx = graph.nodeIndex(token, label, 'rect');
108
+ }
109
+ else {
110
+ idx = graph.nodeIndex(token, null, 'rect');
111
+ }
112
+ if (idx === null)
113
+ return null;
114
+ const node = graph.nodes[idx];
115
+ node.sections ??= [[], []];
116
+ node.sections[0] = [displayGenerics(node.label)];
117
+ return idx;
118
+ }
119
+ /** Whitespace tokens, quoted spans kept whole so aliases may contain spaces. */
120
+ function erTokens(s) {
121
+ const out = [];
122
+ let cur = '';
123
+ let inQuotes = false;
124
+ for (const c of s) {
125
+ if (c === '"') {
126
+ inQuotes = !inQuotes;
127
+ cur += c;
128
+ }
129
+ else if (!inQuotes && /\s/.test(c)) {
130
+ if (cur !== '') {
131
+ out.push(cur);
132
+ cur = '';
133
+ }
134
+ }
135
+ else {
136
+ cur += c;
137
+ }
138
+ }
139
+ if (cur !== '')
140
+ out.push(cur);
141
+ return out;
142
+ }
143
+ function splitErRelationship(st) {
144
+ const split = splitOnce(st, ':');
145
+ const rel = split ? split[0] : st;
146
+ const label = split ? split[1].trim() : null;
147
+ return erTokens(rel).some((t) => parseErOp(t) !== null) ? { rel, label } : null;
148
+ }
149
+ const isAscii = (s) => {
150
+ for (let i = 0; i < s.length; i++)
151
+ if (s.charCodeAt(i) > 0x7f)
152
+ return false;
153
+ return true;
154
+ };
155
+ /** A crow's-foot operator: two cardinality glyphs around `--` or `..`. */
156
+ function parseErOp(tok) {
157
+ if (tok.length !== 6 || !isAscii(tok))
158
+ return null;
159
+ const mid = tok.slice(2, 4);
160
+ const line = mid === '--' ? 'solid' : mid === '..' ? 'dotted' : null;
161
+ if (line === null)
162
+ return null;
163
+ const cardL = erCard(tok.slice(0, 2));
164
+ const cardR = erCard(tok.slice(4, 6));
165
+ return cardL === null || cardR === null ? null : { cardL, cardR, line };
166
+ }
167
+ function erCard(tok) {
168
+ switch (tok) {
169
+ case '|o':
170
+ case 'o|':
171
+ return '0..1';
172
+ case '||':
173
+ return '1';
174
+ case '}o':
175
+ case 'o{':
176
+ return '*';
177
+ case '}|':
178
+ case '|{':
179
+ return '1..*';
180
+ default:
181
+ return null;
182
+ }
183
+ }
184
+ /** ER attributes are `type name`; a trailing quoted comment is dropped. */
185
+ export function pushErAttribute(node, raw) {
186
+ const attrs = node.sections[1];
187
+ const parts = [];
188
+ for (const tok of words(raw)) {
189
+ if (tok.startsWith('"'))
190
+ break;
191
+ parts.push(tok);
192
+ }
193
+ if (parts.length === 0)
194
+ return;
195
+ const line = decodeHtmlEntities(parts.join(' '));
196
+ if (attrs.length < MAX_MEMBERS)
197
+ attrs.push(line);
198
+ else if (attrs.length === MAX_MEMBERS)
199
+ attrs.push('…');
200
+ }
201
+ //# sourceMappingURL=er.js.map