@cardenelabs/dragon 0.34.0 → 0.35.1
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 +39 -720
- package/dist/cli.cjs +4529 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +4523 -0
- package/dist/cli.js.map +1 -0
- package/dist/export.cjs +3912 -0
- package/dist/export.cjs.map +1 -0
- package/dist/export.d.cts +36 -0
- package/dist/export.d.ts +36 -0
- package/dist/export.js +3907 -0
- package/dist/export.js.map +1 -0
- package/dist/fonts.cjs +7 -0
- package/dist/fonts.cjs.map +1 -0
- package/dist/fonts.js +4 -0
- package/dist/fonts.js.map +1 -0
- package/dist/index.cjs +4105 -20356
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +227 -5522
- package/dist/index.d.ts +227 -5522
- package/dist/index.js +4091 -20283
- package/dist/index.js.map +1 -1
- package/dist/player.global.js +2 -0
- package/dist/schema-BBWfniwH.d.cts +112 -0
- package/dist/schema-BBWfniwH.d.ts +112 -0
- package/package.json +53 -27
- package/src/cli.ts +237 -0
- package/src/export/capture.ts +277 -0
- package/src/export/fonts.ts +68 -0
- package/src/export/html.ts +32 -0
- package/src/export/index.ts +146 -0
- package/src/field-messages.ts +104 -0
- package/src/figure-text.ts +42 -0
- package/src/index.ts +30 -283
- package/src/layout/architecture.ts +381 -0
- package/src/layout/measure.ts +21 -0
- package/src/layout/scene.ts +353 -0
- package/src/layout/sequence.ts +189 -0
- package/src/load-fonts.ts +8 -0
- package/src/look/fonts.ts +33 -0
- package/src/look/palettes.json +209 -0
- package/src/look/values.ts +155 -0
- package/src/markdown.ts +35 -0
- package/src/messages.ts +122 -0
- package/src/parser.ts +342 -310
- package/src/player-global.ts +4 -0
- package/src/player.ts +271 -0
- package/src/printer.ts +76 -0
- package/src/render/icons.ts +41 -0
- package/src/render/motion.ts +106 -0
- package/src/render/parts.ts +279 -0
- package/src/render/svg.ts +341 -0
- package/src/schema.ts +161 -16
- package/src/schemas/diagram.schema.json +150 -0
- package/src/schemas/validate-diagram.d.ts +12 -0
- package/src/schemas/validate-diagram.js +8 -0
- package/src/timeline.ts +166 -0
- package/src/tokenize.ts +20 -281
- package/src/version.ts +55 -0
- package/src/view.ts +16 -0
- package/src/words.ts +79 -0
- package/examples/quick-start.md +0 -142
- package/src/canvas-bounds.ts +0 -52
- package/src/color.ts +0 -197
- package/src/compile/actor-ids.ts +0 -297
- package/src/compile/actor-option-notice.ts +0 -379
- package/src/compile/actors.ts +0 -103
- package/src/compile/c4.ts +0 -74
- package/src/compile/chain.ts +0 -93
- package/src/compile/chart-fields.ts +0 -193
- package/src/compile/deep-rewrite.ts +0 -30
- package/src/compile/derived-source.ts +0 -18
- package/src/compile/direction.ts +0 -102
- package/src/compile/edge-option-notice.ts +0 -112
- package/src/compile/er-relation.ts +0 -200
- package/src/compile/flow.ts +0 -54
- package/src/compile/flowchart.ts +0 -344
- package/src/compile/funnel.ts +0 -87
- package/src/compile/gantt.ts +0 -337
- package/src/compile/generic.ts +0 -696
- package/src/compile/hierarchy.ts +0 -92
- package/src/compile/journey.ts +0 -93
- package/src/compile/kinds.ts +0 -139
- package/src/compile/lanes.ts +0 -138
- package/src/compile/metro.ts +0 -271
- package/src/compile/mind.ts +0 -393
- package/src/compile/node-title.ts +0 -24
- package/src/compile/notice.ts +0 -141
- package/src/compile/order.ts +0 -57
- package/src/compile/parts-lookup.ts +0 -17
- package/src/compile/parts.ts +0 -2709
- package/src/compile/placement-apply.ts +0 -689
- package/src/compile/placement.ts +0 -15
- package/src/compile/quadrant.ts +0 -265
- package/src/compile/record.ts +0 -75
- package/src/compile/row-marks.ts +0 -128
- package/src/compile/rows.ts +0 -40
- package/src/compile/sequence.ts +0 -305
- package/src/compile/slug.ts +0 -37
- package/src/compile/stages.ts +0 -247
- package/src/compile/subtitle.ts +0 -35
- package/src/compile/swimlane-marks.ts +0 -87
- package/src/compile/swimlane.ts +0 -82
- package/src/compile/timeline.ts +0 -213
- package/src/compile/topology.ts +0 -44
- package/src/compile/tree.ts +0 -69
- package/src/compile/value-chart.ts +0 -337
- package/src/compile/values.ts +0 -432
- package/src/compile/word-state.ts +0 -82
- package/src/compile.ts +0 -3168
- package/src/focus.ts +0 -46
- package/src/input-size.ts +0 -181
- package/src/json-line-map.ts +0 -30
- package/src/json-parser.ts +0 -4306
- package/src/keywords.ts +0 -417
- package/src/near-name.ts +0 -42
- package/src/notation-lint.ts +0 -342
- package/src/relative-pos.ts +0 -251
- package/src/schemas/diagram.json +0 -3088
- package/src/types.ts +0 -1009
- package/src/v05/index.ts +0 -9
- package/src/v05/input-table.generated.ts +0 -138
- package/src/v05/parser-types.ts +0 -32
- package/src/v05/parser.ts +0 -6668
- package/src/v05/readout-table.generated.ts +0 -1088
- package/src/v05/removed-syntax.ts +0 -115
- package/src/value-syntax.ts +0 -313
- package/src/write-position.ts +0 -270
package/src/compile/values.ts
DELETED
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
import { applyDerivedValues } from "@cardenelabs/cdl";
|
|
2
|
-
import type { CdlDiagram } from "@cardenelabs/cdl";
|
|
3
|
-
import type { DslActor, DslDocument, DslStep, DslValue } from "../types";
|
|
4
|
-
import type { CompileNotice } from "./notice";
|
|
5
|
-
/**
|
|
6
|
-
* 値と式の扱い (#2036 で `compile.ts` から移した)。
|
|
7
|
-
*
|
|
8
|
-
* 書いた値を図に載せ、段ごとに値が動く区間を組み、解けなかった値を書いた人に伝える。
|
|
9
|
-
*
|
|
10
|
-
* 外へ出す口は 3 つ。 入口が呼ぶ `foldValueTriggers` と `attachDerivedValues`、
|
|
11
|
-
* 矢印の内訳を読む側が呼ぶ `鎖のどの行から来たか`。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 鎖の N 本目の矢印が、本文のどの行から来たかを返す (#1267)。
|
|
16
|
-
*
|
|
17
|
-
* `compileFlow` は登場人物を書いた順に繋ぎ、説明文は **その箱を to に持つ行** から拾う。
|
|
18
|
-
* 書いた側の端 (from) は使わない。 そのため `A -> C` と書いても矢印は `A -> B` になり、
|
|
19
|
-
* (from, to) の一致では対応が取れない (実測 = 説明文だけが載り、指定が黙って落ちていた)。
|
|
20
|
-
*
|
|
21
|
-
* 説明文を決めた規則と同じ規則で指定も決める = 説明文と指定が必ず同じ行から来る。
|
|
22
|
-
*/
|
|
23
|
-
export function 鎖のどの行から来たか(
|
|
24
|
-
doc: DslDocument,
|
|
25
|
-
並び: readonly DslActor[],
|
|
26
|
-
edgeIndex: number,
|
|
27
|
-
): DslStep | undefined {
|
|
28
|
-
const to = 並び[edgeIndex + 1];
|
|
29
|
-
if (to === undefined) return undefined;
|
|
30
|
-
return doc.flow.find((s) => s.to === to.name);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** 数を式に埋める。 指数表記 (`1e-7`) は engine の式が読めないため十進で書く */
|
|
34
|
-
function 式に書く数(n: number): string {
|
|
35
|
-
if (!Number.isFinite(n)) return "0";
|
|
36
|
-
const text = String(n);
|
|
37
|
-
if (!/[eE]/.test(text)) return text;
|
|
38
|
-
|
|
39
|
-
// Number の有効桁を丸めず、指数表記だけを通常の十進表記へ展開する。 `toFixed(6)` では
|
|
40
|
-
// 1e-7 が 0 になり、正しく読める `to` の値が動かなくなる。
|
|
41
|
-
const [coefficient = "0", exponentText = "0"] = text.toLowerCase().split("e");
|
|
42
|
-
const negative = coefficient.startsWith("-");
|
|
43
|
-
const unsigned = negative ? coefficient.slice(1) : coefficient;
|
|
44
|
-
const [whole = "0", fraction = ""] = unsigned.split(".");
|
|
45
|
-
const digits = `${whole}${fraction}`;
|
|
46
|
-
const decimalAt = whole.length + Number(exponentText);
|
|
47
|
-
let expanded: string;
|
|
48
|
-
if (decimalAt <= 0) expanded = `0.${"0".repeat(-decimalAt)}${digits}`;
|
|
49
|
-
else if (decimalAt >= digits.length)
|
|
50
|
-
expanded = `${digits}${"0".repeat(decimalAt - digits.length)}`;
|
|
51
|
-
else expanded = `${digits.slice(0, decimalAt)}.${digits.slice(decimalAt)}`;
|
|
52
|
-
return negative ? `-${expanded}` : expanded;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** 段の中で 1 本の値が動く区間。 `from` から `to` へ `[start, end]` の間で線形に動く */
|
|
56
|
-
type 動く区間 = { 段: number; start: number; end: number; dur: number; from: number; to: number };
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 相手の値が境目を通る時刻を求める。
|
|
60
|
-
*
|
|
61
|
-
* 相手は段の中で線形に動くので、 境目を通る時刻は逆算できる。 始めから成り立っているなら
|
|
62
|
-
* 相手が動き始めた時刻、 終わりまで成り立たないなら「通らない」 とする。
|
|
63
|
-
*
|
|
64
|
-
* `>` と `!=` の厳密な瞬間は境目の直後だが、 1 frame 未満の差なので境目そのものを返す。
|
|
65
|
-
*/
|
|
66
|
-
function 境目を通る時刻(区間: 動く区間, op: string, 境目: number): number | null {
|
|
67
|
-
const 満たす = (x: number): boolean => {
|
|
68
|
-
switch (op) {
|
|
69
|
-
case ">=":
|
|
70
|
-
return x >= 境目;
|
|
71
|
-
case ">":
|
|
72
|
-
return x > 境目;
|
|
73
|
-
case "<=":
|
|
74
|
-
return x <= 境目;
|
|
75
|
-
case "<":
|
|
76
|
-
return x < 境目;
|
|
77
|
-
case "==":
|
|
78
|
-
return x === 境目;
|
|
79
|
-
case "!=":
|
|
80
|
-
return x !== 境目;
|
|
81
|
-
default:
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
if (満たす(区間.from)) return 区間.start;
|
|
86
|
-
// `==` は終点が一致しなくても、線形補間の途中で境目を通る。 終点だけを見ると
|
|
87
|
-
// 0 → 100 に対する `== 50` を「満たさない」と誤判定する。
|
|
88
|
-
if (op === "==") {
|
|
89
|
-
const min = Math.min(区間.from, 区間.to);
|
|
90
|
-
const max = Math.max(区間.from, 区間.to);
|
|
91
|
-
if (境目 < min || 境目 > max) return null;
|
|
92
|
-
} else if (!満たす(区間.to)) {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
if (区間.to === 区間.from) return null;
|
|
96
|
-
const t = 区間.start + (区間.dur * (境目 - 区間.from)) / (区間.to - 区間.from);
|
|
97
|
-
if (!Number.isFinite(t)) return null;
|
|
98
|
-
return Math.min(Math.max(t, 区間.start), 区間.end);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* きっかけ形の値 (`trigger` / `to` / `dur`) を、段の時計を読む式へ畳む (#1161 段 2)。
|
|
103
|
-
*
|
|
104
|
-
* ## なぜ式へ畳むのか
|
|
105
|
-
*
|
|
106
|
-
* 描画側の動きの模型は段と、 段の中の線形補間しか持たない。 条件で動き出す仕組みも、 値ごとの
|
|
107
|
-
* 長さも無い (実測)。 そのままでは `trigger` も `dur` も渡せない。
|
|
108
|
-
*
|
|
109
|
-
* 一方で描画側は `derived` の式を **毎 frame** 解く。 そこで段に時計を 1 本引き
|
|
110
|
-
* (`0` から段の長さまでの補間)、 各値を「時計を読む傾斜」 として書けば、 段を割らずに
|
|
111
|
-
* 値ごとの長さを守れる。
|
|
112
|
-
*
|
|
113
|
-
* ```
|
|
114
|
-
* 値 = from + (to - from) * min(max((時計 - 開始) / 長さ, 0), 1)
|
|
115
|
-
* ```
|
|
116
|
-
*
|
|
117
|
-
* `min` / `max` で挟むのは、 開始前は `from` のまま、 終了後は `to` のまま止めるため。
|
|
118
|
-
*
|
|
119
|
-
* ## 段を割らない
|
|
120
|
-
*
|
|
121
|
-
* 段は見出し / 本文 / 印を持つ表示物なので、 割ると段送りの見え方と件数が変わる。 時計を使えば
|
|
122
|
-
* 段は 1 つのまま値だけが順に動く。 収まらない形 (開始 + 長さ > 段の長さ) は畳まずに知らせる。
|
|
123
|
-
*
|
|
124
|
-
* ## 連鎖の解き方
|
|
125
|
-
*
|
|
126
|
-
* `trigger: <相手> >= <境目>` は、 相手も段の中で線形に動くため境目を通る時刻を逆算できる。
|
|
127
|
-
* 相手が動く値でない (式だけ、 または初期値のまま) 場合は時刻が決まらないので畳まない。
|
|
128
|
-
*
|
|
129
|
-
* 返すのは名前から式への表で、 `attachDerivedValues` がこれを `derived` に載せる。 畳めなかった
|
|
130
|
-
* 値は表に入らないため図に載らない = 半端に止まった値を黙って置かない。
|
|
131
|
-
*/
|
|
132
|
-
export function foldValueTriggers(
|
|
133
|
-
diagram: CdlDiagram,
|
|
134
|
-
doc: DslDocument,
|
|
135
|
-
onNotice?: (n: CompileNotice) => void,
|
|
136
|
-
): Map<string, string> {
|
|
137
|
-
const 出力 = new Map<string, string>();
|
|
138
|
-
const きっかけ付き = (doc.values ?? []).filter((v) => v.trigger !== undefined);
|
|
139
|
-
if (きっかけ付き.length === 0) return 出力;
|
|
140
|
-
|
|
141
|
-
// 同じ名前を 2 度書いた時は先に書いた方を使う (`values` の既存の扱いと揃える)
|
|
142
|
-
const 宣言 = new Map<string, DslValue>();
|
|
143
|
-
for (const v of きっかけ付き) if (!宣言.has(v.name)) 宣言.set(v.name, v);
|
|
144
|
-
|
|
145
|
-
const 初期値 = new Map<string, number>();
|
|
146
|
-
for (const s of diagram.states) {
|
|
147
|
-
const n = Number(s.initial);
|
|
148
|
-
if (Number.isFinite(n)) 初期値.set(s.id, n);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 段は書いた名前でも slug でも指せる。 `focus:` が名前で指せるのと揃える
|
|
152
|
-
const 段の番号 = new Map<string, number>();
|
|
153
|
-
diagram.phases.forEach((p, i) => {
|
|
154
|
-
if (!段の番号.has(p.title)) 段の番号.set(p.title, i);
|
|
155
|
-
if (!段の番号.has(p.id)) 段の番号.set(p.id, i);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const 解けた = new Map<string, 動く区間>();
|
|
159
|
-
const 解けない = new Set<string>();
|
|
160
|
-
const 解決中 = new Set<string>();
|
|
161
|
-
|
|
162
|
-
const 知らせる = (v: DslValue, message: string, hint: string): void => {
|
|
163
|
-
onNotice?.({
|
|
164
|
-
kind: "value-trigger-unresolved",
|
|
165
|
-
actor: v.name,
|
|
166
|
-
line: v.pos?.line ?? 0,
|
|
167
|
-
message,
|
|
168
|
-
hint,
|
|
169
|
-
});
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const 解く = (name: string): 動く区間 | null => {
|
|
173
|
-
const 既出 = 解けた.get(name);
|
|
174
|
-
if (既出) return 既出;
|
|
175
|
-
if (解けない.has(name)) return null;
|
|
176
|
-
const v = 宣言.get(name);
|
|
177
|
-
if (!v || !v.trigger) return null;
|
|
178
|
-
if (解決中.has(name)) {
|
|
179
|
-
解けない.add(name);
|
|
180
|
-
知らせる(
|
|
181
|
-
v,
|
|
182
|
-
`"${name}" のきっかけが一周しています`,
|
|
183
|
-
"どれか 1 つを `trigger: step ...` に変える",
|
|
184
|
-
);
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
解決中.add(name);
|
|
188
|
-
const 区間 = 組み立てる(v);
|
|
189
|
-
解決中.delete(name);
|
|
190
|
-
if (!区間) {
|
|
191
|
-
解けない.add(name);
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
解けた.set(name, 区間);
|
|
195
|
-
return 区間;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const 組み立てる = (v: DslValue): 動く区間 | null => {
|
|
199
|
-
const trigger = v.trigger!;
|
|
200
|
-
const from = 初期値.get(v.name) ?? 0;
|
|
201
|
-
const to = v.to ?? 0;
|
|
202
|
-
const dur = v.durationMs ?? 0;
|
|
203
|
-
let 段 = 0;
|
|
204
|
-
let start = 0;
|
|
205
|
-
if (trigger.kind === "step") {
|
|
206
|
-
const idx = 段の番号.get(trigger.step);
|
|
207
|
-
if (idx === undefined) {
|
|
208
|
-
知らせる(
|
|
209
|
-
v,
|
|
210
|
-
`"${trigger.step}" という段がありません`,
|
|
211
|
-
"`animation:` にその名前の段を書くか、 段の名前に合わせる",
|
|
212
|
-
);
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
段 = idx;
|
|
216
|
-
start = 0;
|
|
217
|
-
} else {
|
|
218
|
-
const 相手 = 解く(trigger.source);
|
|
219
|
-
if (!相手) {
|
|
220
|
-
知らせる(
|
|
221
|
-
v,
|
|
222
|
-
`"${trigger.source}" が動く値でないため、 きっかけの時刻を決められません`,
|
|
223
|
-
"見張る相手も `trigger:` を持つ値にする",
|
|
224
|
-
);
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
const at = 境目を通る時刻(相手, trigger.op, trigger.threshold);
|
|
228
|
-
if (at === null) {
|
|
229
|
-
知らせる(
|
|
230
|
-
v,
|
|
231
|
-
`"${trigger.source}" は ${trigger.op} ${trigger.threshold} を満たしません`,
|
|
232
|
-
"相手が通る値を境目にするか、 相手の `to` を見直す",
|
|
233
|
-
);
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
段 = 相手.段;
|
|
237
|
-
start = at;
|
|
238
|
-
}
|
|
239
|
-
const 段の長さ = diagram.phases[段]?.duration ?? 0;
|
|
240
|
-
if (start + dur > 段の長さ) {
|
|
241
|
-
知らせる(
|
|
242
|
-
v,
|
|
243
|
-
`段 "${diagram.phases[段]?.title ?? ""}" (${段の長さ}ms) に収まりません (${Math.round(start + dur)}ms 必要)`,
|
|
244
|
-
"段を長くするか `dur` を短くする",
|
|
245
|
-
);
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
return { 段, start, end: start + dur, dur, from, to };
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
for (const v of 宣言.values()) 解く(v.name);
|
|
252
|
-
if (解けた.size === 0) return 出力;
|
|
253
|
-
|
|
254
|
-
// 時計は段ごとに 1 本。 名前が既にある時は末尾に数を足してずらす = 書いた値を上書きしない
|
|
255
|
-
const 使用中 = new Set(diagram.states.map((s) => s.id));
|
|
256
|
-
for (const v of doc.values ?? []) 使用中.add(v.name);
|
|
257
|
-
const 段ごとの時計 = new Map<number, string>();
|
|
258
|
-
const 時計を用意する = (段: number): string => {
|
|
259
|
-
const 既出 = 段ごとの時計.get(段);
|
|
260
|
-
if (既出) return 既出;
|
|
261
|
-
let 名前 = `__step_clock_${段}`;
|
|
262
|
-
let 連番 = 2;
|
|
263
|
-
while (使用中.has(名前)) 名前 = `__step_clock_${段}_${連番++}`;
|
|
264
|
-
使用中.add(名前);
|
|
265
|
-
段ごとの時計.set(段, 名前);
|
|
266
|
-
diagram.states.push({ id: 名前, initial: 0 });
|
|
267
|
-
const 段の中身 = diagram.phases[段];
|
|
268
|
-
if (段の中身)
|
|
269
|
-
段の中身.tweens = [...段の中身.tweens, { stateId: 名前, from: 0, to: 段の中身.duration }];
|
|
270
|
-
return 名前;
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
for (const [name, 区間] of 解けた) {
|
|
274
|
-
const 時計 = 時計を用意する(区間.段);
|
|
275
|
-
const 進み = `min(max(({${時計}} - ${式に書く数(区間.start)}) / ${式に書く数(区間.dur)}, 0), 1)`;
|
|
276
|
-
出力.set(name, `((${進み} * ${式に書く数(区間.to - 区間.from)}) + ${式に書く数(区間.from)})`);
|
|
277
|
-
}
|
|
278
|
-
return 出力;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* 記法の `values:` を図に載せる (#1162)。
|
|
283
|
-
*
|
|
284
|
-
* `values` は「他の値から自動で決まる値」 で、 時間を持たない。 参照した値が動けば常に
|
|
285
|
-
* 追随する。 解くのは描画側 (`@cardenelabs/cdl` の `applyDerivedValues`) で、 段の値を出した
|
|
286
|
-
* 後に参照順で解いて `stateValues` に載せる。 **毎 frame ここを通る**ので、 掛け算や比較の
|
|
287
|
-
* ように端点 2 点では表せない関係も段の補間の途中で正しい値になる。
|
|
288
|
-
*
|
|
289
|
-
* ここは載せるだけで、 式は評価しない。 評価を compile 時に畳むと段の補間中に決まり直せない。
|
|
290
|
-
*
|
|
291
|
-
* **出口で 1 度だけ載せる** (`compileToCdl` の出口)。 図種ごとの組み立て器で書くとどれかを
|
|
292
|
-
* 見落とす (`injectStaticPhase` と同じ理由)。
|
|
293
|
-
*
|
|
294
|
-
* 名前が `states` と重なった場合は `values` を優先し、 重なったことを伝える。 spec の
|
|
295
|
-
* 4 節で決めた挙動で、 黙って一方を捨てると「書いたのに効かない」 が残る。
|
|
296
|
-
*/
|
|
297
|
-
export function attachDerivedValues(
|
|
298
|
-
diagram: CdlDiagram,
|
|
299
|
-
doc: DslDocument,
|
|
300
|
-
onNotice?: (n: CompileNotice) => void,
|
|
301
|
-
inheritedSourceLines?: ReadonlyMap<string, readonly number[]>,
|
|
302
|
-
foldedTriggers?: ReadonlyMap<string, string>,
|
|
303
|
-
): void {
|
|
304
|
-
const values = doc.values ?? [];
|
|
305
|
-
// 本文に値を書いていなくても、重ねた見本が値を持つことがある (#1180)。 その場合も
|
|
306
|
-
// 解けなかった分は伝える = 見本の中で止まった値も、画面には `{名前}` の生の形で出る
|
|
307
|
-
if (values.length === 0) {
|
|
308
|
-
if ((diagram.derived?.length ?? 0) > 0) {
|
|
309
|
-
reportUnresolvedValues(diagram, doc, onNotice, inheritedSourceLines);
|
|
310
|
-
}
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// 名前が重なったかは **図に載った状態** で見る。 書いた `states:` だけを見ると、見本から
|
|
315
|
-
// 引き継いだ状態 (`alias__id`) との重なりを見落とす
|
|
316
|
-
const 状態の名前 = new Set(diagram.states.map((s) => s.id));
|
|
317
|
-
for (const v of values) {
|
|
318
|
-
if (!状態の名前.has(v.name)) continue;
|
|
319
|
-
// きっかけ形は `states:` の値を **動き始めの値として使う**。 両方書くのが正しい形なので
|
|
320
|
-
// 重なりとして知らせない (#1161)。 知らせると、 仕様どおりに書いた図が毎回警告を出す
|
|
321
|
-
if (v.trigger !== undefined) continue;
|
|
322
|
-
onNotice?.({
|
|
323
|
-
kind: "value-shadows-state",
|
|
324
|
-
actor: v.name,
|
|
325
|
-
line: v.pos?.line ?? 0,
|
|
326
|
-
message: `"${v.name}" を states と values の両方に書いています。 values を使います`,
|
|
327
|
-
hint: "states から外すか、 values の名前を変える",
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// **見本から引き継いだ分に足す** (#1180)。 代入で書くと、重ねた見本が持つ値が消える。
|
|
332
|
-
//
|
|
333
|
-
// 本文に書いた分を先に置く = engine は同じ名前では先に書いた式を使うため、名前が重なった
|
|
334
|
-
// 時に本文が勝つ。 重なったことは engine の知らせ (`duplicate-id`) がそのまま伝える
|
|
335
|
-
// きっかけ形は `foldValueTriggers` が畳んだ式を使う。 畳めなかった値はここに現れないため
|
|
336
|
-
// 図に載らない = 半端に止まった値を黙って置かない (知らせは畳む時点で出している)
|
|
337
|
-
const 載せる: Array<{ id: string; expression: string }> = [];
|
|
338
|
-
for (const v of values) {
|
|
339
|
-
const expression = v.expression ?? foldedTriggers?.get(v.name);
|
|
340
|
-
if (expression === undefined) continue;
|
|
341
|
-
載せる.push({ id: v.name, expression });
|
|
342
|
-
}
|
|
343
|
-
diagram.derived = [...載せる, ...(diagram.derived ?? [])];
|
|
344
|
-
reportUnresolvedValues(diagram, doc, onNotice, inheritedSourceLines);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* 解けなかった値を書いた人に伝える (#1162)。
|
|
349
|
-
*
|
|
350
|
-
* 描画側は解けない値を黙って飛ばす (`computeStateValues` が engine の知らせを捨てている)。
|
|
351
|
-
* 書き間違えても図は描かれ、箱に `{waiting}` の生の形が出るだけになる。 綴りを疑う以外に
|
|
352
|
-
* 手掛かりが無いので、組み立ての時点で分かる分をここで伝える。
|
|
353
|
-
*
|
|
354
|
-
* **判定は engine にさせる**。 解く順序と、止める条件 (輪 / 無い名前 / 読めない式 / 数として
|
|
355
|
-
* 読めない値) は engine が持つ。 同じ判定を書き直すと、描画は動くのに知らせだけ出る
|
|
356
|
-
* (またはその逆) 状態を作る。
|
|
357
|
-
*
|
|
358
|
-
* 見るのは初期値 1 組だけ。 輪 / 無い名前 / 読めない式 / 二重宣言は値に依らないのでこれで
|
|
359
|
-
* 全て取れる。 段の途中でだけ起きる形 (割る数が段の途中で 0 になる等) は取れない =
|
|
360
|
-
* 毎 frame の知らせは engine 側が返し口を持たないため、ここでは扱わない。
|
|
361
|
-
*/
|
|
362
|
-
function reportUnresolvedValues(
|
|
363
|
-
diagram: CdlDiagram,
|
|
364
|
-
doc: DslDocument,
|
|
365
|
-
onNotice?: (n: CompileNotice) => void,
|
|
366
|
-
inheritedSourceLines?: ReadonlyMap<string, readonly number[]>,
|
|
367
|
-
): void {
|
|
368
|
-
if (!onNotice) return;
|
|
369
|
-
// 描画側 (`computeStateValues`) が段を進める前に組み立てるのと同じ形。 値を解く手順は
|
|
370
|
-
// engine に渡すので、ここで組み立てるのは初期値の表だけにする
|
|
371
|
-
//
|
|
372
|
-
// **継承を持たない入れ物で作る**。 engine 側 (`computeStateValues` /
|
|
373
|
-
// `applyDerivedValues`) が同じ形で組むため、ここを通常の object にすると
|
|
374
|
-
// `__proto__` のような名前で **組み立てだけが「値が無い」 と知らせる** 状態ができる
|
|
375
|
-
// (実測 = 描画は `a = 5` を出すのに、知らせは `value-unresolved` を出していた)。
|
|
376
|
-
//
|
|
377
|
-
// engine が `{}` で組んでいた頃はここも `{}` で揃えていた。 cdl 側が継承なしに
|
|
378
|
-
// 揃えた (cdl#456 / cdl#490) ので、こちらも合わせる。 **揃っていることが要点**で、
|
|
379
|
-
// どちらの形にするかは engine が決める
|
|
380
|
-
const 初期値: Record<string, string> = Object.create(null) as Record<string, string>;
|
|
381
|
-
for (const s of diagram.states) 初期値[s.id] = String(s.initial);
|
|
382
|
-
|
|
383
|
-
// engine は同じ名前では先に書いた式を使う。 Map の一括生成で後ろから
|
|
384
|
-
// 上書きすると、先の式の未解決を後の行の問題として伝えてしまう
|
|
385
|
-
const 最初の行 = new Map<string, number>();
|
|
386
|
-
const 重複した行 = new Map<string, number[]>();
|
|
387
|
-
for (const v of doc.values ?? []) {
|
|
388
|
-
if (!最初の行.has(v.name)) {
|
|
389
|
-
最初の行.set(v.name, v.pos?.line ?? 0);
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
const 同じ名前の行 = 重複した行.get(v.name) ?? [];
|
|
393
|
-
同じ名前の行.push(v.pos?.line ?? 0);
|
|
394
|
-
重複した行.set(v.name, 同じ名前の行);
|
|
395
|
-
}
|
|
396
|
-
// 本文の値は `attachDerivedValues` が先頭へ置き、見本から引き継いだ値はその後ろに残る。
|
|
397
|
-
// 同じ順で行を足すことで、duplicate-id を「後から書かれた宣言」へ正確に戻す。
|
|
398
|
-
for (const [id, lines] of inheritedSourceLines ?? []) {
|
|
399
|
-
for (const line of lines) {
|
|
400
|
-
if (!最初の行.has(id)) {
|
|
401
|
-
最初の行.set(id, line);
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
const 同じ名前の行 = 重複した行.get(id) ?? [];
|
|
405
|
-
同じ名前の行.push(line);
|
|
406
|
-
重複した行.set(id, 同じ名前の行);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
for (const n of applyDerivedValues(初期値, diagram.derived).notices) {
|
|
410
|
-
onNotice({
|
|
411
|
-
kind: n.kind === "duplicate-id" ? "value-duplicate" : "value-unresolved",
|
|
412
|
-
actor: n.id,
|
|
413
|
-
// 重複は後から書いた宣言そのものを、式の問題は engine が使う最初の宣言を指す
|
|
414
|
-
line:
|
|
415
|
-
n.kind === "duplicate-id"
|
|
416
|
-
? (重複した行.get(n.id)?.shift() ?? 最初の行.get(n.id) ?? 0)
|
|
417
|
-
: (最初の行.get(n.id) ?? 0),
|
|
418
|
-
message: n.message,
|
|
419
|
-
hint: VALUE_NOTICE_HINT[n.kind],
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/** 止まった理由ごとの直し方。 engine の知らせは何が起きたかまでで、直し方は記法側が持つ */
|
|
425
|
-
const VALUE_NOTICE_HINT: Readonly<Record<string, string>> = {
|
|
426
|
-
cycle: "参照が一周しています。 どれか 1 つを states の初期値に変える",
|
|
427
|
-
"unknown-reference": "その名前の states / values を足すか、綴りを直す",
|
|
428
|
-
"parse-error": "式に書けるのは四則 (+ - * /) と括弧、比較、min / max だけ",
|
|
429
|
-
"eval-error": "初期値で計算できない形です。 割る数や、数として読めない初期値を見直す",
|
|
430
|
-
"duplicate-id": "同じ名前が 2 度あります。 片方を消すか名前を変える",
|
|
431
|
-
"invalid-id": "名前に使えるのは英数字と _ だけ",
|
|
432
|
-
};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type { CdlDiagram } from "@cardenelabs/cdl";
|
|
2
|
-
/**
|
|
3
|
-
* 書いた語を図の状態へ読み替える表 (#2030 で `compile.ts` から移した)。
|
|
4
|
-
*
|
|
5
|
-
* 道のり図の顔と、四象限の置き場所。 どちらも記法に書く語と描画側の値が 1 対 1 で対応する。
|
|
6
|
-
*
|
|
7
|
-
* **葉に置く** = 図種ごとの組み立て器と、`compile.ts` に残る読み替えの両方が呼ぶ。
|
|
8
|
-
* どちらかの側に置くと、もう片方から逆向きの取り込みが生まれる。
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 気持ちの言葉。 書きやすさのため日本語で受ける。
|
|
13
|
-
*
|
|
14
|
-
* **`Map` で持つ**。 plain object だと `__proto__` / `constructor` が親から引けてしまい、
|
|
15
|
-
* 書ける語の一覧に無い入力が値として通る (review 指摘)。 型は付いていても中身は object や
|
|
16
|
-
* function になり、 描画側へそのまま流れる。
|
|
17
|
-
*/
|
|
18
|
-
export const 気持ち = new Map<string, "delighted" | "happy" | "neutral" | "frustrated" | "angry">([
|
|
19
|
-
["最高", "delighted"],
|
|
20
|
-
["満足", "happy"],
|
|
21
|
-
["普通", "neutral"],
|
|
22
|
-
["不満", "frustrated"],
|
|
23
|
-
["怒り", "angry"],
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
/** 区画の言葉。 縦横の位置をそのまま書く。 `気持ち` と同じ理由で `Map` で持つ */
|
|
27
|
-
export const 区画 = new Map<string, "topLeft" | "topRight" | "bottomLeft" | "bottomRight">([
|
|
28
|
-
["左上", "topLeft"],
|
|
29
|
-
["右上", "topRight"],
|
|
30
|
-
["左下", "bottomLeft"],
|
|
31
|
-
["右下", "bottomRight"],
|
|
32
|
-
]);
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 記法の語で書いた状態を、図の語へ直す (#1201)。
|
|
36
|
-
*
|
|
37
|
-
* 語の欄が `{名前}` を持つとき、その名前が指す状態には記法の語 (「不満」 「左上」) が
|
|
38
|
-
* 入っている。 描画側が知っているのは図の語 (`frustrated` / `topLeft`) なので、ここで直す。
|
|
39
|
-
*
|
|
40
|
-
* **記法の語彙に engine の内部語を混ぜないため**にこの形にしている。 状態にも図の語を
|
|
41
|
-
* 書かせる形なら直す処理は要らないが、記法の語と内部語が同じ file に並ぶことになる。
|
|
42
|
-
*
|
|
43
|
-
* 直すのは語の欄から参照されている名前だけ。 同じ名前を数の欄からも参照している図では
|
|
44
|
-
* 直さない (数として読めなくなるため)。
|
|
45
|
-
*
|
|
46
|
-
* **この「数の欄からも参照している」 分岐は、到達する入力を今は作れない**。 記法の図は
|
|
47
|
-
* 1 つの型しか持たず、語の欄を持つ型 (`journey` / `quadrant`) と数の欄を持つ型
|
|
48
|
-
* (`bar` / `line` / `pie` / `funnel`) は同時に現れないため。 変異試験でもこの行を外して
|
|
49
|
-
* 検査が落ちないことを確かめた = 覆えていない。 見本を重ねる経路で両方の欄を持つ箱が
|
|
50
|
-
* できた時のために残す。
|
|
51
|
-
*/
|
|
52
|
-
export function 語の状態を図の語へ直す(diagram: CdlDiagram): void {
|
|
53
|
-
const 対象 = new Map<string, Map<string, string>>();
|
|
54
|
-
const 数の欄から = new Set<string>();
|
|
55
|
-
const 拾う = (v: unknown, 語表: Map<string, string>) => {
|
|
56
|
-
const m = typeof v === "string" ? v.match(/^\{(\w+)/) : null;
|
|
57
|
-
if (m) 対象.set(m[1]!, 語表);
|
|
58
|
-
};
|
|
59
|
-
for (const n of diagram.nodes) {
|
|
60
|
-
for (const st of n.journeyData ?? []) 拾う(st.emotion, 気持ち);
|
|
61
|
-
for (const it of n.quadrantData?.items ?? []) 拾う(it.quadrant, 区画);
|
|
62
|
-
for (const d of n.chartData ?? []) {
|
|
63
|
-
const m = typeof d.value === "string" ? d.value.match(/^\{(\w+)/) : null;
|
|
64
|
-
if (m) 数の欄から.add(m[1]!);
|
|
65
|
-
}
|
|
66
|
-
for (const f of n.funnelData ?? []) {
|
|
67
|
-
const m = typeof f.count === "string" ? f.count.match(/^\{(\w+)/) : null;
|
|
68
|
-
if (m) 数の欄から.add(m[1]!);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (対象.size === 0) return;
|
|
72
|
-
|
|
73
|
-
const 直す = (名前: string, 値: string | number): string | number => {
|
|
74
|
-
const 語表 = 対象.get(名前);
|
|
75
|
-
if (!語表 || 数の欄から.has(名前)) return 値;
|
|
76
|
-
return 語表.get(String(値).trim()) ?? 値;
|
|
77
|
-
};
|
|
78
|
-
for (const s of diagram.states) s.initial = 直す(s.id, s.initial);
|
|
79
|
-
for (const p of diagram.phases) {
|
|
80
|
-
for (const st of p.sets) st.value = 直す(st.stateId, st.value);
|
|
81
|
-
}
|
|
82
|
-
}
|