@cardenelabs/dragon 0.16.0 → 0.17.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/src/index.ts CHANGED
@@ -76,6 +76,7 @@ export {
76
76
  MAX_PART_SCALE,
77
77
  partDrawsInDiagram,
78
78
  partIsMeasurable,
79
+ partsBaseRows,
79
80
  partsGridCenters,
80
81
  } from "./compile";
81
82
  // 色として読めるかの判定と、 図の外を指す値かの判定。 状態の上書きを受け取る側 / 画面が色欄を
@@ -21,6 +21,7 @@
21
21
  import {
22
22
  DRAW_WORDS,
23
23
  EDGE_SIDE_VALUES,
24
+ EDGE_HEAD_VALUES,
24
25
  NODE_KIND_VALID,
25
26
  PRESET_TYPES,
26
27
  STYLE_VALID,
@@ -36,7 +37,17 @@ import {
36
37
  type 図形の定義,
37
38
  } from "./v05/parser";
38
39
  import type { CompileToCdlOpts } from "./compile";
39
- import type { CdlDiagram, NodeKind, Tone, EdgeStyle } from "@cardenelabs/cdl";
40
+ import type {
41
+ CdlDiagram,
42
+ NodeKind,
43
+ Tone,
44
+ EdgeStyle,
45
+ EdgeHead,
46
+ EdgeHeadFill,
47
+ EdgeReveal,
48
+ ClassRelationType,
49
+ SequenceMessageKind,
50
+ } from "@cardenelabs/cdl";
40
51
  import { extractIdentifiers, parseFormula } from "@cardenelabs/cdl";
41
52
  import type {
42
53
  DslDocument,
@@ -172,6 +183,10 @@ export interface DragonJson {
172
183
  lanes: string[];
173
184
  }
174
185
  >;
186
+ /** 順序図で面が動いている間の帯 (#1466)。 記法の最上位 `bands:` と同じ */
187
+ bands?: { actor: string; from: number; to: number }[];
188
+ /** 矢印をいつ出すか (#1470)。 記法の最上位 `reveal:` と同じ */
189
+ reveal?: EdgeReveal;
175
190
  }
176
191
 
177
192
  export interface JsonActor {
@@ -294,6 +309,8 @@ export interface JsonActor {
294
309
  * 読めない項目名として知らせる)。
295
310
  */
296
311
  scale?: number;
312
+ /** 行頭の印 (#1466)。 行と対で読む。 語の意味は図の種類が決める */
313
+ marks?: string[];
297
314
  }
298
315
 
299
316
  /** 2 軸で仕分ける図の軸の名前 (#1294)。 記法の `axes:` と同じ形 */
@@ -317,6 +334,21 @@ export interface JsonStep {
317
334
  sub?: string;
318
335
  /** 矢印がどの辺から出るか (#1385)。 記法の `side:` と同じ */
319
336
  side?: "top" | "right" | "bottom" | "left";
337
+ /**
338
+ * 矢印の先の形 (#1462)。 記法の `{ head: triangle }` と同じ。
339
+ *
340
+ * 三角 (継ぐ) / 菱 (持つ) / 開いた矢 (使う) / 鳥の足 (多)。
341
+ * 書かなければ従来どおり塗った三角になる。
342
+ */
343
+ head?: EdgeHead;
344
+ /** 出どころ側の端の形と、両端の塗り (#1466)。 記法の `{ tailHead: diamond }` 等と同じ */
345
+ tailHead?: EdgeHead;
346
+ headFill?: EdgeHeadFill;
347
+ tailHeadFill?: EdgeHeadFill;
348
+ /** クラス図の関係の語 (#1466)。 書くと端の形 / 塗り / 線種がまとめて決まる */
349
+ relation?: ClassRelationType;
350
+ /** 順序図の言づての種類 (#1466)。 `call` / `return` / `fire` */
351
+ kind?: SequenceMessageKind;
320
352
  /**
321
353
  * 矢印の色 (#1304)。 記法の `(成功)` と同じく別名 (`成功` / `neutral` 等) も受ける。
322
354
  *
@@ -451,6 +483,10 @@ export const ACCEPTED_KEYS = {
451
483
  // 押下などの出来事で動く仕掛けと、巻き上げに応じて進む値 (#1393)
452
484
  "events",
453
485
  "scrolls",
486
+ // 順序図で面が動いている間の帯 (#1466)
487
+ "bands",
488
+ // 矢印をいつ出すか (#1470)
489
+ "reveal",
454
490
  ],
455
491
  actor: [
456
492
  "name",
@@ -491,6 +527,8 @@ export const ACCEPTED_KEYS = {
491
527
  "opacity",
492
528
  "renderOffsetX",
493
529
  "renderOffsetY",
530
+ // 行頭の印 (#1466)。 行ごとに 1 つ、図の種類ごとの語で書く
531
+ "marks",
494
532
  ],
495
533
  step: [
496
534
  "from",
@@ -499,6 +537,14 @@ export const ACCEPTED_KEYS = {
499
537
  "sub",
500
538
  // 矢印がどの辺から出るか (#1385)
501
539
  "side",
540
+ // 矢印の先の形 (#1462)
541
+ "head",
542
+ // 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
543
+ "tailHead",
544
+ "headFill",
545
+ "tailHeadFill",
546
+ "relation",
547
+ "kind",
502
548
  "tone",
503
549
  "style",
504
550
  "guard",
@@ -561,6 +607,7 @@ export type 欄の型 =
561
607
  | "色"
562
608
  | "線種"
563
609
  | "辺"
610
+ | "端の形"
564
611
  | "色か色番号"
565
612
  | "描くもの"
566
613
  | "必須の図種"
@@ -592,6 +639,10 @@ export const 欄の型表 = {
592
639
  // 押下と巻き上げ (#1393)。 中身は下の検査が 1 件ずつ見る
593
640
  events: "並び",
594
641
  scrolls: "object",
642
+ // 順序図で面が動いている間の帯 (#1466)
643
+ bands: "並び",
644
+ // 矢印をいつ出すか (#1470)
645
+ reveal: "非空の文字列",
595
646
  },
596
647
  actor: {
597
648
  name: "必須の非空文字列",
@@ -631,6 +682,8 @@ export const 欄の型表 = {
631
682
  opacity: "数か文字列",
632
683
  renderOffsetX: "数か文字列",
633
684
  renderOffsetY: "数か文字列",
685
+ // 行頭の印 (#1466)
686
+ marks: "文字列の並び",
634
687
  },
635
688
  step: {
636
689
  from: "必須の文字列",
@@ -639,6 +692,13 @@ export const 欄の型表 = {
639
692
  sub: "文字列",
640
693
  // 矢印がどの辺から出るか (#1385)
641
694
  side: "辺",
695
+ head: "端の形",
696
+ // 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
697
+ tailHead: "端の形",
698
+ headFill: "非空の文字列",
699
+ tailHeadFill: "非空の文字列",
700
+ relation: "非空の文字列",
701
+ kind: "非空の文字列",
642
702
  tone: "色",
643
703
  style: "線種",
644
704
  guard: "文字列",
@@ -890,6 +950,18 @@ function 値を検査(
890
950
  });
891
951
  }
892
952
  return;
953
+ case "端の形":
954
+ if (v === undefined) return;
955
+ // 受ける語は記法と同じ一覧を見る (`EDGE_HEAD_VALUES`)。 写すと描画側が形を増やした時に
956
+ // 片方だけ古くなる
957
+ if (typeof v !== "string" || !EDGE_HEAD_VALUES.includes(v)) {
958
+ errors.push({
959
+ path,
960
+ message: `${名前} must be one of: ${EDGE_HEAD_VALUES.join(", ")}`,
961
+ hint: typeof v === "string" ? `got "${v}"` : `got ${typeof v}`,
962
+ });
963
+ }
964
+ return;
893
965
  case "描くもの":
894
966
  if (v === undefined) return;
895
967
  // 受ける語は記法と同じ一覧を見る (`DRAW_WORDS`)。 写すと語が増えた時に片方だけ古くなる
@@ -1202,6 +1274,40 @@ function 表で中身を検査する(
1202
1274
  * 委ねる作りなので (`checkFieldType` の `case "並び"`)、ここで見ないと `readouts: 1` が
1203
1275
  * 素通りする。
1204
1276
  */
1277
+ /**
1278
+ * 順序図の帯の並びを検査する (#1466)。
1279
+ *
1280
+ * **外側の形もここで見る**。 `欄の型表` は「並び」 とだけ宣言し、中身の検査は専用の検査に
1281
+ * 委ねる作りなので、ここで見ないと `bands: 1` が素通りする。
1282
+ */
1283
+ function validateBands(v: unknown, errors: JsonDslError[]): void {
1284
+ if (v === undefined) return;
1285
+ if (!Array.isArray(v)) {
1286
+ errors.push({
1287
+ path: "$.bands",
1288
+ message: "bands must be an array of band objects",
1289
+ hint: `got ${v === null ? "null" : typeof v}`,
1290
+ });
1291
+ return;
1292
+ }
1293
+ v.forEach((b, i) => {
1294
+ const path = `$.bands[${i}]`;
1295
+ if (typeof b !== "object" || b === null || Array.isArray(b)) {
1296
+ errors.push({ path, message: "band must be an object", hint: "{ actor, from, to } の形で書く" });
1297
+ return;
1298
+ }
1299
+ const o = b as Record<string, unknown>;
1300
+ if (typeof o.actor !== "string" || o.actor === "") {
1301
+ errors.push({ path: `${path}.actor`, message: "band.actor must be a non-empty string" });
1302
+ }
1303
+ for (const k of ["from", "to"] as const) {
1304
+ if (typeof o[k] !== "number" || !Number.isInteger(o[k]) || (o[k] as number) < 0) {
1305
+ errors.push({ path: `${path}.${k}`, message: `band.${k} must be a non-negative integer` });
1306
+ }
1307
+ }
1308
+ });
1309
+ }
1310
+
1205
1311
  function validateReadouts(v: unknown, errors: JsonDslError[]): void {
1206
1312
  if (v === undefined) return;
1207
1313
  if (!Array.isArray(v)) {
@@ -1879,6 +1985,7 @@ function validateJson(
1879
1985
  checkUnknownKeys(j, "root", "$", errors);
1880
1986
  // 値を見せる部品の中身を、記法と同じ表で見る (#1374)
1881
1987
  validateReadouts(j.readouts, errors);
1988
+ validateBands(j.bands, errors);
1882
1989
  // 読む人が動かすつまみの中身も、記法と同じ表で見る (#1389)
1883
1990
  validateInputs(j.inputs, errors);
1884
1991
  // 式は描画側の parser に通す (#1391)
@@ -2181,6 +2288,8 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2181
2288
  value: a.value,
2182
2289
  previous: a.previous,
2183
2290
  rows: a.rows,
2291
+ // 行頭の印 (#1466)。 行と対で読む
2292
+ marks: a.marks,
2184
2293
  lane: a.lane,
2185
2294
  stack: a.stack,
2186
2295
  initial: a.initial,
@@ -2240,6 +2349,14 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2240
2349
  label: s.label,
2241
2350
  sub: s.sub,
2242
2351
  side: s.side as "top" | "right" | "bottom" | "left" | undefined,
2352
+ // 矢印の先の形 (#1462)。 読めない語は組み立てが落とす
2353
+ head: s.head,
2354
+ // 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
2355
+ tailHead: s.tailHead,
2356
+ headFill: s.headFill,
2357
+ tailHeadFill: s.tailHeadFill,
2358
+ relation: s.relation,
2359
+ msgKind: s.kind,
2243
2360
  // 箱と同じ読み替えを通す (#1304)。 通さないと `tone: "成功"` が色名として解決されないまま
2244
2361
  // 図に届き、同じ値が箱では色になり矢印では色にならない
2245
2362
  tone: resolveTone(s.tone),
@@ -2373,6 +2490,10 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2373
2490
  formulas,
2374
2491
  events,
2375
2492
  scrolls,
2493
+ // 順序図で面が動いている間の帯 (#1466)
2494
+ bands: json.bands,
2495
+ // 矢印をいつ出すか (#1470)
2496
+ reveal: json.reveal,
2376
2497
  pos: p0,
2377
2498
  };
2378
2499
  }
@@ -123,6 +123,13 @@
123
123
  },
124
124
  "description": "storage 内の column 列 (kind: storage 用)"
125
125
  },
126
+ "marks": {
127
+ "type": "array",
128
+ "items": {
129
+ "type": "string"
130
+ },
131
+ "description": "行頭の印。 rows と同じ数だけ並べる。 語の意味は図の種類が決める = er は pk / fk / opt、class は + / - と () の有無、state は entry / do / exit / internal"
132
+ },
126
133
  "lane": {
127
134
  "type": "string",
128
135
  "description": "swimlane / topology で属する lane id"
@@ -457,7 +464,7 @@
457
464
  },
458
465
  "style": {
459
466
  "type": "string",
460
- "enum": ["solid", "dotted-flow"],
467
+ "enum": ["solid", "dotted-flow", "dashed"],
461
468
  "description": "矢印の線種"
462
469
  },
463
470
  "guard": {
@@ -473,6 +480,36 @@
473
480
  "enum": ["top", "right", "bottom", "left"],
474
481
  "description": "矢印がどの辺から出るか。 書かなければ描画側が自動で選ぶ。"
475
482
  },
483
+ "head": {
484
+ "type": "string",
485
+ "enum": ["none", "triangle", "diamond", "open", "crow", "one", "zero-one", "many", "zero-many"],
486
+ "description": "着き先側の端の形。 端の形で関係の種類を示す = triangle (継ぐ) / diamond (持つ) / open (使う) / crow (多) / one / zero-one / many / zero-many (個数)。 書かなければ塗った三角になる。"
487
+ },
488
+ "tailHead": {
489
+ "type": "string",
490
+ "enum": ["none", "triangle", "diamond", "open", "crow", "one", "zero-one", "many", "zero-many"],
491
+ "description": "出どころ側の端の形。 両端に別々の印を立てる時に書く。"
492
+ },
493
+ "headFill": {
494
+ "type": "string",
495
+ "enum": ["solid", "hollow"],
496
+ "description": "着き先側の端の塗り。 中空は「弱い関係」 を表す。"
497
+ },
498
+ "tailHeadFill": {
499
+ "type": "string",
500
+ "enum": ["solid", "hollow"],
501
+ "description": "出どころ側の端の塗り。"
502
+ },
503
+ "relation": {
504
+ "type": "string",
505
+ "enum": ["extends", "implements", "aggregates", "composes", "associates", "uses"],
506
+ "description": "クラス図の関係の語。 書くと端の形 / 塗り / 線種がまとめて決まる。"
507
+ },
508
+ "kind": {
509
+ "type": "string",
510
+ "enum": ["call", "return", "fire"],
511
+ "description": "順序図の言づての種類。 call (返事を待つ) / return (返し) / fire (返事を待たない)。"
512
+ },
476
513
  "labelOffsetX": {
477
514
  "type": "number",
478
515
  "description": "ラベル位置 x 調整"
@@ -1759,6 +1796,25 @@
1759
1796
  "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
1760
1797
  }
1761
1798
  },
