@cardenelabs/dragon 0.26.0 → 0.28.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 +42 -8
- package/dist/index.cjs +1417 -369
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +225 -113
- package/dist/index.d.ts +225 -113
- package/dist/index.js +1416 -370
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/compile.ts +1572 -303
- package/src/index.ts +5 -3
- package/src/json-parser.ts +171 -71
- package/src/notation-lint.ts +112 -74
- package/src/schemas/diagram.json +92 -30
- package/src/types.ts +59 -40
- package/src/v05/input-table.generated.ts +6 -4
- package/src/v05/parser-types.ts +12 -1
- package/src/v05/parser.ts +412 -77
package/src/v05/parser.ts
CHANGED
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
44
|
import type { NodeKind, Tone, EdgeStyle, EdgeHead, EdgeHeadFill, ClassRelationType, SequenceMessageKind } from "@cardenelabs/cdl";
|
|
45
|
-
import { TONES, NODE_KINDS, EDGE_HEADS, EDGE_HEAD_FILLS, EDGE_STYLES, EDGE_REVEALS, CLASS_RELATION_LOOK, SEQUENCE_MESSAGE_LOOK, parseFormula } from "@cardenelabs/cdl";
|
|
46
|
-
import type { EdgeReveal } from "@cardenelabs/cdl";
|
|
45
|
+
import { TONES, NODE_KINDS, EDGE_HEADS, EDGE_HEAD_FILLS, EDGE_STYLES, EDGE_REVEALS, RELATION_FOCUSES, CLASS_RELATION_LOOK, SEQUENCE_MESSAGE_LOOK, parseFormula } from "@cardenelabs/cdl";
|
|
46
|
+
import type { EdgeReveal, RelationFocus } from "@cardenelabs/cdl";
|
|
47
47
|
import { TONE_ALIAS, NODE_KIND_ALIAS, DIRECTIONS, resolveDirection, PALETTES, resolvePalette } from "../keywords";
|
|
48
48
|
import type { DslPalette } from "../keywords";
|
|
49
49
|
import type { DslDirection } from "../keywords";
|
|
@@ -66,7 +66,6 @@ import type {
|
|
|
66
66
|
DslFormula,
|
|
67
67
|
DslEventBinding,
|
|
68
68
|
DslScrollTrigger,
|
|
69
|
-
DslActorNodeOverride,
|
|
70
69
|
DslStep,
|
|
71
70
|
DslAnimate,
|
|
72
71
|
DslState,
|
|
@@ -80,6 +79,7 @@ import type {
|
|
|
80
79
|
DslBand,
|
|
81
80
|
DslGroup,
|
|
82
81
|
DslViewport,
|
|
82
|
+
LayoutPos,
|
|
83
83
|
} from "../types";
|
|
84
84
|
|
|
85
85
|
export type V05ParseResult = { ok: true; doc: DslDocument } | { ok: false; errors: DslError[] };
|
|
@@ -159,6 +159,8 @@ export const TOP_LEVEL_KEYS = [
|
|
|
159
159
|
"bands",
|
|
160
160
|
// 矢印をいつ出すか (#1470)
|
|
161
161
|
"reveal",
|
|
162
|
+
// 箱に触れると関係する線だけを光らせるか (#1757)
|
|
163
|
+
"relations",
|
|
162
164
|
/*
|
|
163
165
|
* 図の並ぶ向き (#1494)。
|
|
164
166
|
*
|
|
@@ -468,6 +470,7 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
468
470
|
let eyebrow: string | null = null;
|
|
469
471
|
let eyebrowLine = 0;
|
|
470
472
|
let reveal: EdgeReveal | null = null;
|
|
473
|
+
let relations: RelationFocus | null = null;
|
|
471
474
|
let direction: DslDirection | null = null;
|
|
472
475
|
let directionLine = 0;
|
|
473
476
|
let palette: DslPalette | null = null;
|
|
@@ -537,6 +540,29 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
537
540
|
i += 1;
|
|
538
541
|
continue;
|
|
539
542
|
}
|
|
543
|
+
if (head.key === "relations") {
|
|
544
|
+
/*
|
|
545
|
+
* 箱に触れると関係する線だけを光らせるか (#1757)。
|
|
546
|
+
*
|
|
547
|
+
* クラス図と ER 図は順番を持たない図で、段を追う見せ方は読み手の問い
|
|
548
|
+
* (この箱はどこと繋がっているか) に答えない。 `hover` と書くと、箱に触れた時に
|
|
549
|
+
* その箱 ・ 繋がる線 ・ 相手の箱だけが光る (`cdl#806`)。
|
|
550
|
+
*/
|
|
551
|
+
const v = (head.value ?? "").trim();
|
|
552
|
+
if (v.length > 0) {
|
|
553
|
+
if ((RELATION_FOCUSES as readonly string[]).includes(v)) {
|
|
554
|
+
relations = v as RelationFocus;
|
|
555
|
+
} else {
|
|
556
|
+
errors.push({
|
|
557
|
+
line: line.no,
|
|
558
|
+
message: `relations が読めません (書いた値: ${v})`,
|
|
559
|
+
hint: `使える語 = ${RELATION_FOCUSES.join(" / ")}`,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
i += 1;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
540
566
|
if (head.key === "direction") {
|
|
541
567
|
/*
|
|
542
568
|
* 図の並ぶ向き (#1494)。
|
|
@@ -722,6 +748,13 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
722
748
|
const inline = head.value?.trim();
|
|
723
749
|
if (inline && inline.startsWith("{") && inline.endsWith("}")) {
|
|
724
750
|
const opts = parseInlineMapping(inline.slice(1, -1));
|
|
751
|
+
中括弧の知らない項目名を知らせる(
|
|
752
|
+
inline.slice(1, -1),
|
|
753
|
+
Object.keys(VIEWPORT_VALUE_KINDS),
|
|
754
|
+
"viewport の ",
|
|
755
|
+
line.no,
|
|
756
|
+
errors,
|
|
757
|
+
);
|
|
725
758
|
viewport = {
|
|
726
759
|
...表で読む(VIEWPORT_VALUE_KINDS, opts, "viewport の ", line.no, errors),
|
|
727
760
|
pos: { line: line.no },
|
|
@@ -730,15 +763,37 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
730
763
|
continue;
|
|
731
764
|
}
|
|
732
765
|
// block: viewport:\n width: 1400\n height: 900\n ...
|
|
733
|
-
|
|
766
|
+
// **`collectIndentedList` を使わない** (#1968)。 あちらは `:` を含まない行を黙って捨てるため、
|
|
767
|
+
// 書き間違えた行が「書かなかった」 と同じになる (`axes:` と同じ理由)
|
|
768
|
+
const { items, next } = collectIndentedRaw(lines, i + 1, line.indent);
|
|
734
769
|
const opts: Record<string, string> = {};
|
|
735
770
|
// 知らせは **値を書いた行** を指す (#1306)。 `viewport:` の行を指すと、欄が縦に並ぶ形で
|
|
736
771
|
// どの行を直せばよいか分からない
|
|
737
772
|
const optLines: Record<string, number> = {};
|
|
773
|
+
const 使える = Object.keys(VIEWPORT_VALUE_KINDS);
|
|
738
774
|
for (const it of items) {
|
|
739
|
-
|
|
740
|
-
|
|
775
|
+
// 先頭の `- ` を付けて並べる形も受ける (`collectIndentedList` が落としていた分を揃える)
|
|
776
|
+
const 行 = it.trimmed.replace(/^-\s+/, "");
|
|
777
|
+
const m = 行.match(/^([a-zA-Z][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
|
|
778
|
+
if (!m) {
|
|
779
|
+
errors.push({
|
|
780
|
+
line: it.no,
|
|
781
|
+
message: `viewport の行が読めません: "${it.trimmed}"`,
|
|
782
|
+
hint: `use \`width: 1400\` (使える項目 = ${使える.join(", ")})`,
|
|
783
|
+
});
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
741
786
|
const 欄 = m[1] ?? "";
|
|
787
|
+
if (!使える.includes(欄)) {
|
|
788
|
+
errors.push({
|
|
789
|
+
line: it.no,
|
|
790
|
+
message: `viewport の項目名が読めません: "${欄}"`,
|
|
791
|
+
hint: `使える項目 = ${使える.join(", ")}`,
|
|
792
|
+
});
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
// 値を書かなかった形 (`width:`) は「書かなかった」 と同じに扱う (1 行の形と揃える)
|
|
796
|
+
if ((m[2] ?? "").trim() === "") continue;
|
|
742
797
|
opts[欄] = stripQuotes((m[2] ?? "").trim());
|
|
743
798
|
optLines[欄] = it.no;
|
|
744
799
|
}
|
|
@@ -785,6 +840,13 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
785
840
|
continue;
|
|
786
841
|
}
|
|
787
842
|
const opts = parseInlineMapping(m[2] ?? "");
|
|
843
|
+
中括弧の知らない項目名を知らせる(
|
|
844
|
+
m[2] ?? "",
|
|
845
|
+
m[1] === "x" ? AXIS_X_KEYS : AXIS_Y_KEYS,
|
|
846
|
+
`軸 ${m[1]} の `,
|
|
847
|
+
it.no,
|
|
848
|
+
errors,
|
|
849
|
+
);
|
|
788
850
|
if (m[1] === "x") 組み立て.x = { left: opts.left, right: opts.right };
|
|
789
851
|
else 組み立て.y = { bottom: opts.bottom, top: opts.top };
|
|
790
852
|
}
|
|
@@ -828,10 +890,21 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
828
890
|
if (m) {
|
|
829
891
|
const id = m[1]!;
|
|
830
892
|
const opts = parseInlineMapping(m[2]!);
|
|
893
|
+
中括弧の知らない項目名を知らせる(
|
|
894
|
+
m[2]!,
|
|
895
|
+
LANE_INLINE_KEYS,
|
|
896
|
+
`縦列 ${id} の `,
|
|
897
|
+
it.no,
|
|
898
|
+
errors,
|
|
899
|
+
);
|
|
831
900
|
lanesMap[id] = {
|
|
832
901
|
id,
|
|
833
902
|
...表で読む(LANE_VALUE_KINDS, opts, `縦列 ${id} の `, it.no, errors),
|
|
834
903
|
label: opts.label,
|
|
904
|
+
// 位置のずらし (#1971)
|
|
905
|
+
layoutPos: ずらしにまとめる(
|
|
906
|
+
表で読む(OFFSET_VALUE_KINDS, opts, `縦列 ${id} の `, it.no, errors),
|
|
907
|
+
),
|
|
835
908
|
pos: { line: it.no },
|
|
836
909
|
};
|
|
837
910
|
} else {
|
|
@@ -1003,6 +1076,7 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
1003
1076
|
if (m) {
|
|
1004
1077
|
const id = m[1]!;
|
|
1005
1078
|
const opts = parseInlineMapping(m[2]!);
|
|
1079
|
+
中括弧の知らない項目名を知らせる(m[2]!, GROUP_INLINE_KEYS, `組 ${id} の `, it.no, errors);
|
|
1006
1080
|
const lanesList = (opts.lanes ?? "")
|
|
1007
1081
|
.replace(/^\[|\]$/g, "")
|
|
1008
1082
|
.split(",")
|
|
@@ -1060,6 +1134,7 @@ export function parseTextDslV05(src: string): V05ParseResult {
|
|
|
1060
1134
|
type: type!,
|
|
1061
1135
|
...(eyebrow !== null ? { eyebrow, eyebrowPos: { line: eyebrowLine } } : {}),
|
|
1062
1136
|
...(reveal !== null ? { reveal } : {}),
|
|
1137
|
+
...(relations !== null ? { relations } : {}),
|
|
1063
1138
|
...(direction !== null ? { direction, directionPos: { line: directionLine } } : {}),
|
|
1064
1139
|
...(palette !== null ? { palette } : {}),
|
|
1065
1140
|
...(axes !== undefined ? { axes, axesPos: { line: axesLine } } : {}),
|
|
@@ -1405,6 +1480,23 @@ export const VIEWPORT_VALUE_KINDS = {
|
|
|
1405
1480
|
labelMargin: "数",
|
|
1406
1481
|
} as const satisfies Record<Exclude<keyof DslViewport, "pos">, 値の形>;
|
|
1407
1482
|
|
|
1483
|
+
/**
|
|
1484
|
+
* 位置のずらしの 2 欄 (#1971)。 箱 (縦に並べた形と中括弧の形) と縦列 (中括弧の形) で同じ読み方をする。
|
|
1485
|
+
*
|
|
1486
|
+
* JSON の `pos: { x, y }` と同じ `layoutPos` に入る。 記法の `pos:` は座標 (`posX` / `posY`) の意味で
|
|
1487
|
+
* 使われているため、ずらしは別の名前にする。
|
|
1488
|
+
*/
|
|
1489
|
+
export const OFFSET_VALUE_KINDS = {
|
|
1490
|
+
offsetX: "数",
|
|
1491
|
+
offsetY: "数",
|
|
1492
|
+
} as const satisfies Record<string, 値の形>;
|
|
1493
|
+
|
|
1494
|
+
/** 読めたずらしを `layoutPos` にまとめる。 片方だけ書いた時は残りを 0 とする (JSON は 2 欄とも必須) */
|
|
1495
|
+
function ずらしにまとめる(v: { offsetX?: number; offsetY?: number }): LayoutPos | undefined {
|
|
1496
|
+
if (v.offsetX === undefined && v.offsetY === undefined) return undefined;
|
|
1497
|
+
return { x: v.offsetX ?? 0, y: v.offsetY ?? 0 };
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1408
1500
|
/**
|
|
1409
1501
|
* 縦列の欄 (#1306)。 `label` は文字列なので表に載せない (記法の値は全て文字列で、
|
|
1410
1502
|
* 文字列の欄には読めない値という状態が無い)。
|
|
@@ -1416,6 +1508,31 @@ export const LANE_VALUE_KINDS = {
|
|
|
1416
1508
|
lifeline: "真偽",
|
|
1417
1509
|
} as const satisfies Record<string, 値の形>;
|
|
1418
1510
|
|
|
1511
|
+
/**
|
|
1512
|
+
* 縦列の中括弧に書ける項目 (#1968)。 数と真偽の欄は `LANE_VALUE_KINDS` と `OFFSET_VALUE_KINDS` から
|
|
1513
|
+
* 導き、表に載せない文字列の `label` だけを足す。
|
|
1514
|
+
*/
|
|
1515
|
+
export const LANE_INLINE_KEYS: readonly (keyof DslLane | keyof typeof OFFSET_VALUE_KINDS)[] = [
|
|
1516
|
+
...(Object.keys(LANE_VALUE_KINDS) as (keyof typeof LANE_VALUE_KINDS)[]),
|
|
1517
|
+
// 位置のずらし (#1971)。 JSON の縦列の `pos` と同じ `layoutPos` に入る
|
|
1518
|
+
...(Object.keys(OFFSET_VALUE_KINDS) as (keyof typeof OFFSET_VALUE_KINDS)[]),
|
|
1519
|
+
"label",
|
|
1520
|
+
];
|
|
1521
|
+
|
|
1522
|
+
/** 組の中括弧に書ける項目 (#1968)。 `groups:` の読み手が拾う欄と揃える */
|
|
1523
|
+
export const GROUP_INLINE_KEYS = ["label", "lanes"] as const satisfies readonly Exclude<
|
|
1524
|
+
keyof DslGroup,
|
|
1525
|
+
"id" | "pos"
|
|
1526
|
+
>[];
|
|
1527
|
+
|
|
1528
|
+
/** 軸の名前の中括弧に書ける項目 (#1968)。 横軸は左右、縦軸は下上 */
|
|
1529
|
+
export const AXIS_X_KEYS = ["left", "right"] as const satisfies readonly (keyof NonNullable<
|
|
1530
|
+
DslAxes["x"]
|
|
1531
|
+
>)[];
|
|
1532
|
+
export const AXIS_Y_KEYS = ["bottom", "top"] as const satisfies readonly (keyof NonNullable<
|
|
1533
|
+
DslAxes["y"]
|
|
1534
|
+
>)[];
|
|
1535
|
+
|
|
1419
1536
|
/**
|
|
1420
1537
|
* 箱の中に描く図形 (`shape:`) と、値を見せる部品 (`readouts:`) の欄 (#1374)。
|
|
1421
1538
|
*
|
|
@@ -1595,6 +1712,60 @@ function 組の並びとして読む(raw: string): Record<string, string>[] | un
|
|
|
1595
1712
|
return 出;
|
|
1596
1713
|
}
|
|
1597
1714
|
|
|
1715
|
+
/** 選択肢の組が持てる項目。 描画側の `CdlInputOption` の組と同じ 2 つ */
|
|
1716
|
+
const 選択肢の組の項目 = ["value", "label"] as const;
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* `["green", { value: "red", label: "失敗" }]` の形を選択肢の並びに読む (#1920)。
|
|
1720
|
+
*
|
|
1721
|
+
* 要素は文字列 (値と名前を兼ねる) か、`value` と `label` を持つ組。 2 つを混ぜて書ける。
|
|
1722
|
+
* 割るのは `splitInlineFields` = 引用符と括弧の中の `,` を区切りと取り違えない
|
|
1723
|
+
* (`並びとして読む` は組の中の `,` で割るので使えない)。
|
|
1724
|
+
*
|
|
1725
|
+
* 組の誤り (項目の欠け / 知らない項目 / 空の値) は行番号付きで知らせ、並びごと落とす。
|
|
1726
|
+
* 読めた組だけを渡すと選択肢の数が変わり、既定値が選択肢に無い図になる。
|
|
1727
|
+
*/
|
|
1728
|
+
function 選択肢の並びとして読む(
|
|
1729
|
+
値: string,
|
|
1730
|
+
名: string,
|
|
1731
|
+
line: number,
|
|
1732
|
+
errors: DslError[],
|
|
1733
|
+
): (string | { value: string; label: string })[] | undefined {
|
|
1734
|
+
const 中身 = 値.trim();
|
|
1735
|
+
if (!中身.startsWith("[") || !中身.endsWith("]")) {
|
|
1736
|
+
errors.push({ line, message: `${名} の並びが読めません: "${値}"`, hint: '`["green", { value: "red", label: "失敗" }]` の形で書く' });
|
|
1737
|
+
return undefined;
|
|
1738
|
+
}
|
|
1739
|
+
const 出: (string | { value: string; label: string })[] = [];
|
|
1740
|
+
const 誤りの数 = errors.length;
|
|
1741
|
+
splitInlineFields(中身.slice(1, -1)).forEach((生, i) => {
|
|
1742
|
+
const 要素 = 生.trim();
|
|
1743
|
+
if (!要素.startsWith("{")) {
|
|
1744
|
+
const 文字列 = stripQuotes(要素);
|
|
1745
|
+
if (文字列 !== "") 出.push(文字列);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
if (!要素.endsWith("}")) {
|
|
1749
|
+
errors.push({ line, message: `${名}[${i}] の組が読めません: "${要素}"`, hint: '`{ value: "red", label: "失敗" }` の形で書く' });
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
const 組 = parseInlineMapping(要素.slice(1, -1));
|
|
1753
|
+
for (const k of Object.keys(組)) {
|
|
1754
|
+
if ((選択肢の組の項目 as readonly string[]).includes(k)) continue;
|
|
1755
|
+
errors.push({ line, message: `${名}[${i}] の項目名が読めません: "${k}"`, hint: `使える項目 = ${選択肢の組の項目.join(", ")}` });
|
|
1756
|
+
}
|
|
1757
|
+
for (const k of 選択肢の組の項目) {
|
|
1758
|
+
if (組[k] === undefined) {
|
|
1759
|
+
errors.push({ line, message: `${名}[${i}].${k} は必ず書きます`, hint: `必須の項目 = ${選択肢の組の項目.join(", ")}` });
|
|
1760
|
+
} else if (組[k] === "") {
|
|
1761
|
+
errors.push({ line, message: `${名}[${i}].${k} が空です`, hint: k === "label" ? "名前を書かないなら文字列だけを並べる" : "値を 1 文字以上書く" });
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
if (組.value && 組.label) 出.push({ value: 組.value, label: 組.label });
|
|
1765
|
+
});
|
|
1766
|
+
return errors.length === 誤りの数 ? 出 : undefined;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1598
1769
|
function 部品の組を検査する(
|
|
1599
1770
|
kind: string,
|
|
1600
1771
|
読めた: Record<string, unknown>,
|
|
@@ -1646,7 +1817,8 @@ function 表に従って読む(
|
|
|
1646
1817
|
if (形 === undefined) {
|
|
1647
1818
|
errors.push({
|
|
1648
1819
|
line,
|
|
1649
|
-
|
|
1820
|
+
// 接頭は呼び手が「の」 まで書く (`図形の ` / `部品 ring の `)。 ここで足すと「の の」 に重なる (#1968)
|
|
1821
|
+
message: `${接頭.trimEnd()}項目名が読めません: "${欄}"`,
|
|
1650
1822
|
hint: `使える項目 = ${Object.keys(定義.欄).join(", ")}`,
|
|
1651
1823
|
});
|
|
1652
1824
|
continue;
|
|
@@ -1688,6 +1860,9 @@ function 表に従って読む(
|
|
|
1688
1860
|
} else {
|
|
1689
1861
|
out[欄] = 数;
|
|
1690
1862
|
}
|
|
1863
|
+
} else if (形 === "選択肢の並び") {
|
|
1864
|
+
const 選択肢 = 選択肢の並びとして読む(値, `${接頭}${欄}`, line, errors);
|
|
1865
|
+
if (選択肢 !== undefined) out[欄] = 選択肢;
|
|
1691
1866
|
} else if (形 === "組の並び") {
|
|
1692
1867
|
const 組 = 組の並びとして読む(値);
|
|
1693
1868
|
if (組 !== undefined) out[欄] = 組;
|
|
@@ -2075,6 +2250,9 @@ function 巻き上げとして読む(
|
|
|
2075
2250
|
*
|
|
2076
2251
|
* **式は描画側の parser に通す**。 自前で書き方を決めると、通ったのに描画側が
|
|
2077
2252
|
* 解けない式を受けてしまう。 描画側が投げた誤りの本文をそのまま知らせに載せる。
|
|
2253
|
+
*
|
|
2254
|
+
* 名札を付ける時は `名前: { expression: "式", label: "名札" }` の組で書く (#1916)。
|
|
2255
|
+
* 描画側は名札を操作部の名前に描く (cdl#853)。 巻き取り (`scrolls:`) と同じ組の形にそろえる。
|
|
2078
2256
|
*/
|
|
2079
2257
|
function 式として読む(行: string, line: number, errors: DslError[]): DslFormula | undefined {
|
|
2080
2258
|
const c = 行.indexOf(":");
|
|
@@ -2087,12 +2265,22 @@ function 式として読む(行: string, line: number, errors: DslError[]): DslF
|
|
|
2087
2265
|
return undefined;
|
|
2088
2266
|
}
|
|
2089
2267
|
const 名前 = 行.slice(0, c).trim();
|
|
2090
|
-
const 式 = stripQuotes(行.slice(c + 1).trim());
|
|
2091
2268
|
if (!isValueName(名前)) {
|
|
2092
2269
|
// 名前の規則は状態と揃える = 式の名前も `{名前}` で箱の文字に差し込める
|
|
2093
2270
|
errors.push({ line, ...valueNameIssue(名前) });
|
|
2094
2271
|
return undefined;
|
|
2095
2272
|
}
|
|
2273
|
+
const 組 = 式の組に割る(行);
|
|
2274
|
+
let 式: string;
|
|
2275
|
+
let 名札: string | undefined;
|
|
2276
|
+
if (組 === undefined) {
|
|
2277
|
+
式 = stripQuotes(行.slice(c + 1).trim());
|
|
2278
|
+
} else {
|
|
2279
|
+
const 読めた = 式の組を読む(名前, 組, line, errors);
|
|
2280
|
+
if (読めた === undefined) return undefined;
|
|
2281
|
+
式 = 読めた.expression;
|
|
2282
|
+
名札 = 読めた.label;
|
|
2283
|
+
}
|
|
2096
2284
|
if (式 === "") {
|
|
2097
2285
|
errors.push({
|
|
2098
2286
|
line,
|
|
@@ -2111,7 +2299,71 @@ function 式として読む(行: string, line: number, errors: DslError[]): DslF
|
|
|
2111
2299
|
});
|
|
2112
2300
|
return undefined;
|
|
2113
2301
|
}
|
|
2114
|
-
return { id: 名前, expression: 式, pos: { line } };
|
|
2302
|
+
return { id: 名前, expression: 式, ...(名札 !== undefined ? { label: 名札 } : {}), pos: { line } };
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
/** 組の中の項目名の形。 `parseInlineMapping` が項目名として受ける字と同じ */
|
|
2306
|
+
const 組の項目名 = /^([^\s:,{}[\]"']+)\s*:\s*(.*)$/s;
|
|
2307
|
+
|
|
2308
|
+
/**
|
|
2309
|
+
* 式の行が組の形 (`{ expression: ..., label: ... }`) なら中括弧の内側を返す (#1916)。
|
|
2310
|
+
*
|
|
2311
|
+
* **中括弧で名前を囲った式と取り違えない**。 式は `{input} * 2` のように名前を中括弧で
|
|
2312
|
+
* 囲ってよく (#1391)、`v: {input}` は中括弧だけの行になる。 組とみなすのは、行全体が
|
|
2313
|
+
* 1 つの中括弧の塊で、**最初の項目が `名前:` の形を持つ** 時だけ。
|
|
2314
|
+
*/
|
|
2315
|
+
function 式の組に割る(行: string): string | undefined {
|
|
2316
|
+
const 割れた = 名前と中括弧に割る(行);
|
|
2317
|
+
if (割れた === undefined) return undefined;
|
|
2318
|
+
const 最初 = splitInlineFields(割れた[1])[0]?.trim() ?? "";
|
|
2319
|
+
return 組の項目名.test(最初) ? 割れた[1] : undefined;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
/**
|
|
2323
|
+
* 式の組の中身を読む (#1916)。 読めなければ知らせて `undefined` を返す。
|
|
2324
|
+
*
|
|
2325
|
+
* 式の中身と空の式の検査は呼び手が文字列の形と同じ経路で行う。 ここで見るのは組の形だけ。
|
|
2326
|
+
*/
|
|
2327
|
+
function 式の組を読む(
|
|
2328
|
+
名前: string,
|
|
2329
|
+
中身: string,
|
|
2330
|
+
line: number,
|
|
2331
|
+
errors: DslError[],
|
|
2332
|
+
): { expression: string; label?: string } | undefined {
|
|
2333
|
+
const 使える = ["expression", "label"];
|
|
2334
|
+
const 読めた: Record<string, string> = {};
|
|
2335
|
+
for (const 欄 of splitInlineFields(中身).map((s) => s.trim()).filter((s) => s !== "")) {
|
|
2336
|
+
const m = 欄.match(組の項目名);
|
|
2337
|
+
if (!m) {
|
|
2338
|
+
errors.push({
|
|
2339
|
+
line,
|
|
2340
|
+
message: `式 "${名前}" の項目が読めません: "${欄}"`,
|
|
2341
|
+
hint: '`{ expression: "input * 2", label: "2 倍" }` の形で書く。 `,` を含む式は引用符で囲む',
|
|
2342
|
+
});
|
|
2343
|
+
return undefined;
|
|
2344
|
+
}
|
|
2345
|
+
if (!使える.includes(m[1]!)) {
|
|
2346
|
+
errors.push({
|
|
2347
|
+
line,
|
|
2348
|
+
message: `式 "${名前}" の項目名が読めません: "${m[1]}"`,
|
|
2349
|
+
hint: `使える項目 = ${使える.join(", ")}`,
|
|
2350
|
+
});
|
|
2351
|
+
return undefined;
|
|
2352
|
+
}
|
|
2353
|
+
読めた[m[1]!] = stripQuotes(m[2]!.trim());
|
|
2354
|
+
}
|
|
2355
|
+
if (読めた.label !== undefined && 読めた.label.trim() === "") {
|
|
2356
|
+
errors.push({
|
|
2357
|
+
line,
|
|
2358
|
+
message: `式 "${名前}" の名札が空です`,
|
|
2359
|
+
hint: '名札を付けないなら `label` を書かない。 付けるなら `label: "2 倍"` のように書く',
|
|
2360
|
+
});
|
|
2361
|
+
return undefined;
|
|
2362
|
+
}
|
|
2363
|
+
return {
|
|
2364
|
+
expression: 読めた.expression ?? "",
|
|
2365
|
+
...(読めた.label !== undefined ? { label: 読めた.label } : {}),
|
|
2366
|
+
};
|
|
2115
2367
|
}
|
|
2116
2368
|
|
|
2117
2369
|
/**
|
|
@@ -2142,14 +2394,6 @@ function つまみとして読む(
|
|
|
2142
2394
|
return { id, kind, ...読めた } as DslInput;
|
|
2143
2395
|
}
|
|
2144
2396
|
|
|
2145
|
-
/** 箱の中の要素の欄 (#1306)。 `DslActorNodeOverride` の全欄を覆う */
|
|
2146
|
-
export const ACTOR_NODE_VALUE_KINDS = {
|
|
2147
|
-
posX: "数",
|
|
2148
|
-
posY: "数",
|
|
2149
|
-
posW: "数",
|
|
2150
|
-
posH: "数",
|
|
2151
|
-
} as const satisfies Record<keyof DslActorNodeOverride, 値の形>;
|
|
2152
|
-
|
|
2153
2397
|
/** 中括弧の形で箱に書ける、数と真偽の欄 (#1306) */
|
|
2154
2398
|
export const ACTOR_INLINE_VALUE_KINDS = {
|
|
2155
2399
|
stack: "数",
|
|
@@ -2409,7 +2653,7 @@ export function splitColorValue(raw: string): { tone?: Tone; hex?: string } {
|
|
|
2409
2653
|
* v0.4 で使えた箱の種類の名前 (#1301)。
|
|
2410
2654
|
*
|
|
2411
2655
|
* v0.5 の受理集合 (`NODE_KIND_VALID`) に無いため、書くと見本 (parts) の名前として扱われ、
|
|
2412
|
-
*
|
|
2656
|
+
* カタログに無ければ `actor` に潰れて **黙って消えていた**。 見本の名前と区別が付かないので、
|
|
2413
2657
|
* 「v0.4 で種類として使えた語」 であることを根拠に誤りとして知らせる。
|
|
2414
2658
|
*
|
|
2415
2659
|
* 対応は `keywords.ts` の `NODE_KIND_ALIAS` が持つ (日本語 → 英語の種類名)。
|
|
@@ -2423,7 +2667,7 @@ function v04の種類名(値: string): string | undefined {
|
|
|
2423
2667
|
/**
|
|
2424
2668
|
* 箱の種類に v0.4 の日本語を書いた時に知らせる (#1301)。
|
|
2425
2669
|
*
|
|
2426
|
-
*
|
|
2670
|
+
* 黙って見本の名前として扱うと、カタログに無い場合に `actor` へ潰れて手掛かりが残らない。
|
|
2427
2671
|
*/
|
|
2428
2672
|
function reportV04Kind(kindRaw: string, line: number, errors: DslError[]): void {
|
|
2429
2673
|
const 英語 = v04の種類名(kindRaw);
|
|
@@ -2663,6 +2907,17 @@ function applyContinuationLines(actor: DslActor, rest: Line[], errors: DslError[
|
|
|
2663
2907
|
case "posY":
|
|
2664
2908
|
out.posY = 数として読む(raw, "箱の posY", ln.no, errors);
|
|
2665
2909
|
break;
|
|
2910
|
+
// 位置のずらし (#1971)。 中括弧の形と同じ表で読み、片方だけ書いた時は残りを 0 とする
|
|
2911
|
+
case "offsetX":
|
|
2912
|
+
case "offsetY": {
|
|
2913
|
+
const 読めた = 表で読む(OFFSET_VALUE_KINDS, { [key]: raw }, "箱の ", ln.no, errors);
|
|
2914
|
+
const 前 = out.layoutPos;
|
|
2915
|
+
out.layoutPos = ずらしにまとめる({
|
|
2916
|
+
offsetX: key === "offsetX" ? 読めた.offsetX : 前?.x,
|
|
2917
|
+
offsetY: key === "offsetY" ? 読めた.offsetY : 前?.y,
|
|
2918
|
+
});
|
|
2919
|
+
break;
|
|
2920
|
+
}
|
|
2666
2921
|
case "大きさ":
|
|
2667
2922
|
case "size": {
|
|
2668
2923
|
// `大きさ: 400,200` の形。 位置と揃える
|
|
@@ -2737,6 +2992,11 @@ function applyContinuationLines(actor: DslActor, rest: Line[], errors: DslError[
|
|
|
2737
2992
|
touchedState = true;
|
|
2738
2993
|
}
|
|
2739
2994
|
if (touchedState) out.stateOverride = state;
|
|
2995
|
+
// 色の名前は部品に効かない。 中括弧の形と同じく箱の色から外し、書いた名前として残す (#1973)
|
|
2996
|
+
if (out.tone !== undefined) {
|
|
2997
|
+
out.partColorName = out.tone;
|
|
2998
|
+
delete out.tone;
|
|
2999
|
+
}
|
|
2740
3000
|
return out;
|
|
2741
3001
|
}
|
|
2742
3002
|
/*
|
|
@@ -2803,6 +3063,9 @@ export const ACTOR_ITEM_KEYS: ReadonlySet<string> = new Set([
|
|
|
2803
3063
|
"pos",
|
|
2804
3064
|
"posX",
|
|
2805
3065
|
"posY",
|
|
3066
|
+
// 位置のずらし (#1971)
|
|
3067
|
+
"offsetX",
|
|
3068
|
+
"offsetY",
|
|
2806
3069
|
"大きさ",
|
|
2807
3070
|
"size",
|
|
2808
3071
|
"倍率",
|
|
@@ -2941,7 +3204,7 @@ function collectAnimationSteps(
|
|
|
2941
3204
|
* `{ v: 50, count: 100 }` state override map に集約する経路。
|
|
2942
3205
|
* 予約語衝突時は `state: { v: 50 }` 明示 fallback を使う (別 field で処理)。
|
|
2943
3206
|
*/
|
|
2944
|
-
const ACTOR_RESERVED_FIELDS: ReadonlySet<string> = new Set([
|
|
3207
|
+
export const ACTOR_RESERVED_FIELDS: ReadonlySet<string> = new Set([
|
|
2945
3208
|
"kind",
|
|
2946
3209
|
"subtitle",
|
|
2947
3210
|
"eyebrow",
|
|
@@ -2962,11 +3225,15 @@ const ACTOR_RESERVED_FIELDS: ReadonlySet<string> = new Set([
|
|
|
2962
3225
|
"posY",
|
|
2963
3226
|
"posW",
|
|
2964
3227
|
"posH",
|
|
2965
|
-
//
|
|
2966
|
-
"
|
|
3228
|
+
// 位置のずらし (#1971)。 見本では見本 1 つ分をまとめてずらす欄で、状態の名前としては読まない
|
|
3229
|
+
"offsetX",
|
|
3230
|
+
"offsetY",
|
|
2967
3231
|
// 図形の倍率 (#1026)。 状態の名前としては読まない
|
|
2968
3232
|
"scale",
|
|
2969
3233
|
"倍率",
|
|
3234
|
+
// 色番号 (#1973)。 見本の色の状態へまとめて入れる欄で、状態の名前としては読まない。
|
|
3235
|
+
// 縦に並べた形 (`COLOR_KEYS`) と JSON は元から色として読んでいた
|
|
3236
|
+
"color",
|
|
2970
3237
|
// 日本語の項目名 (#1026)。 中括弧の形が日本語の項目名を読めるようになったため、
|
|
2971
3238
|
// ここに載せないと状態の名前として拾われる。 縦に並べた形での意味 (位置 / 大きさ 等) は
|
|
2972
3239
|
// 中括弧の形では未対応なので、これまでどおり落とす方に揃える
|
|
@@ -3003,52 +3270,6 @@ function extractStateOverride(
|
|
|
3003
3270
|
return count > 0 ? out : undefined;
|
|
3004
3271
|
}
|
|
3005
3272
|
|
|
3006
|
-
/**
|
|
3007
|
-
* canvas pivot UX 修正 (B1) = actor entry の inline map から `nodes: { header: { posX: ..., ... }, ... }`
|
|
3008
|
-
* 形式の nested override を抽出する。 outer parseInlineMapping が opts.nodes を string としてそのまま
|
|
3009
|
-
* 保持 (value 内 nested `{ }` は depth-aware で保護済) しているので、 本 fn で「outer `{...}` を剥がして
|
|
3010
|
-
* key: sub-map ペアに再 split → 各 sub-map を parseInlineMapping で解いて posX/Y/W/H に coerce」 する。
|
|
3011
|
-
* 未 field or 空 object なら undefined 返し (caller は actor.nodes を set しない)。
|
|
3012
|
-
*/
|
|
3013
|
-
function parseActorNodesField(
|
|
3014
|
-
raw: string | undefined,
|
|
3015
|
-
line: number,
|
|
3016
|
-
errors: DslError[],
|
|
3017
|
-
): Record<string, DslActorNodeOverride> | undefined {
|
|
3018
|
-
if (!raw) return undefined;
|
|
3019
|
-
const trimmed = raw.trim();
|
|
3020
|
-
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return undefined;
|
|
3021
|
-
const inner = trimmed.slice(1, -1).trim();
|
|
3022
|
-
if (!inner) return undefined;
|
|
3023
|
-
// depth-aware split (parseInlineMapping と同じ logic を local reuse、 nested `{ }` / `[ ]` 保護)
|
|
3024
|
-
const parts: string[] = [];
|
|
3025
|
-
let depth = 0;
|
|
3026
|
-
let buf = "";
|
|
3027
|
-
for (let i = 0; i < inner.length; i += 1) {
|
|
3028
|
-
const c = inner[i]!;
|
|
3029
|
-
if (c === "[" || c === "{") depth += 1;
|
|
3030
|
-
else if (c === "]" || c === "}") depth -= 1;
|
|
3031
|
-
if (c === "," && depth === 0) {
|
|
3032
|
-
parts.push(buf);
|
|
3033
|
-
buf = "";
|
|
3034
|
-
continue;
|
|
3035
|
-
}
|
|
3036
|
-
buf += c;
|
|
3037
|
-
}
|
|
3038
|
-
if (buf.trim()) parts.push(buf);
|
|
3039
|
-
const out: Record<string, DslActorNodeOverride> = {};
|
|
3040
|
-
for (const p of parts) {
|
|
3041
|
-
const colonIdx = p.indexOf(":");
|
|
3042
|
-
if (colonIdx < 0) continue;
|
|
3043
|
-
const key = p.slice(0, colonIdx).trim();
|
|
3044
|
-
const val = p.slice(colonIdx + 1).trim();
|
|
3045
|
-
if (!key || !val.startsWith("{") || !val.endsWith("}")) continue;
|
|
3046
|
-
const nodeOpts = parseInlineMapping(val.slice(1, -1));
|
|
3047
|
-
out[key] = 表で読む(ACTOR_NODE_VALUE_KINDS, nodeOpts, `nodes の ${key} の `, line, errors);
|
|
3048
|
-
}
|
|
3049
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
3273
|
function coerceStateValue(raw: string): number | string | boolean {
|
|
3053
3274
|
const stripped = stripQuotes(raw);
|
|
3054
3275
|
if (stripped === "true") return true;
|
|
@@ -3132,7 +3353,9 @@ export const INLINE_ACTOR_KEYS: ReadonlySet<string> = new Set([
|
|
|
3132
3353
|
"initial",
|
|
3133
3354
|
"final",
|
|
3134
3355
|
"tone",
|
|
3135
|
-
|
|
3356
|
+
// 色の欄 (#1969)。 縦に並べた形と JSON は `color` を受けるのに、中括弧の形だけが知らない
|
|
3357
|
+
// 項目名として落としていた。 見本 (parts) では従来どおり状態の上書きとして読む
|
|
3358
|
+
"color",
|
|
3136
3359
|
// 体験の道筋の欄 (#1251)。 他の図種では組み立て側が知らせる
|
|
3137
3360
|
"touchpoint",
|
|
3138
3361
|
"opportunity",
|
|
@@ -3143,6 +3366,9 @@ export const INLINE_ACTOR_KEYS: ReadonlySet<string> = new Set([
|
|
|
3143
3366
|
"posY",
|
|
3144
3367
|
"posW",
|
|
3145
3368
|
"posH",
|
|
3369
|
+
// 位置のずらし (#1971)
|
|
3370
|
+
"offsetX",
|
|
3371
|
+
"offsetY",
|
|
3146
3372
|
// 箱の中に描く図形 (#1374)
|
|
3147
3373
|
"shape",
|
|
3148
3374
|
// その箱を出すかどうかの条件 (#1381)
|
|
@@ -3164,8 +3390,28 @@ export const INLINE_ACTOR_KEYS: ReadonlySet<string> = new Set([
|
|
|
3164
3390
|
]);
|
|
3165
3391
|
// `state` はパーツでだけ意味を持つ (`extractStateOverride` がパーツの時しか作らない)。
|
|
3166
3392
|
// 通常の箱で読める扱いにすると `- A: { state: { foo: 1 } }` が黙って消え、 本 file が塞ごうと
|
|
3167
|
-
// している経路が予約語で残る (Round 1 review の指摘、 実測で確認)。 パーツ側は
|
|
3168
|
-
//
|
|
3393
|
+
// している経路が予約語で残る (Round 1 review の指摘、 実測で確認)。 パーツ側は
|
|
3394
|
+
// `PART_INLINE_UNREADABLE_KEYS` が `state` を外すのでここに載せる必要が無い
|
|
3395
|
+
|
|
3396
|
+
/**
|
|
3397
|
+
* パーツの中括弧で、予約されているのにどの欄も読まない項目名 (#1996)。
|
|
3398
|
+
*
|
|
3399
|
+
* パーツでは中括弧に書いた名前が状態の上書きになる (`extractStateOverride`) ため、読めない
|
|
3400
|
+
* 名前をまとめて知らせることはできない。 一方 `ACTOR_RESERVED_FIELDS` に載る名前は状態の
|
|
3401
|
+
* 上書きから外れるので、`INLINE_ACTOR_KEYS` にも無い名前はどこにも行かずに消える。
|
|
3402
|
+
*
|
|
3403
|
+
* 実測 = `- 甲: { kind: edge-chain, 大きさ: 900,400 }` は書かない時と 1 ピクセルも変わらず
|
|
3404
|
+
* (どちらも `viewBox 1951x552` / パーツの幅 896)、知らせも 1 件も出なかった。 同じ語を縦に
|
|
3405
|
+
* 並べた形で書けば効き、パーツでない箱に中括弧で書けば知らせが出る = 同じ語の扱いが
|
|
3406
|
+
* 「パーツかどうか」 と「書く場所」 で 3 通りに割れていた。
|
|
3407
|
+
*
|
|
3408
|
+
* **2 つの集合の差から導く**。 手で並べると、予約の一覧に名前を足した時に知らせだけが
|
|
3409
|
+
* 取り残される。 `state` だけは差から外す = `extractStateOverride` が明示欄として読むため、
|
|
3410
|
+
* 予約されていても消える名前ではない。
|
|
3411
|
+
*/
|
|
3412
|
+
export const PART_INLINE_UNREADABLE_KEYS: ReadonlySet<string> = new Set(
|
|
3413
|
+
[...ACTOR_RESERVED_FIELDS].filter((key) => !INLINE_ACTOR_KEYS.has(key) && key !== "state"),
|
|
3414
|
+
);
|
|
3169
3415
|
|
|
3170
3416
|
/**
|
|
3171
3417
|
* 矢印の中括弧に書ける欄と、その読み方 (#1275)。
|
|
@@ -3182,6 +3428,8 @@ const EDGE_BIND_INLINE_READERS = {
|
|
|
3182
3428
|
|
|
3183
3429
|
const FLOW_INLINE_READERS = {
|
|
3184
3430
|
sub: (v: string | undefined) => v,
|
|
3431
|
+
// クラス図の出どころ側の多重度 (#1771)。 `sub` (行き先の端) と対
|
|
3432
|
+
tailSub: (v: string | undefined) => v,
|
|
3185
3433
|
guard: (v: string | undefined) => v,
|
|
3186
3434
|
cardinality: (v: string | undefined) => v,
|
|
3187
3435
|
/*
|
|
@@ -3212,7 +3460,7 @@ const FLOW_INLINE_READERS = {
|
|
|
3212
3460
|
// 出どころ側の端の形 (#1466)。 ER は端ごとに違う個数を示すので両端に要る
|
|
3213
3461
|
tailHead: (v: string | undefined) =>
|
|
3214
3462
|
v !== undefined && EDGE_HEAD_VALUES.includes(v) ? (v as EdgeHead) : undefined,
|
|
3215
|
-
// 端の印の塗り (#1466)。
|
|
3463
|
+
// 端の印の塗り (#1466)。 白抜きの菱が集約、塗った菱がコンポジション
|
|
3216
3464
|
headFill: (v: string | undefined) =>
|
|
3217
3465
|
v !== undefined && EDGE_HEAD_FILL_VALUES.includes(v) ? (v as EdgeHeadFill) : undefined,
|
|
3218
3466
|
tailHeadFill: (v: string | undefined) =>
|
|
@@ -3240,6 +3488,12 @@ const FLOW_INLINE_READERS = {
|
|
|
3240
3488
|
* 持たないため、知らせを出せる場所で見る)。
|
|
3241
3489
|
*/
|
|
3242
3490
|
...EDGE_BIND_INLINE_READERS,
|
|
3491
|
+
/*
|
|
3492
|
+
* 部品の端で矢印を繋ぐ要素の id (#1979)。 部品かどうか、要素が在るかは組み立てで部品の図を
|
|
3493
|
+
* 引いてから決まるため、ここでは字をそのまま通す
|
|
3494
|
+
*/
|
|
3495
|
+
fromPartNode: (v: string | undefined) => v,
|
|
3496
|
+
toPartNode: (v: string | undefined) => v,
|
|
3243
3497
|
// 数と真偽の欄は `FLOW_INLINE_VALUE_KINDS` の表が読む (#1306)。 ここでは名前だけを持つ =
|
|
3244
3498
|
// 読める欄の一覧 (`FLOW_INLINE_KEYS`) は本表から導くため、載せないと欄ごと消える
|
|
3245
3499
|
labelOffsetX: null,
|
|
@@ -3262,7 +3516,10 @@ export const FLOW_INLINE_KEYS = Object.keys(
|
|
|
3262
3516
|
* 5 つの箱すべてで題が捨てられて識別子 (`root` 等) が出ていた。 知らせも出ないため、 書いた
|
|
3263
3517
|
* 人には「書いたのに図が変わらない」 としか見えない。
|
|
3264
3518
|
*
|
|
3265
|
-
*
|
|
3519
|
+
* パーツでは名前の大半を知らせない。 中括弧に書いた名前は状態の上書きとして意味を持つ
|
|
3520
|
+
* (`extractStateOverride`)。 ただし状態の上書きからも外れる名前 (`PART_INLINE_UNREADABLE_KEYS`)
|
|
3521
|
+
* だけは知らせる = どの欄も読まず状態にも入らないため、黙ると「書いたのに図が変わらない」 が
|
|
3522
|
+
* 手掛かりなしで起きる (#1996)。
|
|
3266
3523
|
*/
|
|
3267
3524
|
function reportUnknownInlineKeys(
|
|
3268
3525
|
isPart: boolean,
|
|
@@ -3270,7 +3527,6 @@ function reportUnknownInlineKeys(
|
|
|
3270
3527
|
line: number,
|
|
3271
3528
|
errors: DslError[],
|
|
3272
3529
|
): void {
|
|
3273
|
-
if (isPart) return;
|
|
3274
3530
|
// 値が空の形 (`{ title: }`) も見る。 `parseInlineMapping` は値が 1 文字以上ある項目しか
|
|
3275
3531
|
// 拾わないため、 その結果を走査すると空白の有無で知らせが消える (実測 = `{title:}` と
|
|
3276
3532
|
// `{ title:}` は黙って通り、 `{ title: }` だけ知らせが出た)。 契約が入力の整形に依存する
|
|
@@ -3280,6 +3536,17 @@ function reportUnknownInlineKeys(
|
|
|
3280
3536
|
if (idx < 0) continue;
|
|
3281
3537
|
const key = field.slice(0, idx).trim();
|
|
3282
3538
|
if (!key) continue;
|
|
3539
|
+
if (isPart) {
|
|
3540
|
+
if (!PART_INLINE_UNREADABLE_KEYS.has(key)) continue;
|
|
3541
|
+
// 行き先を添える。 「読めません」 だけだと、どこにも書けないのか書く場所が違うのかが
|
|
3542
|
+
// 分からない。 この 3 つは縦に並べた形では効く
|
|
3543
|
+
errors.push({
|
|
3544
|
+
line,
|
|
3545
|
+
message: `項目名が読めません: "${key}"`,
|
|
3546
|
+
hint: `1 行の中括弧では読めません。名前の下に段を分けた形で書くと効きます`,
|
|
3547
|
+
});
|
|
3548
|
+
continue;
|
|
3549
|
+
}
|
|
3283
3550
|
if (INLINE_ACTOR_KEYS.has(key)) continue;
|
|
3284
3551
|
errors.push({
|
|
3285
3552
|
line,
|
|
@@ -3289,6 +3556,40 @@ function reportUnknownInlineKeys(
|
|
|
3289
3556
|
}
|
|
3290
3557
|
}
|
|
3291
3558
|
|
|
3559
|
+
/**
|
|
3560
|
+
* 箱以外の中括弧に書かれた読めない項目名を知らせる (#1968)。
|
|
3561
|
+
*
|
|
3562
|
+
* レーン / 視点 / 矢印 / 組 / 軸の名前 の中括弧は、読む欄だけを拾って残りを黙って捨てていた。
|
|
3563
|
+
* 実測 = レーンに `widht: 280` と書くと幅が既定のまま描かれ、 知らせは 1 件も出なかった。
|
|
3564
|
+
* 箱 (`reportUnknownInlineKeys`) と段は知らせるので、 同じ記法の中で書く場所によって扱いが割れていた。
|
|
3565
|
+
*
|
|
3566
|
+
* 項目名は `splitInlineFields` で拾う。 `parseInlineMapping` の結果を見ると値が空の項目
|
|
3567
|
+
* (`{ widht: }`) を拾えず、 空白の有無で知らせが消える (箱の側が #1090 で踏んだ形)。
|
|
3568
|
+
*
|
|
3569
|
+
* `使える` は呼出側が各欄の表から導いて渡す。 ここに一覧を持つと、 表に欄を足した時に
|
|
3570
|
+
* 知らせだけが取り残される。
|
|
3571
|
+
*/
|
|
3572
|
+
function 中括弧の知らない項目名を知らせる(
|
|
3573
|
+
inner: string,
|
|
3574
|
+
使える: readonly string[],
|
|
3575
|
+
接頭: string,
|
|
3576
|
+
line: number,
|
|
3577
|
+
errors: DslError[],
|
|
3578
|
+
): void {
|
|
3579
|
+
for (const field of splitInlineFields(inner)) {
|
|
3580
|
+
const idx = field.indexOf(":");
|
|
3581
|
+
if (idx < 0) continue;
|
|
3582
|
+
const key = field.slice(0, idx).trim();
|
|
3583
|
+
if (!key || 使える.includes(key)) continue;
|
|
3584
|
+
// 接頭は値の欄名に続ける形 (`縦列 l1 の `) で末尾に空白を持つ。 日本語へ続ける時は空白を落とす
|
|
3585
|
+
errors.push({
|
|
3586
|
+
line,
|
|
3587
|
+
message: `${接頭.trimEnd()}項目名が読めません: "${key}"`,
|
|
3588
|
+
hint: `使える項目 = ${使える.join(", ")}`,
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3292
3593
|
function parseActor(line: Line, errors: DslError[]): DslActor | null {
|
|
3293
3594
|
// 5 形式 サポート:
|
|
3294
3595
|
// 1. `Client` ... name のみ、 kind=actor default
|
|
@@ -3322,6 +3623,16 @@ function parseActor(line: Line, errors: DslError[]): DslActor | null {
|
|
|
3322
3623
|
if (!isPart && opts.tone !== undefined && resolveTone(opts.tone) === undefined) {
|
|
3323
3624
|
report読めない色(opts.tone, line.no, errors, { 線種も受ける: false });
|
|
3324
3625
|
}
|
|
3626
|
+
// `color` は縦に並べた形と JSON と同じ振り分けを通す (#1969)。 色の名前は箱の色、`#` で
|
|
3627
|
+
// 始まる値は色番号になる。
|
|
3628
|
+
//
|
|
3629
|
+
// **見本でも色として読む** (#1973)。 縦に並べた形と JSON は見本の `color` を色番号として
|
|
3630
|
+
// 見本の色の状態へ入れる。 中括弧の形だけが状態の名前 `color` として拾い、 その名前の状態を
|
|
3631
|
+
// 持つ見本が無いため黙って捨てていた (実測 = `state-indicator` の塗りが既定の緑のまま)
|
|
3632
|
+
const 色 = opts.color !== undefined ? splitColorValue(opts.color) : {};
|
|
3633
|
+
if (opts.color !== undefined && !色.tone && !色.hex) {
|
|
3634
|
+
report読めない色(opts.color, line.no, errors, { 線種も受ける: false });
|
|
3635
|
+
}
|
|
3325
3636
|
const kind = isPart
|
|
3326
3637
|
? NODE_KIND_DEFAULT
|
|
3327
3638
|
: resolveNodeKind(NODE_KIND_VALID.has(kindRaw) ? kindRaw : "");
|
|
@@ -3384,15 +3695,18 @@ function parseActor(line: Line, errors: DslError[]): DslActor | null {
|
|
|
3384
3695
|
: 値に追随する欄として読む(opts.renderOffsetY, "箱の renderOffsetY ", line.no, errors),
|
|
3385
3696
|
...表で読む(ACTOR_INLINE_VALUE_KINDS, opts, "箱の ", line.no, errors),
|
|
3386
3697
|
// parts では `tone` を状態の上書きとして従来から使えるため、 色として横取りしない
|
|
3387
|
-
tone
|
|
3698
|
+
// `tone` と `color` を両方書いた時は `tone` を採る (JSON の読み方と揃える)
|
|
3699
|
+
tone: isPart ? undefined : (resolveTone(opts.tone) ?? 色.tone),
|
|
3700
|
+
colorHex: 色.hex,
|
|
3701
|
+
partColorName: isPart ? 色.tone : undefined,
|
|
3388
3702
|
partId: isPart ? kindRaw : undefined,
|
|
3389
3703
|
stateOverride: isPart ? extractStateOverride(opts) : undefined,
|
|
3390
3704
|
// canvas pivot 新 spec = 絶対座標 field は `ACTOR_INLINE_VALUE_KINDS` の表が読む (#1306)
|
|
3391
3705
|
// 図形の倍率 (#1026)。 どれが効くかは `resolveScale` が 1 箇所で決める
|
|
3392
3706
|
scale: inlineScale.scale,
|
|
3393
3707
|
scaleKeys: inlineScale.keys.length ? inlineScale.keys : undefined,
|
|
3394
|
-
//
|
|
3395
|
-
|
|
3708
|
+
// 位置のずらし (#1971)。 見本 (parts) でも見本 1 つ分をまとめてずらす
|
|
3709
|
+
layoutPos: ずらしにまとめる(表で読む(OFFSET_VALUE_KINDS, opts, "箱の ", line.no, errors)),
|
|
3396
3710
|
pos: { line: line.no },
|
|
3397
3711
|
};
|
|
3398
3712
|
}
|
|
@@ -3464,6 +3778,7 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
|
|
|
3464
3778
|
let 数と真偽: 読んだ結果<typeof FLOW_INLINE_VALUE_KINDS> | undefined;
|
|
3465
3779
|
if (塊) {
|
|
3466
3780
|
const opts = parseInlineMapping(塊.中身);
|
|
3781
|
+
中括弧の知らない項目名を知らせる(塊.中身, FLOW_INLINE_KEYS, "矢印の ", line.no, errors);
|
|
3467
3782
|
// `parseInlineMapping` は値が 1 文字もない `widthBind:` を拾わない。 3 欄は空を
|
|
3468
3783
|
// 「書かなかった」扱いにせず知らせる契約なので、書かれた値を空も含めて上書きする。
|
|
3469
3784
|
// 同じ欄を複数回書いた時は通常の mapping と同じく後勝ちにする。
|
|
@@ -3511,6 +3826,7 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
|
|
|
3511
3826
|
rest = 塊.前.trim();
|
|
3512
3827
|
}
|
|
3513
3828
|
const sub = 中括弧.sub as string | undefined;
|
|
3829
|
+
const tailSub = 中括弧.tailSub as string | undefined;
|
|
3514
3830
|
const guard = 中括弧.guard as string | undefined;
|
|
3515
3831
|
const cardinality = 中括弧.cardinality as string | undefined;
|
|
3516
3832
|
const side = 中括弧.side as "top" | "right" | "bottom" | "left" | undefined;
|
|
@@ -3546,6 +3862,9 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
|
|
|
3546
3862
|
const labelOffsetX = 数と真偽?.labelOffsetX;
|
|
3547
3863
|
const labelOffsetY = 数と真偽?.labelOffsetY;
|
|
3548
3864
|
const overlay = 数と真偽?.overlay;
|
|
3865
|
+
// 空の名指し (`toPartNode: ""`) は書かなかったことにする = 要素 1 つの部品なら自動で繋ぐ
|
|
3866
|
+
const fromPartNode = (中括弧.fromPartNode as string | undefined) || undefined;
|
|
3867
|
+
const toPartNode = (中括弧.toPartNode as string | undefined) || undefined;
|
|
3549
3868
|
// 色と線種を末尾から取る。 括弧 (`(成功)`) と空白区切り (`成功`) の両方を受け付ける。
|
|
3550
3869
|
//
|
|
3551
3870
|
// 括弧は従来の書き方で、 catalog が使っている。 空白区切りは登場人物と揃えた形。
|
|
@@ -3600,6 +3919,7 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
|
|
|
3600
3919
|
tone,
|
|
3601
3920
|
style,
|
|
3602
3921
|
sub,
|
|
3922
|
+
tailSub,
|
|
3603
3923
|
guard,
|
|
3604
3924
|
cardinality,
|
|
3605
3925
|
side,
|
|
@@ -3617,6 +3937,8 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
|
|
|
3617
3937
|
labelOffsetX,
|
|
3618
3938
|
labelOffsetY,
|
|
3619
3939
|
overlay,
|
|
3940
|
+
...(fromPartNode !== undefined ? { fromPartNode } : {}),
|
|
3941
|
+
...(toPartNode !== undefined ? { toPartNode } : {}),
|
|
3620
3942
|
pos: { line: line.no },
|
|
3621
3943
|
};
|
|
3622
3944
|
}
|
|
@@ -3955,10 +4277,19 @@ function 段の項目のヒント(書いた名前: string): string {
|
|
|
3955
4277
|
* | `tree` | `tree` | 根から下へ枝が伸びる |
|
|
3956
4278
|
* | `gantt` | `gantt` | 各帯の始端から右へ帯が伸びる |
|
|
3957
4279
|
* | `funnel` | `funnel` | 上端から順に段が積まれる |
|
|
4280
|
+
* | `slope` | `slope` | 左の軸から右の軸へ線が伸びる |
|
|
4281
|
+
* | `gauge` | `gauge` | 9 時から上を通って 3 時へ弧が伸びる |
|
|
4282
|
+
* | `radial` | `radial` | 各輪が 12 時から時計回りに開く |
|
|
4283
|
+
* | `stacked` | `stacked` | 左端から右へ帯が伸びる |
|
|
4284
|
+
* | `waffle` | `waffle` | 左上から読む向きに印が 1 個ずつ埋まる |
|
|
3958
4285
|
*
|
|
3959
4286
|
* `quadrant` は入れない。 4 つの区画に項目を置く図で **項目に順序が無く**、起点を決められない
|
|
3960
4287
|
* (順番を書いた順で決めると、動きが図の意味を持たない)。
|
|
3961
4288
|
*
|
|
4289
|
+
* `stat` も入れない。 値 1 つを大きく示す図で、描画側が起点から描く動きを持たない
|
|
4290
|
+
* (`cdl` の `DRAW_KINDS`)。 `waffle` は 1 個 = 1% の印を読む向きに埋める動きを
|
|
4291
|
+
* 持つようになったので入れた (cdl#719)。
|
|
4292
|
+
*
|
|
3962
4293
|
* いまは語と図種が同じ綴りだが、**同じものとして扱わない**。 語は書き手が書く名前で、
|
|
3963
4294
|
* 図種は `type:` が取る値。 片方だけ別名を足したくなった時に、対応が表に残っている形にする。
|
|
3964
4295
|
*/
|
|
@@ -3972,6 +4303,10 @@ export const DRAW_TARGETS: ReadonlyMap<string, PresetType> = new Map<string, Pre
|
|
|
3972
4303
|
["gantt", "gantt"],
|
|
3973
4304
|
["funnel", "funnel"],
|
|
3974
4305
|
["slope", "slope"],
|
|
4306
|
+
["gauge", "gauge"],
|
|
4307
|
+
["radial", "radial"],
|
|
4308
|
+
["stacked", "stacked"],
|
|
4309
|
+
["waffle", "waffle"],
|
|
3975
4310
|
]);
|
|
3976
4311
|
|
|
3977
4312
|
/** `draw:` に書ける語。 表から導く (#1314) */
|