sketchmark 1.0.3 → 1.1.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/README.md +40 -29
- package/dist/index.cjs +324 -453
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +324 -453
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +324 -453
- package/package.json +1 -1
package/dist/sketchmark.iife.js
CHANGED
|
@@ -228,7 +228,7 @@ var AIDiagram = (function (exports) {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
// ============================================================
|
|
231
|
-
// sketchmark
|
|
231
|
+
// sketchmark - Parser (Tokens -> DiagramAST)
|
|
232
232
|
// ============================================================
|
|
233
233
|
let _uid = 0;
|
|
234
234
|
function uid(prefix) {
|
|
@@ -287,15 +287,16 @@ var AIDiagram = (function (exports) {
|
|
|
287
287
|
s.textAlign = p["text-align"];
|
|
288
288
|
if (p.padding)
|
|
289
289
|
s.padding = parseFloat(p.padding);
|
|
290
|
-
if (p["vertical-align"])
|
|
290
|
+
if (p["vertical-align"]) {
|
|
291
291
|
s.verticalAlign = p["vertical-align"];
|
|
292
|
+
}
|
|
292
293
|
if (p["line-height"])
|
|
293
294
|
s.lineHeight = parseFloat(p["line-height"]);
|
|
294
295
|
if (p["letter-spacing"])
|
|
295
296
|
s.letterSpacing = parseFloat(p["letter-spacing"]);
|
|
296
297
|
if (p.font)
|
|
297
298
|
s.font = p.font;
|
|
298
|
-
const dashVal = p
|
|
299
|
+
const dashVal = p.dash || p["stroke-dash"];
|
|
299
300
|
if (dashVal) {
|
|
300
301
|
const parts = dashVal
|
|
301
302
|
.split(",")
|
|
@@ -306,10 +307,15 @@ var AIDiagram = (function (exports) {
|
|
|
306
307
|
}
|
|
307
308
|
return s;
|
|
308
309
|
}
|
|
310
|
+
function isValueToken(t) {
|
|
311
|
+
return !!t && (t.type === "IDENT" || t.type === "STRING" || t.type === "KEYWORD");
|
|
312
|
+
}
|
|
313
|
+
function isPropKeyToken(t) {
|
|
314
|
+
return !!t && (t.type === "IDENT" || t.type === "KEYWORD");
|
|
315
|
+
}
|
|
309
316
|
function parse(src) {
|
|
310
317
|
resetUid();
|
|
311
318
|
const tokens = tokenize$1(src).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
|
|
312
|
-
// Collapse multiple consecutive NEWLINEs into one
|
|
313
319
|
const flat = [];
|
|
314
320
|
let lastNL = false;
|
|
315
321
|
for (const t of tokens) {
|
|
@@ -338,11 +344,9 @@ var AIDiagram = (function (exports) {
|
|
|
338
344
|
config: {},
|
|
339
345
|
rootOrder: [],
|
|
340
346
|
};
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
const groupIds = new Set();
|
|
345
|
-
const markdownIds = new Set();
|
|
347
|
+
const authoredEntityKinds = new Map();
|
|
348
|
+
const unresolvedGroupItems = new Map();
|
|
349
|
+
const groupTokens = new Map();
|
|
346
350
|
let i = 0;
|
|
347
351
|
const cur = () => flat[i] ?? flat[flat.length - 1];
|
|
348
352
|
const peek1 = () => flat[i + 1] ?? flat[flat.length - 1];
|
|
@@ -351,7 +355,6 @@ var AIDiagram = (function (exports) {
|
|
|
351
355
|
while (cur().type === "NEWLINE")
|
|
352
356
|
skip();
|
|
353
357
|
};
|
|
354
|
-
// Consume until EOL, return all tokens
|
|
355
358
|
function lineTokens() {
|
|
356
359
|
const acc = [];
|
|
357
360
|
while (cur().type !== "NEWLINE" && cur().type !== "EOF") {
|
|
@@ -362,24 +365,104 @@ var AIDiagram = (function (exports) {
|
|
|
362
365
|
skip();
|
|
363
366
|
return acc;
|
|
364
367
|
}
|
|
368
|
+
function requireExplicitId(keywordTok, toks) {
|
|
369
|
+
const first = toks[0];
|
|
370
|
+
if (!isValueToken(first) || toks[1]?.type === "EQUALS") {
|
|
371
|
+
throw new ParseError(`${keywordTok.value} requires an explicit id before properties`, keywordTok.line, keywordTok.col);
|
|
372
|
+
}
|
|
373
|
+
return first.value;
|
|
374
|
+
}
|
|
375
|
+
function parseSimpleProps(toks, startIndex) {
|
|
376
|
+
const props = {};
|
|
377
|
+
let j = startIndex;
|
|
378
|
+
while (j < toks.length - 1) {
|
|
379
|
+
const key = toks[j];
|
|
380
|
+
const eq = toks[j + 1];
|
|
381
|
+
if (isPropKeyToken(key) && eq?.type === "EQUALS" && j + 2 < toks.length) {
|
|
382
|
+
props[key.value] = toks[j + 2].value;
|
|
383
|
+
j += 3;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
j++;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return props;
|
|
390
|
+
}
|
|
391
|
+
function parseGroupProps(toks, startIndex) {
|
|
392
|
+
const props = {};
|
|
393
|
+
const itemIds = [];
|
|
394
|
+
let j = startIndex;
|
|
395
|
+
while (j < toks.length) {
|
|
396
|
+
const key = toks[j];
|
|
397
|
+
const eq = toks[j + 1];
|
|
398
|
+
if (!isPropKeyToken(key) || eq?.type !== "EQUALS") {
|
|
399
|
+
j++;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (key.value === "items") {
|
|
403
|
+
const open = toks[j + 2];
|
|
404
|
+
if (open?.type !== "LBRACKET") {
|
|
405
|
+
throw new ParseError(`items must use bracket syntax like items=[a,b]`, key.line, key.col);
|
|
406
|
+
}
|
|
407
|
+
j += 3;
|
|
408
|
+
while (j < toks.length && toks[j].type !== "RBRACKET") {
|
|
409
|
+
const tok = toks[j];
|
|
410
|
+
if (tok.type === "COMMA") {
|
|
411
|
+
j++;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (!isValueToken(tok)) {
|
|
415
|
+
const invalidTok = toks[j];
|
|
416
|
+
throw new ParseError(`items can only contain ids like items=[a,b]`, invalidTok.line, invalidTok.col);
|
|
417
|
+
}
|
|
418
|
+
itemIds.push(tok.value);
|
|
419
|
+
j++;
|
|
420
|
+
if (toks[j]?.type === "COMMA") {
|
|
421
|
+
j++;
|
|
422
|
+
}
|
|
423
|
+
else if (toks[j] && toks[j].type !== "RBRACKET") {
|
|
424
|
+
throw new ParseError(`Expected ',' or ']' in items list`, toks[j].line, toks[j].col);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (toks[j]?.type !== "RBRACKET") {
|
|
428
|
+
throw new ParseError(`Unterminated items list; expected ']'`, key.line, key.col);
|
|
429
|
+
}
|
|
430
|
+
j++;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (j + 2 < toks.length) {
|
|
434
|
+
props[key.value] = toks[j + 2].value;
|
|
435
|
+
j += 3;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
j++;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return { props, itemIds };
|
|
442
|
+
}
|
|
443
|
+
function registerAuthoredId(id, kind, tok) {
|
|
444
|
+
const existing = authoredEntityKinds.get(id);
|
|
445
|
+
if (existing) {
|
|
446
|
+
throw new ParseError(`Duplicate id "${id}" already declared as a ${existing}`, tok.line, tok.col);
|
|
447
|
+
}
|
|
448
|
+
authoredEntityKinds.set(id, kind);
|
|
449
|
+
}
|
|
365
450
|
function parseDataArray() {
|
|
366
451
|
const rows = [];
|
|
367
452
|
while (cur().type !== "LBRACKET" && cur().type !== "EOF")
|
|
368
453
|
skip();
|
|
369
|
-
skip();
|
|
454
|
+
skip();
|
|
370
455
|
skipNL();
|
|
371
456
|
while (cur().type !== "RBRACKET" && cur().type !== "EOF") {
|
|
372
457
|
skipNL();
|
|
373
458
|
if (cur().type === "RBRACKET" || cur().type === "EOF")
|
|
374
|
-
break;
|
|
459
|
+
break;
|
|
375
460
|
if (cur().type === "LBRACKET") {
|
|
376
461
|
skip();
|
|
377
462
|
const row = [];
|
|
378
463
|
while (cur().type !== "RBRACKET" && cur().type !== "EOF") {
|
|
379
464
|
const v = cur();
|
|
380
|
-
if (v.type === "STRING" ||
|
|
381
|
-
v.type === "IDENT" ||
|
|
382
|
-
v.type === "KEYWORD") {
|
|
465
|
+
if (v.type === "STRING" || v.type === "IDENT" || v.type === "KEYWORD") {
|
|
383
466
|
row.push(v.value);
|
|
384
467
|
skip();
|
|
385
468
|
}
|
|
@@ -390,8 +473,9 @@ var AIDiagram = (function (exports) {
|
|
|
390
473
|
else if (v.type === "COMMA" || v.type === "NEWLINE") {
|
|
391
474
|
skip();
|
|
392
475
|
}
|
|
393
|
-
else
|
|
476
|
+
else {
|
|
394
477
|
break;
|
|
478
|
+
}
|
|
395
479
|
}
|
|
396
480
|
if (cur().type === "RBRACKET")
|
|
397
481
|
skip();
|
|
@@ -400,46 +484,25 @@ var AIDiagram = (function (exports) {
|
|
|
400
484
|
else if (cur().type === "COMMA" || cur().type === "NEWLINE") {
|
|
401
485
|
skip();
|
|
402
486
|
}
|
|
403
|
-
else
|
|
487
|
+
else {
|
|
404
488
|
skip();
|
|
489
|
+
}
|
|
405
490
|
}
|
|
406
491
|
if (cur().type === "RBRACKET")
|
|
407
492
|
skip();
|
|
408
493
|
return rows;
|
|
409
494
|
}
|
|
410
|
-
function parseNode(shape
|
|
411
|
-
|
|
495
|
+
function parseNode(shape) {
|
|
496
|
+
const keywordTok = cur();
|
|
497
|
+
skip();
|
|
412
498
|
const toks = lineTokens();
|
|
413
|
-
|
|
414
|
-
const props =
|
|
415
|
-
let j = 0;
|
|
416
|
-
// First token may be the node id
|
|
417
|
-
if (j < toks.length &&
|
|
418
|
-
(toks[j].type === "IDENT" || toks[j].type === "STRING")) {
|
|
419
|
-
id = toks[j++].value;
|
|
420
|
-
}
|
|
421
|
-
// Remaining tokens are key=value pairs
|
|
422
|
-
while (j < toks.length) {
|
|
423
|
-
const t = toks[j];
|
|
424
|
-
if ((t.type === "IDENT" || t.type === "KEYWORD") &&
|
|
425
|
-
j + 1 < toks.length &&
|
|
426
|
-
toks[j + 1].type === "EQUALS") {
|
|
427
|
-
const key = t.value;
|
|
428
|
-
j += 2;
|
|
429
|
-
if (j < toks.length) {
|
|
430
|
-
props[key] = toks[j].value;
|
|
431
|
-
j++;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
else
|
|
435
|
-
j++;
|
|
436
|
-
}
|
|
499
|
+
const id = requireExplicitId(keywordTok, toks);
|
|
500
|
+
const props = parseSimpleProps(toks, 1);
|
|
437
501
|
const node = {
|
|
438
502
|
kind: "node",
|
|
439
503
|
id,
|
|
440
504
|
shape,
|
|
441
505
|
label: props.label || "",
|
|
442
|
-
...(groupId ? { groupId } : {}),
|
|
443
506
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
444
507
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
445
508
|
...(props.deg ? { deg: parseFloat(props.deg) } : {}),
|
|
@@ -457,113 +520,56 @@ var AIDiagram = (function (exports) {
|
|
|
457
520
|
node.pathData = props.value;
|
|
458
521
|
return node;
|
|
459
522
|
}
|
|
460
|
-
function
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
throw new ParseError("Expected edge target", 0, 0);
|
|
464
|
-
const toId = toTok.value;
|
|
465
|
-
const props = {};
|
|
466
|
-
let j = 0;
|
|
467
|
-
while (j < rest.length) {
|
|
468
|
-
const t = rest[j];
|
|
469
|
-
if ((t.type === "IDENT" || t.type === "KEYWORD") &&
|
|
470
|
-
j + 1 < rest.length &&
|
|
471
|
-
rest[j + 1].type === "EQUALS") {
|
|
472
|
-
const key = t.value;
|
|
473
|
-
j += 2;
|
|
474
|
-
if (j < rest.length) {
|
|
475
|
-
props[key] = rest[j].value;
|
|
476
|
-
j++;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
else
|
|
480
|
-
j++;
|
|
481
|
-
}
|
|
482
|
-
const dashed = connector.includes("--") ||
|
|
483
|
-
connector.includes(".-") ||
|
|
484
|
-
connector.includes("-.");
|
|
485
|
-
const bidir = connector.includes("<") && connector.includes(">");
|
|
486
|
-
return {
|
|
487
|
-
kind: "edge",
|
|
488
|
-
id: uid("edge"),
|
|
489
|
-
from: fromId,
|
|
490
|
-
to: toId,
|
|
491
|
-
connector: connector,
|
|
492
|
-
label: props.label,
|
|
493
|
-
dashed,
|
|
494
|
-
bidirectional: bidir,
|
|
495
|
-
style: propsToStyle(props),
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
// ── parseNote → returns ASTNode with shape='note' ────────
|
|
499
|
-
function parseNote(groupId) {
|
|
500
|
-
skip(); // 'note'
|
|
523
|
+
function parseNote() {
|
|
524
|
+
const keywordTok = cur();
|
|
525
|
+
skip();
|
|
501
526
|
const toks = lineTokens();
|
|
502
|
-
|
|
503
|
-
if (toks[0])
|
|
504
|
-
id = toks[0].value;
|
|
527
|
+
const id = requireExplicitId(keywordTok, toks);
|
|
505
528
|
const props = {};
|
|
506
529
|
let j = 1;
|
|
507
|
-
// Backward compat: second token is a bare/quoted string → label
|
|
508
530
|
if (toks[1] &&
|
|
509
531
|
(toks[1].type === "STRING" ||
|
|
510
532
|
(toks[1].type === "IDENT" && toks[2]?.type !== "EQUALS"))) {
|
|
511
533
|
props.label = toks[1].value;
|
|
512
534
|
j = 2;
|
|
513
535
|
}
|
|
514
|
-
|
|
515
|
-
while (j < toks.length - 1) {
|
|
516
|
-
const k = toks[j];
|
|
517
|
-
const eq = toks[j + 1];
|
|
518
|
-
if (eq && eq.type === "EQUALS" && j + 2 < toks.length) {
|
|
519
|
-
props[k.value] = toks[j + 2].value;
|
|
520
|
-
j += 3;
|
|
521
|
-
}
|
|
522
|
-
else
|
|
523
|
-
j++;
|
|
524
|
-
}
|
|
525
|
-
// Support multiline via literal \n in label string
|
|
526
|
-
const rawLabel = props.label ?? "";
|
|
536
|
+
Object.assign(props, parseSimpleProps(toks, j));
|
|
527
537
|
return {
|
|
528
538
|
kind: "node",
|
|
529
539
|
id,
|
|
530
540
|
shape: "note",
|
|
531
|
-
label:
|
|
532
|
-
groupId,
|
|
541
|
+
label: (props.label ?? "").replace(/\\n/g, "\n"),
|
|
533
542
|
theme: props.theme,
|
|
534
543
|
style: propsToStyle(props),
|
|
535
544
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
536
545
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
537
546
|
};
|
|
538
547
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
skip();
|
|
548
|
+
function parseGroup() {
|
|
549
|
+
const keywordTok = cur();
|
|
550
|
+
skip();
|
|
542
551
|
const toks = lineTokens();
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
552
|
+
if (toks.some((t) => t.type === "LBRACE" || t.type === "RBRACE")) {
|
|
553
|
+
throw new ParseError(`Nested group blocks were removed. Use ${keywordTok.value} <id> items=[...] instead.`, keywordTok.line, keywordTok.col);
|
|
554
|
+
}
|
|
555
|
+
const id = requireExplicitId(keywordTok, toks);
|
|
546
556
|
const props = {};
|
|
547
557
|
let j = 1;
|
|
548
|
-
// Backward compat: second token is a quoted/bare string (old label syntax)
|
|
549
558
|
if (toks[1] &&
|
|
550
559
|
(toks[1].type === "STRING" ||
|
|
551
560
|
(toks[1].type === "IDENT" && toks[2]?.type !== "EQUALS"))) {
|
|
552
561
|
props.label = toks[1].value;
|
|
553
562
|
j = 2;
|
|
554
563
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
props[k.value] = toks[j + 2].value;
|
|
561
|
-
j += 3;
|
|
562
|
-
}
|
|
563
|
-
else
|
|
564
|
-
j++;
|
|
564
|
+
const parsed = parseGroupProps(toks, j);
|
|
565
|
+
Object.assign(props, parsed.props);
|
|
566
|
+
skipNL();
|
|
567
|
+
if (cur().type === "LBRACE") {
|
|
568
|
+
throw new ParseError(`Nested group blocks were removed. Use ${keywordTok.value} ${id} items=[...] instead.`, cur().line, cur().col);
|
|
565
569
|
}
|
|
566
|
-
|
|
570
|
+
unresolvedGroupItems.set(id, parsed.itemIds);
|
|
571
|
+
groupTokens.set(id, keywordTok);
|
|
572
|
+
return {
|
|
567
573
|
kind: "group",
|
|
568
574
|
id,
|
|
569
575
|
label: props.label ?? "",
|
|
@@ -576,109 +582,43 @@ var AIDiagram = (function (exports) {
|
|
|
576
582
|
justify: props.justify,
|
|
577
583
|
theme: props.theme,
|
|
578
584
|
style: propsToStyle(props),
|
|
579
|
-
width: props.width !== undefined ? parseFloat(props.width) : undefined,
|
|
585
|
+
width: props.width !== undefined ? parseFloat(props.width) : undefined,
|
|
580
586
|
height: props.height !== undefined ? parseFloat(props.height) : undefined,
|
|
581
587
|
};
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const isBare = v === "bare";
|
|
597
|
-
const nested = parseGroup();
|
|
598
|
-
if (isBare) {
|
|
599
|
-
nested.label = "";
|
|
600
|
-
nested.style = {
|
|
601
|
-
...nested.style,
|
|
602
|
-
fill: nested.style?.fill ?? "none",
|
|
603
|
-
stroke: nested.style?.stroke ?? "none",
|
|
604
|
-
strokeWidth: nested.style?.strokeWidth ?? 0,
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
ast.groups.push(nested);
|
|
608
|
-
groupIds.add(nested.id);
|
|
609
|
-
group.children.push({ kind: "group", id: nested.id });
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
// ── Table ─────────────────────────────────────────
|
|
613
|
-
if (v === "table") {
|
|
614
|
-
const tbl = parseTable();
|
|
615
|
-
ast.tables.push(tbl);
|
|
616
|
-
tableIds.add(tbl.id);
|
|
617
|
-
group.children.push({ kind: "table", id: tbl.id });
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
// ── Note (parsed as node with shape='note') ──────
|
|
621
|
-
if (v === "note") {
|
|
622
|
-
const note = parseNote(id);
|
|
623
|
-
ast.nodes.push(note);
|
|
624
|
-
nodeIds.add(note.id);
|
|
625
|
-
group.children.push({ kind: "node", id: note.id });
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
// ── Markdown ───────────────────────────────────────
|
|
629
|
-
if (v === "markdown") {
|
|
630
|
-
const md = parseMarkdown(id);
|
|
631
|
-
ast.markdowns.push(md);
|
|
632
|
-
markdownIds.add(md.id);
|
|
633
|
-
group.children.push({ kind: "markdown", id: md.id });
|
|
634
|
-
continue;
|
|
635
|
-
}
|
|
636
|
-
if (v === "bare") {
|
|
637
|
-
// treat exactly like 'group' but inject defaults
|
|
638
|
-
const grp = parseGroup(); // reuse parseGroup
|
|
639
|
-
grp.label = "";
|
|
640
|
-
grp.style = { ...grp.style, stroke: "none", fill: "none" };
|
|
641
|
-
// rest is identical to group handling
|
|
642
|
-
}
|
|
643
|
-
// ── Chart ──────────────────────────────────────────
|
|
644
|
-
if (CHART_TYPES.includes(v)) {
|
|
645
|
-
const chart = parseChart(v);
|
|
646
|
-
ast.charts.push(chart);
|
|
647
|
-
chartIds.add(chart.id);
|
|
648
|
-
group.children.push({ kind: "chart", id: chart.id });
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
// ── Node shape ────────────────────────────────────
|
|
652
|
-
if (SHAPES$1.includes(v)) {
|
|
653
|
-
const node = parseNode(v, id);
|
|
654
|
-
if (!nodeIds.has(node.id)) {
|
|
655
|
-
nodeIds.add(node.id);
|
|
656
|
-
ast.nodes.push(node);
|
|
657
|
-
}
|
|
658
|
-
group.children.push({ kind: "node", id: node.id });
|
|
659
|
-
continue;
|
|
588
|
+
}
|
|
589
|
+
function parseEdge(fromId, connector, rest) {
|
|
590
|
+
const toTok = rest.shift();
|
|
591
|
+
if (!toTok)
|
|
592
|
+
throw new ParseError("Expected edge target", 0, 0);
|
|
593
|
+
const props = {};
|
|
594
|
+
let j = 0;
|
|
595
|
+
while (j < rest.length) {
|
|
596
|
+
const t = rest[j];
|
|
597
|
+
if ((t.type === "IDENT" || t.type === "KEYWORD") &&
|
|
598
|
+
j + 1 < rest.length &&
|
|
599
|
+
rest[j + 1].type === "EQUALS") {
|
|
600
|
+
props[t.value] = rest[j + 2]?.value ?? "";
|
|
601
|
+
j += 3;
|
|
660
602
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
cur().type === "STRING" ||
|
|
664
|
-
cur().type === "KEYWORD") {
|
|
665
|
-
const nextTok = flat[i + 1];
|
|
666
|
-
if (nextTok && nextTok.type === "ARROW") {
|
|
667
|
-
const lineToks = lineTokens();
|
|
668
|
-
if (lineToks.length >= 3 && lineToks[1].type === "ARROW") {
|
|
669
|
-
const fromId = lineToks[0].value;
|
|
670
|
-
const conn = lineToks[1].value;
|
|
671
|
-
const edge = parseEdge(fromId, conn, lineToks.slice(2));
|
|
672
|
-
ast.edges.push(edge);
|
|
673
|
-
}
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
603
|
+
else {
|
|
604
|
+
j++;
|
|
676
605
|
}
|
|
677
|
-
skip();
|
|
678
606
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
607
|
+
const dashed = connector.includes("--") ||
|
|
608
|
+
connector.includes(".-") ||
|
|
609
|
+
connector.includes("-.");
|
|
610
|
+
const bidirectional = connector.includes("<") && connector.includes(">");
|
|
611
|
+
return {
|
|
612
|
+
kind: "edge",
|
|
613
|
+
id: uid("edge"),
|
|
614
|
+
from: fromId,
|
|
615
|
+
to: toTok.value,
|
|
616
|
+
connector: connector,
|
|
617
|
+
label: props.label,
|
|
618
|
+
dashed,
|
|
619
|
+
bidirectional,
|
|
620
|
+
style: propsToStyle(props),
|
|
621
|
+
};
|
|
682
622
|
}
|
|
683
623
|
function parseStep() {
|
|
684
624
|
skip();
|
|
@@ -689,12 +629,10 @@ var AIDiagram = (function (exports) {
|
|
|
689
629
|
target = `${toks[1].value}${toks[2].value}${toks[3].value}`;
|
|
690
630
|
}
|
|
691
631
|
const step = { kind: "step", action, target };
|
|
692
|
-
// narrate: text is the value, not a target
|
|
693
632
|
if (action === "narrate") {
|
|
694
633
|
step.target = "";
|
|
695
634
|
step.value = toks[1]?.value ?? "";
|
|
696
635
|
}
|
|
697
|
-
// bracket: needs two targets
|
|
698
636
|
if (action === "bracket" && toks.length >= 3) {
|
|
699
637
|
step.target = toks[1]?.value ?? "";
|
|
700
638
|
step.target2 = toks[2]?.value ?? "";
|
|
@@ -704,7 +642,6 @@ var AIDiagram = (function (exports) {
|
|
|
704
642
|
const k = toks[j]?.value;
|
|
705
643
|
const eq = toks[j + 1];
|
|
706
644
|
const vt = toks[j + 2];
|
|
707
|
-
// key=value form
|
|
708
645
|
if (eq?.type === "EQUALS" && vt) {
|
|
709
646
|
if (k === "dx") {
|
|
710
647
|
step.dx = parseFloat(vt.value);
|
|
@@ -736,12 +673,7 @@ var AIDiagram = (function (exports) {
|
|
|
736
673
|
j += 2;
|
|
737
674
|
continue;
|
|
738
675
|
}
|
|
739
|
-
if (k === "fill") {
|
|
740
|
-
step.value = vt.value;
|
|
741
|
-
j += 2;
|
|
742
|
-
continue;
|
|
743
|
-
}
|
|
744
|
-
if (k === "color") {
|
|
676
|
+
if (k === "fill" || k === "color") {
|
|
745
677
|
step.value = vt.value;
|
|
746
678
|
j += 2;
|
|
747
679
|
continue;
|
|
@@ -752,7 +684,6 @@ var AIDiagram = (function (exports) {
|
|
|
752
684
|
continue;
|
|
753
685
|
}
|
|
754
686
|
}
|
|
755
|
-
// bare key value (legacy)
|
|
756
687
|
if (k === "delay" && eq?.type === "NUMBER") {
|
|
757
688
|
step.delay = parseFloat(eq.value);
|
|
758
689
|
j++;
|
|
@@ -766,89 +697,16 @@ var AIDiagram = (function (exports) {
|
|
|
766
697
|
if (k === "trigger") {
|
|
767
698
|
step.trigger = eq?.value;
|
|
768
699
|
j++;
|
|
769
|
-
continue;
|
|
770
700
|
}
|
|
771
701
|
}
|
|
772
702
|
return step;
|
|
773
703
|
}
|
|
774
|
-
// function parseStep(): ASTStep {
|
|
775
|
-
// skip(); // 'step'
|
|
776
|
-
// const toks = lineTokens();
|
|
777
|
-
// const action = (toks[0]?.value ?? "highlight") as AnimationAction;
|
|
778
|
-
// let target = toks[1]?.value ?? "";
|
|
779
|
-
// if (toks[2]?.type === "ARROW" && toks[3]) {
|
|
780
|
-
// target = `${toks[1].value}${toks[2].value}${toks[3].value}`;
|
|
781
|
-
// }
|
|
782
|
-
// const step: ASTStep = { kind: "step", action, target };
|
|
783
|
-
// for (let j = 2; j < toks.length - 1; j++) {
|
|
784
|
-
// const k = toks[j].value;
|
|
785
|
-
// const eq = toks[j + 1];
|
|
786
|
-
// const vt = toks[j + 2];
|
|
787
|
-
// // key=value form (dx=50, dy=-80, duration=600)
|
|
788
|
-
// if (eq?.type === "EQUALS" && vt) {
|
|
789
|
-
// if (k === "dx") {
|
|
790
|
-
// step.dx = parseFloat(vt.value);
|
|
791
|
-
// j += 2;
|
|
792
|
-
// continue;
|
|
793
|
-
// }
|
|
794
|
-
// if (k === "dy") {
|
|
795
|
-
// step.dy = parseFloat(vt.value);
|
|
796
|
-
// j += 2;
|
|
797
|
-
// continue;
|
|
798
|
-
// }
|
|
799
|
-
// if (k === "duration") {
|
|
800
|
-
// step.duration = parseFloat(vt.value);
|
|
801
|
-
// j += 2;
|
|
802
|
-
// continue;
|
|
803
|
-
// }
|
|
804
|
-
// if (k === "delay") {
|
|
805
|
-
// step.delay = parseFloat(vt.value);
|
|
806
|
-
// j += 2;
|
|
807
|
-
// continue;
|
|
808
|
-
// }
|
|
809
|
-
// }
|
|
810
|
-
// // bare key value form (legacy: delay 500, duration 400)
|
|
811
|
-
// if (k === "delay" && eq?.type === "NUMBER") {
|
|
812
|
-
// step.delay = parseFloat(eq.value);
|
|
813
|
-
// j++;
|
|
814
|
-
// }
|
|
815
|
-
// if (k === "duration" && eq?.type === "NUMBER") {
|
|
816
|
-
// step.duration = parseFloat(eq.value);
|
|
817
|
-
// j++;
|
|
818
|
-
// }
|
|
819
|
-
// if (k === "trigger") {
|
|
820
|
-
// step.trigger = eq?.value as AnimationTrigger;
|
|
821
|
-
// j++;
|
|
822
|
-
// }
|
|
823
|
-
// if (k === "factor") {
|
|
824
|
-
// step.factor = parseFloat(vt.value);
|
|
825
|
-
// j += 2;
|
|
826
|
-
// continue;
|
|
827
|
-
// }
|
|
828
|
-
// if (k === "deg") {
|
|
829
|
-
// step.deg = parseFloat(vt.value);
|
|
830
|
-
// j += 2;
|
|
831
|
-
// continue;
|
|
832
|
-
// }
|
|
833
|
-
// }
|
|
834
|
-
// return step;
|
|
835
|
-
// }
|
|
836
704
|
function parseChart(chartType) {
|
|
705
|
+
const keywordTok = cur();
|
|
837
706
|
skip();
|
|
838
707
|
const toks = lineTokens();
|
|
839
|
-
const id = toks
|
|
840
|
-
const props =
|
|
841
|
-
let j = 1;
|
|
842
|
-
while (j < toks.length - 1) {
|
|
843
|
-
const k = toks[j];
|
|
844
|
-
const eq = toks[j + 1];
|
|
845
|
-
if (eq?.type === "EQUALS" && j + 2 < toks.length) {
|
|
846
|
-
props[k.value] = toks[j + 2].value;
|
|
847
|
-
j += 3;
|
|
848
|
-
}
|
|
849
|
-
else
|
|
850
|
-
j++;
|
|
851
|
-
}
|
|
708
|
+
const id = requireExplicitId(keywordTok, toks);
|
|
709
|
+
const props = parseSimpleProps(toks, 1);
|
|
852
710
|
let dataRows = [];
|
|
853
711
|
skipNL();
|
|
854
712
|
while (cur().type !== "EOF" && cur().value !== "end") {
|
|
@@ -861,30 +719,31 @@ var AIDiagram = (function (exports) {
|
|
|
861
719
|
}
|
|
862
720
|
else if ((cur().type === "IDENT" || cur().type === "KEYWORD") &&
|
|
863
721
|
peek1().type === "EQUALS") {
|
|
864
|
-
const
|
|
722
|
+
const key = cur().value;
|
|
865
723
|
skip();
|
|
866
724
|
skip();
|
|
867
|
-
props[
|
|
725
|
+
props[key] = cur().value;
|
|
868
726
|
skip();
|
|
869
727
|
}
|
|
870
728
|
else if (SHAPES$1.includes(v) ||
|
|
871
729
|
v === "step" ||
|
|
872
730
|
v === "group" ||
|
|
873
|
-
v === "
|
|
731
|
+
v === "bare" ||
|
|
732
|
+
v === "note" ||
|
|
874
733
|
v === "table" ||
|
|
875
|
-
v === "config" ||
|
|
876
|
-
v === "theme" ||
|
|
734
|
+
v === "config" ||
|
|
735
|
+
v === "theme" ||
|
|
877
736
|
v === "style" ||
|
|
878
737
|
v === "markdown" ||
|
|
879
738
|
CHART_TYPES.includes(v)) {
|
|
880
739
|
break;
|
|
881
740
|
}
|
|
882
741
|
else if (peek1().type === "ARROW") {
|
|
883
|
-
// ← ADD THIS WHOLE BLOCK
|
|
884
742
|
break;
|
|
885
743
|
}
|
|
886
|
-
else
|
|
744
|
+
else {
|
|
887
745
|
skip();
|
|
746
|
+
}
|
|
888
747
|
}
|
|
889
748
|
const headers = dataRows[0]?.map(String) ?? [];
|
|
890
749
|
const rows = dataRows.slice(1);
|
|
@@ -901,29 +760,19 @@ var AIDiagram = (function (exports) {
|
|
|
901
760
|
};
|
|
902
761
|
}
|
|
903
762
|
function parseTable() {
|
|
904
|
-
|
|
763
|
+
const keywordTok = cur();
|
|
764
|
+
skip();
|
|
905
765
|
const toks = lineTokens();
|
|
906
|
-
|
|
907
|
-
if (toks[0])
|
|
908
|
-
id = toks[0].value;
|
|
766
|
+
const id = requireExplicitId(keywordTok, toks);
|
|
909
767
|
const props = {};
|
|
910
768
|
let j = 1;
|
|
911
|
-
// label="..." or bare second token
|
|
912
769
|
if (toks[1] &&
|
|
913
770
|
(toks[1].type === "STRING" ||
|
|
914
771
|
(toks[1].type === "IDENT" && toks[2]?.type !== "EQUALS"))) {
|
|
915
772
|
props.label = toks[1].value;
|
|
916
773
|
j = 2;
|
|
917
774
|
}
|
|
918
|
-
|
|
919
|
-
const k = toks[j], eq = toks[j + 1];
|
|
920
|
-
if (eq?.type === "EQUALS" && j + 2 < toks.length) {
|
|
921
|
-
props[k.value] = toks[j + 2].value;
|
|
922
|
-
j += 3;
|
|
923
|
-
}
|
|
924
|
-
else
|
|
925
|
-
j++;
|
|
926
|
-
}
|
|
775
|
+
Object.assign(props, parseSimpleProps(toks, j));
|
|
927
776
|
const table = {
|
|
928
777
|
kind: "table",
|
|
929
778
|
id,
|
|
@@ -950,7 +799,8 @@ var AIDiagram = (function (exports) {
|
|
|
950
799
|
while (cur().type !== "NEWLINE" && cur().type !== "EOF") {
|
|
951
800
|
if (cur().type === "STRING" ||
|
|
952
801
|
cur().type === "IDENT" ||
|
|
953
|
-
cur().type === "NUMBER"
|
|
802
|
+
cur().type === "NUMBER" ||
|
|
803
|
+
cur().type === "KEYWORD") {
|
|
954
804
|
cells.push(cur().value);
|
|
955
805
|
}
|
|
956
806
|
skip();
|
|
@@ -959,31 +809,20 @@ var AIDiagram = (function (exports) {
|
|
|
959
809
|
skip();
|
|
960
810
|
table.rows.push({ kind: v === "header" ? "header" : "data", cells });
|
|
961
811
|
}
|
|
962
|
-
else
|
|
812
|
+
else {
|
|
963
813
|
skip();
|
|
814
|
+
}
|
|
964
815
|
}
|
|
965
816
|
if (cur().type === "RBRACE")
|
|
966
817
|
skip();
|
|
967
818
|
return table;
|
|
968
819
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
skip();
|
|
820
|
+
function parseMarkdown() {
|
|
821
|
+
const keywordTok = cur();
|
|
822
|
+
skip();
|
|
972
823
|
const toks = lineTokens();
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
id = toks[0].value;
|
|
976
|
-
const props = {};
|
|
977
|
-
let j = 1;
|
|
978
|
-
while (j < toks.length - 1) {
|
|
979
|
-
const k = toks[j], eq = toks[j + 1];
|
|
980
|
-
if (eq?.type === "EQUALS" && j + 2 < toks.length) {
|
|
981
|
-
props[k.value] = toks[j + 2].value;
|
|
982
|
-
j += 3;
|
|
983
|
-
}
|
|
984
|
-
else
|
|
985
|
-
j++;
|
|
986
|
-
}
|
|
824
|
+
const id = requireExplicitId(keywordTok, toks);
|
|
825
|
+
const props = parseSimpleProps(toks, 1);
|
|
987
826
|
skipNL();
|
|
988
827
|
let content = "";
|
|
989
828
|
if (cur().type === "STRING_BLOCK") {
|
|
@@ -1000,7 +839,6 @@ var AIDiagram = (function (exports) {
|
|
|
1000
839
|
style: propsToStyle(props),
|
|
1001
840
|
};
|
|
1002
841
|
}
|
|
1003
|
-
// ── Main parse loop ─────────────────────────────────────
|
|
1004
842
|
skipNL();
|
|
1005
843
|
if (cur().value === "diagram")
|
|
1006
844
|
skip();
|
|
@@ -1019,19 +857,16 @@ var AIDiagram = (function (exports) {
|
|
|
1019
857
|
}
|
|
1020
858
|
if (v === "end")
|
|
1021
859
|
break;
|
|
1022
|
-
// direction — silently ignored (removed from engine)
|
|
1023
860
|
if (v === "direction") {
|
|
1024
861
|
lineTokens();
|
|
1025
862
|
continue;
|
|
1026
863
|
}
|
|
1027
|
-
// layout
|
|
1028
864
|
if (v === "layout") {
|
|
1029
865
|
skip();
|
|
1030
866
|
ast.layout = cur().value ?? "column";
|
|
1031
867
|
skip();
|
|
1032
868
|
continue;
|
|
1033
869
|
}
|
|
1034
|
-
// title
|
|
1035
870
|
if (v === "title") {
|
|
1036
871
|
skip();
|
|
1037
872
|
const toks = lineTokens();
|
|
@@ -1041,14 +876,10 @@ var AIDiagram = (function (exports) {
|
|
|
1041
876
|
ast.title = toks[idx + 2]?.value ?? "";
|
|
1042
877
|
}
|
|
1043
878
|
else {
|
|
1044
|
-
ast.title = toks
|
|
1045
|
-
.map((t2) => t2.value)
|
|
1046
|
-
.join(" ")
|
|
1047
|
-
.replace(/"/g, "");
|
|
879
|
+
ast.title = toks.map((t2) => t2.value).join(" ").replace(/"/g, "");
|
|
1048
880
|
}
|
|
1049
881
|
continue;
|
|
1050
882
|
}
|
|
1051
|
-
// description
|
|
1052
883
|
if (v === "description") {
|
|
1053
884
|
skip();
|
|
1054
885
|
ast.description = lineTokens()
|
|
@@ -1057,65 +888,38 @@ var AIDiagram = (function (exports) {
|
|
|
1057
888
|
.replace(/"/g, "");
|
|
1058
889
|
continue;
|
|
1059
890
|
}
|
|
1060
|
-
// config
|
|
1061
891
|
if (v === "config") {
|
|
1062
892
|
skip();
|
|
1063
|
-
const
|
|
893
|
+
const key = cur().value;
|
|
1064
894
|
skip();
|
|
1065
895
|
if (cur().type === "EQUALS")
|
|
1066
896
|
skip();
|
|
1067
|
-
const
|
|
897
|
+
const value = cur().value;
|
|
1068
898
|
skip();
|
|
1069
|
-
ast.config[
|
|
899
|
+
ast.config[key] = value;
|
|
1070
900
|
continue;
|
|
1071
901
|
}
|
|
1072
|
-
// style
|
|
1073
902
|
if (v === "style") {
|
|
1074
903
|
skip();
|
|
1075
904
|
const targetId = cur().value;
|
|
1076
905
|
skip();
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
let j = 0;
|
|
1080
|
-
while (j < lineToks.length - 1) {
|
|
1081
|
-
const k2 = lineToks[j];
|
|
1082
|
-
const eq = lineToks[j + 1];
|
|
1083
|
-
if (eq.type === "EQUALS") {
|
|
1084
|
-
p[k2.value] = lineToks[j + 2]?.value ?? "";
|
|
1085
|
-
j += 3;
|
|
1086
|
-
}
|
|
1087
|
-
else
|
|
1088
|
-
j++;
|
|
1089
|
-
}
|
|
1090
|
-
ast.styles[targetId] = { ...ast.styles[targetId], ...propsToStyle(p) };
|
|
906
|
+
const props = parseSimpleProps(lineTokens(), 0);
|
|
907
|
+
ast.styles[targetId] = { ...ast.styles[targetId], ...propsToStyle(props) };
|
|
1091
908
|
continue;
|
|
1092
909
|
}
|
|
1093
|
-
// theme
|
|
1094
910
|
if (v === "theme") {
|
|
1095
911
|
skip();
|
|
1096
912
|
const toks = lineTokens();
|
|
1097
913
|
const themeId = toks[0]?.value;
|
|
1098
914
|
if (!themeId)
|
|
1099
915
|
continue;
|
|
1100
|
-
|
|
1101
|
-
let j = 1;
|
|
1102
|
-
while (j < toks.length - 1) {
|
|
1103
|
-
const k2 = toks[j];
|
|
1104
|
-
const eq = toks[j + 1];
|
|
1105
|
-
if (eq && eq.type === "EQUALS" && j + 2 < toks.length) {
|
|
1106
|
-
props[k2.value] = toks[j + 2].value;
|
|
1107
|
-
j += 3;
|
|
1108
|
-
}
|
|
1109
|
-
else
|
|
1110
|
-
j++;
|
|
1111
|
-
}
|
|
1112
|
-
ast.themes[themeId] = propsToStyle(props);
|
|
916
|
+
ast.themes[themeId] = propsToStyle(parseSimpleProps(toks, 1));
|
|
1113
917
|
continue;
|
|
1114
918
|
}
|
|
1115
|
-
// group
|
|
1116
919
|
if (v === "group" || v === "bare") {
|
|
1117
920
|
const isBare = v === "bare";
|
|
1118
921
|
const grp = parseGroup();
|
|
922
|
+
registerAuthoredId(grp.id, "group", t);
|
|
1119
923
|
if (isBare) {
|
|
1120
924
|
grp.label = "";
|
|
1121
925
|
grp.style = {
|
|
@@ -1126,36 +930,34 @@ var AIDiagram = (function (exports) {
|
|
|
1126
930
|
};
|
|
1127
931
|
}
|
|
1128
932
|
ast.groups.push(grp);
|
|
1129
|
-
groupIds.add(grp.id);
|
|
1130
933
|
ast.rootOrder.push({ kind: "group", id: grp.id });
|
|
1131
934
|
continue;
|
|
1132
935
|
}
|
|
1133
|
-
// table
|
|
1134
936
|
if (v === "table") {
|
|
1135
937
|
const tbl = parseTable();
|
|
938
|
+
registerAuthoredId(tbl.id, "table", t);
|
|
1136
939
|
ast.tables.push(tbl);
|
|
1137
|
-
tableIds.add(tbl.id);
|
|
1138
940
|
ast.rootOrder.push({ kind: "table", id: tbl.id });
|
|
1139
941
|
continue;
|
|
1140
942
|
}
|
|
1141
|
-
// note (parsed as node with shape='note')
|
|
1142
943
|
if (v === "note") {
|
|
1143
944
|
const note = parseNote();
|
|
945
|
+
registerAuthoredId(note.id, "node", t);
|
|
1144
946
|
ast.nodes.push(note);
|
|
1145
|
-
nodeIds.add(note.id);
|
|
1146
947
|
ast.rootOrder.push({ kind: "node", id: note.id });
|
|
1147
948
|
continue;
|
|
1148
949
|
}
|
|
1149
|
-
// beat { ... } — parallel steps
|
|
1150
950
|
if (v === "beat") {
|
|
1151
|
-
skip();
|
|
951
|
+
skip();
|
|
1152
952
|
skipNL();
|
|
1153
953
|
if (cur().type === "LBRACE") {
|
|
1154
954
|
skip();
|
|
1155
955
|
skipNL();
|
|
1156
956
|
}
|
|
1157
957
|
const children = [];
|
|
1158
|
-
while (cur().type !== "RBRACE" &&
|
|
958
|
+
while (cur().type !== "RBRACE" &&
|
|
959
|
+
cur().value !== "end" &&
|
|
960
|
+
cur().type !== "EOF") {
|
|
1159
961
|
skipNL();
|
|
1160
962
|
if (cur().type === "RBRACE")
|
|
1161
963
|
break;
|
|
@@ -1171,69 +973,120 @@ var AIDiagram = (function (exports) {
|
|
|
1171
973
|
ast.steps.push({ kind: "beat", children });
|
|
1172
974
|
continue;
|
|
1173
975
|
}
|
|
1174
|
-
// step
|
|
1175
976
|
if (v === "step") {
|
|
1176
977
|
ast.steps.push(parseStep());
|
|
1177
978
|
continue;
|
|
1178
979
|
}
|
|
1179
|
-
// charts
|
|
1180
980
|
if (CHART_TYPES.includes(v)) {
|
|
1181
981
|
const chart = parseChart(v);
|
|
982
|
+
registerAuthoredId(chart.id, "chart", t);
|
|
1182
983
|
ast.charts.push(chart);
|
|
1183
|
-
|
|
1184
|
-
ast.rootOrder.push({ kind: "chart", id: chart.id }); // ← ADD
|
|
984
|
+
ast.rootOrder.push({ kind: "chart", id: chart.id });
|
|
1185
985
|
continue;
|
|
1186
986
|
}
|
|
1187
987
|
if (v === "markdown") {
|
|
1188
988
|
const md = parseMarkdown();
|
|
989
|
+
registerAuthoredId(md.id, "markdown", t);
|
|
1189
990
|
ast.markdowns.push(md);
|
|
1190
|
-
markdownIds.add(md.id);
|
|
1191
991
|
ast.rootOrder.push({ kind: "markdown", id: md.id });
|
|
1192
992
|
continue;
|
|
1193
993
|
}
|
|
1194
|
-
// edge: A -> B (MUST come before shape check)
|
|
1195
994
|
if (t.type === "IDENT" || t.type === "STRING" || t.type === "KEYWORD") {
|
|
1196
995
|
const nextTok = flat[i + 1];
|
|
1197
996
|
if (nextTok && nextTok.type === "ARROW") {
|
|
1198
997
|
const lineToks = lineTokens();
|
|
1199
998
|
if (lineToks.length >= 3 && lineToks[1].type === "ARROW") {
|
|
1200
999
|
const fromId = lineToks[0].value;
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
ast.edges.push(edge);
|
|
1204
|
-
// Auto-create implied nodes if they don't exist yet
|
|
1205
|
-
for (const nid of [fromId, edge.to]) {
|
|
1206
|
-
if (!nodeIds.has(nid) &&
|
|
1207
|
-
!tableIds.has(nid) &&
|
|
1208
|
-
!chartIds.has(nid) &&
|
|
1209
|
-
!groupIds.has(nid)) {
|
|
1210
|
-
nodeIds.add(nid);
|
|
1211
|
-
ast.nodes.push({
|
|
1212
|
-
kind: "node",
|
|
1213
|
-
id: nid,
|
|
1214
|
-
shape: "box",
|
|
1215
|
-
label: nid,
|
|
1216
|
-
style: {},
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1000
|
+
const connector = lineToks[1].value;
|
|
1001
|
+
ast.edges.push(parseEdge(fromId, connector, lineToks.slice(2)));
|
|
1220
1002
|
continue;
|
|
1221
1003
|
}
|
|
1222
1004
|
}
|
|
1223
1005
|
}
|
|
1224
|
-
// node shapes — only reached if NOT followed by an arrow
|
|
1225
1006
|
if (SHAPES$1.includes(v)) {
|
|
1226
1007
|
const node = parseNode(v);
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
ast.rootOrder.push({ kind: "node", id: node.id });
|
|
1231
|
-
}
|
|
1008
|
+
registerAuthoredId(node.id, "node", t);
|
|
1009
|
+
ast.nodes.push(node);
|
|
1010
|
+
ast.rootOrder.push({ kind: "node", id: node.id });
|
|
1232
1011
|
continue;
|
|
1233
1012
|
}
|
|
1234
1013
|
skip();
|
|
1235
1014
|
}
|
|
1236
|
-
|
|
1015
|
+
const allKnownIds = new Set(authoredEntityKinds.keys());
|
|
1016
|
+
for (const edge of ast.edges) {
|
|
1017
|
+
for (const id of [edge.from, edge.to]) {
|
|
1018
|
+
if (allKnownIds.has(id))
|
|
1019
|
+
continue;
|
|
1020
|
+
allKnownIds.add(id);
|
|
1021
|
+
ast.nodes.push({
|
|
1022
|
+
kind: "node",
|
|
1023
|
+
id,
|
|
1024
|
+
shape: "box",
|
|
1025
|
+
label: id,
|
|
1026
|
+
style: {},
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const entityKindById = new Map();
|
|
1031
|
+
ast.nodes.forEach((node) => entityKindById.set(node.id, "node"));
|
|
1032
|
+
ast.groups.forEach((group) => entityKindById.set(group.id, "group"));
|
|
1033
|
+
ast.tables.forEach((table) => entityKindById.set(table.id, "table"));
|
|
1034
|
+
ast.charts.forEach((chart) => entityKindById.set(chart.id, "chart"));
|
|
1035
|
+
ast.markdowns.forEach((md) => entityKindById.set(md.id, "markdown"));
|
|
1036
|
+
for (const group of ast.groups) {
|
|
1037
|
+
const itemIds = unresolvedGroupItems.get(group.id) ?? [];
|
|
1038
|
+
group.children = itemIds.map((itemId) => {
|
|
1039
|
+
if (itemId === group.id) {
|
|
1040
|
+
const tok = groupTokens.get(group.id) ?? cur();
|
|
1041
|
+
throw new ParseError(`Group "${group.id}" cannot include itself in items=[...]`, tok.line, tok.col);
|
|
1042
|
+
}
|
|
1043
|
+
const kind = entityKindById.get(itemId);
|
|
1044
|
+
if (!kind) {
|
|
1045
|
+
const tok = groupTokens.get(group.id) ?? cur();
|
|
1046
|
+
throw new ParseError(`Group "${group.id}" references unknown item "${itemId}" in items=[...]`, tok.line, tok.col);
|
|
1047
|
+
}
|
|
1048
|
+
return { kind, id: itemId };
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
const parentByItemId = new Map();
|
|
1052
|
+
for (const group of ast.groups) {
|
|
1053
|
+
for (const child of group.children) {
|
|
1054
|
+
const existingParent = parentByItemId.get(child.id);
|
|
1055
|
+
if (existingParent) {
|
|
1056
|
+
const tok = groupTokens.get(group.id) ?? cur();
|
|
1057
|
+
throw new ParseError(`Item "${child.id}" cannot belong to both "${existingParent}" and "${group.id}"`, tok.line, tok.col);
|
|
1058
|
+
}
|
|
1059
|
+
parentByItemId.set(child.id, group.id);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const groupsById = new Map(ast.groups.map((group) => [group.id, group]));
|
|
1063
|
+
const visiting = new Set();
|
|
1064
|
+
const visited = new Set();
|
|
1065
|
+
const stack = [];
|
|
1066
|
+
function visitGroup(groupId) {
|
|
1067
|
+
if (visiting.has(groupId)) {
|
|
1068
|
+
const start = stack.indexOf(groupId);
|
|
1069
|
+
const cycle = (start >= 0 ? stack.slice(start) : stack).concat(groupId);
|
|
1070
|
+
const tok = groupTokens.get(groupId) ?? cur();
|
|
1071
|
+
throw new ParseError(`Group cycle detected: ${cycle.join(" -> ")}`, tok.line, tok.col);
|
|
1072
|
+
}
|
|
1073
|
+
if (visited.has(groupId))
|
|
1074
|
+
return;
|
|
1075
|
+
visiting.add(groupId);
|
|
1076
|
+
stack.push(groupId);
|
|
1077
|
+
const group = groupsById.get(groupId);
|
|
1078
|
+
if (group) {
|
|
1079
|
+
for (const child of group.children) {
|
|
1080
|
+
if (child.kind === "group")
|
|
1081
|
+
visitGroup(child.id);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
stack.pop();
|
|
1085
|
+
visiting.delete(groupId);
|
|
1086
|
+
visited.add(groupId);
|
|
1087
|
+
}
|
|
1088
|
+
for (const group of ast.groups)
|
|
1089
|
+
visitGroup(group.id);
|
|
1237
1090
|
for (const node of ast.nodes) {
|
|
1238
1091
|
if (ast.styles[node.id]) {
|
|
1239
1092
|
node.style = { ...ast.styles[node.id], ...node.style };
|
|
@@ -3568,6 +3421,16 @@ var AIDiagram = (function (exports) {
|
|
|
3568
3421
|
// ============================================================
|
|
3569
3422
|
// ── Build scene graph from AST ────────────────────────────
|
|
3570
3423
|
function buildSceneGraph(ast) {
|
|
3424
|
+
const nodeParentById = new Map();
|
|
3425
|
+
const groupParentById = new Map();
|
|
3426
|
+
for (const g of ast.groups) {
|
|
3427
|
+
for (const child of g.children) {
|
|
3428
|
+
if (child.kind === "node")
|
|
3429
|
+
nodeParentById.set(child.id, g.id);
|
|
3430
|
+
if (child.kind === "group")
|
|
3431
|
+
groupParentById.set(child.id, g.id);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3571
3434
|
const nodes = ast.nodes.map((n) => {
|
|
3572
3435
|
const themeStyle = n.theme ? (ast.themes[n.theme] ?? {}) : {};
|
|
3573
3436
|
return {
|
|
@@ -3575,7 +3438,7 @@ var AIDiagram = (function (exports) {
|
|
|
3575
3438
|
shape: n.shape,
|
|
3576
3439
|
label: n.label,
|
|
3577
3440
|
style: { ...ast.styles[n.id], ...themeStyle, ...n.style },
|
|
3578
|
-
groupId: n.
|
|
3441
|
+
groupId: nodeParentById.get(n.id),
|
|
3579
3442
|
width: n.width,
|
|
3580
3443
|
height: n.height,
|
|
3581
3444
|
deg: n.deg,
|
|
@@ -3597,7 +3460,7 @@ var AIDiagram = (function (exports) {
|
|
|
3597
3460
|
return {
|
|
3598
3461
|
id: g.id,
|
|
3599
3462
|
label: g.label,
|
|
3600
|
-
parentId:
|
|
3463
|
+
parentId: groupParentById.get(g.id),
|
|
3601
3464
|
children: g.children,
|
|
3602
3465
|
layout: (g.layout ?? "column"),
|
|
3603
3466
|
columns: g.columns ?? 1,
|
|
@@ -3645,28 +3508,21 @@ var AIDiagram = (function (exports) {
|
|
|
3645
3508
|
h: c.height ?? CHART.defaultH,
|
|
3646
3509
|
};
|
|
3647
3510
|
});
|
|
3648
|
-
const markdowns = (ast.markdowns ?? []).map(m => {
|
|
3511
|
+
const markdowns = (ast.markdowns ?? []).map((m) => {
|
|
3649
3512
|
const themeStyle = m.theme ? (ast.themes[m.theme] ?? {}) : {};
|
|
3650
3513
|
return {
|
|
3651
3514
|
id: m.id,
|
|
3652
3515
|
content: m.content,
|
|
3653
3516
|
lines: parseMarkdownContent(m.content),
|
|
3654
|
-
style: { ...themeStyle, ...m.style },
|
|
3517
|
+
style: { ...ast.styles[m.id], ...themeStyle, ...m.style },
|
|
3655
3518
|
width: m.width,
|
|
3656
3519
|
height: m.height,
|
|
3657
|
-
x: 0,
|
|
3520
|
+
x: 0,
|
|
3521
|
+
y: 0,
|
|
3522
|
+
w: 0,
|
|
3523
|
+
h: 0,
|
|
3658
3524
|
};
|
|
3659
3525
|
});
|
|
3660
|
-
// Set parentId for nested groups
|
|
3661
|
-
for (const g of groups) {
|
|
3662
|
-
for (const child of g.children) {
|
|
3663
|
-
if (child.kind === "group") {
|
|
3664
|
-
const nested = groups.find((gg) => gg.id === child.id);
|
|
3665
|
-
if (nested)
|
|
3666
|
-
nested.parentId = g.id;
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
}
|
|
3670
3526
|
const edges = ast.edges.map((e) => ({
|
|
3671
3527
|
id: e.id,
|
|
3672
3528
|
from: e.from,
|
|
@@ -4793,15 +4649,30 @@ var AIDiagram = (function (exports) {
|
|
|
4793
4649
|
// 4. Build root order
|
|
4794
4650
|
// sg.rootOrder preserves DSL declaration order.
|
|
4795
4651
|
// Fall back: groups, then nodes, then tables.
|
|
4796
|
-
const
|
|
4797
|
-
|
|
4798
|
-
:
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4652
|
+
const defaultRootOrder = [
|
|
4653
|
+
...rootGroups.map((g) => ({ kind: "group", id: g.id })),
|
|
4654
|
+
...rootNodes.map((n) => ({ kind: "node", id: n.id })),
|
|
4655
|
+
...rootTables.map((t) => ({ kind: "table", id: t.id })),
|
|
4656
|
+
...rootCharts.map((c) => ({ kind: "chart", id: c.id })),
|
|
4657
|
+
...rootMarkdowns.map((m) => ({ kind: "markdown", id: m.id })),
|
|
4658
|
+
];
|
|
4659
|
+
const rootOrderSource = sg.rootOrder?.length ? sg.rootOrder : defaultRootOrder;
|
|
4660
|
+
const rootOrder = rootOrderSource.filter((ref) => {
|
|
4661
|
+
switch (ref.kind) {
|
|
4662
|
+
case "group":
|
|
4663
|
+
return !nestedGroupIds.has(ref.id);
|
|
4664
|
+
case "node":
|
|
4665
|
+
return !groupedNodeIds.has(ref.id);
|
|
4666
|
+
case "table":
|
|
4667
|
+
return !groupedTableIds.has(ref.id);
|
|
4668
|
+
case "chart":
|
|
4669
|
+
return !groupedChartIds.has(ref.id);
|
|
4670
|
+
case "markdown":
|
|
4671
|
+
return !groupedMarkdownIds.has(ref.id);
|
|
4672
|
+
default:
|
|
4673
|
+
return true;
|
|
4674
|
+
}
|
|
4675
|
+
});
|
|
4805
4676
|
// 5. Root-level layout
|
|
4806
4677
|
// sg.layout:
|
|
4807
4678
|
// 'row' → items flow left to right (default)
|