@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/v05/parser.ts CHANGED
@@ -41,8 +41,9 @@
41
41
  * 出力は v0.4 と同じ DslDocument。 既存 compile.ts で CdlDiagram に変換できる。
42
42
  */
43
43
 
44
- import type { NodeKind, Tone, EdgeStyle } from "@cardenelabs/cdl";
45
- import { TONES, NODE_KINDS, parseFormula } from "@cardenelabs/cdl";
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";
46
47
  import { TONE_ALIAS, NODE_KIND_ALIAS } from "../keywords";
47
48
  import { parseRelativePos, orderByDependency } from "../relative-pos";
48
49
  import {
@@ -74,6 +75,7 @@ import type {
74
75
  DslError,
75
76
  PresetType,
76
77
  DslLane,
78
+ DslBand,
77
79
  DslGroup,
78
80
  DslViewport,
79
81
  } from "../types";
@@ -83,6 +85,26 @@ export type V05ParseResult = { ok: true; doc: DslDocument } | { ok: false; error
83
85
  /** 矢印を出す辺。 Text DSL / JSON validator / 公開 schema の 3 経路で同じ 4 値を使う。 */
84
86
  export const EDGE_SIDE_VALUES = ["top", "right", "bottom", "left"] as const;
85
87
 
88
+ /**
89
+ * 矢印の先の形として書ける語 (#1462)。
90
+ *
91
+ * **描画側から導く**。 手で並べると、描画側が形を増やした時に書けないままになる。
92
+ */
93
+ export const EDGE_HEAD_VALUES: readonly string[] = EDGE_HEADS;
94
+
95
+ /** 端の印の塗り方 (#1466)。 描画側の一覧から導く = 描画側が増やせば書ける */
96
+ export const EDGE_HEAD_FILL_VALUES: readonly string[] = EDGE_HEAD_FILLS;
97
+
98
+ /**
99
+ * クラス図の関係の種類 (#1466)。 描画側の表 (`CLASS_RELATION_LOOK`) の key から導く。
100
+ *
101
+ * 手で並べると、描画側が種類を足した時にここだけ取り残されて書けないままになる。
102
+ */
103
+ export const CLASS_RELATION_VALUES: readonly string[] = Object.keys(CLASS_RELATION_LOOK);
104
+
105
+ /** 順序図の言づての種類 (#1466)。 描画側の表から導く */
106
+ export const SEQ_MESSAGE_VALUES: readonly string[] = Object.keys(SEQUENCE_MESSAGE_LOOK);
107
+
86
108
  /**
87
109
  * 記法が受ける top-level の項目 (#1190)。
88
110
  *
@@ -115,6 +137,10 @@ export const TOP_LEVEL_KEYS = [
115
137
  "events",
116
138
  // 巻き上げに応じて進む値 (#1393)
117
139
  "scrolls",
140
+ // 動いている間の帯 (#1466)。 順序図だけが読む
141
+ "bands",
142
+ // 矢印をいつ出すか (#1470)
143
+ "reveal",
118
144
  ] as const;
119
145
 
120
146
  /**
@@ -335,11 +361,14 @@ export const NODE_KIND_VALID: ReadonlySet<string> = new Set<string>([
335
361
  const TONE_VALID: ReadonlySet<string> = new Set<string>(TONES);
336
362
 
337
363
  /**
338
- * 受理する線種。 `EdgeStyle` は型だけで実体を持たないため、 実行時の一覧はここが唯一の出どころ。
364
+ * 受理する線種。 **描画側の一覧から導く** (#1466)。
339
365
  *
340
- * JSON 経路も同じ集合を読む (#1304)。 別に持つと、 線種が増えた時に片方だけ取り残される。
366
+ * 以前は手で並べており、描画側が `dashed` を足した時にここだけ取り残されて「線種が
367
+ * 読めません」 で落ちた (実測)。 色名 (`TONE_VALID`) と同じく描画側を出どころにする。
368
+ *
369
+ * JSON 経路も同じ集合を読む (#1304)。 別に持つと、線種が増えた時に片方だけ取り残される。
341
370
  */
342
- export const STYLE_VALID: ReadonlySet<string> = new Set<string>(["solid", "dotted-flow"]);
371
+ export const STYLE_VALID: ReadonlySet<string> = new Set<string>(EDGE_STYLES);
343
372
 
344
373
  /**
345
374
  * 色の名前として書ける語の一覧 (#1304)。 知らせの `hint` に出す。
@@ -396,6 +425,7 @@ export function parseTextDslV05(src: string): V05ParseResult {
396
425
  let type: PresetType | null = null;
397
426
  let eyebrow: string | null = null;
398
427
  let eyebrowLine = 0;
428
+ let reveal: EdgeReveal | null = null;
399
429
  let axes: DslAxes | undefined = undefined;
400
430
  let axesLine = 0;
401
431
  let actors: DslActor[] = [];
@@ -404,6 +434,8 @@ export function parseTextDslV05(src: string): V05ParseResult {
404
434
  const values: DslValue[] = [];
405
435
  let viewport: DslViewport | undefined = undefined;
406
436
  let lanesMap: Record<string, DslLane> | undefined = undefined;
437
+ // 動いている間の帯 (#1466)。 順序図だけが読む
438
+ let bands: DslBand[] | undefined = undefined;
407
439
  let readoutsList: DslReadout[] | undefined = undefined;
408
440
  let inputsList: DslInput[] | undefined = undefined;
409
441
  let formulasList: DslFormula[] | undefined = undefined;
@@ -437,6 +469,29 @@ export function parseTextDslV05(src: string): V05ParseResult {
437
469
  i += 1;
438
470
  continue;
439
471
  }
472
+ if (head.key === "reveal") {
473
+ /*
474
+ * 矢印をいつ出すか (#1470)。
475
+ *
476
+ * 既定 (`phase`) は「段が名指しする矢印は、その段が来るまで描かない」。 段を進めるごとに
477
+ * 関係が増える図で、全ての矢印が 1 段目から見えていたことへの対処 (`cdl#582`)。
478
+ * `all` と書くと段に関わらず最初から全部描く。
479
+ */
480
+ const v = (head.value ?? "").trim();
481
+ if (v.length > 0) {
482
+ if ((EDGE_REVEALS as readonly string[]).includes(v)) {
483
+ reveal = v as EdgeReveal;
484
+ } else {
485
+ errors.push({
486
+ line: line.no,
487
+ message: `reveal が読めません (書いた値: ${v})`,
488
+ hint: `使える語 = ${EDGE_REVEALS.join(" / ")}`,
489
+ });
490
+ }
491
+ }
492
+ i += 1;
493
+ continue;
494
+ }
440
495
  if (head.key === "eyebrow") {
441
496
  // 空で書いた形 (`eyebrow:`) は「書かなかった」 と同じにする。 空文字を残すと
442
497
  // 描画側が中身のない帯を出す
@@ -645,6 +700,31 @@ export function parseTextDslV05(src: string): V05ParseResult {
645
700
  i = next;
646
701
  continue;
647
702
  }
703
+ if (head.key === "bands") {
704
+ /*
705
+ * 動いている間の帯 (#1466)。 `- DB: 1..2` の形で、段の番号の区間を書く。
706
+ *
707
+ * 面ごとに 2 行以上書ける = 途中で手が空く面はそこで切れる。 書かない図は
708
+ * 組み立て器が「最初に関わった段から最後まで」 の 1 本にする。
709
+ */
710
+ const { items, next } = collectIndentedList(lines, i + 1, line.indent);
711
+ bands = [];
712
+ for (const it of items) {
713
+ // 一覧の読み手が先頭の `- ` を落とす形と落とさない形の両方を受ける
714
+ const m = it.trimmed.match(/^(?:-\s*)?(.+?)\s*:\s*(\d+)\s*\.\.\s*(\d+)\s*$/);
715
+ if (!m) {
716
+ errors.push({
717
+ line: it.no,
718
+ message: `帯の書き方が読めません: "${it.trimmed}"`,
719
+ hint: "use `- DB: 1..2` (面の名前と、段の番号の区間)",
720
+ });
721
+ continue;
722
+ }
723
+ bands.push({ actor: m[1]!, from: Number(m[2]), to: Number(m[3]) });
724
+ }
725
+ i = next;
726
+ continue;
727
+ }
648
728
  if (head.key === "lanes") {
649
729
  // lanes:\n l1: { x: 0, width: 320, label: "..." }\n l2: { ... }
650
730
  const { items, next } = collectIndentedList(lines, i + 1, line.indent);
@@ -885,6 +965,7 @@ export function parseTextDslV05(src: string): V05ParseResult {
885
965
  title: title!,
886
966
  type: type!,
887
967
  ...(eyebrow !== null ? { eyebrow, eyebrowPos: { line: eyebrowLine } } : {}),
968
+ ...(reveal !== null ? { reveal } : {}),
888
969
  ...(axes !== undefined ? { axes, axesPos: { line: axesLine } } : {}),
889
970
  actors,
890
971
  flow,
@@ -892,6 +973,7 @@ export function parseTextDslV05(src: string): V05ParseResult {
892
973
  ...(values.length > 0 ? { values } : {}),
893
974
  viewport,
894
975
  lanes: lanesMap,
976
+ ...(bands && bands.length > 0 ? { bands } : {}),
895
977
  readouts: readoutsList,
896
978
  inputs: inputsList,
897
979
  formulas: formulasList,
@@ -2423,6 +2505,22 @@ function applyContinuationLines(actor: DslActor, rest: Line[], errors: DslError[
2423
2505
  .map((x) => stripQuotes(x.trim()))
2424
2506
  .filter(Boolean);
2425
2507
  break;
2508
+ /*
2509
+ * 行頭の印 (#1466)。 `rows` と同じ並びで、空文字はその行に印を付けない。
2510
+ *
2511
+ * **語の意味は図の種類が決める**。 ER は `pk` / `fk` / `opt`、状態遷移は
2512
+ * `entry` / `exit` / `do` / `internal`。 印の 2 軸 (形 × 塗り) は共通だが、その軸が
2513
+ * 何を指すかは種類ごとに違う = 1 つの語彙に畳むと、どの図でも意味が合わない語が残る。
2514
+ *
2515
+ * 空の要素を捨てない (`filter(Boolean)` を掛けない) = 並びが `rows` とずれる。
2516
+ */
2517
+ case "marks":
2518
+ case "印":
2519
+ out.marks = raw
2520
+ .replace(/^\[|\]$/g, "")
2521
+ .split(/,(?![^[]*\])/)
2522
+ .map((x) => stripQuotes(x.trim()));
2523
+ break;
2426
2524
  case "位置":
2427
2525
  case "pos": {
2428
2526
  // `位置: 300,200` の形。 posX と posY は両方揃わないと効かないので、 1 つの項目に
@@ -2587,6 +2685,9 @@ export const ACTOR_ITEM_KEYS: ReadonlySet<string> = new Set([
2587
2685
  "前の値",
2588
2686
  "rows",
2589
2687
  "行",
2688
+ // 行頭の印 (#1466)。 `rows` と同じ並びで、形 × 塗り の 2 軸を語で書く
2689
+ "marks",
2690
+ "印",
2590
2691
  // 箱の中に描く図形 (#1374)
2591
2692
  "shape",
2592
2693
  "図形",
@@ -2751,6 +2852,7 @@ const ACTOR_RESERVED_FIELDS: ReadonlySet<string> = new Set([
2751
2852
  "value",
2752
2853
  "previous",
2753
2854
  "rows",
2855
+ "marks",
2754
2856
  "lane",
2755
2857
  "stack",
2756
2858
  "initial",
@@ -2904,6 +3006,7 @@ export const INLINE_ACTOR_ALIASES: Record<string, string> = {
2904
3006
  値: "value",
2905
3007
  前の値: "previous",
2906
3008
  行: "rows",
3009
+ 印: "marks",
2907
3010
  };
2908
3011
 
2909
3012
  /** 中括弧に書かれた日本語の項目名を、同じ意味の英語名に寄せる (#1301) */
@@ -2927,6 +3030,7 @@ export const INLINE_ACTOR_KEYS: ReadonlySet<string> = new Set([
2927
3030
  "value",
2928
3031
  "previous",
2929
3032
  "rows",
3033
+ "marks",
2930
3034
  "lane",
2931
3035
  "stack",
2932
3036
  "initial",
@@ -2989,6 +3093,33 @@ const FLOW_INLINE_READERS = {
2989
3093
  v !== undefined && (EDGE_SIDE_VALUES as readonly string[]).includes(v)
2990
3094
  ? (v as "top" | "right" | "bottom" | "left")
2991
3095
  : undefined,
3096
+ // 矢印の先の形 (#1462)。 書かなければ描画側の既定 (塗った三角) になる
3097
+ head: (v: string | undefined) =>
3098
+ v !== undefined && EDGE_HEAD_VALUES.includes(v) ? (v as EdgeHead) : undefined,
3099
+ // 出どころ側の端の形 (#1466)。 ER は端ごとに違う個数を示すので両端に要る
3100
+ tailHead: (v: string | undefined) =>
3101
+ v !== undefined && EDGE_HEAD_VALUES.includes(v) ? (v as EdgeHead) : undefined,
3102
+ // 端の印の塗り (#1466)。 白抜きの菱が「持つ」、塗った菱が「抱える」
3103
+ headFill: (v: string | undefined) =>
3104
+ v !== undefined && EDGE_HEAD_FILL_VALUES.includes(v) ? (v as EdgeHeadFill) : undefined,
3105
+ tailHeadFill: (v: string | undefined) =>
3106
+ v !== undefined && EDGE_HEAD_FILL_VALUES.includes(v) ? (v as EdgeHeadFill) : undefined,
3107
+ /*
3108
+ * クラス図の関係の種類 (#1466)。 書くと **線と端の形と塗りと付く側** がまとめて決まる
3109
+ * (`CLASS_RELATION_LOOK`)。
3110
+ *
3111
+ * 4 つを個別に書かせない = 組合せは 6 通りしか無く、1 つでも書き違えると読み手に別の意味で
3112
+ * 伝わる (菱を逆に置くと持ち主が入れ替わる)。 種類で書けばその 6 通りから外れない。
3113
+ */
3114
+ relation: (v: string | undefined) =>
3115
+ v !== undefined && CLASS_RELATION_VALUES.includes(v) ? (v as ClassRelationType) : undefined,
3116
+ /*
3117
+ * 順序図の言づての種類 (#1466)。 書くと線と矢の形がまとめて決まる。
3118
+ *
3119
+ * 呼ぶ (実線 + 塗った矢) / 返す (破線 + 開いた矢) / 投げる (実線 + 開いた矢) の 3 つ。
3120
+ */
3121
+ kind: (v: string | undefined) =>
3122
+ v !== undefined && SEQ_MESSAGE_VALUES.includes(v) ? (v as SequenceMessageKind) : undefined,
2992
3123
  /*
2993
3124
  * 矢印を値に追随させる 3 欄 (#1396)。 箱の `wBind` (#1392) と同じく文字列だけを取る。
2994
3125
  *
@@ -3104,6 +3235,13 @@ function parseActor(line: Line, errors: DslError[]): DslActor | null {
3104
3235
  .map((x) => stripQuotes(x.trim()))
3105
3236
  .filter(Boolean)
3106
3237
  : undefined,
3238
+ // 行頭の印 (#1466)。 `rows` と同じ並びなので **空の要素を捨てない**
3239
+ marks: opts.marks
3240
+ ? opts.marks
3241
+ .replace(/^\[|\]$/g, "")
3242
+ .split(/,(?![^[]*\])/)
3243
+ .map((x) => stripQuotes(x.trim()))
3244
+ : undefined,
3107
3245
  lane: opts.lane,
3108
3246
  // 箱の中に描く図形 (#1374)。 パーツでは状態の上書きとして意味を持つため横取りしない
3109
3247
  shape:
@@ -3236,6 +3374,26 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
3236
3374
  hint: `使える値 = ${EDGE_SIDE_VALUES.join(", ")}`,
3237
3375
  });
3238
3376
  }
3377
+ // 読めない語を黙って捨てない (#1462)。 捨てると「書いたのに端の形が変わらない」 が
3378
+ // 手掛かりなしで起きる
3379
+ // 読めない語を黙って捨てない (#1462 / #1466)。 捨てると「書いたのに見た目が変わらない」 が
3380
+ // 手掛かりなしで起きる。 端の 3 欄と関係の種類を同じ形で見る
3381
+ for (const [欄, 使える] of [
3382
+ ["head", EDGE_HEAD_VALUES],
3383
+ ["tailHead", EDGE_HEAD_VALUES],
3384
+ ["headFill", EDGE_HEAD_FILL_VALUES],
3385
+ ["tailHeadFill", EDGE_HEAD_FILL_VALUES],
3386
+ ["relation", CLASS_RELATION_VALUES],
3387
+ ["kind", SEQ_MESSAGE_VALUES],
3388
+ ] as const) {
3389
+ if (opts[欄] !== undefined && 中括弧[欄] === undefined) {
3390
+ errors.push({
3391
+ line: line.no,
3392
+ message: `矢印の ${欄} が読めません: "${opts[欄]}"`,
3393
+ hint: `使える値 = ${使える.join(", ")}`,
3394
+ });
3395
+ }
3396
+ }
3239
3397
  数と真偽 = 表で読む(FLOW_INLINE_VALUE_KINDS, opts, "矢印の ", line.no, errors);
3240
3398
  rest = 塊.前.trim();
3241
3399
  }
@@ -3243,6 +3401,12 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
3243
3401
  const guard = 中括弧.guard as string | undefined;
3244
3402
  const cardinality = 中括弧.cardinality as string | undefined;
3245
3403
  const side = 中括弧.side as "top" | "right" | "bottom" | "left" | undefined;
3404
+ const head = 中括弧.head as EdgeHead | undefined;
3405
+ const tailHead = 中括弧.tailHead as EdgeHead | undefined;
3406
+ const headFill = 中括弧.headFill as EdgeHeadFill | undefined;
3407
+ const tailHeadFill = 中括弧.tailHeadFill as EdgeHeadFill | undefined;
3408
+ const relation = 中括弧.relation as ClassRelationType | undefined;
3409
+ const msgKind = 中括弧.kind as SequenceMessageKind | undefined;
3246
3410
  // 値に追随する 3 欄 (#1396)。 空は捨てずに知らせる = 描画側は空文字を既定値へ落とさず
3247
3411
  // そのまま置換に使うため、書き忘れが「線が消えた」 形で出る
3248
3412
  const widthBind = 追随する大きさとして読む(
@@ -3323,6 +3487,12 @@ function parseFlowStep(line: Line, no: number, errors: DslError[]): DslStep | nu
3323
3487
  guard,
3324
3488
  cardinality,
3325
3489
  side,
3490
+ head,
3491
+ tailHead,
3492
+ headFill,
3493
+ tailHeadFill,
3494
+ relation,
3495
+ msgKind,
3326
3496
  widthBind,
3327
3497
  strokeBind,
3328
3498
  dashOffsetBind,