1799
+ "reveal": {
1800
+ "type": "string",
1801
+ "enum": ["phase", "all"],
1802
+ "description": "矢印をいつ出すか。 phase (既定) = 段が名指しする矢印はその段が来るまで描かない。 all = 段に関わらず最初から全部描く。"
1803
+ },
1804
+ "bands": {
1805
+ "type": "array",
1806
+ "description": "順序図で面が動いている間の帯。 書かなければ面ごとに「最初に関わった段から最後まで」 の 1 本になる。",
1807
+ "items": {
1808
+ "type": "object",
1809
+ "required": ["actor", "from", "to"],
1810
+ "additionalProperties": false,
1811
+ "properties": {
1812
+ "actor": { "type": "string", "description": "帯を出す面の名前" },
1813
+ "from": { "type": "integer", "minimum": 0, "description": "始まりの言づての番号 (0 起点)" },
1814
+ "to": { "type": "integer", "minimum": 0, "description": "終わりの言づての番号 (0 起点)" }
1815
+ }
1816
+ }
1817
+ },
1762
1818
  "events": {
1763
1819
  "type": "array",
1764
1820
  "description": "押下などの出来事で動く仕掛け。 相手は名前で指す (box / lane / arrow / diagram のどれか 1 つ)。",
package/src/tokenize.ts CHANGED
@@ -33,7 +33,7 @@
33
33
  * 分割して返す = 参照だけ別の色にできる。
34
34
  */
35
35
  import { TONE_ALIAS, ARROW_PATTERNS } from "./keywords";
36
- import { TONES } from "@cardenelabs/cdl";
36
+ import { TONES, EDGE_STYLES } from "@cardenelabs/cdl";
37
37
  import type { Tone } from "@cardenelabs/cdl";
38
38
 
39
39
  /** 分解した部分の種類 (#1310) */
@@ -49,12 +49,12 @@ export type トークン = {
49
49
  };
50
50
 
51
51
  /**
52
- * 線種の一覧 (#1310)。 `v05/parser.ts` の `STYLE_VALID` と同じ値を持つ。
52
+ * 線種の一覧 (#1310 → #1466)。
53
53
  *
54
- * あちらを import すると parser 全体を引き込むため、分解器では持ち直す。 **2 箇所に
55
- * 分かれるので検査で突き合わせる** (`tokenize.test.ts`)。
54
+ * 描画側 (`EDGE_STYLES`) から導く。 手で並べると、描画側が線種を増やした時に書けないままになる
55
+ * (実測 = `dashed` を足した時に 3 箇所のうち 2 箇所が古いままだった)。
56
56
  */
57
- const 線種 = ["solid", "dotted-flow"] as const;
57
+ const 線種 = EDGE_STYLES;
58
58
 
59
59
  /** 色名として読める語 (小文字で引く)。 正規の色名と別名の両方 */
60
60
  const 色名の表 = new Map<string, Tone>([
package/src/types.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * docs/cdl/text-dsl-spec.md の文法を AST に変換した中間表現
4
4
  */
5
5
 
6
- import type { CdlDiagram, NodeKind, Tone, EdgeStyle } from "@cardenelabs/cdl";
6
+ import type { CdlDiagram, NodeKind, Tone, EdgeStyle, EdgeHead, EdgeHeadFill, EdgeReveal, ClassRelationType, SequenceMessageKind } from "@cardenelabs/cdl";
7
7
  import type { DslOnlyKind } from "./v05/parser";
8
8
 
9
9
  /**
@@ -152,6 +152,20 @@ export type DslDocument = {
152
152
  /** v0.5+ 拡張 ... viewport / lanes / groups */
153
153
  viewport?: DslViewport;
154
154
  lanes?: Record<string, DslLane>;
155
+ /**
156
+ * 動いている間の帯 (#1466)。 順序図だけが読む。
157
+ *
158
+ * 書かなければ面ごとに「最初に関わった段から最後まで」 の 1 本。 途中で手が空く面を
159
+ * 分けたい図だけ書く = どこで手が空くかは言づての並びからは決まらない。
160
+ */
161
+ bands?: DslBand[];
162
+ /**
163
+ * 矢印をいつ出すか (`reveal:`、 #1470)。
164
+ *
165
+ * 既定 (`phase`) は「段が名指しする矢印は、その段が来るまで描かない」。 `all` と書くと
166
+ * 段に関わらず最初から全部描く。 描き手の `CdlDiagram.edgeReveal` にそのまま渡る。
167
+ */
168
+ reveal?: EdgeReveal;
155
169
  groups?: Record<string, DslGroup>;
156
170
  /**
157
171
  * 値を見せる部品 (`readouts:`、 #1374)。 割合の輪や数え上げを図の脇に出す。
@@ -230,6 +244,14 @@ export type DslActor = {
230
244
  */
231
245
  previous?: string;
232
246
  rows?: string[];
247
+ /**
248
+ * 行頭の印 (#1466)。 `rows` と同じ並びで、空文字はその行に印を付けない。
249
+ *
250
+ * **語の意味は図の種類が決める**。 ER は `pk` / `fk` / `opt`、状態遷移は
251
+ * `entry` / `exit` / `do` / `internal`。 印の 2 軸 (形 × 塗り) は共通だが、その軸が
252
+ * 何を指すかは種類ごとに違う。
253
+ */
254
+ marks?: string[];
233
255
  /**
234
256
  * 箱の中に描く図形 (`shape:`、 #1374)。 水位や角度を状態で動かせる。
235
257
  *
@@ -417,6 +439,35 @@ export type DslStep = {
417
439
  dashOffsetBind?: string;
418
440
  /** 矢印がどの辺から出るか (#1385)。 書かなければ描画側が自動で選ぶ */
419
441
  side?: "top" | "right" | "bottom" | "left";
442
+ /**
443
+ * 矢印の先の形 (#1462)。 書かなければ従来どおり塗った三角。
444
+ *
445
+ * 4 図の設計は **端の形で関係の種類を示す** = 三角 (継ぐ) / 菱 (持つ) /
446
+ * 開いた矢 (使う) / 鳥の足 (多)。 書けないと 4 種とも同じ三角になり、
447
+ * 線の種類 (実線 / 点線) だけで 6 種の関係を区別することになる。
448
+ *
449
+ * 受ける語は描画側の `EDGE_HEADS` から導く = 描画側が増やせば書けるようになる。
450
+ */
451
+ head?: EdgeHead;
452
+ /** 出どころ側の端の形 (#1466)。 ER は端ごとに違う個数を示すので両端に要る */
453
+ tailHead?: EdgeHead;
454
+ /** 端の印の塗り (#1466)。 白抜きの菱が「持つ」、塗った菱が「抱える」 */
455
+ headFill?: EdgeHeadFill;
456
+ /** 出どころ側の印の塗り (#1466) */
457
+ tailHeadFill?: EdgeHeadFill;
458
+ /**
459
+ * クラス図の関係の種類 (#1466)。 書くと線と端の形と塗りと付く側がまとめて決まる。
460
+ *
461
+ * 4 つを個別に書かせないのは、組合せが 6 通りしか無く、1 つでも書き違えると読み手に
462
+ * 別の意味で伝わるため (菱を逆に置くと持ち主が入れ替わる)。
463
+ */
464
+ relation?: ClassRelationType;
465
+ /**
466
+ * 言づての種類 (#1466)。 順序図で線と矢の形がまとめて決まる。
467
+ *
468
+ * `kind` にしないのは、箱の種類 (`DslActor.kind`) と同じ語が別の意味を持つため。
469
+ */
470
+ msgKind?: SequenceMessageKind;
420
471
  labelOffsetX?: number;
421
472
  labelOffsetY?: number;
422
473
  /** true で説明文を矢印の線の上に重ねる。 分岐図の条件ラベル用。 */
@@ -541,6 +592,9 @@ export type DslValue = {
541
592
  );
542
593
 
543
594
  /** ステップ (phase) */
595
+ /** 動いている間の帯 (#1466)。 順序図で、面がいつ動いているかを段の番号で持つ */
596
+ export type DslBand = { actor: string; from: number; to: number };
597
+
544
598
  export type DslPhase = {
545
599
  name: string;
546
600
  durationMs: number;