@vizij/node-graph-authoring 0.0.2 → 0.0.5
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 +74 -1
- package/dist/chunk-XYPRZPXJ.js +4334 -0
- package/dist/cli/reportIr.cjs +4046 -0
- package/dist/cli/reportIr.js +246 -0
- package/dist/index.cjs +2544 -512
- package/dist/index.d.cts +387 -36
- package/dist/index.d.ts +387 -36
- package/dist/index.js +58 -2284
- package/package.json +14 -5
|
@@ -0,0 +1,4046 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/cli/reportIr.ts
|
|
5
|
+
var import_node_fs = require("fs");
|
|
6
|
+
var import_node_path = require("path");
|
|
7
|
+
|
|
8
|
+
// src/graphBuilder.ts
|
|
9
|
+
var import_utils2 = require("@vizij/utils");
|
|
10
|
+
var import_utils3 = require("@vizij/utils");
|
|
11
|
+
|
|
12
|
+
// src/state.ts
|
|
13
|
+
var import_utils = require("@vizij/utils");
|
|
14
|
+
var VECTOR_ANIMATABLE_TYPES = /* @__PURE__ */ new Set(["vector2", "vector3", "euler", "rgb"]);
|
|
15
|
+
function deriveComponentValueType(component) {
|
|
16
|
+
if (component.component) {
|
|
17
|
+
return "scalar";
|
|
18
|
+
}
|
|
19
|
+
return VECTOR_ANIMATABLE_TYPES.has(component.animatableType) ? "vector" : "scalar";
|
|
20
|
+
}
|
|
21
|
+
function isBindingValueType(value) {
|
|
22
|
+
return value === "scalar" || value === "vector";
|
|
23
|
+
}
|
|
24
|
+
function getTargetValueType(target) {
|
|
25
|
+
return isBindingValueType(target.valueType) ? target.valueType : "scalar";
|
|
26
|
+
}
|
|
27
|
+
function sanitizeSlotValueType(value, targetType) {
|
|
28
|
+
return isBindingValueType(value) ? value : targetType;
|
|
29
|
+
}
|
|
30
|
+
function bindingTargetFromComponent(component) {
|
|
31
|
+
return {
|
|
32
|
+
id: component.id,
|
|
33
|
+
defaultValue: component.defaultValue,
|
|
34
|
+
range: {
|
|
35
|
+
min: component.range.min,
|
|
36
|
+
max: component.range.max
|
|
37
|
+
},
|
|
38
|
+
valueType: deriveComponentValueType(component)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function bindingTargetFromInput(input) {
|
|
42
|
+
return {
|
|
43
|
+
id: input.id,
|
|
44
|
+
defaultValue: input.defaultValue,
|
|
45
|
+
range: {
|
|
46
|
+
min: input.range.min,
|
|
47
|
+
max: input.range.max
|
|
48
|
+
},
|
|
49
|
+
valueType: "scalar"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var DEFAULT_INPUT_RANGE = { min: -1, max: 1 };
|
|
53
|
+
var DEFAULT_INPUT_ANCHOR = 0;
|
|
54
|
+
var LEGACY_SLOT_PATTERN = /^slot_(\d+)$/i;
|
|
55
|
+
var PRIMARY_SLOT_ID = "s1";
|
|
56
|
+
var PRIMARY_SLOT_ALIAS = "s1";
|
|
57
|
+
function defaultSlotId(index) {
|
|
58
|
+
return `s${index + 1}`;
|
|
59
|
+
}
|
|
60
|
+
function normalizeSlotId(value, index) {
|
|
61
|
+
if (value && value.length > 0) {
|
|
62
|
+
const match = value.match(LEGACY_SLOT_PATTERN);
|
|
63
|
+
if (match) {
|
|
64
|
+
const suffix = match[1] ?? String(index + 1);
|
|
65
|
+
return `s${suffix}`;
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
return defaultSlotId(index);
|
|
70
|
+
}
|
|
71
|
+
function normalizeSlotAlias(value, fallback, index) {
|
|
72
|
+
if (value && value.length > 0) {
|
|
73
|
+
const match = value.match(LEGACY_SLOT_PATTERN);
|
|
74
|
+
if (match) {
|
|
75
|
+
const suffix = match[1] ?? String(index + 1);
|
|
76
|
+
return { alias: `s${suffix}`, replaced: value };
|
|
77
|
+
}
|
|
78
|
+
return { alias: value, replaced: null };
|
|
79
|
+
}
|
|
80
|
+
if (fallback && fallback.length > 0) {
|
|
81
|
+
return { alias: fallback, replaced: null };
|
|
82
|
+
}
|
|
83
|
+
return { alias: defaultSlotId(index), replaced: null };
|
|
84
|
+
}
|
|
85
|
+
function rewriteLegacyExpression(expression, replacements) {
|
|
86
|
+
if (expression.trim().length === 0) {
|
|
87
|
+
return expression;
|
|
88
|
+
}
|
|
89
|
+
return expression.replace(/\bslot_(\d+)\b/gi, (match, digits) => {
|
|
90
|
+
const replacement = replacements.get(match);
|
|
91
|
+
if (replacement) {
|
|
92
|
+
return replacement;
|
|
93
|
+
}
|
|
94
|
+
return `s${digits}`;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function clamp(value, min, max) {
|
|
98
|
+
return Math.min(max, Math.max(min, value));
|
|
99
|
+
}
|
|
100
|
+
function isFiniteNumber(value) {
|
|
101
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
102
|
+
}
|
|
103
|
+
function deriveOutputDefaults(target) {
|
|
104
|
+
const { min, max } = target.range;
|
|
105
|
+
const anchor = clamp(target.defaultValue, min, max);
|
|
106
|
+
return {
|
|
107
|
+
outLow: min,
|
|
108
|
+
outAnchor: anchor,
|
|
109
|
+
outHigh: max
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function deriveInputDefaults() {
|
|
113
|
+
return {
|
|
114
|
+
inLow: DEFAULT_INPUT_RANGE.min,
|
|
115
|
+
inAnchor: DEFAULT_INPUT_ANCHOR,
|
|
116
|
+
inHigh: DEFAULT_INPUT_RANGE.max
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function migrateLegacyRemap(legacy, target) {
|
|
120
|
+
const inputDefaults = deriveInputDefaults();
|
|
121
|
+
const outputDefaults = deriveOutputDefaults(target);
|
|
122
|
+
const defaults = {
|
|
123
|
+
inLow: inputDefaults.inLow,
|
|
124
|
+
inAnchor: inputDefaults.inAnchor,
|
|
125
|
+
inHigh: inputDefaults.inHigh,
|
|
126
|
+
outLow: outputDefaults.outLow,
|
|
127
|
+
outAnchor: outputDefaults.outAnchor,
|
|
128
|
+
outHigh: outputDefaults.outHigh
|
|
129
|
+
};
|
|
130
|
+
if ("inLow" in legacy && "inHigh" in legacy && "outLow" in legacy && "outHigh" in legacy) {
|
|
131
|
+
const inLow2 = isFiniteNumber(legacy.inLow) ? legacy.inLow : defaults.inLow;
|
|
132
|
+
const inAnchor2 = isFiniteNumber(legacy.inAnchor) ? legacy.inAnchor : defaults.inAnchor;
|
|
133
|
+
const inHigh2 = isFiniteNumber(legacy.inHigh) ? legacy.inHigh : defaults.inHigh;
|
|
134
|
+
let outLow = isFiniteNumber(legacy.outLow) ? legacy.outLow : defaults.outLow;
|
|
135
|
+
let outHigh = isFiniteNumber(legacy.outHigh) ? legacy.outHigh : defaults.outHigh;
|
|
136
|
+
if (outLow > outHigh) {
|
|
137
|
+
const low = outHigh;
|
|
138
|
+
const high = outLow;
|
|
139
|
+
outLow = low;
|
|
140
|
+
outHigh = high;
|
|
141
|
+
}
|
|
142
|
+
const outAnchor2 = clamp(
|
|
143
|
+
isFiniteNumber(legacy.outAnchor) ? legacy.outAnchor : defaults.outAnchor,
|
|
144
|
+
outLow,
|
|
145
|
+
outHigh
|
|
146
|
+
);
|
|
147
|
+
return {
|
|
148
|
+
inLow: inLow2,
|
|
149
|
+
inAnchor: inAnchor2,
|
|
150
|
+
inHigh: inHigh2,
|
|
151
|
+
outLow,
|
|
152
|
+
outAnchor: outAnchor2,
|
|
153
|
+
outHigh
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const legacyTyped = legacy;
|
|
157
|
+
const inLow = isFiniteNumber(legacyTyped.inMin) ? legacyTyped.inMin : defaults.inLow;
|
|
158
|
+
const inHigh = isFiniteNumber(legacyTyped.inMax) ? legacyTyped.inMax : defaults.inHigh;
|
|
159
|
+
const inAnchor = (inLow + inHigh) / 2;
|
|
160
|
+
const legacyOutMid = isFiniteNumber(legacyTyped.outMin) && isFiniteNumber(legacyTyped.outMax) ? (legacyTyped.outMin + legacyTyped.outMax) / 2 : defaults.outAnchor;
|
|
161
|
+
const outAnchor = clamp(legacyOutMid, defaults.outLow, defaults.outHigh);
|
|
162
|
+
return {
|
|
163
|
+
inLow,
|
|
164
|
+
inAnchor,
|
|
165
|
+
inHigh,
|
|
166
|
+
outLow: defaults.outLow,
|
|
167
|
+
outAnchor,
|
|
168
|
+
outHigh: defaults.outHigh
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function normalizeRemap(remap, target) {
|
|
172
|
+
if (!remap) {
|
|
173
|
+
return createDefaultRemap(target);
|
|
174
|
+
}
|
|
175
|
+
return migrateLegacyRemap(remap, target);
|
|
176
|
+
}
|
|
177
|
+
function cloneRemap(remap) {
|
|
178
|
+
return (0, import_utils.cloneRemapSettings)(remap);
|
|
179
|
+
}
|
|
180
|
+
function sanitizeRemap(remap, target) {
|
|
181
|
+
const normalized = normalizeRemap(remap, target);
|
|
182
|
+
const outputDefaults = deriveOutputDefaults(target);
|
|
183
|
+
if (!Number.isFinite(normalized.outLow)) {
|
|
184
|
+
normalized.outLow = outputDefaults.outLow;
|
|
185
|
+
}
|
|
186
|
+
if (!Number.isFinite(normalized.outHigh)) {
|
|
187
|
+
normalized.outHigh = outputDefaults.outHigh;
|
|
188
|
+
}
|
|
189
|
+
if (!Number.isFinite(normalized.outAnchor)) {
|
|
190
|
+
normalized.outAnchor = outputDefaults.outAnchor;
|
|
191
|
+
}
|
|
192
|
+
if (normalized.outLow > normalized.outHigh) {
|
|
193
|
+
const low = normalized.outHigh;
|
|
194
|
+
const high = normalized.outLow;
|
|
195
|
+
normalized.outLow = low;
|
|
196
|
+
normalized.outHigh = high;
|
|
197
|
+
}
|
|
198
|
+
normalized.outAnchor = clamp(
|
|
199
|
+
normalized.outAnchor,
|
|
200
|
+
normalized.outLow,
|
|
201
|
+
normalized.outHigh
|
|
202
|
+
);
|
|
203
|
+
return normalized;
|
|
204
|
+
}
|
|
205
|
+
function createDefaultRemap(target) {
|
|
206
|
+
const inputDefaults = deriveInputDefaults();
|
|
207
|
+
const outputDefaults = deriveOutputDefaults(target);
|
|
208
|
+
return {
|
|
209
|
+
inLow: inputDefaults.inLow,
|
|
210
|
+
inAnchor: inputDefaults.inAnchor,
|
|
211
|
+
inHigh: inputDefaults.inHigh,
|
|
212
|
+
outLow: outputDefaults.outLow,
|
|
213
|
+
outAnchor: outputDefaults.outAnchor,
|
|
214
|
+
outHigh: outputDefaults.outHigh
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function ensurePrimarySlot(binding, target) {
|
|
218
|
+
const normalizedBindingRemap = sanitizeRemap(binding.remap, target);
|
|
219
|
+
const targetValueType = getTargetValueType(target);
|
|
220
|
+
const aliasReplacements = /* @__PURE__ */ new Map();
|
|
221
|
+
const sourceSlots = Array.isArray(binding.slots) && binding.slots.length > 0 ? binding.slots : [
|
|
222
|
+
{
|
|
223
|
+
id: PRIMARY_SLOT_ID,
|
|
224
|
+
alias: PRIMARY_SLOT_ALIAS,
|
|
225
|
+
inputId: binding.inputId ?? null,
|
|
226
|
+
remap: cloneRemap(normalizedBindingRemap),
|
|
227
|
+
valueType: targetValueType
|
|
228
|
+
}
|
|
229
|
+
];
|
|
230
|
+
const normalizedSlots = sourceSlots.map(
|
|
231
|
+
(slot, index) => {
|
|
232
|
+
const normalizedId = normalizeSlotId(slot.id, index);
|
|
233
|
+
const { alias: normalizedAlias, replaced } = normalizeSlotAlias(
|
|
234
|
+
slot.alias,
|
|
235
|
+
normalizedId,
|
|
236
|
+
index
|
|
237
|
+
);
|
|
238
|
+
if (replaced && replaced !== normalizedAlias) {
|
|
239
|
+
aliasReplacements.set(replaced, normalizedAlias);
|
|
240
|
+
}
|
|
241
|
+
const slotRemapSource = slot.remap ?? (index === 0 ? normalizedBindingRemap : createDefaultRemap(target));
|
|
242
|
+
const normalizedSlotRemap = sanitizeRemap(slotRemapSource, target);
|
|
243
|
+
const inputId = slot.inputId !== void 0 && slot.inputId !== null ? slot.inputId : index === 0 ? binding.inputId ?? null : null;
|
|
244
|
+
const slotValueType = sanitizeSlotValueType(
|
|
245
|
+
slot.valueType,
|
|
246
|
+
targetValueType
|
|
247
|
+
);
|
|
248
|
+
return {
|
|
249
|
+
id: normalizedId,
|
|
250
|
+
alias: normalizedAlias,
|
|
251
|
+
inputId,
|
|
252
|
+
remap: cloneRemap(normalizedSlotRemap),
|
|
253
|
+
valueType: slotValueType
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
const primary = normalizedSlots[0];
|
|
258
|
+
const primaryRemap = sanitizeRemap(primary.remap, target);
|
|
259
|
+
const primaryInputId = primary.inputId === import_utils.SELF_BINDING_ID ? import_utils.SELF_BINDING_ID : primary.inputId ?? binding.inputId ?? null;
|
|
260
|
+
const primaryAlias = primaryInputId === import_utils.SELF_BINDING_ID ? "self" : primary.alias || PRIMARY_SLOT_ALIAS;
|
|
261
|
+
normalizedSlots[0] = {
|
|
262
|
+
...primary,
|
|
263
|
+
id: primary.id || PRIMARY_SLOT_ID,
|
|
264
|
+
alias: primaryAlias,
|
|
265
|
+
inputId: primaryInputId,
|
|
266
|
+
remap: cloneRemap(primaryRemap),
|
|
267
|
+
valueType: sanitizeSlotValueType(primary.valueType, targetValueType)
|
|
268
|
+
};
|
|
269
|
+
normalizedSlots.slice(1).forEach((slot, index) => {
|
|
270
|
+
const slotRemap = sanitizeRemap(slot.remap, target);
|
|
271
|
+
normalizedSlots[index + 1] = {
|
|
272
|
+
...slot,
|
|
273
|
+
id: slot.id || defaultSlotId(index + 1),
|
|
274
|
+
alias: slot.alias || defaultSlotId(index + 1),
|
|
275
|
+
remap: cloneRemap(slotRemap),
|
|
276
|
+
valueType: sanitizeSlotValueType(slot.valueType, targetValueType)
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
const rawExpression = typeof binding.expression === "string" ? binding.expression.trim() : "";
|
|
280
|
+
const canonicalExpression = buildCanonicalExpressionFromSlots(normalizedSlots);
|
|
281
|
+
let expression;
|
|
282
|
+
if (expressionMatchesAliasOnly(rawExpression, normalizedSlots)) {
|
|
283
|
+
expression = canonicalExpression;
|
|
284
|
+
} else {
|
|
285
|
+
expression = rewriteLegacyExpression(rawExpression, aliasReplacements);
|
|
286
|
+
}
|
|
287
|
+
const normalizedBinding = {
|
|
288
|
+
...binding,
|
|
289
|
+
inputId: normalizedSlots[0].inputId ?? null,
|
|
290
|
+
remap: cloneRemap(primaryRemap),
|
|
291
|
+
slots: normalizedSlots,
|
|
292
|
+
expression
|
|
293
|
+
};
|
|
294
|
+
return normalizedBinding;
|
|
295
|
+
}
|
|
296
|
+
function ensureBindingStructure(binding, target) {
|
|
297
|
+
return ensurePrimarySlot(binding, target);
|
|
298
|
+
}
|
|
299
|
+
function sanitizeLiteral(value) {
|
|
300
|
+
if (!Number.isFinite(value)) {
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
if (Object.is(value, -0)) {
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
306
|
+
return value;
|
|
307
|
+
}
|
|
308
|
+
function formatVectorLiteral(values) {
|
|
309
|
+
return `vec(${values.map((value) => sanitizeLiteral(value)).join(", ")})`;
|
|
310
|
+
}
|
|
311
|
+
function buildPiecewiseRemapExpression(alias, remap) {
|
|
312
|
+
const sanitizedAlias = alias && alias.trim().length > 0 ? alias.trim() : PRIMARY_SLOT_ALIAS;
|
|
313
|
+
const inputBreakpoints = [remap.inLow, remap.inAnchor, remap.inHigh];
|
|
314
|
+
const outputBreakpoints = [remap.outLow, remap.outAnchor, remap.outHigh];
|
|
315
|
+
return `piecewise_remap(${sanitizedAlias}, ${formatVectorLiteral(
|
|
316
|
+
inputBreakpoints
|
|
317
|
+
)}, ${formatVectorLiteral(outputBreakpoints)})`;
|
|
318
|
+
}
|
|
319
|
+
function isSelfAlias(alias) {
|
|
320
|
+
return alias.trim().toLowerCase() === "self";
|
|
321
|
+
}
|
|
322
|
+
function buildDefaultSlotExpression(alias, inputId, remap) {
|
|
323
|
+
const sanitizedAlias = alias && alias.trim().length > 0 ? alias.trim() : PRIMARY_SLOT_ALIAS;
|
|
324
|
+
if (inputId === import_utils.SELF_BINDING_ID || isSelfAlias(sanitizedAlias)) {
|
|
325
|
+
return sanitizedAlias;
|
|
326
|
+
}
|
|
327
|
+
return buildPiecewiseRemapExpression(sanitizedAlias, remap);
|
|
328
|
+
}
|
|
329
|
+
function normalizeSlotAliasForExpression(slot, index) {
|
|
330
|
+
if (slot.alias && slot.alias.trim().length > 0) {
|
|
331
|
+
return slot.alias.trim();
|
|
332
|
+
}
|
|
333
|
+
if (slot.id && slot.id.trim().length > 0) {
|
|
334
|
+
return slot.id.trim();
|
|
335
|
+
}
|
|
336
|
+
return defaultSlotId(index);
|
|
337
|
+
}
|
|
338
|
+
function buildAliasOnlyExpression(slots) {
|
|
339
|
+
if (!slots.length) {
|
|
340
|
+
return PRIMARY_SLOT_ALIAS;
|
|
341
|
+
}
|
|
342
|
+
return slots.map((slot, index) => normalizeSlotAliasForExpression(slot, index)).join(" + ");
|
|
343
|
+
}
|
|
344
|
+
function buildCanonicalExpressionFromSlots(slots) {
|
|
345
|
+
if (!slots.length) {
|
|
346
|
+
return PRIMARY_SLOT_ALIAS;
|
|
347
|
+
}
|
|
348
|
+
return slots.map(
|
|
349
|
+
(slot, index) => buildDefaultSlotExpression(
|
|
350
|
+
normalizeSlotAliasForExpression(slot, index),
|
|
351
|
+
slot.inputId ?? null,
|
|
352
|
+
slot.remap
|
|
353
|
+
)
|
|
354
|
+
).join(" + ");
|
|
355
|
+
}
|
|
356
|
+
function expressionsEquivalent(left, right) {
|
|
357
|
+
return left.trim() === right.trim();
|
|
358
|
+
}
|
|
359
|
+
function expressionMatchesAliasOnly(expression, slots) {
|
|
360
|
+
if (expression.trim().length === 0) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
const aliasOnly = buildAliasOnlyExpression(slots);
|
|
364
|
+
return expressionsEquivalent(expression, aliasOnly);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/expression.ts
|
|
368
|
+
var STANDARD_WHITESPACE = /\s/;
|
|
369
|
+
var EXTRA_WHITESPACE_CHARS = /* @__PURE__ */ new Set(["\0", "\u200B", "\uFEFF"]);
|
|
370
|
+
function isWhitespaceCharacter(char) {
|
|
371
|
+
if (!char) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
return STANDARD_WHITESPACE.test(char) || EXTRA_WHITESPACE_CHARS.has(char);
|
|
375
|
+
}
|
|
376
|
+
var IDENT_START = /[A-Za-z_]/;
|
|
377
|
+
var IDENT_PART = /[A-Za-z0-9_]/;
|
|
378
|
+
var DIGIT = /[0-9]/;
|
|
379
|
+
var ControlExpressionParser = class {
|
|
380
|
+
constructor(input) {
|
|
381
|
+
this.input = input;
|
|
382
|
+
this.index = 0;
|
|
383
|
+
this.errors = [];
|
|
384
|
+
}
|
|
385
|
+
parse() {
|
|
386
|
+
this.skipWhitespace();
|
|
387
|
+
const node = this.parseExpression();
|
|
388
|
+
this.skipWhitespace();
|
|
389
|
+
if (!node) {
|
|
390
|
+
if (this.errors.length === 0) {
|
|
391
|
+
this.errors.push({
|
|
392
|
+
index: this.index,
|
|
393
|
+
message: "Empty expression."
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return { node: null, errors: this.errors };
|
|
397
|
+
}
|
|
398
|
+
if (!this.isAtEnd()) {
|
|
399
|
+
this.errors.push({
|
|
400
|
+
index: this.index,
|
|
401
|
+
message: `Unexpected token "${this.peek()}"`
|
|
402
|
+
});
|
|
403
|
+
return { node: null, errors: this.errors };
|
|
404
|
+
}
|
|
405
|
+
return { node, errors: this.errors };
|
|
406
|
+
}
|
|
407
|
+
parseExpression() {
|
|
408
|
+
return this.parseLogicalOr();
|
|
409
|
+
}
|
|
410
|
+
parseLogicalOr() {
|
|
411
|
+
let left = this.parseLogicalAnd();
|
|
412
|
+
if (!left) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
while (true) {
|
|
416
|
+
this.skipWhitespace();
|
|
417
|
+
const operator = this.matchAny(["||"]);
|
|
418
|
+
if (!operator) {
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
const right = this.parseLogicalAnd();
|
|
422
|
+
if (!right) {
|
|
423
|
+
this.errors.push({
|
|
424
|
+
index: this.index,
|
|
425
|
+
message: "Expected expression after operator."
|
|
426
|
+
});
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
left = {
|
|
430
|
+
type: "Binary",
|
|
431
|
+
operator,
|
|
432
|
+
left,
|
|
433
|
+
right
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return left;
|
|
437
|
+
}
|
|
438
|
+
parseLogicalAnd() {
|
|
439
|
+
let left = this.parseComparison();
|
|
440
|
+
if (!left) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
while (true) {
|
|
444
|
+
this.skipWhitespace();
|
|
445
|
+
const operator = this.matchAny(["&&"]);
|
|
446
|
+
if (!operator) {
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
const right = this.parseComparison();
|
|
450
|
+
if (!right) {
|
|
451
|
+
this.errors.push({
|
|
452
|
+
index: this.index,
|
|
453
|
+
message: "Expected expression after operator."
|
|
454
|
+
});
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
left = {
|
|
458
|
+
type: "Binary",
|
|
459
|
+
operator,
|
|
460
|
+
left,
|
|
461
|
+
right
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return left;
|
|
465
|
+
}
|
|
466
|
+
parseComparison() {
|
|
467
|
+
let left = this.parseAdditive();
|
|
468
|
+
if (!left) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
while (true) {
|
|
472
|
+
this.skipWhitespace();
|
|
473
|
+
if (this.input.startsWith(">=", this.index) || this.input.startsWith("<=", this.index)) {
|
|
474
|
+
const op = this.input.slice(this.index, this.index + 2);
|
|
475
|
+
this.index += 2;
|
|
476
|
+
this.errors.push({
|
|
477
|
+
index: this.index - 2,
|
|
478
|
+
message: `Operator "${op}" is not supported.`
|
|
479
|
+
});
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
const operator = this.matchAny(["==", "!=", ">", "<"]);
|
|
483
|
+
if (!operator) {
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
const right = this.parseAdditive();
|
|
487
|
+
if (!right) {
|
|
488
|
+
this.errors.push({
|
|
489
|
+
index: this.index,
|
|
490
|
+
message: "Expected expression after operator."
|
|
491
|
+
});
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
left = {
|
|
495
|
+
type: "Binary",
|
|
496
|
+
operator,
|
|
497
|
+
left,
|
|
498
|
+
right
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return left;
|
|
502
|
+
}
|
|
503
|
+
parseAdditive() {
|
|
504
|
+
let left = this.parseMultiplicative();
|
|
505
|
+
if (!left) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
while (true) {
|
|
509
|
+
this.skipWhitespace();
|
|
510
|
+
const operator = this.peek();
|
|
511
|
+
if (operator !== "+" && operator !== "-") {
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
this.index += 1;
|
|
515
|
+
const right = this.parseMultiplicative();
|
|
516
|
+
if (!right) {
|
|
517
|
+
this.errors.push({
|
|
518
|
+
index: this.index,
|
|
519
|
+
message: "Expected expression after operator."
|
|
520
|
+
});
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
left = {
|
|
524
|
+
type: "Binary",
|
|
525
|
+
operator,
|
|
526
|
+
left,
|
|
527
|
+
right
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
return left;
|
|
531
|
+
}
|
|
532
|
+
parseMultiplicative() {
|
|
533
|
+
let left = this.parseUnary();
|
|
534
|
+
if (!left) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
while (true) {
|
|
538
|
+
this.skipWhitespace();
|
|
539
|
+
const operator = this.peek();
|
|
540
|
+
if (operator !== "*" && operator !== "/") {
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
this.index += 1;
|
|
544
|
+
const right = this.parseUnary();
|
|
545
|
+
if (!right) {
|
|
546
|
+
this.errors.push({
|
|
547
|
+
index: this.index,
|
|
548
|
+
message: "Expected expression after operator."
|
|
549
|
+
});
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
left = {
|
|
553
|
+
type: "Binary",
|
|
554
|
+
operator,
|
|
555
|
+
left,
|
|
556
|
+
right
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return left;
|
|
560
|
+
}
|
|
561
|
+
parseUnary() {
|
|
562
|
+
this.skipWhitespace();
|
|
563
|
+
const char = this.peek();
|
|
564
|
+
if (!char) {
|
|
565
|
+
this.errors.push({
|
|
566
|
+
index: this.index,
|
|
567
|
+
message: "Unexpected end of expression."
|
|
568
|
+
});
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
if (char === "+" || char === "-" || char === "!") {
|
|
572
|
+
this.index += 1;
|
|
573
|
+
const operand = this.parseUnary();
|
|
574
|
+
if (!operand) {
|
|
575
|
+
this.errors.push({
|
|
576
|
+
index: this.index,
|
|
577
|
+
message: `Expected operand after unary "${char}".`
|
|
578
|
+
});
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
type: "Unary",
|
|
583
|
+
operator: char,
|
|
584
|
+
operand
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
return this.parsePrimary();
|
|
588
|
+
}
|
|
589
|
+
parsePrimary() {
|
|
590
|
+
this.skipWhitespace();
|
|
591
|
+
const char = this.peek();
|
|
592
|
+
if (!char) {
|
|
593
|
+
this.errors.push({
|
|
594
|
+
index: this.index,
|
|
595
|
+
message: "Unexpected end of expression."
|
|
596
|
+
});
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
if (char === "(") {
|
|
600
|
+
this.index += 1;
|
|
601
|
+
const expression = this.parseExpression();
|
|
602
|
+
this.skipWhitespace();
|
|
603
|
+
if (this.peek() === ")") {
|
|
604
|
+
this.index += 1;
|
|
605
|
+
return expression;
|
|
606
|
+
}
|
|
607
|
+
this.errors.push({
|
|
608
|
+
index: this.index,
|
|
609
|
+
message: "Unmatched parenthesis."
|
|
610
|
+
});
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
if (char === "[") {
|
|
614
|
+
this.index += 1;
|
|
615
|
+
return this.parseVectorLiteral("]", "vector literal");
|
|
616
|
+
}
|
|
617
|
+
if (IDENT_START.test(char)) {
|
|
618
|
+
return this.parseIdentifierOrFunction();
|
|
619
|
+
}
|
|
620
|
+
if (DIGIT.test(char) || char === ".") {
|
|
621
|
+
return this.parseNumber();
|
|
622
|
+
}
|
|
623
|
+
this.errors.push({
|
|
624
|
+
index: this.index,
|
|
625
|
+
message: `Unexpected character "${char}".`
|
|
626
|
+
});
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
parseIdentifierOrFunction() {
|
|
630
|
+
const start = this.index;
|
|
631
|
+
while (!this.isAtEnd() && IDENT_PART.test(this.peek())) {
|
|
632
|
+
this.index += 1;
|
|
633
|
+
}
|
|
634
|
+
const name = this.input.slice(start, this.index);
|
|
635
|
+
if (!name) {
|
|
636
|
+
this.errors.push({
|
|
637
|
+
index: start,
|
|
638
|
+
message: "Invalid identifier."
|
|
639
|
+
});
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
this.skipWhitespace();
|
|
643
|
+
if (this.peek() === "(") {
|
|
644
|
+
this.index += 1;
|
|
645
|
+
if (name.toLowerCase() === "vec") {
|
|
646
|
+
return this.parseVectorLiteral(")", `"${name}" literal`);
|
|
647
|
+
}
|
|
648
|
+
const args = [];
|
|
649
|
+
this.skipWhitespace();
|
|
650
|
+
if (this.peek() === ")") {
|
|
651
|
+
this.index += 1;
|
|
652
|
+
return {
|
|
653
|
+
type: "Function",
|
|
654
|
+
name,
|
|
655
|
+
args
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
while (true) {
|
|
659
|
+
const argument = this.parseExpression();
|
|
660
|
+
if (!argument) {
|
|
661
|
+
this.errors.push({
|
|
662
|
+
index: this.index,
|
|
663
|
+
message: `Expected expression for argument ${args.length + 1} of "${name}".`
|
|
664
|
+
});
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
args.push(argument);
|
|
668
|
+
this.skipWhitespace();
|
|
669
|
+
const next = this.peek();
|
|
670
|
+
if (next === ",") {
|
|
671
|
+
this.index += 1;
|
|
672
|
+
this.skipWhitespace();
|
|
673
|
+
if (this.peek() === ")") {
|
|
674
|
+
this.errors.push({
|
|
675
|
+
index: this.index,
|
|
676
|
+
message: `Expected expression after "," in call to "${name}".`
|
|
677
|
+
});
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (next === ")") {
|
|
683
|
+
this.index += 1;
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
if (next === null) {
|
|
687
|
+
this.errors.push({
|
|
688
|
+
index: this.index,
|
|
689
|
+
message: `Unterminated call to "${name}".`
|
|
690
|
+
});
|
|
691
|
+
} else {
|
|
692
|
+
this.errors.push({
|
|
693
|
+
index: this.index,
|
|
694
|
+
message: `Expected "," or ")" in call to "${name}".`
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
type: "Function",
|
|
701
|
+
name,
|
|
702
|
+
args
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
type: "Reference",
|
|
707
|
+
name
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
parseVectorLiteral(terminator, context) {
|
|
711
|
+
const values = this.parseVectorLiteralValues(terminator, context);
|
|
712
|
+
if (!values) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
type: "VectorLiteral",
|
|
717
|
+
values
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
parseVectorLiteralValues(terminator, context) {
|
|
721
|
+
const values = [];
|
|
722
|
+
this.skipWhitespace();
|
|
723
|
+
if (this.peek() === terminator) {
|
|
724
|
+
this.errors.push({
|
|
725
|
+
index: this.index,
|
|
726
|
+
message: `Expected at least one value in ${context}.`
|
|
727
|
+
});
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
while (true) {
|
|
731
|
+
this.skipWhitespace();
|
|
732
|
+
const literalValue = this.parseNumericLiteralValue();
|
|
733
|
+
if (literalValue === null) {
|
|
734
|
+
this.errors.push({
|
|
735
|
+
index: this.index,
|
|
736
|
+
message: `Expected numeric literal in ${context}.`
|
|
737
|
+
});
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
values.push(literalValue);
|
|
741
|
+
this.skipWhitespace();
|
|
742
|
+
const next = this.peek();
|
|
743
|
+
if (next === ",") {
|
|
744
|
+
this.index += 1;
|
|
745
|
+
this.skipWhitespace();
|
|
746
|
+
if (this.peek() === terminator) {
|
|
747
|
+
this.errors.push({
|
|
748
|
+
index: this.index,
|
|
749
|
+
message: `Expected value after "," in ${context}.`
|
|
750
|
+
});
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (next === terminator) {
|
|
756
|
+
this.index += 1;
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
if (next === null) {
|
|
760
|
+
this.errors.push({
|
|
761
|
+
index: this.index,
|
|
762
|
+
message: `Unterminated ${context}.`
|
|
763
|
+
});
|
|
764
|
+
} else {
|
|
765
|
+
this.errors.push({
|
|
766
|
+
index: this.index,
|
|
767
|
+
message: `Expected "," or "${terminator}" in ${context}.`
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
if (values.length === 0) {
|
|
773
|
+
this.errors.push({
|
|
774
|
+
index: this.index,
|
|
775
|
+
message: `Expected at least one value in ${context}.`
|
|
776
|
+
});
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
return values;
|
|
780
|
+
}
|
|
781
|
+
parseNumericLiteralValue(allowLeadingSign = true) {
|
|
782
|
+
const start = this.index;
|
|
783
|
+
if (allowLeadingSign && (this.peek() === "+" || this.peek() === "-")) {
|
|
784
|
+
this.index += 1;
|
|
785
|
+
}
|
|
786
|
+
let hasDigits = false;
|
|
787
|
+
let seenDecimal = false;
|
|
788
|
+
let seenExponent = false;
|
|
789
|
+
let exponentDigits = false;
|
|
790
|
+
let inExponent = false;
|
|
791
|
+
while (!this.isAtEnd()) {
|
|
792
|
+
const char = this.peek();
|
|
793
|
+
if (DIGIT.test(char)) {
|
|
794
|
+
hasDigits = true;
|
|
795
|
+
if (inExponent) {
|
|
796
|
+
exponentDigits = true;
|
|
797
|
+
}
|
|
798
|
+
this.index += 1;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (char === "." && !seenDecimal && !seenExponent) {
|
|
802
|
+
seenDecimal = true;
|
|
803
|
+
this.index += 1;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if ((char === "e" || char === "E") && !seenExponent && hasDigits) {
|
|
807
|
+
seenExponent = true;
|
|
808
|
+
inExponent = true;
|
|
809
|
+
exponentDigits = false;
|
|
810
|
+
this.index += 1;
|
|
811
|
+
const sign = this.peek();
|
|
812
|
+
if (sign === "+" || sign === "-") {
|
|
813
|
+
this.index += 1;
|
|
814
|
+
}
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
if (!hasDigits || seenExponent && !exponentDigits) {
|
|
820
|
+
this.index = start;
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
const raw = this.input.slice(start, this.index);
|
|
824
|
+
const value = Number(raw);
|
|
825
|
+
if (!Number.isFinite(value)) {
|
|
826
|
+
this.index = start;
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
return value;
|
|
830
|
+
}
|
|
831
|
+
parseNumber() {
|
|
832
|
+
const start = this.index;
|
|
833
|
+
const value = this.parseNumericLiteralValue(false);
|
|
834
|
+
if (value === null) {
|
|
835
|
+
this.errors.push({
|
|
836
|
+
index: start,
|
|
837
|
+
message: "Invalid numeric literal."
|
|
838
|
+
});
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
type: "Literal",
|
|
843
|
+
value
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
matchAny(operators) {
|
|
847
|
+
for (const operator of operators) {
|
|
848
|
+
if (this.input.startsWith(operator, this.index)) {
|
|
849
|
+
this.index += operator.length;
|
|
850
|
+
return operator;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
skipWhitespace() {
|
|
856
|
+
while (!this.isAtEnd()) {
|
|
857
|
+
const char = this.peek();
|
|
858
|
+
if (!isWhitespaceCharacter(char)) {
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
this.index += 1;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
peek() {
|
|
865
|
+
if (this.index >= this.input.length) {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
return this.input[this.index] ?? null;
|
|
869
|
+
}
|
|
870
|
+
isAtEnd() {
|
|
871
|
+
return this.index >= this.input.length;
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
function parseControlExpression(expression) {
|
|
875
|
+
const parser = new ControlExpressionParser(expression);
|
|
876
|
+
return parser.parse();
|
|
877
|
+
}
|
|
878
|
+
function collectExpressionReferences(node, target = /* @__PURE__ */ new Set()) {
|
|
879
|
+
if (!node) {
|
|
880
|
+
return target;
|
|
881
|
+
}
|
|
882
|
+
switch (node.type) {
|
|
883
|
+
case "Reference":
|
|
884
|
+
target.add(node.name);
|
|
885
|
+
break;
|
|
886
|
+
case "Unary":
|
|
887
|
+
collectExpressionReferences(node.operand, target);
|
|
888
|
+
break;
|
|
889
|
+
case "Binary":
|
|
890
|
+
collectExpressionReferences(node.left, target);
|
|
891
|
+
collectExpressionReferences(node.right, target);
|
|
892
|
+
break;
|
|
893
|
+
case "Function":
|
|
894
|
+
node.args.forEach((arg) => collectExpressionReferences(arg, target));
|
|
895
|
+
break;
|
|
896
|
+
default:
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
return target;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/expressionFunctions.ts
|
|
903
|
+
var import_metadata = require("@vizij/node-graph-wasm/metadata");
|
|
904
|
+
var FUNCTION_OVERRIDES = {
|
|
905
|
+
case: {
|
|
906
|
+
replaceInputs: [
|
|
907
|
+
{ id: "selector", optional: false, valueType: "any" },
|
|
908
|
+
{ id: "default", optional: false, valueType: "any" }
|
|
909
|
+
],
|
|
910
|
+
variadic: { id: "operand", min: 1, max: null, valueType: "any" },
|
|
911
|
+
minArgs: 3,
|
|
912
|
+
maxArgs: null,
|
|
913
|
+
params: []
|
|
914
|
+
},
|
|
915
|
+
slew: {
|
|
916
|
+
dropTrailingInputs: 1,
|
|
917
|
+
params: [
|
|
918
|
+
{
|
|
919
|
+
id: "max_rate",
|
|
920
|
+
label: "max_rate",
|
|
921
|
+
optional: false,
|
|
922
|
+
valueType: "scalar"
|
|
923
|
+
}
|
|
924
|
+
],
|
|
925
|
+
minArgs: 2,
|
|
926
|
+
maxArgs: 2
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
var FUNCTION_CONFIGS = [
|
|
930
|
+
{
|
|
931
|
+
typeId: "sin",
|
|
932
|
+
names: ["sin"],
|
|
933
|
+
category: "math",
|
|
934
|
+
description: "Sine of the input value (radians)."
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
typeId: "cos",
|
|
938
|
+
names: ["cos"],
|
|
939
|
+
category: "math",
|
|
940
|
+
description: "Cosine of the input value (radians)."
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
typeId: "tan",
|
|
944
|
+
names: ["tan"],
|
|
945
|
+
category: "math",
|
|
946
|
+
description: "Tangent of the input value (radians)."
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
typeId: "power",
|
|
950
|
+
names: ["power", "pow"],
|
|
951
|
+
category: "math",
|
|
952
|
+
description: "Raise a base value to an exponent."
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
typeId: "log",
|
|
956
|
+
names: ["log"],
|
|
957
|
+
category: "math",
|
|
958
|
+
description: "Logarithm of the input value."
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
typeId: "clamp",
|
|
962
|
+
names: ["clamp"],
|
|
963
|
+
category: "utility",
|
|
964
|
+
description: "Clamp an input between min/max."
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
typeId: "remap",
|
|
968
|
+
names: ["remap"],
|
|
969
|
+
category: "utility",
|
|
970
|
+
description: "Linearly map a value from the input range to the output range."
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
typeId: "centered_remap",
|
|
974
|
+
names: ["centeredremap", "centered_remap"],
|
|
975
|
+
category: "utility",
|
|
976
|
+
description: "Remap a value using in/out ranges that include explicit anchors."
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
typeId: "piecewise_remap",
|
|
980
|
+
names: ["piecewiseremap", "piecewise_remap"],
|
|
981
|
+
category: "utility",
|
|
982
|
+
description: "Map a value through piecewise-linear breakpoints."
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
typeId: "abs",
|
|
986
|
+
names: ["abs"],
|
|
987
|
+
category: "math",
|
|
988
|
+
description: "Absolute value of the input."
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
typeId: "add",
|
|
992
|
+
names: ["add"],
|
|
993
|
+
category: "math",
|
|
994
|
+
description: "Sum all operands together."
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
typeId: "multiply",
|
|
998
|
+
names: ["multiply"],
|
|
999
|
+
category: "math",
|
|
1000
|
+
description: "Multiply operands together."
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
typeId: "subtract",
|
|
1004
|
+
names: ["subtract"],
|
|
1005
|
+
category: "math",
|
|
1006
|
+
description: "Subtract rhs from lhs."
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
typeId: "divide",
|
|
1010
|
+
names: ["divide"],
|
|
1011
|
+
category: "math",
|
|
1012
|
+
description: "Divide lhs by rhs."
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
typeId: "min",
|
|
1016
|
+
names: ["min"],
|
|
1017
|
+
category: "math",
|
|
1018
|
+
description: "Minimum of all operands."
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
typeId: "max",
|
|
1022
|
+
names: ["max"],
|
|
1023
|
+
category: "math",
|
|
1024
|
+
description: "Maximum of all operands."
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
typeId: "modulo",
|
|
1028
|
+
names: ["modulo", "mod"],
|
|
1029
|
+
category: "math",
|
|
1030
|
+
description: "Modulo of lhs by rhs."
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
typeId: "greaterthan",
|
|
1034
|
+
names: ["greaterthan", "gt"],
|
|
1035
|
+
minArgs: 2,
|
|
1036
|
+
maxArgs: 2,
|
|
1037
|
+
category: "logic",
|
|
1038
|
+
description: "Return true when lhs > rhs."
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
typeId: "lessthan",
|
|
1042
|
+
names: ["lessthan", "lt"],
|
|
1043
|
+
minArgs: 2,
|
|
1044
|
+
maxArgs: 2,
|
|
1045
|
+
category: "logic",
|
|
1046
|
+
description: "Return true when lhs < rhs."
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
typeId: "equal",
|
|
1050
|
+
names: ["equal", "eq"],
|
|
1051
|
+
minArgs: 2,
|
|
1052
|
+
maxArgs: 2,
|
|
1053
|
+
category: "logic",
|
|
1054
|
+
description: "Return true when operands are equal."
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
typeId: "notequal",
|
|
1058
|
+
names: ["notequal", "neq", "ne"],
|
|
1059
|
+
minArgs: 2,
|
|
1060
|
+
maxArgs: 2,
|
|
1061
|
+
category: "logic",
|
|
1062
|
+
description: "Return true when operands differ."
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
typeId: "and",
|
|
1066
|
+
names: ["and"],
|
|
1067
|
+
minArgs: 2,
|
|
1068
|
+
maxArgs: 2,
|
|
1069
|
+
category: "logic",
|
|
1070
|
+
description: "Logical AND."
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
typeId: "or",
|
|
1074
|
+
names: ["or"],
|
|
1075
|
+
minArgs: 2,
|
|
1076
|
+
maxArgs: 2,
|
|
1077
|
+
category: "logic",
|
|
1078
|
+
description: "Logical OR."
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
typeId: "xor",
|
|
1082
|
+
names: ["xor"],
|
|
1083
|
+
minArgs: 2,
|
|
1084
|
+
maxArgs: 2,
|
|
1085
|
+
category: "logic",
|
|
1086
|
+
description: "Logical XOR."
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
typeId: "not",
|
|
1090
|
+
names: ["not"],
|
|
1091
|
+
minArgs: 1,
|
|
1092
|
+
maxArgs: 1,
|
|
1093
|
+
category: "logic",
|
|
1094
|
+
description: "Logical NOT of input."
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
typeId: "if",
|
|
1098
|
+
names: ["if"],
|
|
1099
|
+
minArgs: 2,
|
|
1100
|
+
maxArgs: 3,
|
|
1101
|
+
category: "logic",
|
|
1102
|
+
description: "Conditional branch with optional fallback."
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
typeId: "round",
|
|
1106
|
+
names: ["round"],
|
|
1107
|
+
category: "math",
|
|
1108
|
+
description: "Round input using the configured mode (floor/ceil/trunc)."
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
typeId: "case",
|
|
1112
|
+
names: ["case"],
|
|
1113
|
+
minArgs: 3,
|
|
1114
|
+
category: "logic",
|
|
1115
|
+
description: "Route the selector value to matching labeled branches."
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
typeId: "time",
|
|
1119
|
+
names: ["time"],
|
|
1120
|
+
minArgs: 0,
|
|
1121
|
+
maxArgs: 0,
|
|
1122
|
+
category: "time",
|
|
1123
|
+
description: "Graph time in seconds."
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
typeId: "oscillator",
|
|
1127
|
+
names: ["oscillator"],
|
|
1128
|
+
minArgs: 2,
|
|
1129
|
+
maxArgs: 2,
|
|
1130
|
+
category: "time",
|
|
1131
|
+
description: "Sine/cosine oscillator driven by time."
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
typeId: "spring",
|
|
1135
|
+
names: ["spring"],
|
|
1136
|
+
category: "time",
|
|
1137
|
+
description: "Spring toward the target with configurable stiffness and damping."
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
typeId: "damp",
|
|
1141
|
+
names: ["damp"],
|
|
1142
|
+
category: "time",
|
|
1143
|
+
description: "Damp input values toward zero at the specified rate."
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
typeId: "slew",
|
|
1147
|
+
names: ["slew"],
|
|
1148
|
+
category: "time",
|
|
1149
|
+
description: "Limit the rate of change between inputs over time."
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
typeId: "default-blend",
|
|
1153
|
+
names: ["defaultblend", "blend"],
|
|
1154
|
+
category: "utility",
|
|
1155
|
+
description: "Blend operand values using optional baseline/offset and weight inputs."
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
typeId: "blendweightedaverage",
|
|
1159
|
+
names: ["blendweightedaverage"],
|
|
1160
|
+
category: "utility",
|
|
1161
|
+
description: "Compute weighted average blends using aggregate sums."
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
typeId: "blendadditive",
|
|
1165
|
+
names: ["blendadditive"],
|
|
1166
|
+
category: "utility",
|
|
1167
|
+
description: "Additive blending helper."
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
typeId: "blendmultiply",
|
|
1171
|
+
names: ["blendmultiply"],
|
|
1172
|
+
category: "utility",
|
|
1173
|
+
description: "Multiplicative blending helper."
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
typeId: "blendweightedoverlay",
|
|
1177
|
+
names: ["blendweightedoverlay"],
|
|
1178
|
+
category: "utility",
|
|
1179
|
+
description: "Overlay blending helper driven by weighted sums."
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
typeId: "blendweightedaverageoverlay",
|
|
1183
|
+
names: ["blendweightedaverageoverlay"],
|
|
1184
|
+
category: "utility",
|
|
1185
|
+
description: "Overlay helper that adds averaged deltas to the base value."
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
typeId: "blendmax",
|
|
1189
|
+
names: ["blendmax"],
|
|
1190
|
+
category: "utility",
|
|
1191
|
+
description: "Select the operand whose effective weight is largest."
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
typeId: "vec3cross",
|
|
1195
|
+
names: ["vec3cross", "cross"],
|
|
1196
|
+
category: "vector",
|
|
1197
|
+
description: "Cross product of vectors A \xD7 B."
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
typeId: "vectoradd",
|
|
1201
|
+
names: ["vectoradd", "vadd"],
|
|
1202
|
+
category: "vector",
|
|
1203
|
+
description: "Element-wise sum of vectors."
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
typeId: "vectorsubtract",
|
|
1207
|
+
names: ["vectorsubtract", "vsub"],
|
|
1208
|
+
category: "vector",
|
|
1209
|
+
description: "Element-wise subtraction of vectors."
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
typeId: "vectormultiply",
|
|
1213
|
+
names: ["vectormultiply", "vmul"],
|
|
1214
|
+
category: "vector",
|
|
1215
|
+
description: "Element-wise multiplication of vectors."
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
typeId: "vectorscale",
|
|
1219
|
+
names: ["vectorscale", "vscale"],
|
|
1220
|
+
category: "vector",
|
|
1221
|
+
description: "Scale a vector by a scalar value."
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
typeId: "vectornormalize",
|
|
1225
|
+
names: ["vectornormalize", "vnormalize"],
|
|
1226
|
+
category: "vector",
|
|
1227
|
+
description: "Normalize a vector to unit length."
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
typeId: "vectordot",
|
|
1231
|
+
names: ["vectordot", "vdot"],
|
|
1232
|
+
category: "vector",
|
|
1233
|
+
description: "Dot product of vectors A \xB7 B."
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
typeId: "vectorlength",
|
|
1237
|
+
names: ["vectorlength", "vlength"],
|
|
1238
|
+
category: "vector",
|
|
1239
|
+
description: "Euclidean length of a vector."
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
typeId: "vectorindex",
|
|
1243
|
+
names: ["vectorindex", "vindex"],
|
|
1244
|
+
category: "vector",
|
|
1245
|
+
description: "Extract a component from a vector."
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
typeId: "vectorconstant",
|
|
1249
|
+
names: ["vectorconstant", "vconst"],
|
|
1250
|
+
category: "vector",
|
|
1251
|
+
description: "Constant vector value."
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
typeId: "join",
|
|
1255
|
+
names: ["join"],
|
|
1256
|
+
category: "vector",
|
|
1257
|
+
description: "Join scalar inputs into a vector."
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
typeId: "split",
|
|
1261
|
+
names: ["split"],
|
|
1262
|
+
category: "vector",
|
|
1263
|
+
description: "Split a vector into scalar outputs."
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
typeId: "vectormin",
|
|
1267
|
+
names: ["vectormin"],
|
|
1268
|
+
category: "vector",
|
|
1269
|
+
description: "Minimum component in a vector."
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
typeId: "vectormax",
|
|
1273
|
+
names: ["vectormax"],
|
|
1274
|
+
category: "vector",
|
|
1275
|
+
description: "Maximum component in a vector."
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
typeId: "vectormean",
|
|
1279
|
+
names: ["vectormean"],
|
|
1280
|
+
category: "vector",
|
|
1281
|
+
description: "Mean of the provided vector values."
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
typeId: "vectormedian",
|
|
1285
|
+
names: ["vectormedian"],
|
|
1286
|
+
category: "vector",
|
|
1287
|
+
description: "Median of the provided vector values."
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
typeId: "vectormode",
|
|
1291
|
+
names: ["vectormode"],
|
|
1292
|
+
category: "vector",
|
|
1293
|
+
description: "Mode of the provided vector values."
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
typeId: "weightedsumvector",
|
|
1297
|
+
names: ["weightedsumvector", "vectorsum"],
|
|
1298
|
+
category: "vector",
|
|
1299
|
+
description: "Weighted sum across vectors with optional masks."
|
|
1300
|
+
}
|
|
1301
|
+
];
|
|
1302
|
+
var SCALAR_FUNCTIONS = /* @__PURE__ */ new Map();
|
|
1303
|
+
var SCALAR_FUNCTION_VOCABULARY = [];
|
|
1304
|
+
for (const config of FUNCTION_CONFIGS) {
|
|
1305
|
+
const signature = (0, import_metadata.requireNodeSignature)(config.typeId);
|
|
1306
|
+
const signatureInputs = signature.inputs;
|
|
1307
|
+
let inputs = signatureInputs.map((input) => ({
|
|
1308
|
+
id: input.id,
|
|
1309
|
+
optional: Boolean(input.optional),
|
|
1310
|
+
valueType: inferExpressionValueType(input.ty)
|
|
1311
|
+
}));
|
|
1312
|
+
let variadic = signature.variadic_inputs ? {
|
|
1313
|
+
id: signature.variadic_inputs.id,
|
|
1314
|
+
min: signature.variadic_inputs.min,
|
|
1315
|
+
max: typeof signature.variadic_inputs.max === "number" ? signature.variadic_inputs.max : null,
|
|
1316
|
+
valueType: inferExpressionValueType(signature.variadic_inputs.ty)
|
|
1317
|
+
} : null;
|
|
1318
|
+
const baseRequiredInputs = inputs.filter((input) => !input.optional).length;
|
|
1319
|
+
const variadicMin = variadic?.min ?? 0;
|
|
1320
|
+
const derivedMin = baseRequiredInputs + variadicMin;
|
|
1321
|
+
let derivedMax;
|
|
1322
|
+
if (variadic) {
|
|
1323
|
+
derivedMax = variadic.max === null ? null : inputs.length + (variadic.max ?? 0);
|
|
1324
|
+
} else {
|
|
1325
|
+
derivedMax = inputs.length;
|
|
1326
|
+
}
|
|
1327
|
+
let params = buildParamArgumentSpecs(signature);
|
|
1328
|
+
const override = FUNCTION_OVERRIDES[config.typeId];
|
|
1329
|
+
if (override) {
|
|
1330
|
+
if (override.replaceInputs) {
|
|
1331
|
+
inputs = override.replaceInputs.map((input) => ({ ...input }));
|
|
1332
|
+
} else if (override.dropTrailingInputs) {
|
|
1333
|
+
const dropCount = Math.min(override.dropTrailingInputs, inputs.length);
|
|
1334
|
+
inputs = inputs.slice(0, inputs.length - dropCount);
|
|
1335
|
+
}
|
|
1336
|
+
if ("variadic" in override) {
|
|
1337
|
+
variadic = override.variadic ?? null;
|
|
1338
|
+
}
|
|
1339
|
+
if (override.params) {
|
|
1340
|
+
params = override.params.map((param) => ({ ...param }));
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
const requiredParamCount = params.filter((param) => !param.optional).length;
|
|
1344
|
+
const minArgs = override?.minArgs !== void 0 ? override.minArgs : config.minArgs !== void 0 ? config.minArgs : derivedMin + requiredParamCount;
|
|
1345
|
+
const maxArgs = override?.maxArgs !== void 0 ? override.maxArgs : config.maxArgs !== void 0 ? config.maxArgs : derivedMax === null ? null : derivedMax + params.length;
|
|
1346
|
+
const definition = {
|
|
1347
|
+
nodeType: config.typeId,
|
|
1348
|
+
inputs,
|
|
1349
|
+
variadic,
|
|
1350
|
+
params,
|
|
1351
|
+
minArgs,
|
|
1352
|
+
maxArgs,
|
|
1353
|
+
resultValueType: inferExpressionValueType(
|
|
1354
|
+
signature.outputs?.[0]?.ty ?? null
|
|
1355
|
+
)
|
|
1356
|
+
};
|
|
1357
|
+
if (config.typeId === "if" || config.typeId === "case") {
|
|
1358
|
+
const adjustedInputs = definition.inputs.map(
|
|
1359
|
+
(input, index) => {
|
|
1360
|
+
if (config.typeId === "if" && index === 0) {
|
|
1361
|
+
return input;
|
|
1362
|
+
}
|
|
1363
|
+
return {
|
|
1364
|
+
...input,
|
|
1365
|
+
valueType: "any"
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
);
|
|
1369
|
+
definition.inputs = adjustedInputs;
|
|
1370
|
+
if (definition.variadic) {
|
|
1371
|
+
definition.variadic = {
|
|
1372
|
+
...definition.variadic,
|
|
1373
|
+
valueType: "any"
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
definition.resultValueType = "any";
|
|
1377
|
+
}
|
|
1378
|
+
const names = new Set(
|
|
1379
|
+
[config.typeId, ...config.names].map((name) => name.toLowerCase())
|
|
1380
|
+
);
|
|
1381
|
+
names.forEach((name) => {
|
|
1382
|
+
SCALAR_FUNCTIONS.set(name, definition);
|
|
1383
|
+
});
|
|
1384
|
+
const orderedNames = Array.from(names);
|
|
1385
|
+
const displayName = orderedNames[0] ?? config.typeId;
|
|
1386
|
+
SCALAR_FUNCTION_VOCABULARY.push({
|
|
1387
|
+
name: displayName,
|
|
1388
|
+
aliases: orderedNames.slice(1),
|
|
1389
|
+
nodeType: config.typeId,
|
|
1390
|
+
category: config.category ?? "math",
|
|
1391
|
+
description: config.description
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
function buildParamArgumentSpecs(signature) {
|
|
1395
|
+
if (!signature || !Array.isArray(signature.params)) {
|
|
1396
|
+
return [];
|
|
1397
|
+
}
|
|
1398
|
+
const params = signature.params;
|
|
1399
|
+
return params.map((param) => ({
|
|
1400
|
+
id: param.id,
|
|
1401
|
+
label: param.label ?? param.id,
|
|
1402
|
+
doc: param.doc ?? void 0,
|
|
1403
|
+
optional: param.default_json !== void 0,
|
|
1404
|
+
valueType: inferExpressionValueType(param.ty),
|
|
1405
|
+
min: typeof param.min === "number" ? param.min : void 0,
|
|
1406
|
+
max: typeof param.max === "number" ? param.max : void 0
|
|
1407
|
+
}));
|
|
1408
|
+
}
|
|
1409
|
+
function inferExpressionValueType(ty) {
|
|
1410
|
+
if (!ty) {
|
|
1411
|
+
return "scalar";
|
|
1412
|
+
}
|
|
1413
|
+
const normalized = ty.toLowerCase();
|
|
1414
|
+
if (normalized === "any") {
|
|
1415
|
+
return "any";
|
|
1416
|
+
}
|
|
1417
|
+
if (normalized === "vector" || normalized === "vec2" || normalized === "vec3" || normalized === "vec4" || normalized === "quat" || normalized === "colorrgba" || normalized === "transform" || normalized === "array" || normalized === "list" || normalized === "record") {
|
|
1418
|
+
return "vector";
|
|
1419
|
+
}
|
|
1420
|
+
if (normalized === "bool" || normalized === "boolean") {
|
|
1421
|
+
return "boolean";
|
|
1422
|
+
}
|
|
1423
|
+
return "scalar";
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/expressionVariables.ts
|
|
1427
|
+
var DefaultExpressionVariableTable = class {
|
|
1428
|
+
constructor() {
|
|
1429
|
+
this.variables = /* @__PURE__ */ new Map();
|
|
1430
|
+
this.order = [];
|
|
1431
|
+
}
|
|
1432
|
+
registerSlotVariable(options) {
|
|
1433
|
+
this.upsert({
|
|
1434
|
+
name: options.name,
|
|
1435
|
+
kind: "slot",
|
|
1436
|
+
nodeId: options.nodeId,
|
|
1437
|
+
metadata: {
|
|
1438
|
+
slotId: options.slotId,
|
|
1439
|
+
slotAlias: options.slotAlias,
|
|
1440
|
+
inputId: options.inputId,
|
|
1441
|
+
targetId: options.targetId,
|
|
1442
|
+
animatableId: options.animatableId,
|
|
1443
|
+
component: options.component,
|
|
1444
|
+
valueType: options.valueType,
|
|
1445
|
+
remap: options.remap,
|
|
1446
|
+
autoRemap: options.autoRemap
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
registerReservedVariable(options) {
|
|
1451
|
+
this.upsert({
|
|
1452
|
+
name: options.name,
|
|
1453
|
+
kind: "reserved",
|
|
1454
|
+
nodeId: options.nodeId,
|
|
1455
|
+
description: options.description,
|
|
1456
|
+
metadata: {
|
|
1457
|
+
targetId: options.targetId,
|
|
1458
|
+
animatableId: options.animatableId,
|
|
1459
|
+
component: options.component
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
resolve(name) {
|
|
1464
|
+
return this.variables.get(name) ?? null;
|
|
1465
|
+
}
|
|
1466
|
+
resolveNodeId(name) {
|
|
1467
|
+
return this.variables.get(name)?.nodeId ?? null;
|
|
1468
|
+
}
|
|
1469
|
+
entries() {
|
|
1470
|
+
return this.order.map((name) => this.variables.get(name)).filter((entry) => Boolean(entry));
|
|
1471
|
+
}
|
|
1472
|
+
firstNodeId() {
|
|
1473
|
+
for (const name of this.order) {
|
|
1474
|
+
const nodeId = this.variables.get(name)?.nodeId;
|
|
1475
|
+
if (nodeId) {
|
|
1476
|
+
return nodeId;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
missing(names) {
|
|
1482
|
+
const missing = [];
|
|
1483
|
+
for (const name of names) {
|
|
1484
|
+
const entry = this.variables.get(name);
|
|
1485
|
+
if (!entry) {
|
|
1486
|
+
missing.push({ name, reason: "unknown" });
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (!entry.nodeId) {
|
|
1490
|
+
missing.push({
|
|
1491
|
+
name,
|
|
1492
|
+
reason: "unresolved",
|
|
1493
|
+
entry
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return missing;
|
|
1498
|
+
}
|
|
1499
|
+
upsert(entry) {
|
|
1500
|
+
if (!this.variables.has(entry.name)) {
|
|
1501
|
+
this.order.push(entry.name);
|
|
1502
|
+
}
|
|
1503
|
+
this.variables.set(entry.name, entry);
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
function createExpressionVariableTable() {
|
|
1507
|
+
return new DefaultExpressionVariableTable();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/expressionVocabulary.ts
|
|
1511
|
+
var RESERVED_EXPRESSION_VARIABLES = [
|
|
1512
|
+
{
|
|
1513
|
+
name: "self",
|
|
1514
|
+
description: "Output of the current binding.",
|
|
1515
|
+
scope: "binding",
|
|
1516
|
+
defaultValueType: "scalar",
|
|
1517
|
+
available: true
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
name: "time",
|
|
1521
|
+
description: "Elapsed time in seconds since the rig started.",
|
|
1522
|
+
scope: "graph",
|
|
1523
|
+
defaultValueType: "scalar",
|
|
1524
|
+
available: true
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
name: "deltaTime",
|
|
1528
|
+
description: "Seconds elapsed since the previous frame update.",
|
|
1529
|
+
scope: "graph",
|
|
1530
|
+
defaultValueType: "scalar",
|
|
1531
|
+
available: true
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
name: "frame",
|
|
1535
|
+
description: "Current frame counter.",
|
|
1536
|
+
scope: "graph",
|
|
1537
|
+
defaultValueType: "scalar",
|
|
1538
|
+
available: true
|
|
1539
|
+
}
|
|
1540
|
+
];
|
|
1541
|
+
|
|
1542
|
+
// src/graphBuilder.ts
|
|
1543
|
+
var import_metadata2 = require("@vizij/node-graph-wasm/metadata");
|
|
1544
|
+
|
|
1545
|
+
// src/ir/builder.ts
|
|
1546
|
+
var graphSequence = 0;
|
|
1547
|
+
function generateGraphId(faceId) {
|
|
1548
|
+
graphSequence += 1;
|
|
1549
|
+
return `ir_${faceId}_${graphSequence}`;
|
|
1550
|
+
}
|
|
1551
|
+
var DefaultIrGraphBuilder = class {
|
|
1552
|
+
constructor(options) {
|
|
1553
|
+
this.nodes = [];
|
|
1554
|
+
this.edges = [];
|
|
1555
|
+
this.constants = [];
|
|
1556
|
+
this.issues = [];
|
|
1557
|
+
this.faceId = options.faceId;
|
|
1558
|
+
this.summary = {
|
|
1559
|
+
faceId: options.faceId,
|
|
1560
|
+
inputs: [],
|
|
1561
|
+
outputs: [],
|
|
1562
|
+
bindings: []
|
|
1563
|
+
};
|
|
1564
|
+
this.metadata = {
|
|
1565
|
+
source: options.source ?? "ir-builder",
|
|
1566
|
+
registryVersion: options.registryVersion,
|
|
1567
|
+
generatedAt: options.generatedAt,
|
|
1568
|
+
annotations: options.annotations
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
addNode(node) {
|
|
1572
|
+
this.nodes.push(node);
|
|
1573
|
+
return node;
|
|
1574
|
+
}
|
|
1575
|
+
addEdge(edge) {
|
|
1576
|
+
this.edges.push(edge);
|
|
1577
|
+
return edge;
|
|
1578
|
+
}
|
|
1579
|
+
addConstant(constant) {
|
|
1580
|
+
this.constants.push(constant);
|
|
1581
|
+
return constant;
|
|
1582
|
+
}
|
|
1583
|
+
addIssue(issue) {
|
|
1584
|
+
this.issues.push(issue);
|
|
1585
|
+
return issue;
|
|
1586
|
+
}
|
|
1587
|
+
setSummary(summary) {
|
|
1588
|
+
this.summary = summary;
|
|
1589
|
+
}
|
|
1590
|
+
updateMetadata(metadata) {
|
|
1591
|
+
this.metadata = {
|
|
1592
|
+
...this.metadata,
|
|
1593
|
+
...metadata
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
build() {
|
|
1597
|
+
return {
|
|
1598
|
+
id: generateGraphId(this.faceId),
|
|
1599
|
+
faceId: this.faceId,
|
|
1600
|
+
nodes: [...this.nodes],
|
|
1601
|
+
edges: [...this.edges],
|
|
1602
|
+
constants: [...this.constants],
|
|
1603
|
+
issues: [...this.issues],
|
|
1604
|
+
summary: { ...this.summary },
|
|
1605
|
+
metadata: { ...this.metadata }
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
function createIrGraphBuilder(options) {
|
|
1610
|
+
return new DefaultIrGraphBuilder(options);
|
|
1611
|
+
}
|
|
1612
|
+
function toIrBindingSummary(summaries) {
|
|
1613
|
+
return summaries.map((summary) => ({
|
|
1614
|
+
...summary,
|
|
1615
|
+
remap: { ...summary.remap },
|
|
1616
|
+
issues: summary.issues ? [...summary.issues] : void 0
|
|
1617
|
+
}));
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// src/ir/compiler.ts
|
|
1621
|
+
function compileIrGraph(graph, options = {}) {
|
|
1622
|
+
const preferLegacy = options.preferLegacySpec === true;
|
|
1623
|
+
const legacySpec = graph.legacy?.spec;
|
|
1624
|
+
if (preferLegacy && legacySpec) {
|
|
1625
|
+
return {
|
|
1626
|
+
spec: legacySpec,
|
|
1627
|
+
issues: [...graph.issues]
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
const convertedNodes = graph.nodes.map(convertIrNodeToNodeSpec);
|
|
1631
|
+
const existingNodeIds = new Set(convertedNodes.map((node) => node.id));
|
|
1632
|
+
graph.constants.forEach((constant) => {
|
|
1633
|
+
if (existingNodeIds.has(constant.id)) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
convertedNodes.push(convertIrConstantToNodeSpec(constant));
|
|
1637
|
+
});
|
|
1638
|
+
const convertedEdges = graph.edges.map(convertIrEdgeToGraphEdge);
|
|
1639
|
+
const spec = {
|
|
1640
|
+
nodes: convertedNodes,
|
|
1641
|
+
edges: convertedEdges.length > 0 ? convertedEdges : void 0
|
|
1642
|
+
};
|
|
1643
|
+
inlineSingleUseConstants(spec);
|
|
1644
|
+
const metadata = extractGraphSpecMetadata(graph);
|
|
1645
|
+
if (metadata !== void 0) {
|
|
1646
|
+
spec.metadata = metadata;
|
|
1647
|
+
}
|
|
1648
|
+
return {
|
|
1649
|
+
spec,
|
|
1650
|
+
issues: [...graph.issues]
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
function convertIrNodeToNodeSpec(node) {
|
|
1654
|
+
const spec = {
|
|
1655
|
+
id: node.id,
|
|
1656
|
+
type: node.type
|
|
1657
|
+
};
|
|
1658
|
+
if (node.params) {
|
|
1659
|
+
spec.params = cloneJsonLike(node.params);
|
|
1660
|
+
}
|
|
1661
|
+
if (node.inputDefaults) {
|
|
1662
|
+
spec.input_defaults = cloneJsonLike(node.inputDefaults);
|
|
1663
|
+
}
|
|
1664
|
+
if (node.metadata) {
|
|
1665
|
+
spec.metadata = cloneJsonLike(node.metadata);
|
|
1666
|
+
}
|
|
1667
|
+
return spec;
|
|
1668
|
+
}
|
|
1669
|
+
function convertIrConstantToNodeSpec(constant) {
|
|
1670
|
+
const spec = {
|
|
1671
|
+
id: constant.id,
|
|
1672
|
+
type: "constant",
|
|
1673
|
+
params: {
|
|
1674
|
+
value: constant.value
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
if (constant.metadata) {
|
|
1678
|
+
spec.metadata = cloneJsonLike(constant.metadata);
|
|
1679
|
+
}
|
|
1680
|
+
return spec;
|
|
1681
|
+
}
|
|
1682
|
+
function convertIrEdgeToGraphEdge(edge) {
|
|
1683
|
+
return {
|
|
1684
|
+
from: {
|
|
1685
|
+
node_id: edge.from.nodeId,
|
|
1686
|
+
output: edge.from.portId
|
|
1687
|
+
},
|
|
1688
|
+
to: {
|
|
1689
|
+
node_id: edge.to.nodeId,
|
|
1690
|
+
input: edge.to.portId
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function extractGraphSpecMetadata(graph) {
|
|
1695
|
+
const annotations = graph.metadata.annotations;
|
|
1696
|
+
if (!annotations?.graphSpecMetadata) {
|
|
1697
|
+
return void 0;
|
|
1698
|
+
}
|
|
1699
|
+
return cloneJsonLike(annotations.graphSpecMetadata);
|
|
1700
|
+
}
|
|
1701
|
+
function cloneJsonLike(value) {
|
|
1702
|
+
if (value === void 0 || value === null) {
|
|
1703
|
+
return value;
|
|
1704
|
+
}
|
|
1705
|
+
return JSON.parse(JSON.stringify(value));
|
|
1706
|
+
}
|
|
1707
|
+
function inlineSingleUseConstants(spec) {
|
|
1708
|
+
const nodes = spec.nodes ?? [];
|
|
1709
|
+
const edges = spec.edges ? [...spec.edges] : [];
|
|
1710
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
1711
|
+
const constantUsage = /* @__PURE__ */ new Map();
|
|
1712
|
+
edges.forEach((edge) => {
|
|
1713
|
+
const source = nodeById.get(edge.from.node_id);
|
|
1714
|
+
if (source?.type === "constant") {
|
|
1715
|
+
constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
const updatedEdges = [];
|
|
1719
|
+
const constantsToRemove = /* @__PURE__ */ new Set();
|
|
1720
|
+
edges.forEach((edge) => {
|
|
1721
|
+
const source = nodeById.get(edge.from.node_id);
|
|
1722
|
+
if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
|
|
1723
|
+
const target = nodeById.get(edge.to.node_id);
|
|
1724
|
+
if (target) {
|
|
1725
|
+
const value = source.params.value;
|
|
1726
|
+
if (value !== void 0) {
|
|
1727
|
+
target.input_defaults = {
|
|
1728
|
+
...target.input_defaults ?? {},
|
|
1729
|
+
[edge.to.input ?? "in"]: value
|
|
1730
|
+
};
|
|
1731
|
+
nodeById.set(target.id, target);
|
|
1732
|
+
constantsToRemove.add(source.id);
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
updatedEdges.push(edge);
|
|
1738
|
+
});
|
|
1739
|
+
spec.nodes = nodes.filter((node) => !constantsToRemove.has(node.id));
|
|
1740
|
+
spec.edges = updatedEdges.length > 0 ? updatedEdges : void 0;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// src/bindingMetadata.ts
|
|
1744
|
+
function cloneOperand(entry) {
|
|
1745
|
+
return JSON.parse(JSON.stringify(entry));
|
|
1746
|
+
}
|
|
1747
|
+
function describeSlot(entry) {
|
|
1748
|
+
const metadata = entry.metadata;
|
|
1749
|
+
return {
|
|
1750
|
+
kind: "slot",
|
|
1751
|
+
ref: entry.name,
|
|
1752
|
+
alias: metadata?.slotAlias ?? entry.name,
|
|
1753
|
+
slotId: metadata?.slotId,
|
|
1754
|
+
inputId: metadata?.inputId ?? null,
|
|
1755
|
+
valueType: metadata?.valueType
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
function describeReserved(entry) {
|
|
1759
|
+
return {
|
|
1760
|
+
kind: "reserved",
|
|
1761
|
+
ref: entry.name,
|
|
1762
|
+
description: entry.description
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
function describeReference(node, variables) {
|
|
1766
|
+
const entry = variables.resolve(node.name);
|
|
1767
|
+
if (!entry) {
|
|
1768
|
+
return {
|
|
1769
|
+
kind: "unknown",
|
|
1770
|
+
ref: node.name
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
if (entry.kind === "slot") {
|
|
1774
|
+
return describeSlot(entry);
|
|
1775
|
+
}
|
|
1776
|
+
if (entry.kind === "reserved") {
|
|
1777
|
+
return describeReserved(entry);
|
|
1778
|
+
}
|
|
1779
|
+
return {
|
|
1780
|
+
kind: "unknown",
|
|
1781
|
+
ref: entry.name
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
function stringifyExpression(node) {
|
|
1785
|
+
switch (node.type) {
|
|
1786
|
+
case "Literal":
|
|
1787
|
+
return Number.isFinite(node.value) ? `${node.value}` : "0";
|
|
1788
|
+
case "VectorLiteral":
|
|
1789
|
+
return `vec(${node.values.join(", ")})`;
|
|
1790
|
+
case "Reference":
|
|
1791
|
+
return node.name;
|
|
1792
|
+
case "Unary":
|
|
1793
|
+
return `${node.operator}${stringifyExpression(node.operand)}`;
|
|
1794
|
+
case "Binary":
|
|
1795
|
+
return `${stringifyExpression(node.left)} ${node.operator} ${stringifyExpression(node.right)}`;
|
|
1796
|
+
case "Function":
|
|
1797
|
+
return `${node.name}(${node.args.map(stringifyExpression).join(", ")})`;
|
|
1798
|
+
default:
|
|
1799
|
+
return "";
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function describeOperand(node, variables) {
|
|
1803
|
+
switch (node.type) {
|
|
1804
|
+
case "Literal":
|
|
1805
|
+
return {
|
|
1806
|
+
kind: "literal",
|
|
1807
|
+
literalValue: node.value
|
|
1808
|
+
};
|
|
1809
|
+
case "Reference":
|
|
1810
|
+
return describeReference(node, variables);
|
|
1811
|
+
case "Unary":
|
|
1812
|
+
case "Binary":
|
|
1813
|
+
case "Function":
|
|
1814
|
+
case "VectorLiteral":
|
|
1815
|
+
return {
|
|
1816
|
+
kind: "expression",
|
|
1817
|
+
expression: stringifyExpression(node)
|
|
1818
|
+
};
|
|
1819
|
+
default:
|
|
1820
|
+
return { kind: "unknown" };
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
function describeCaseExpression(node, variables) {
|
|
1824
|
+
if (node.type !== "Function") {
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
if (node.name.toLowerCase() !== "case") {
|
|
1828
|
+
return null;
|
|
1829
|
+
}
|
|
1830
|
+
if ((node.args?.length ?? 0) < 3) {
|
|
1831
|
+
return null;
|
|
1832
|
+
}
|
|
1833
|
+
const [selector, defaultBranch, ...branches] = node.args;
|
|
1834
|
+
return {
|
|
1835
|
+
kind: "case",
|
|
1836
|
+
selector: describeOperand(selector, variables),
|
|
1837
|
+
defaultBranch: describeOperand(defaultBranch, variables),
|
|
1838
|
+
branches: branches.map((branch) => describeOperand(branch, variables))
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function buildBindingMetadataFromExpression(node, variables) {
|
|
1842
|
+
if (!node) {
|
|
1843
|
+
return void 0;
|
|
1844
|
+
}
|
|
1845
|
+
const caseMetadata = describeCaseExpression(node, variables);
|
|
1846
|
+
if (!caseMetadata) {
|
|
1847
|
+
return void 0;
|
|
1848
|
+
}
|
|
1849
|
+
return {
|
|
1850
|
+
expression: {
|
|
1851
|
+
case: {
|
|
1852
|
+
kind: "case",
|
|
1853
|
+
selector: caseMetadata.selector ? cloneOperand(caseMetadata.selector) : void 0,
|
|
1854
|
+
defaultBranch: caseMetadata.defaultBranch ? cloneOperand(caseMetadata.defaultBranch) : void 0,
|
|
1855
|
+
branches: caseMetadata.branches.map(cloneOperand)
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// src/graphBuilder.ts
|
|
1862
|
+
function evaluateBinding({
|
|
1863
|
+
binding,
|
|
1864
|
+
target,
|
|
1865
|
+
targetId,
|
|
1866
|
+
animatableId,
|
|
1867
|
+
component,
|
|
1868
|
+
safeId,
|
|
1869
|
+
context,
|
|
1870
|
+
selfNodeId
|
|
1871
|
+
}) {
|
|
1872
|
+
const { nodes, edges, ensureInputNode, bindingIssues, summaryBindings } = context;
|
|
1873
|
+
const exprContext = {
|
|
1874
|
+
componentSafeId: safeId,
|
|
1875
|
+
nodes,
|
|
1876
|
+
edges,
|
|
1877
|
+
constants: /* @__PURE__ */ new Map(),
|
|
1878
|
+
counter: 0,
|
|
1879
|
+
reservedNodes: /* @__PURE__ */ new Map(),
|
|
1880
|
+
nodeValueTypes: /* @__PURE__ */ new Map(),
|
|
1881
|
+
slotRemapNodes: /* @__PURE__ */ new Map(),
|
|
1882
|
+
graphReservedNodes: context.graphReservedNodes,
|
|
1883
|
+
generateReservedNodeId: context.generateReservedNodeId
|
|
1884
|
+
};
|
|
1885
|
+
const targetValueType = target.valueType === "vector" ? "vector" : "scalar";
|
|
1886
|
+
const variableTable = createExpressionVariableTable();
|
|
1887
|
+
const slotSummaries = [];
|
|
1888
|
+
const expressionIssues = [];
|
|
1889
|
+
const rawExpression = typeof binding.expression === "string" ? binding.expression : "";
|
|
1890
|
+
const trimmedExpression = rawExpression.trim();
|
|
1891
|
+
let hasActiveSlot = false;
|
|
1892
|
+
binding.slots.forEach((slot, index) => {
|
|
1893
|
+
const aliasBase = slot.alias?.trim() ?? "";
|
|
1894
|
+
const fallbackAlias = `s${index + 1}`;
|
|
1895
|
+
const alias = aliasBase.length > 0 ? aliasBase : fallbackAlias;
|
|
1896
|
+
const slotId = slot.id && slot.id.length > 0 ? slot.id : alias;
|
|
1897
|
+
const slotValueType = slot.valueType === "vector" ? "vector" : "scalar";
|
|
1898
|
+
let slotOutputId;
|
|
1899
|
+
if (slot.inputId === import_utils3.SELF_BINDING_ID) {
|
|
1900
|
+
if (selfNodeId) {
|
|
1901
|
+
slotOutputId = selfNodeId;
|
|
1902
|
+
hasActiveSlot = true;
|
|
1903
|
+
setNodeValueType(
|
|
1904
|
+
exprContext,
|
|
1905
|
+
slotOutputId,
|
|
1906
|
+
slotValueType === "vector" ? "vector" : "scalar"
|
|
1907
|
+
);
|
|
1908
|
+
} else {
|
|
1909
|
+
expressionIssues.push("Self reference unavailable for this input.");
|
|
1910
|
+
slotOutputId = getConstantNodeId(exprContext, target.defaultValue);
|
|
1911
|
+
}
|
|
1912
|
+
} else if (slot.inputId) {
|
|
1913
|
+
const inputNode = ensureInputNode(slot.inputId);
|
|
1914
|
+
if (inputNode) {
|
|
1915
|
+
slotOutputId = inputNode.nodeId;
|
|
1916
|
+
hasActiveSlot = true;
|
|
1917
|
+
setNodeValueType(
|
|
1918
|
+
exprContext,
|
|
1919
|
+
slotOutputId,
|
|
1920
|
+
slotValueType === "vector" ? "vector" : "scalar"
|
|
1921
|
+
);
|
|
1922
|
+
} else {
|
|
1923
|
+
expressionIssues.push(`Missing standard input "${slot.inputId}".`);
|
|
1924
|
+
slotOutputId = getConstantNodeId(exprContext, 0);
|
|
1925
|
+
}
|
|
1926
|
+
} else {
|
|
1927
|
+
slotOutputId = getConstantNodeId(exprContext, 0);
|
|
1928
|
+
}
|
|
1929
|
+
variableTable.registerSlotVariable({
|
|
1930
|
+
name: alias,
|
|
1931
|
+
nodeId: slotOutputId,
|
|
1932
|
+
slotId,
|
|
1933
|
+
slotAlias: alias,
|
|
1934
|
+
inputId: slot.inputId ?? null,
|
|
1935
|
+
targetId,
|
|
1936
|
+
animatableId,
|
|
1937
|
+
component,
|
|
1938
|
+
valueType: slotValueType,
|
|
1939
|
+
remap: { ...slot.remap },
|
|
1940
|
+
autoRemap: slot.inputId !== import_utils3.SELF_BINDING_ID && slotValueType === "scalar"
|
|
1941
|
+
});
|
|
1942
|
+
setNodeValueType(
|
|
1943
|
+
exprContext,
|
|
1944
|
+
slotOutputId,
|
|
1945
|
+
slotValueType === "vector" ? "vector" : "scalar"
|
|
1946
|
+
);
|
|
1947
|
+
slotSummaries.push({
|
|
1948
|
+
targetId,
|
|
1949
|
+
animatableId,
|
|
1950
|
+
component,
|
|
1951
|
+
slotId,
|
|
1952
|
+
slotAlias: alias,
|
|
1953
|
+
inputId: slot.inputId ?? null,
|
|
1954
|
+
remap: { ...slot.remap },
|
|
1955
|
+
expression: trimmedExpression,
|
|
1956
|
+
valueType: slotValueType,
|
|
1957
|
+
nodeId: slotOutputId,
|
|
1958
|
+
expressionNodeId: slotOutputId
|
|
1959
|
+
});
|
|
1960
|
+
});
|
|
1961
|
+
if (slotSummaries.length === 0) {
|
|
1962
|
+
const alias = PRIMARY_SLOT_ALIAS;
|
|
1963
|
+
const constantId = getConstantNodeId(exprContext, 0);
|
|
1964
|
+
variableTable.registerSlotVariable({
|
|
1965
|
+
name: alias,
|
|
1966
|
+
nodeId: constantId,
|
|
1967
|
+
slotId: PRIMARY_SLOT_ID,
|
|
1968
|
+
slotAlias: alias,
|
|
1969
|
+
inputId: null,
|
|
1970
|
+
targetId,
|
|
1971
|
+
animatableId,
|
|
1972
|
+
component,
|
|
1973
|
+
valueType: targetValueType,
|
|
1974
|
+
remap: createDefaultRemap(target),
|
|
1975
|
+
autoRemap: false
|
|
1976
|
+
});
|
|
1977
|
+
setNodeValueType(
|
|
1978
|
+
exprContext,
|
|
1979
|
+
constantId,
|
|
1980
|
+
targetValueType === "vector" ? "vector" : "scalar"
|
|
1981
|
+
);
|
|
1982
|
+
slotSummaries.push({
|
|
1983
|
+
targetId,
|
|
1984
|
+
animatableId,
|
|
1985
|
+
component,
|
|
1986
|
+
slotId: PRIMARY_SLOT_ID,
|
|
1987
|
+
slotAlias: alias,
|
|
1988
|
+
inputId: null,
|
|
1989
|
+
remap: createDefaultRemap(target),
|
|
1990
|
+
expression: trimmedExpression,
|
|
1991
|
+
valueType: targetValueType,
|
|
1992
|
+
nodeId: constantId,
|
|
1993
|
+
expressionNodeId: constantId
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
RESERVED_EXPRESSION_VARIABLES.forEach((reserved) => {
|
|
1997
|
+
if (reserved.available === false) {
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
let nodeId = null;
|
|
2001
|
+
if (reserved.name === "self") {
|
|
2002
|
+
nodeId = selfNodeId ?? null;
|
|
2003
|
+
} else {
|
|
2004
|
+
nodeId = ensureReservedVariableNode(reserved.name, exprContext);
|
|
2005
|
+
}
|
|
2006
|
+
variableTable.registerReservedVariable({
|
|
2007
|
+
name: reserved.name,
|
|
2008
|
+
nodeId,
|
|
2009
|
+
description: reserved.description,
|
|
2010
|
+
targetId: reserved.scope === "binding" ? targetId : void 0,
|
|
2011
|
+
animatableId: reserved.scope === "binding" ? animatableId : void 0,
|
|
2012
|
+
component: reserved.scope === "binding" ? component : void 0
|
|
2013
|
+
});
|
|
2014
|
+
});
|
|
2015
|
+
const defaultAlias = slotSummaries[0]?.slotAlias ?? PRIMARY_SLOT_ALIAS;
|
|
2016
|
+
const expressionText = trimmedExpression.length > 0 ? trimmedExpression : defaultAlias;
|
|
2017
|
+
const parseResult = parseControlExpression(expressionText);
|
|
2018
|
+
let expressionAst = null;
|
|
2019
|
+
if (parseResult.node && parseResult.errors.length === 0) {
|
|
2020
|
+
const references = Array.from(
|
|
2021
|
+
collectExpressionReferences(parseResult.node)
|
|
2022
|
+
);
|
|
2023
|
+
const missing = variableTable.missing(references);
|
|
2024
|
+
if (missing.length === 0) {
|
|
2025
|
+
expressionAst = parseResult.node;
|
|
2026
|
+
} else {
|
|
2027
|
+
validateLiteralParamArguments(parseResult.node, expressionIssues);
|
|
2028
|
+
missing.forEach((missingVar) => {
|
|
2029
|
+
if (missingVar.reason === "unresolved" && missingVar.entry?.kind === "reserved") {
|
|
2030
|
+
expressionIssues.push(
|
|
2031
|
+
`Reserved variable "${missingVar.name}" is unavailable for this binding.`
|
|
2032
|
+
);
|
|
2033
|
+
} else {
|
|
2034
|
+
expressionIssues.push(`Unknown control "${missingVar.name}".`);
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
} else {
|
|
2039
|
+
parseResult.errors.forEach((error) => {
|
|
2040
|
+
expressionIssues.push(error.message);
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
const expressionMetadata = buildBindingMetadataFromExpression(
|
|
2044
|
+
expressionAst,
|
|
2045
|
+
variableTable
|
|
2046
|
+
);
|
|
2047
|
+
let valueNodeId = null;
|
|
2048
|
+
if (expressionAst) {
|
|
2049
|
+
valueNodeId = materializeExpression(
|
|
2050
|
+
expressionAst,
|
|
2051
|
+
exprContext,
|
|
2052
|
+
variableTable,
|
|
2053
|
+
expressionIssues
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
if (!valueNodeId) {
|
|
2057
|
+
const fallbackNodeId = variableTable.resolveNodeId(defaultAlias) ?? variableTable.firstNodeId();
|
|
2058
|
+
valueNodeId = fallbackNodeId ?? getConstantNodeId(exprContext, 0);
|
|
2059
|
+
}
|
|
2060
|
+
const issuesCopy = expressionIssues.length ? [...new Set(expressionIssues)] : void 0;
|
|
2061
|
+
slotSummaries.forEach((summary) => {
|
|
2062
|
+
summary.expression = expressionText;
|
|
2063
|
+
summary.expressionNodeId = valueNodeId;
|
|
2064
|
+
if (expressionMetadata) {
|
|
2065
|
+
summary.metadata = expressionMetadata;
|
|
2066
|
+
}
|
|
2067
|
+
if (issuesCopy && issuesCopy.length > 0) {
|
|
2068
|
+
summary.issues = issuesCopy;
|
|
2069
|
+
const issueSet = bindingIssues.get(summary.targetId) ?? /* @__PURE__ */ new Set();
|
|
2070
|
+
issuesCopy.forEach((issue) => issueSet.add(issue));
|
|
2071
|
+
bindingIssues.set(summary.targetId, issueSet);
|
|
2072
|
+
}
|
|
2073
|
+
});
|
|
2074
|
+
summaryBindings.push(...slotSummaries);
|
|
2075
|
+
return {
|
|
2076
|
+
valueNodeId,
|
|
2077
|
+
hasActiveSlot
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
function sanitizeNodeId(value) {
|
|
2081
|
+
return value.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
2082
|
+
}
|
|
2083
|
+
function buildRigInputPath(faceId, inputPath) {
|
|
2084
|
+
let trimmed = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
|
|
2085
|
+
if (!trimmed) {
|
|
2086
|
+
return `rig/${faceId}`;
|
|
2087
|
+
}
|
|
2088
|
+
while (trimmed.startsWith("rig/")) {
|
|
2089
|
+
const segments = trimmed.split("/");
|
|
2090
|
+
if (segments.length >= 3) {
|
|
2091
|
+
const existingFaceId = segments[1];
|
|
2092
|
+
const remainder = segments.slice(2).join("/");
|
|
2093
|
+
if (existingFaceId === faceId) {
|
|
2094
|
+
return trimmed;
|
|
2095
|
+
}
|
|
2096
|
+
trimmed = remainder || "";
|
|
2097
|
+
} else {
|
|
2098
|
+
trimmed = segments.slice(1).join("/");
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
const suffix = trimmed ? `/${trimmed}` : "";
|
|
2102
|
+
return `rig/${faceId}${suffix}`;
|
|
2103
|
+
}
|
|
2104
|
+
function getComponentOrder(animatable) {
|
|
2105
|
+
switch (animatable.type) {
|
|
2106
|
+
case "vector2":
|
|
2107
|
+
return ["x", "y"];
|
|
2108
|
+
case "vector3":
|
|
2109
|
+
case "euler":
|
|
2110
|
+
return ["x", "y", "z"];
|
|
2111
|
+
case "rgb":
|
|
2112
|
+
return ["r", "g", "b"];
|
|
2113
|
+
default:
|
|
2114
|
+
return null;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
function isComponentRecord(value) {
|
|
2118
|
+
return typeof value === "object" && value !== null;
|
|
2119
|
+
}
|
|
2120
|
+
function componentIndex(component) {
|
|
2121
|
+
switch (component) {
|
|
2122
|
+
case "x":
|
|
2123
|
+
case "r":
|
|
2124
|
+
return 0;
|
|
2125
|
+
case "y":
|
|
2126
|
+
case "g":
|
|
2127
|
+
return 1;
|
|
2128
|
+
default:
|
|
2129
|
+
return 2;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
function extractComponentDefault(value, component) {
|
|
2133
|
+
if (typeof value === "number") {
|
|
2134
|
+
return value;
|
|
2135
|
+
}
|
|
2136
|
+
if (Array.isArray(value)) {
|
|
2137
|
+
const candidate = value[componentIndex(component)];
|
|
2138
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
2139
|
+
return candidate;
|
|
2140
|
+
}
|
|
2141
|
+
return 0;
|
|
2142
|
+
}
|
|
2143
|
+
if (value && typeof value === "object") {
|
|
2144
|
+
if (!isComponentRecord(value)) {
|
|
2145
|
+
return 0;
|
|
2146
|
+
}
|
|
2147
|
+
const direct = value[component];
|
|
2148
|
+
if (typeof direct === "number" && Number.isFinite(direct)) {
|
|
2149
|
+
return direct;
|
|
2150
|
+
}
|
|
2151
|
+
const alt = component === "x" ? value.r : component === "y" ? value.g : value.b;
|
|
2152
|
+
if (typeof alt === "number" && Number.isFinite(alt)) {
|
|
2153
|
+
return alt;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
return 0;
|
|
2157
|
+
}
|
|
2158
|
+
function setNodeValueType(context, nodeId, valueType) {
|
|
2159
|
+
context.nodeValueTypes.set(nodeId, valueType);
|
|
2160
|
+
}
|
|
2161
|
+
function getNodeValueType(context, nodeId) {
|
|
2162
|
+
return context.nodeValueTypes.get(nodeId) ?? "any";
|
|
2163
|
+
}
|
|
2164
|
+
function matchesValueType(actual, expected) {
|
|
2165
|
+
if (expected === "any" || actual === "any") {
|
|
2166
|
+
return true;
|
|
2167
|
+
}
|
|
2168
|
+
if (expected === "scalar") {
|
|
2169
|
+
return actual === "scalar" || actual === "boolean";
|
|
2170
|
+
}
|
|
2171
|
+
return actual === expected;
|
|
2172
|
+
}
|
|
2173
|
+
function ensureOperandValueType(context, operandId, expected, functionName, inputId, issues) {
|
|
2174
|
+
if (expected === "any") {
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
const actual = getNodeValueType(context, operandId);
|
|
2178
|
+
if (matchesValueType(actual, expected)) {
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
const expectation = expected === "vector" ? "a vector" : expected === "boolean" ? "a boolean" : "a scalar";
|
|
2182
|
+
issues.push(
|
|
2183
|
+
`Function "${functionName}" expects ${expectation} input for "${inputId}", but the expression produced ${actual}.`
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
function resolveSlotReferenceNode(entry, fallbackNodeId, context) {
|
|
2187
|
+
if (entry.kind !== "slot") {
|
|
2188
|
+
return fallbackNodeId;
|
|
2189
|
+
}
|
|
2190
|
+
const metadata = entry.metadata;
|
|
2191
|
+
if (!metadata || metadata.autoRemap === false || !metadata.remap) {
|
|
2192
|
+
return fallbackNodeId;
|
|
2193
|
+
}
|
|
2194
|
+
if (metadata.valueType !== "scalar") {
|
|
2195
|
+
return fallbackNodeId;
|
|
2196
|
+
}
|
|
2197
|
+
if (isIdentityRemap(metadata.remap)) {
|
|
2198
|
+
return fallbackNodeId;
|
|
2199
|
+
}
|
|
2200
|
+
const cacheKey = metadata.slotId ?? entry.name;
|
|
2201
|
+
const existing = context.slotRemapNodes.get(cacheKey);
|
|
2202
|
+
if (existing) {
|
|
2203
|
+
return existing;
|
|
2204
|
+
}
|
|
2205
|
+
const remapNodeId = createSlotRemapNode(
|
|
2206
|
+
context,
|
|
2207
|
+
fallbackNodeId,
|
|
2208
|
+
metadata.remap,
|
|
2209
|
+
metadata.slotAlias ?? cacheKey
|
|
2210
|
+
);
|
|
2211
|
+
context.slotRemapNodes.set(cacheKey, remapNodeId);
|
|
2212
|
+
return remapNodeId;
|
|
2213
|
+
}
|
|
2214
|
+
function createSlotRemapNode(context, sourceNodeId, remap, slotKey) {
|
|
2215
|
+
const safeSlotKey = sanitizeNodeId(slotKey || "slot");
|
|
2216
|
+
const nodeId = `slot_remap_${safeSlotKey}_${context.counter++}`;
|
|
2217
|
+
context.nodes.push({
|
|
2218
|
+
id: nodeId,
|
|
2219
|
+
type: "centered_remap",
|
|
2220
|
+
inputDefaults: {
|
|
2221
|
+
in_low: remap.inLow,
|
|
2222
|
+
in_anchor: remap.inAnchor,
|
|
2223
|
+
in_high: remap.inHigh,
|
|
2224
|
+
out_low: remap.outLow,
|
|
2225
|
+
out_anchor: remap.outAnchor,
|
|
2226
|
+
out_high: remap.outHigh
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
context.edges.push({
|
|
2230
|
+
from: { nodeId: sourceNodeId },
|
|
2231
|
+
to: { nodeId, portId: "in" }
|
|
2232
|
+
});
|
|
2233
|
+
setNodeValueType(context, nodeId, "scalar");
|
|
2234
|
+
return nodeId;
|
|
2235
|
+
}
|
|
2236
|
+
function isIdentityRemap(remap) {
|
|
2237
|
+
return nearlyEqual(remap.inLow, remap.outLow) && nearlyEqual(remap.inAnchor, remap.outAnchor) && nearlyEqual(remap.inHigh, remap.outHigh);
|
|
2238
|
+
}
|
|
2239
|
+
function nearlyEqual(a, b, epsilon = 1e-4) {
|
|
2240
|
+
return Math.abs(a - b) <= epsilon;
|
|
2241
|
+
}
|
|
2242
|
+
var REMAP_FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
2243
|
+
"piecewise_remap",
|
|
2244
|
+
"centered_remap",
|
|
2245
|
+
"remap"
|
|
2246
|
+
]);
|
|
2247
|
+
function shouldSkipAutoRemapForArgument(nodeType, index) {
|
|
2248
|
+
if (!REMAP_FUNCTION_NODE_TYPES.has(nodeType)) {
|
|
2249
|
+
return false;
|
|
2250
|
+
}
|
|
2251
|
+
return index === 0;
|
|
2252
|
+
}
|
|
2253
|
+
function getConstantNodeId(context, value) {
|
|
2254
|
+
const key = Number.isFinite(value) ? value.toString() : "NaN";
|
|
2255
|
+
const existing = context.constants.get(key);
|
|
2256
|
+
if (existing) {
|
|
2257
|
+
return existing;
|
|
2258
|
+
}
|
|
2259
|
+
const nodeId = `const_${context.componentSafeId}_${context.constants.size + 1}`;
|
|
2260
|
+
context.nodes.push({
|
|
2261
|
+
id: nodeId,
|
|
2262
|
+
type: "constant",
|
|
2263
|
+
params: {
|
|
2264
|
+
value: Number.isFinite(value) ? value : 0
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
context.constants.set(key, nodeId);
|
|
2268
|
+
setNodeValueType(context, nodeId, "scalar");
|
|
2269
|
+
return nodeId;
|
|
2270
|
+
}
|
|
2271
|
+
function getVectorConstantNodeId(context, values) {
|
|
2272
|
+
const normalized = values.map(
|
|
2273
|
+
(value) => Number.isFinite(value) ? value : 0
|
|
2274
|
+
);
|
|
2275
|
+
const key = `vector:${normalized.join(",")}`;
|
|
2276
|
+
const existing = context.constants.get(key);
|
|
2277
|
+
if (existing) {
|
|
2278
|
+
return existing;
|
|
2279
|
+
}
|
|
2280
|
+
const nodeId = `const_${context.componentSafeId}_${context.constants.size + 1}`;
|
|
2281
|
+
context.nodes.push({
|
|
2282
|
+
id: nodeId,
|
|
2283
|
+
type: "constant",
|
|
2284
|
+
params: {
|
|
2285
|
+
value: {
|
|
2286
|
+
vector: normalized
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
context.constants.set(key, nodeId);
|
|
2291
|
+
setNodeValueType(context, nodeId, "vector");
|
|
2292
|
+
return nodeId;
|
|
2293
|
+
}
|
|
2294
|
+
function createBinaryOperationNode(context, operator, leftId, rightId) {
|
|
2295
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
2296
|
+
context.nodes.push({
|
|
2297
|
+
id: nodeId,
|
|
2298
|
+
type: operator
|
|
2299
|
+
});
|
|
2300
|
+
setNodeValueType(context, nodeId, "scalar");
|
|
2301
|
+
const leftInput = operator === "subtract" || operator === "divide" ? "lhs" : "operand_1";
|
|
2302
|
+
const rightInput = operator === "subtract" || operator === "divide" ? "rhs" : "operand_2";
|
|
2303
|
+
context.edges.push({
|
|
2304
|
+
from: { nodeId: leftId },
|
|
2305
|
+
to: { nodeId, portId: leftInput }
|
|
2306
|
+
});
|
|
2307
|
+
context.edges.push({
|
|
2308
|
+
from: { nodeId: rightId },
|
|
2309
|
+
to: { nodeId, portId: rightInput }
|
|
2310
|
+
});
|
|
2311
|
+
return nodeId;
|
|
2312
|
+
}
|
|
2313
|
+
function createVariadicOperationNode(context, operator, operandIds) {
|
|
2314
|
+
if (operandIds.length === 0) {
|
|
2315
|
+
return getConstantNodeId(context, operator === "add" ? 0 : 1);
|
|
2316
|
+
}
|
|
2317
|
+
if (operandIds.length === 1) {
|
|
2318
|
+
return operandIds[0];
|
|
2319
|
+
}
|
|
2320
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
2321
|
+
context.nodes.push({
|
|
2322
|
+
id: nodeId,
|
|
2323
|
+
type: operator
|
|
2324
|
+
});
|
|
2325
|
+
setNodeValueType(context, nodeId, "scalar");
|
|
2326
|
+
operandIds.forEach((operandId, index) => {
|
|
2327
|
+
context.edges.push({
|
|
2328
|
+
from: { nodeId: operandId },
|
|
2329
|
+
to: { nodeId, portId: `operand_${index + 1}` }
|
|
2330
|
+
});
|
|
2331
|
+
});
|
|
2332
|
+
return nodeId;
|
|
2333
|
+
}
|
|
2334
|
+
function createNamedOperationNode(context, operator, inputNames, operandIds, resultType = "scalar", params) {
|
|
2335
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
2336
|
+
const node = {
|
|
2337
|
+
id: nodeId,
|
|
2338
|
+
type: operator
|
|
2339
|
+
};
|
|
2340
|
+
if (params && Object.keys(params).length > 0) {
|
|
2341
|
+
node.params = params;
|
|
2342
|
+
}
|
|
2343
|
+
context.nodes.push(node);
|
|
2344
|
+
setNodeValueType(context, nodeId, resultType);
|
|
2345
|
+
inputNames.forEach((inputName, index) => {
|
|
2346
|
+
const operandId = operandIds[index];
|
|
2347
|
+
context.edges.push({
|
|
2348
|
+
from: { nodeId: operandId },
|
|
2349
|
+
to: { nodeId, portId: inputName }
|
|
2350
|
+
});
|
|
2351
|
+
});
|
|
2352
|
+
return nodeId;
|
|
2353
|
+
}
|
|
2354
|
+
function ensureReservedVariableNode(name, context) {
|
|
2355
|
+
const existing = context.reservedNodes.get(name);
|
|
2356
|
+
if (existing) {
|
|
2357
|
+
return existing;
|
|
2358
|
+
}
|
|
2359
|
+
if (name === "time" || name === "deltaTime" || name === "frame") {
|
|
2360
|
+
const nodeId = ensureGraphTimeNode(context);
|
|
2361
|
+
context.reservedNodes.set(name, nodeId);
|
|
2362
|
+
return nodeId;
|
|
2363
|
+
}
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2366
|
+
function ensureGraphTimeNode(context) {
|
|
2367
|
+
const existing = context.graphReservedNodes.get("time");
|
|
2368
|
+
if (existing) {
|
|
2369
|
+
return existing;
|
|
2370
|
+
}
|
|
2371
|
+
const nodeId = context.generateReservedNodeId("time");
|
|
2372
|
+
context.nodes.push({
|
|
2373
|
+
id: nodeId,
|
|
2374
|
+
type: "time",
|
|
2375
|
+
metadata: {
|
|
2376
|
+
reservedVariable: "time"
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
setNodeValueType(context, nodeId, "scalar");
|
|
2380
|
+
context.graphReservedNodes.set("time", nodeId);
|
|
2381
|
+
return nodeId;
|
|
2382
|
+
}
|
|
2383
|
+
function emitScalarFunctionNode(definition, operands, argNodes, totalArgCount, context, variables, issues, paramArgOverride) {
|
|
2384
|
+
if (definition.nodeType === "case") {
|
|
2385
|
+
return emitCaseFunctionNode(
|
|
2386
|
+
definition,
|
|
2387
|
+
operands,
|
|
2388
|
+
argNodes,
|
|
2389
|
+
context,
|
|
2390
|
+
variables,
|
|
2391
|
+
issues
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
const orderedOperands = operands.slice(0, definition.inputs.length);
|
|
2395
|
+
definition.inputs.forEach((input, index) => {
|
|
2396
|
+
const operandId = orderedOperands[index];
|
|
2397
|
+
if (!operandId) {
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
ensureOperandValueType(
|
|
2401
|
+
context,
|
|
2402
|
+
operandId,
|
|
2403
|
+
input.valueType,
|
|
2404
|
+
definition.nodeType,
|
|
2405
|
+
input.id,
|
|
2406
|
+
issues
|
|
2407
|
+
);
|
|
2408
|
+
});
|
|
2409
|
+
const availableAfterInputs = Math.max(
|
|
2410
|
+
0,
|
|
2411
|
+
totalArgCount - definition.inputs.length
|
|
2412
|
+
);
|
|
2413
|
+
const paramArgCount = definition.params.length > 0 ? Math.min(definition.params.length, availableAfterInputs) : 0;
|
|
2414
|
+
const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
|
|
2415
|
+
const variadicOperands = definition.variadic ? operands.slice(
|
|
2416
|
+
definition.inputs.length,
|
|
2417
|
+
definition.inputs.length + variadicArgCount
|
|
2418
|
+
) : [];
|
|
2419
|
+
if (definition.variadic) {
|
|
2420
|
+
variadicOperands.forEach((operandId, _variadicIndex) => {
|
|
2421
|
+
ensureOperandValueType(
|
|
2422
|
+
context,
|
|
2423
|
+
operandId,
|
|
2424
|
+
definition.variadic.valueType,
|
|
2425
|
+
definition.nodeType,
|
|
2426
|
+
definition.variadic.id,
|
|
2427
|
+
issues
|
|
2428
|
+
);
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
const paramArgStart = definition.inputs.length + variadicArgCount;
|
|
2432
|
+
const paramArgNodes = paramArgCount > 0 ? paramArgOverride ?? argNodes.slice(paramArgStart, paramArgStart + paramArgCount) : [];
|
|
2433
|
+
const isSlewDebugEnabled = typeof process !== "undefined" && process?.env && process.env.DEBUG_SLEW === "1";
|
|
2434
|
+
if (isSlewDebugEnabled && definition.nodeType === "slew") {
|
|
2435
|
+
console.log("slew-debug", {
|
|
2436
|
+
totalArgs: totalArgCount,
|
|
2437
|
+
inputs: definition.inputs.length,
|
|
2438
|
+
paramCount: definition.params.length,
|
|
2439
|
+
paramArgCount,
|
|
2440
|
+
paramArgNodesTypes: paramArgNodes.map((node) => node?.type ?? null)
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
const nodeParams = buildParamAssignments(definition, paramArgNodes, issues);
|
|
2444
|
+
if (definition.variadic && definition.inputs.length === 0) {
|
|
2445
|
+
const nodeId2 = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
2446
|
+
const node = {
|
|
2447
|
+
id: nodeId2,
|
|
2448
|
+
type: definition.nodeType
|
|
2449
|
+
};
|
|
2450
|
+
if (nodeParams) {
|
|
2451
|
+
node.params = nodeParams;
|
|
2452
|
+
}
|
|
2453
|
+
context.nodes.push(node);
|
|
2454
|
+
variadicOperands.forEach((operandId, index) => {
|
|
2455
|
+
context.edges.push({
|
|
2456
|
+
from: { nodeId: operandId },
|
|
2457
|
+
to: {
|
|
2458
|
+
nodeId: nodeId2,
|
|
2459
|
+
portId: `${definition.variadic.id}_${index + 1}`
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
});
|
|
2463
|
+
setNodeValueType(context, nodeId2, definition.resultValueType);
|
|
2464
|
+
return nodeId2;
|
|
2465
|
+
}
|
|
2466
|
+
const providedNames = [];
|
|
2467
|
+
const providedOperands = [];
|
|
2468
|
+
definition.inputs.forEach((input, index) => {
|
|
2469
|
+
const operandId = orderedOperands[index];
|
|
2470
|
+
if (!operandId) {
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
providedNames.push(input.id);
|
|
2474
|
+
providedOperands.push(operandId);
|
|
2475
|
+
});
|
|
2476
|
+
const nodeId = createNamedOperationNode(
|
|
2477
|
+
context,
|
|
2478
|
+
definition.nodeType,
|
|
2479
|
+
providedNames,
|
|
2480
|
+
providedOperands,
|
|
2481
|
+
definition.resultValueType,
|
|
2482
|
+
nodeParams ?? void 0
|
|
2483
|
+
);
|
|
2484
|
+
if (definition.variadic) {
|
|
2485
|
+
variadicOperands.forEach((operandId, index) => {
|
|
2486
|
+
context.edges.push({
|
|
2487
|
+
from: { nodeId: operandId },
|
|
2488
|
+
to: {
|
|
2489
|
+
nodeId,
|
|
2490
|
+
portId: `${definition.variadic.id}_${index + 1}`
|
|
2491
|
+
}
|
|
2492
|
+
});
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
return nodeId;
|
|
2496
|
+
}
|
|
2497
|
+
function emitCaseFunctionNode(definition, operands, argNodes, context, variables, issues) {
|
|
2498
|
+
const selectorId = operands[0];
|
|
2499
|
+
const defaultId = operands[1];
|
|
2500
|
+
const branchOperands = operands.slice(2);
|
|
2501
|
+
const branchArgs = argNodes.slice(2);
|
|
2502
|
+
if (!selectorId || branchOperands.length === 0) {
|
|
2503
|
+
issues.push(
|
|
2504
|
+
'Function "case" requires a selector, default, and at least one branch.'
|
|
2505
|
+
);
|
|
2506
|
+
return getConstantNodeId(context, 0);
|
|
2507
|
+
}
|
|
2508
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
2509
|
+
const caseLabels = branchOperands.map((_, index) => {
|
|
2510
|
+
const extracted = extractCaseLabel(branchArgs[index], variables);
|
|
2511
|
+
if (extracted) {
|
|
2512
|
+
return extracted;
|
|
2513
|
+
}
|
|
2514
|
+
const fallback = `case_${index + 1}`;
|
|
2515
|
+
issues.push(
|
|
2516
|
+
`Case branch ${index + 1} is missing an alias; generated fallback label ${fallback}.`
|
|
2517
|
+
);
|
|
2518
|
+
return fallback;
|
|
2519
|
+
});
|
|
2520
|
+
context.nodes.push({
|
|
2521
|
+
id: nodeId,
|
|
2522
|
+
type: definition.nodeType,
|
|
2523
|
+
params: caseLabels.length > 0 ? { case_labels: caseLabels } : void 0
|
|
2524
|
+
});
|
|
2525
|
+
ensureOperandValueType(
|
|
2526
|
+
context,
|
|
2527
|
+
selectorId,
|
|
2528
|
+
definition.inputs[0]?.valueType ?? "any",
|
|
2529
|
+
definition.nodeType,
|
|
2530
|
+
definition.inputs[0]?.id ?? "selector",
|
|
2531
|
+
issues
|
|
2532
|
+
);
|
|
2533
|
+
context.edges.push({
|
|
2534
|
+
from: { nodeId: selectorId },
|
|
2535
|
+
to: { nodeId, portId: definition.inputs[0]?.id ?? "selector" }
|
|
2536
|
+
});
|
|
2537
|
+
if (defaultId) {
|
|
2538
|
+
ensureOperandValueType(
|
|
2539
|
+
context,
|
|
2540
|
+
defaultId,
|
|
2541
|
+
definition.inputs[1]?.valueType ?? "any",
|
|
2542
|
+
definition.nodeType,
|
|
2543
|
+
"default",
|
|
2544
|
+
issues
|
|
2545
|
+
);
|
|
2546
|
+
context.edges.push({
|
|
2547
|
+
from: { nodeId: defaultId },
|
|
2548
|
+
to: { nodeId, portId: "default" }
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
branchOperands.forEach((operandId, index) => {
|
|
2552
|
+
const portId = `${definition.variadic?.id ?? "operand"}_${index + 1}`;
|
|
2553
|
+
ensureOperandValueType(
|
|
2554
|
+
context,
|
|
2555
|
+
operandId,
|
|
2556
|
+
definition.variadic?.valueType ?? "any",
|
|
2557
|
+
definition.nodeType,
|
|
2558
|
+
portId,
|
|
2559
|
+
issues
|
|
2560
|
+
);
|
|
2561
|
+
context.edges.push({
|
|
2562
|
+
from: { nodeId: operandId },
|
|
2563
|
+
to: { nodeId, portId }
|
|
2564
|
+
});
|
|
2565
|
+
});
|
|
2566
|
+
setNodeValueType(context, nodeId, "any");
|
|
2567
|
+
return nodeId;
|
|
2568
|
+
}
|
|
2569
|
+
function buildParamAssignments(definition, paramArgNodes, issues) {
|
|
2570
|
+
if (!definition.params.length || paramArgNodes.length === 0) {
|
|
2571
|
+
return null;
|
|
2572
|
+
}
|
|
2573
|
+
const assignments = {};
|
|
2574
|
+
paramArgNodes.forEach((node, index) => {
|
|
2575
|
+
const spec = definition.params[index];
|
|
2576
|
+
if (!spec) {
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
const literal = extractParamLiteral(
|
|
2580
|
+
node,
|
|
2581
|
+
spec,
|
|
2582
|
+
definition.nodeType,
|
|
2583
|
+
issues
|
|
2584
|
+
);
|
|
2585
|
+
if (literal !== null) {
|
|
2586
|
+
assignments[spec.id] = literal;
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
return Object.keys(assignments).length > 0 ? assignments : null;
|
|
2590
|
+
}
|
|
2591
|
+
function extractParamLiteral(node, spec, functionName, issues) {
|
|
2592
|
+
if (spec.valueType === "vector") {
|
|
2593
|
+
if (node.type !== "VectorLiteral") {
|
|
2594
|
+
issues.push(
|
|
2595
|
+
`Function "${functionName}" requires a literal vector for "${spec.id}".`
|
|
2596
|
+
);
|
|
2597
|
+
return null;
|
|
2598
|
+
}
|
|
2599
|
+
if (!Array.isArray(node.values) || node.values.length === 0) {
|
|
2600
|
+
issues.push(
|
|
2601
|
+
`Function "${functionName}" requires at least one value for "${spec.id}".`
|
|
2602
|
+
);
|
|
2603
|
+
return null;
|
|
2604
|
+
}
|
|
2605
|
+
return node.values.map((value) => clampScalarParamValue(value, spec));
|
|
2606
|
+
}
|
|
2607
|
+
if (node.type !== "Literal") {
|
|
2608
|
+
issues.push(
|
|
2609
|
+
`Function "${functionName}" requires a literal ${describeParamExpectation(spec.valueType)} for "${spec.id}".`
|
|
2610
|
+
);
|
|
2611
|
+
return null;
|
|
2612
|
+
}
|
|
2613
|
+
const numeric = Number(node.value);
|
|
2614
|
+
if (!Number.isFinite(numeric)) {
|
|
2615
|
+
issues.push(
|
|
2616
|
+
`Function "${functionName}" requires a finite ${describeParamExpectation(spec.valueType)} for "${spec.id}".`
|
|
2617
|
+
);
|
|
2618
|
+
return null;
|
|
2619
|
+
}
|
|
2620
|
+
const clamped = clampScalarParamValue(numeric, spec);
|
|
2621
|
+
if (spec.valueType === "boolean") {
|
|
2622
|
+
return clamped !== 0;
|
|
2623
|
+
}
|
|
2624
|
+
return clamped;
|
|
2625
|
+
}
|
|
2626
|
+
function clampScalarParamValue(value, spec) {
|
|
2627
|
+
let next = Number.isFinite(value) ? value : 0;
|
|
2628
|
+
if (typeof spec.min === "number" && next < spec.min) {
|
|
2629
|
+
next = spec.min;
|
|
2630
|
+
}
|
|
2631
|
+
if (typeof spec.max === "number" && next > spec.max) {
|
|
2632
|
+
next = spec.max;
|
|
2633
|
+
}
|
|
2634
|
+
return next;
|
|
2635
|
+
}
|
|
2636
|
+
function describeParamExpectation(valueType) {
|
|
2637
|
+
switch (valueType) {
|
|
2638
|
+
case "vector":
|
|
2639
|
+
return "vector";
|
|
2640
|
+
case "boolean":
|
|
2641
|
+
return "boolean";
|
|
2642
|
+
default:
|
|
2643
|
+
return "scalar";
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
function extractCaseLabel(node, variables) {
|
|
2647
|
+
if (!node || node.type !== "Reference") {
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
const entry = variables.resolve(node.name);
|
|
2651
|
+
if (entry && entry.metadata && "slotAlias" in entry.metadata) {
|
|
2652
|
+
const alias = entry.metadata.slotAlias?.trim();
|
|
2653
|
+
if (alias && alias.length > 0) {
|
|
2654
|
+
return alias;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return node.name;
|
|
2658
|
+
}
|
|
2659
|
+
function collectOperands(node, operator, target) {
|
|
2660
|
+
if (node.type === "Binary" && node.operator === operator) {
|
|
2661
|
+
collectOperands(node.left, operator, target);
|
|
2662
|
+
collectOperands(node.right, operator, target);
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
target.push(node);
|
|
2666
|
+
}
|
|
2667
|
+
function validateLiteralParamArguments(node, issues) {
|
|
2668
|
+
if (node.type === "Function") {
|
|
2669
|
+
const definition = SCALAR_FUNCTIONS.get(node.name.toLowerCase());
|
|
2670
|
+
if (definition && definition.params.length > 0) {
|
|
2671
|
+
const totalArgCount = node.args.length;
|
|
2672
|
+
const availableAfterInputs = Math.max(
|
|
2673
|
+
0,
|
|
2674
|
+
totalArgCount - definition.inputs.length
|
|
2675
|
+
);
|
|
2676
|
+
const paramArgCount = Math.min(
|
|
2677
|
+
definition.params.length,
|
|
2678
|
+
availableAfterInputs
|
|
2679
|
+
);
|
|
2680
|
+
const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
|
|
2681
|
+
const paramArgStart = definition.inputs.length + variadicArgCount;
|
|
2682
|
+
for (let index = 0; index < paramArgCount; index++) {
|
|
2683
|
+
const spec = definition.params[index];
|
|
2684
|
+
const paramNode = node.args[paramArgStart + index];
|
|
2685
|
+
if (!spec || !paramNode) {
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
extractParamLiteral(paramNode, spec, node.name, issues);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
node.args.forEach((child) => validateLiteralParamArguments(child, issues));
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
if (node.type === "Unary") {
|
|
2695
|
+
validateLiteralParamArguments(node.operand, issues);
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
if (node.type === "Binary") {
|
|
2699
|
+
validateLiteralParamArguments(node.left, issues);
|
|
2700
|
+
validateLiteralParamArguments(node.right, issues);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
var BINARY_FUNCTION_OPERATOR_MAP = {
|
|
2704
|
+
">": "greaterthan",
|
|
2705
|
+
"<": "lessthan",
|
|
2706
|
+
"==": "equal",
|
|
2707
|
+
"!=": "notequal",
|
|
2708
|
+
"&&": "and",
|
|
2709
|
+
"||": "or"
|
|
2710
|
+
};
|
|
2711
|
+
function materializeExpression(node, context, variables, issues, options) {
|
|
2712
|
+
const autoRemap = options?.autoRemap !== false;
|
|
2713
|
+
switch (node.type) {
|
|
2714
|
+
case "Literal": {
|
|
2715
|
+
return getConstantNodeId(context, node.value);
|
|
2716
|
+
}
|
|
2717
|
+
case "VectorLiteral": {
|
|
2718
|
+
return getVectorConstantNodeId(context, node.values);
|
|
2719
|
+
}
|
|
2720
|
+
case "Reference": {
|
|
2721
|
+
const entry = variables.resolve(node.name);
|
|
2722
|
+
if (!entry) {
|
|
2723
|
+
issues.push(`Unknown control "${node.name}".`);
|
|
2724
|
+
return getConstantNodeId(context, 0);
|
|
2725
|
+
}
|
|
2726
|
+
const mappedId = entry.nodeId ?? getConstantNodeId(context, 0);
|
|
2727
|
+
if (!autoRemap || entry.kind !== "slot") {
|
|
2728
|
+
return mappedId;
|
|
2729
|
+
}
|
|
2730
|
+
return resolveSlotReferenceNode(entry, mappedId, context);
|
|
2731
|
+
}
|
|
2732
|
+
case "Unary": {
|
|
2733
|
+
const operandId = materializeExpression(
|
|
2734
|
+
node.operand,
|
|
2735
|
+
context,
|
|
2736
|
+
variables,
|
|
2737
|
+
issues
|
|
2738
|
+
);
|
|
2739
|
+
switch (node.operator) {
|
|
2740
|
+
case "+":
|
|
2741
|
+
return operandId;
|
|
2742
|
+
case "-": {
|
|
2743
|
+
const negativeOne = getConstantNodeId(context, -1);
|
|
2744
|
+
return createVariadicOperationNode(context, "multiply", [
|
|
2745
|
+
negativeOne,
|
|
2746
|
+
operandId
|
|
2747
|
+
]);
|
|
2748
|
+
}
|
|
2749
|
+
case "!": {
|
|
2750
|
+
const definition = SCALAR_FUNCTIONS.get("not");
|
|
2751
|
+
if (!definition) {
|
|
2752
|
+
issues.push('Function "not" is not available in metadata.');
|
|
2753
|
+
return getConstantNodeId(context, 0);
|
|
2754
|
+
}
|
|
2755
|
+
return emitScalarFunctionNode(
|
|
2756
|
+
definition,
|
|
2757
|
+
[operandId],
|
|
2758
|
+
[node.operand],
|
|
2759
|
+
1,
|
|
2760
|
+
context,
|
|
2761
|
+
variables,
|
|
2762
|
+
issues
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
default:
|
|
2766
|
+
issues.push("Unsupported unary operator.");
|
|
2767
|
+
return operandId;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
case "Binary": {
|
|
2771
|
+
const operator = node.operator;
|
|
2772
|
+
if (operator === "+") {
|
|
2773
|
+
const children = [];
|
|
2774
|
+
collectOperands(node, "+", children);
|
|
2775
|
+
const operandIds = children.map(
|
|
2776
|
+
(child) => materializeExpression(child, context, variables, issues)
|
|
2777
|
+
);
|
|
2778
|
+
return createVariadicOperationNode(context, "add", operandIds);
|
|
2779
|
+
}
|
|
2780
|
+
if (operator === "*") {
|
|
2781
|
+
const children = [];
|
|
2782
|
+
collectOperands(node, "*", children);
|
|
2783
|
+
const operandIds = children.map(
|
|
2784
|
+
(child) => materializeExpression(child, context, variables, issues)
|
|
2785
|
+
);
|
|
2786
|
+
return createVariadicOperationNode(context, "multiply", operandIds);
|
|
2787
|
+
}
|
|
2788
|
+
const leftId = materializeExpression(
|
|
2789
|
+
node.left,
|
|
2790
|
+
context,
|
|
2791
|
+
variables,
|
|
2792
|
+
issues
|
|
2793
|
+
);
|
|
2794
|
+
const rightId = materializeExpression(
|
|
2795
|
+
node.right,
|
|
2796
|
+
context,
|
|
2797
|
+
variables,
|
|
2798
|
+
issues
|
|
2799
|
+
);
|
|
2800
|
+
if (operator === "-") {
|
|
2801
|
+
return createBinaryOperationNode(context, "subtract", leftId, rightId);
|
|
2802
|
+
}
|
|
2803
|
+
if (operator === "/") {
|
|
2804
|
+
return createBinaryOperationNode(context, "divide", leftId, rightId);
|
|
2805
|
+
}
|
|
2806
|
+
const mappedFunction = BINARY_FUNCTION_OPERATOR_MAP[operator];
|
|
2807
|
+
if (mappedFunction) {
|
|
2808
|
+
const definition = SCALAR_FUNCTIONS.get(mappedFunction);
|
|
2809
|
+
if (!definition) {
|
|
2810
|
+
issues.push(`Function "${mappedFunction}" is not available.`);
|
|
2811
|
+
return getConstantNodeId(context, 0);
|
|
2812
|
+
}
|
|
2813
|
+
return emitScalarFunctionNode(
|
|
2814
|
+
definition,
|
|
2815
|
+
[leftId, rightId],
|
|
2816
|
+
[node.left, node.right],
|
|
2817
|
+
2,
|
|
2818
|
+
context,
|
|
2819
|
+
variables,
|
|
2820
|
+
issues
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
issues.push(`Unsupported operator "${operator}".`);
|
|
2824
|
+
return getConstantNodeId(context, 0);
|
|
2825
|
+
}
|
|
2826
|
+
case "Function": {
|
|
2827
|
+
const name = node.name;
|
|
2828
|
+
const normalized = name.toLowerCase();
|
|
2829
|
+
const definition = SCALAR_FUNCTIONS.get(normalized);
|
|
2830
|
+
if (!definition) {
|
|
2831
|
+
issues.push(`Unknown function "${name}".`);
|
|
2832
|
+
return getConstantNodeId(context, 0);
|
|
2833
|
+
}
|
|
2834
|
+
const argNodes = node.args;
|
|
2835
|
+
const argCount = argNodes.length;
|
|
2836
|
+
if (argCount < definition.minArgs) {
|
|
2837
|
+
issues.push(
|
|
2838
|
+
`Function "${name}" expects at least ${definition.minArgs} arguments, received ${argCount}.`
|
|
2839
|
+
);
|
|
2840
|
+
return getConstantNodeId(context, 0);
|
|
2841
|
+
}
|
|
2842
|
+
if (definition.maxArgs !== null && argCount > definition.maxArgs) {
|
|
2843
|
+
issues.push(
|
|
2844
|
+
`Function "${name}" expects at most ${definition.maxArgs} arguments, received ${argCount}.`
|
|
2845
|
+
);
|
|
2846
|
+
return getConstantNodeId(context, 0);
|
|
2847
|
+
}
|
|
2848
|
+
if (name === "slew") {
|
|
2849
|
+
const maxRateArg = argNodes[1];
|
|
2850
|
+
if (!maxRateArg || maxRateArg.type !== "Literal") {
|
|
2851
|
+
issues.push(
|
|
2852
|
+
'Function "slew" requires a literal scalar for "max_rate".'
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
const availableAfterInputs = Math.max(
|
|
2857
|
+
0,
|
|
2858
|
+
argCount - definition.inputs.length
|
|
2859
|
+
);
|
|
2860
|
+
const paramArgCount = definition.params.length > 0 ? Math.min(definition.params.length, availableAfterInputs) : 0;
|
|
2861
|
+
const variadicArgCount = definition.variadic ? Math.max(0, availableAfterInputs - paramArgCount) : 0;
|
|
2862
|
+
const operandLimit = Math.min(
|
|
2863
|
+
argCount,
|
|
2864
|
+
definition.inputs.length + variadicArgCount
|
|
2865
|
+
);
|
|
2866
|
+
const operandNodes = argNodes.slice(0, operandLimit);
|
|
2867
|
+
const operands = operandNodes.map(
|
|
2868
|
+
(arg, index) => materializeExpression(
|
|
2869
|
+
arg,
|
|
2870
|
+
context,
|
|
2871
|
+
variables,
|
|
2872
|
+
issues,
|
|
2873
|
+
shouldSkipAutoRemapForArgument(definition.nodeType, index) ? { autoRemap: false } : void 0
|
|
2874
|
+
)
|
|
2875
|
+
);
|
|
2876
|
+
const paramArgStart = operandLimit;
|
|
2877
|
+
const paramArgNodes = paramArgCount > 0 ? argNodes.slice(paramArgStart, paramArgStart + paramArgCount) : [];
|
|
2878
|
+
const paramOverride = definition.nodeType === "case" ? void 0 : paramArgNodes;
|
|
2879
|
+
return emitScalarFunctionNode(
|
|
2880
|
+
definition,
|
|
2881
|
+
operands,
|
|
2882
|
+
argNodes,
|
|
2883
|
+
argCount,
|
|
2884
|
+
context,
|
|
2885
|
+
variables,
|
|
2886
|
+
issues,
|
|
2887
|
+
paramOverride
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
default: {
|
|
2891
|
+
issues.push("Unsupported expression node.");
|
|
2892
|
+
return getConstantNodeId(context, 0);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
function buildRigGraphSpec({
|
|
2897
|
+
faceId,
|
|
2898
|
+
animatables,
|
|
2899
|
+
components,
|
|
2900
|
+
bindings,
|
|
2901
|
+
inputsById,
|
|
2902
|
+
inputBindings,
|
|
2903
|
+
inputMetadata
|
|
2904
|
+
}) {
|
|
2905
|
+
const metadataByInputId = inputMetadata ?? /* @__PURE__ */ new Map();
|
|
2906
|
+
const irBuilder = createIrGraphBuilder({
|
|
2907
|
+
faceId,
|
|
2908
|
+
registryVersion: import_metadata2.nodeRegistryVersion,
|
|
2909
|
+
source: "graphBuilder",
|
|
2910
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2911
|
+
});
|
|
2912
|
+
const nodes = [];
|
|
2913
|
+
const edges = [];
|
|
2914
|
+
const graphReservedNodes = /* @__PURE__ */ new Map();
|
|
2915
|
+
const reservedNodeCounters = /* @__PURE__ */ new Map();
|
|
2916
|
+
const generateReservedNodeId = (kind) => {
|
|
2917
|
+
const nextValue = (reservedNodeCounters.get(kind) ?? 0) + 1;
|
|
2918
|
+
reservedNodeCounters.set(kind, nextValue);
|
|
2919
|
+
return `reserved_${kind}_${nextValue}`;
|
|
2920
|
+
};
|
|
2921
|
+
const inputNodes = /* @__PURE__ */ new Map();
|
|
2922
|
+
const buildingDerived = /* @__PURE__ */ new Set();
|
|
2923
|
+
const computedInputs = /* @__PURE__ */ new Set();
|
|
2924
|
+
const summaryBindings = [];
|
|
2925
|
+
const bindingIssues = /* @__PURE__ */ new Map();
|
|
2926
|
+
const animatableEntries = /* @__PURE__ */ new Map();
|
|
2927
|
+
const outputs = /* @__PURE__ */ new Set();
|
|
2928
|
+
const ensureInputNode = (inputId) => {
|
|
2929
|
+
const existing = inputNodes.get(inputId);
|
|
2930
|
+
if (existing) {
|
|
2931
|
+
return existing;
|
|
2932
|
+
}
|
|
2933
|
+
const input = inputsById.get(inputId);
|
|
2934
|
+
if (!input) {
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
const defaultValue = Number.isFinite(input.defaultValue) ? input.defaultValue : 0;
|
|
2938
|
+
const inputBindingRaw = inputBindings[inputId];
|
|
2939
|
+
if (inputBindingRaw) {
|
|
2940
|
+
if (buildingDerived.has(inputId)) {
|
|
2941
|
+
const issueSet = bindingIssues.get(inputId) ?? /* @__PURE__ */ new Set();
|
|
2942
|
+
issueSet.add("Derived input cycle detected.");
|
|
2943
|
+
bindingIssues.set(inputId, issueSet);
|
|
2944
|
+
return null;
|
|
2945
|
+
}
|
|
2946
|
+
buildingDerived.add(inputId);
|
|
2947
|
+
try {
|
|
2948
|
+
const target = bindingTargetFromInput(input);
|
|
2949
|
+
const binding = ensureBindingStructure(inputBindingRaw, target);
|
|
2950
|
+
const requiresSelf = binding.inputId === import_utils3.SELF_BINDING_ID || binding.slots.some((slot) => slot.inputId === import_utils3.SELF_BINDING_ID);
|
|
2951
|
+
let selfNodeId;
|
|
2952
|
+
if (requiresSelf) {
|
|
2953
|
+
const sliderNodeId = `input_raw_${sanitizeNodeId(inputId)}`;
|
|
2954
|
+
nodes.push({
|
|
2955
|
+
id: sliderNodeId,
|
|
2956
|
+
type: "input",
|
|
2957
|
+
params: {
|
|
2958
|
+
path: buildRigInputPath(faceId, input.path),
|
|
2959
|
+
value: { float: defaultValue }
|
|
2960
|
+
}
|
|
2961
|
+
});
|
|
2962
|
+
selfNodeId = sliderNodeId;
|
|
2963
|
+
}
|
|
2964
|
+
const { valueNodeId, hasActiveSlot } = evaluateBinding({
|
|
2965
|
+
binding,
|
|
2966
|
+
target,
|
|
2967
|
+
targetId: inputId,
|
|
2968
|
+
animatableId: inputId,
|
|
2969
|
+
component: void 0,
|
|
2970
|
+
safeId: sanitizeNodeId(inputId),
|
|
2971
|
+
context: {
|
|
2972
|
+
nodes,
|
|
2973
|
+
edges,
|
|
2974
|
+
ensureInputNode,
|
|
2975
|
+
bindingIssues,
|
|
2976
|
+
summaryBindings,
|
|
2977
|
+
graphReservedNodes,
|
|
2978
|
+
generateReservedNodeId
|
|
2979
|
+
},
|
|
2980
|
+
selfNodeId
|
|
2981
|
+
});
|
|
2982
|
+
if (!valueNodeId || !hasActiveSlot) {
|
|
2983
|
+
const constNodeId = `derived_default_${sanitizeNodeId(inputId)}`;
|
|
2984
|
+
nodes.push({
|
|
2985
|
+
id: constNodeId,
|
|
2986
|
+
type: "constant",
|
|
2987
|
+
params: {
|
|
2988
|
+
value: input.defaultValue
|
|
2989
|
+
}
|
|
2990
|
+
});
|
|
2991
|
+
const record3 = { nodeId: constNodeId, input };
|
|
2992
|
+
inputNodes.set(inputId, record3);
|
|
2993
|
+
return record3;
|
|
2994
|
+
}
|
|
2995
|
+
computedInputs.add(inputId);
|
|
2996
|
+
const record2 = { nodeId: valueNodeId, input };
|
|
2997
|
+
inputNodes.set(inputId, record2);
|
|
2998
|
+
return record2;
|
|
2999
|
+
} finally {
|
|
3000
|
+
buildingDerived.delete(inputId);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
const nodeId = `input_${sanitizeNodeId(inputId)}`;
|
|
3004
|
+
nodes.push({
|
|
3005
|
+
id: nodeId,
|
|
3006
|
+
type: "input",
|
|
3007
|
+
params: {
|
|
3008
|
+
path: buildRigInputPath(faceId, input.path),
|
|
3009
|
+
value: { float: defaultValue }
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
const record = { nodeId, input };
|
|
3013
|
+
inputNodes.set(inputId, record);
|
|
3014
|
+
return record;
|
|
3015
|
+
};
|
|
3016
|
+
const ensureAnimatableEntry = (animatableId) => {
|
|
3017
|
+
const existing = animatableEntries.get(animatableId);
|
|
3018
|
+
if (existing) {
|
|
3019
|
+
return existing;
|
|
3020
|
+
}
|
|
3021
|
+
const animatable = animatables[animatableId];
|
|
3022
|
+
if (!animatable) {
|
|
3023
|
+
return null;
|
|
3024
|
+
}
|
|
3025
|
+
const entry = {
|
|
3026
|
+
animatable,
|
|
3027
|
+
values: /* @__PURE__ */ new Map(),
|
|
3028
|
+
defaults: /* @__PURE__ */ new Map(),
|
|
3029
|
+
isDriven: false
|
|
3030
|
+
};
|
|
3031
|
+
animatableEntries.set(animatableId, entry);
|
|
3032
|
+
return entry;
|
|
3033
|
+
};
|
|
3034
|
+
components.forEach((component) => {
|
|
3035
|
+
const bindingRaw = bindings[component.id];
|
|
3036
|
+
const target = bindingTargetFromComponent(component);
|
|
3037
|
+
const binding = bindingRaw ? ensureBindingStructure(bindingRaw, target) : null;
|
|
3038
|
+
const entry = ensureAnimatableEntry(component.animatableId);
|
|
3039
|
+
if (!entry) {
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
const key = component.component ?? "scalar";
|
|
3043
|
+
let valueNodeId = null;
|
|
3044
|
+
let hasActiveSlot = false;
|
|
3045
|
+
if (binding) {
|
|
3046
|
+
const { valueNodeId: producedNodeId, hasActiveSlot: active } = evaluateBinding({
|
|
3047
|
+
binding,
|
|
3048
|
+
target,
|
|
3049
|
+
targetId: component.id,
|
|
3050
|
+
animatableId: component.animatableId,
|
|
3051
|
+
component: component.component,
|
|
3052
|
+
safeId: component.safeId,
|
|
3053
|
+
context: {
|
|
3054
|
+
nodes,
|
|
3055
|
+
edges,
|
|
3056
|
+
ensureInputNode,
|
|
3057
|
+
bindingIssues,
|
|
3058
|
+
summaryBindings,
|
|
3059
|
+
graphReservedNodes,
|
|
3060
|
+
generateReservedNodeId
|
|
3061
|
+
}
|
|
3062
|
+
});
|
|
3063
|
+
valueNodeId = producedNodeId;
|
|
3064
|
+
hasActiveSlot = active;
|
|
3065
|
+
if (active) {
|
|
3066
|
+
entry.isDriven = true;
|
|
3067
|
+
}
|
|
3068
|
+
} else {
|
|
3069
|
+
summaryBindings.push({
|
|
3070
|
+
targetId: component.id,
|
|
3071
|
+
animatableId: component.animatableId,
|
|
3072
|
+
component: component.component,
|
|
3073
|
+
slotId: PRIMARY_SLOT_ID,
|
|
3074
|
+
slotAlias: PRIMARY_SLOT_ALIAS,
|
|
3075
|
+
inputId: null,
|
|
3076
|
+
remap: createDefaultRemap(target),
|
|
3077
|
+
expression: PRIMARY_SLOT_ALIAS,
|
|
3078
|
+
valueType: target.valueType === "vector" ? "vector" : "scalar",
|
|
3079
|
+
issues: ["Binding not found."],
|
|
3080
|
+
nodeId: PRIMARY_SLOT_ID,
|
|
3081
|
+
expressionNodeId: PRIMARY_SLOT_ID
|
|
3082
|
+
});
|
|
3083
|
+
const fallbackIssues = bindingIssues.get(component.id) ?? /* @__PURE__ */ new Set();
|
|
3084
|
+
fallbackIssues.add("Binding not found.");
|
|
3085
|
+
bindingIssues.set(component.id, fallbackIssues);
|
|
3086
|
+
}
|
|
3087
|
+
if (!valueNodeId || !hasActiveSlot) {
|
|
3088
|
+
entry.defaults.set(key, component.defaultValue);
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
entry.values.set(key, valueNodeId);
|
|
3092
|
+
});
|
|
3093
|
+
inputsById.forEach((_input, inputId) => {
|
|
3094
|
+
ensureInputNode(inputId);
|
|
3095
|
+
});
|
|
3096
|
+
animatableEntries.forEach((entry, animatableId) => {
|
|
3097
|
+
if (!entry.isDriven) {
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
outputs.add(animatableId);
|
|
3101
|
+
const safeId = sanitizeNodeId(animatableId);
|
|
3102
|
+
const order = getComponentOrder(entry.animatable);
|
|
3103
|
+
if (!order) {
|
|
3104
|
+
const valueNodeId = entry.values.get("scalar");
|
|
3105
|
+
if (!valueNodeId) {
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
const outputNodeId2 = `out_${safeId}`;
|
|
3109
|
+
nodes.push({
|
|
3110
|
+
id: outputNodeId2,
|
|
3111
|
+
type: "output",
|
|
3112
|
+
params: {
|
|
3113
|
+
path: animatableId
|
|
3114
|
+
}
|
|
3115
|
+
});
|
|
3116
|
+
edges.push({
|
|
3117
|
+
from: { nodeId: valueNodeId },
|
|
3118
|
+
to: { nodeId: outputNodeId2, portId: "in" }
|
|
3119
|
+
});
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
const joinNodeId = `join_${safeId}`;
|
|
3123
|
+
nodes.push({
|
|
3124
|
+
id: joinNodeId,
|
|
3125
|
+
type: "join"
|
|
3126
|
+
});
|
|
3127
|
+
order.forEach((componentKey, index) => {
|
|
3128
|
+
let sourceId = entry.values.get(componentKey);
|
|
3129
|
+
if (!sourceId) {
|
|
3130
|
+
const componentDefault = entry.defaults.get(componentKey) ?? extractComponentDefault(
|
|
3131
|
+
(0, import_utils2.buildAnimatableValue)(entry.animatable, void 0),
|
|
3132
|
+
componentKey
|
|
3133
|
+
);
|
|
3134
|
+
const constNodeId = `const_${safeId}_${componentKey}`;
|
|
3135
|
+
nodes.push({
|
|
3136
|
+
id: constNodeId,
|
|
3137
|
+
type: "constant",
|
|
3138
|
+
params: {
|
|
3139
|
+
value: componentDefault
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
sourceId = constNodeId;
|
|
3143
|
+
}
|
|
3144
|
+
edges.push({
|
|
3145
|
+
from: { nodeId: sourceId },
|
|
3146
|
+
to: { nodeId: joinNodeId, portId: `operand_${index + 1}` }
|
|
3147
|
+
});
|
|
3148
|
+
});
|
|
3149
|
+
const outputNodeId = `out_${safeId}`;
|
|
3150
|
+
nodes.push({
|
|
3151
|
+
id: outputNodeId,
|
|
3152
|
+
type: "output",
|
|
3153
|
+
params: {
|
|
3154
|
+
path: animatableId
|
|
3155
|
+
}
|
|
3156
|
+
});
|
|
3157
|
+
edges.push({
|
|
3158
|
+
from: { nodeId: joinNodeId },
|
|
3159
|
+
to: { nodeId: outputNodeId, portId: "in" }
|
|
3160
|
+
});
|
|
3161
|
+
});
|
|
3162
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
3163
|
+
nodes.forEach((node) => {
|
|
3164
|
+
nodeById.set(node.id, node);
|
|
3165
|
+
});
|
|
3166
|
+
const constantUsage = /* @__PURE__ */ new Map();
|
|
3167
|
+
edges.forEach((edge) => {
|
|
3168
|
+
const source = nodeById.get(edge.from.nodeId);
|
|
3169
|
+
if (source?.type === "constant") {
|
|
3170
|
+
constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
3173
|
+
const updatedEdges = [];
|
|
3174
|
+
const constantsToRemove = /* @__PURE__ */ new Set();
|
|
3175
|
+
edges.forEach((edge) => {
|
|
3176
|
+
const source = nodeById.get(edge.from.nodeId);
|
|
3177
|
+
if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
|
|
3178
|
+
const target = nodeById.get(edge.to.nodeId);
|
|
3179
|
+
if (target) {
|
|
3180
|
+
const value = source.params.value;
|
|
3181
|
+
if (value !== void 0) {
|
|
3182
|
+
const targetPort = edge.to.portId ?? "in";
|
|
3183
|
+
target.inputDefaults = {
|
|
3184
|
+
...target.inputDefaults ?? {},
|
|
3185
|
+
[targetPort]: value
|
|
3186
|
+
};
|
|
3187
|
+
nodeById.set(target.id, target);
|
|
3188
|
+
constantsToRemove.add(source.id);
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
updatedEdges.push(edge);
|
|
3194
|
+
});
|
|
3195
|
+
const filteredNodes = nodes.filter((node) => !constantsToRemove.has(node.id)).map((node) => nodeById.get(node.id) ?? node);
|
|
3196
|
+
const remapDefaultIssues = validateRemapDefaults(filteredNodes);
|
|
3197
|
+
const dynamicOutputs = Array.from(outputs);
|
|
3198
|
+
const computedInputList = Array.from(computedInputs);
|
|
3199
|
+
const filteredSummaryBindings = summaryBindings.filter(
|
|
3200
|
+
(binding) => outputs.has(binding.animatableId) || computedInputs.has(binding.animatableId)
|
|
3201
|
+
);
|
|
3202
|
+
const vizijMetadata = {
|
|
3203
|
+
vizij: {
|
|
3204
|
+
faceId,
|
|
3205
|
+
inputs: Array.from(inputsById.values()).map((input) => {
|
|
3206
|
+
const meta = metadataByInputId.get(input.id);
|
|
3207
|
+
const derivedRoot = meta?.root ?? input.group;
|
|
3208
|
+
let derivedSource = meta?.source;
|
|
3209
|
+
if (!derivedSource && input.path.startsWith("/standard/")) {
|
|
3210
|
+
derivedSource = "preset";
|
|
3211
|
+
}
|
|
3212
|
+
const entry = {
|
|
3213
|
+
id: input.id,
|
|
3214
|
+
path: input.path,
|
|
3215
|
+
sourceId: input.sourceId,
|
|
3216
|
+
label: input.label,
|
|
3217
|
+
group: input.group,
|
|
3218
|
+
defaultValue: input.defaultValue,
|
|
3219
|
+
range: {
|
|
3220
|
+
min: input.range.min,
|
|
3221
|
+
max: input.range.max
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
if (derivedSource) {
|
|
3225
|
+
entry.source = derivedSource;
|
|
3226
|
+
}
|
|
3227
|
+
if (derivedRoot) {
|
|
3228
|
+
entry.root = derivedRoot;
|
|
3229
|
+
}
|
|
3230
|
+
return entry;
|
|
3231
|
+
}),
|
|
3232
|
+
bindings: filteredSummaryBindings.map((binding) => ({
|
|
3233
|
+
...binding,
|
|
3234
|
+
remap: { ...binding.remap },
|
|
3235
|
+
expression: binding.expression,
|
|
3236
|
+
valueType: binding.valueType,
|
|
3237
|
+
issues: binding.issues ? [...binding.issues] : void 0,
|
|
3238
|
+
metadata: binding.metadata ? cloneJsonLike2(binding.metadata) : void 0
|
|
3239
|
+
}))
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
const issuesByTarget = {};
|
|
3243
|
+
bindingIssues.forEach((issues, targetId) => {
|
|
3244
|
+
if (issues.size === 0) {
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
issuesByTarget[targetId] = Array.from(issues);
|
|
3248
|
+
});
|
|
3249
|
+
const fatalIssues = /* @__PURE__ */ new Set();
|
|
3250
|
+
Object.values(issuesByTarget).forEach((issues) => {
|
|
3251
|
+
issues.forEach((issue) => fatalIssues.add(issue));
|
|
3252
|
+
});
|
|
3253
|
+
remapDefaultIssues.forEach((issue) => fatalIssues.add(issue));
|
|
3254
|
+
const summaryPayload = {
|
|
3255
|
+
faceId,
|
|
3256
|
+
inputs: Array.from(inputNodes.values()).map(
|
|
3257
|
+
({ input }) => buildRigInputPath(faceId, input.path)
|
|
3258
|
+
),
|
|
3259
|
+
outputs: [...dynamicOutputs, ...computedInputList],
|
|
3260
|
+
bindings: filteredSummaryBindings
|
|
3261
|
+
};
|
|
3262
|
+
const irSummary = {
|
|
3263
|
+
faceId: summaryPayload.faceId,
|
|
3264
|
+
inputs: [...summaryPayload.inputs],
|
|
3265
|
+
outputs: [...summaryPayload.outputs],
|
|
3266
|
+
bindings: toIrBindingSummary(
|
|
3267
|
+
filteredSummaryBindings.map((binding) => ({
|
|
3268
|
+
...binding,
|
|
3269
|
+
remap: { ...binding.remap },
|
|
3270
|
+
issues: binding.issues ? [...binding.issues] : void 0
|
|
3271
|
+
}))
|
|
3272
|
+
)
|
|
3273
|
+
};
|
|
3274
|
+
const fatalIssueList = Array.from(fatalIssues);
|
|
3275
|
+
const irIssues = createIrIssuesFromLegacy(issuesByTarget, fatalIssueList);
|
|
3276
|
+
filteredNodes.forEach((node) => {
|
|
3277
|
+
irBuilder.addNode(cloneIrNode(node));
|
|
3278
|
+
const constant = extractIrConstantFromNode(node);
|
|
3279
|
+
if (constant) {
|
|
3280
|
+
irBuilder.addConstant(constant);
|
|
3281
|
+
}
|
|
3282
|
+
});
|
|
3283
|
+
updatedEdges.forEach((edge) => {
|
|
3284
|
+
irBuilder.addEdge(cloneIrEdge(edge));
|
|
3285
|
+
});
|
|
3286
|
+
irBuilder.setSummary(irSummary);
|
|
3287
|
+
irIssues.forEach((issue) => irBuilder.addIssue(issue));
|
|
3288
|
+
irBuilder.updateMetadata({
|
|
3289
|
+
annotations: {
|
|
3290
|
+
graphSpecMetadata: cloneJsonLike2(vizijMetadata)
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
const irGraph = irBuilder.build();
|
|
3294
|
+
const compiled = compileIrGraph(irGraph, { preferLegacySpec: false });
|
|
3295
|
+
return {
|
|
3296
|
+
spec: compiled.spec,
|
|
3297
|
+
summary: summaryPayload,
|
|
3298
|
+
issues: {
|
|
3299
|
+
byTarget: issuesByTarget,
|
|
3300
|
+
fatal: fatalIssueList
|
|
3301
|
+
},
|
|
3302
|
+
ir: {
|
|
3303
|
+
graph: irGraph,
|
|
3304
|
+
compile: (options) => compileIrGraph(irGraph, options)
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
function createIrIssuesFromLegacy(issuesByTarget, fatalIssues) {
|
|
3309
|
+
const fatalSet = new Set(fatalIssues);
|
|
3310
|
+
const collected = [];
|
|
3311
|
+
let counter = 0;
|
|
3312
|
+
Object.entries(issuesByTarget).forEach(([targetId, messages]) => {
|
|
3313
|
+
messages.forEach((message) => {
|
|
3314
|
+
counter += 1;
|
|
3315
|
+
collected.push({
|
|
3316
|
+
id: `issue_${counter}`,
|
|
3317
|
+
severity: fatalSet.has(message) ? "error" : "warning",
|
|
3318
|
+
message,
|
|
3319
|
+
targetId,
|
|
3320
|
+
tags: fatalSet.has(message) ? ["fatal"] : void 0
|
|
3321
|
+
});
|
|
3322
|
+
});
|
|
3323
|
+
});
|
|
3324
|
+
fatalSet.forEach((message) => {
|
|
3325
|
+
const alreadyCaptured = collected.some(
|
|
3326
|
+
(issue) => issue.message === message
|
|
3327
|
+
);
|
|
3328
|
+
if (alreadyCaptured) {
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
counter += 1;
|
|
3332
|
+
collected.push({
|
|
3333
|
+
id: `issue_${counter}`,
|
|
3334
|
+
severity: "error",
|
|
3335
|
+
message,
|
|
3336
|
+
tags: ["fatal"]
|
|
3337
|
+
});
|
|
3338
|
+
});
|
|
3339
|
+
return collected;
|
|
3340
|
+
}
|
|
3341
|
+
function cloneIrNode(node) {
|
|
3342
|
+
return {
|
|
3343
|
+
id: node.id,
|
|
3344
|
+
type: node.type,
|
|
3345
|
+
category: node.category,
|
|
3346
|
+
label: node.label,
|
|
3347
|
+
description: node.description,
|
|
3348
|
+
params: cloneJsonLike2(node.params),
|
|
3349
|
+
inputDefaults: cloneJsonLike2(node.inputDefaults),
|
|
3350
|
+
metadata: cloneJsonLike2(node.metadata)
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
function cloneIrEdge(edge) {
|
|
3354
|
+
return {
|
|
3355
|
+
id: edge.id,
|
|
3356
|
+
from: {
|
|
3357
|
+
nodeId: edge.from.nodeId,
|
|
3358
|
+
portId: edge.from.portId,
|
|
3359
|
+
component: edge.from.component
|
|
3360
|
+
},
|
|
3361
|
+
to: {
|
|
3362
|
+
nodeId: edge.to.nodeId,
|
|
3363
|
+
portId: edge.to.portId,
|
|
3364
|
+
component: edge.to.component
|
|
3365
|
+
},
|
|
3366
|
+
metadata: cloneJsonLike2(edge.metadata)
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
function extractIrConstantFromNode(node) {
|
|
3370
|
+
if (node.type !== "constant") {
|
|
3371
|
+
return null;
|
|
3372
|
+
}
|
|
3373
|
+
const value = node.params?.value;
|
|
3374
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
3375
|
+
return null;
|
|
3376
|
+
}
|
|
3377
|
+
return {
|
|
3378
|
+
id: node.id,
|
|
3379
|
+
value,
|
|
3380
|
+
valueType: "scalar",
|
|
3381
|
+
metadata: cloneJsonLike2(node.metadata)
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
function cloneJsonLike2(value) {
|
|
3385
|
+
if (value === void 0 || value === null) {
|
|
3386
|
+
return value;
|
|
3387
|
+
}
|
|
3388
|
+
return JSON.parse(JSON.stringify(value));
|
|
3389
|
+
}
|
|
3390
|
+
function validateRemapDefaults(nodes) {
|
|
3391
|
+
const issues = [];
|
|
3392
|
+
nodes.forEach((node) => {
|
|
3393
|
+
if (node.type !== "centered_remap") {
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
const defaults = node.inputDefaults ?? {};
|
|
3397
|
+
[
|
|
3398
|
+
"in_low",
|
|
3399
|
+
"in_anchor",
|
|
3400
|
+
"in_high",
|
|
3401
|
+
"out_low",
|
|
3402
|
+
"out_anchor",
|
|
3403
|
+
"out_high"
|
|
3404
|
+
].forEach((key) => {
|
|
3405
|
+
const value = defaults[key];
|
|
3406
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
3407
|
+
issues.push(`Remap node ${node.id} missing ${key} default.`);
|
|
3408
|
+
}
|
|
3409
|
+
});
|
|
3410
|
+
});
|
|
3411
|
+
return issues;
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
// src/ir/inspection.ts
|
|
3415
|
+
var import_metadata3 = require("@vizij/node-graph-wasm/metadata");
|
|
3416
|
+
var MACHINE_REPORT_VERSION = 1;
|
|
3417
|
+
var REMAP_KEYS = [
|
|
3418
|
+
"inLow",
|
|
3419
|
+
"inAnchor",
|
|
3420
|
+
"inHigh",
|
|
3421
|
+
"outLow",
|
|
3422
|
+
"outAnchor",
|
|
3423
|
+
"outHigh"
|
|
3424
|
+
];
|
|
3425
|
+
var DEFAULT_DIFF_LIMIT = 50;
|
|
3426
|
+
function buildMachineReport(result) {
|
|
3427
|
+
return {
|
|
3428
|
+
reportVersion: MACHINE_REPORT_VERSION,
|
|
3429
|
+
faceId: result.summary.faceId,
|
|
3430
|
+
summary: normalizeSummary(result.summary),
|
|
3431
|
+
issues: normalizeIssues(result.issues),
|
|
3432
|
+
irGraph: result.ir?.graph ? normalizeIrGraph(result.ir.graph) : void 0
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
function diffMachineReports(actual, expected, options) {
|
|
3436
|
+
const limit = normalizeDiffLimit(options?.limit);
|
|
3437
|
+
const ctx = {
|
|
3438
|
+
differences: [],
|
|
3439
|
+
limit,
|
|
3440
|
+
limitReached: false
|
|
3441
|
+
};
|
|
3442
|
+
diffValues(actual, expected, "$", ctx);
|
|
3443
|
+
return {
|
|
3444
|
+
equal: ctx.differences.length === 0,
|
|
3445
|
+
differences: ctx.differences,
|
|
3446
|
+
limitReached: ctx.limitReached
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
function normalizeSummary(summary) {
|
|
3450
|
+
return {
|
|
3451
|
+
faceId: summary.faceId,
|
|
3452
|
+
inputs: [...summary.inputs].sort(),
|
|
3453
|
+
outputs: [...summary.outputs].sort(),
|
|
3454
|
+
bindings: normalizeGraphBindingSummaries(summary.bindings)
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3457
|
+
function normalizeGraphBindingSummaries(bindings) {
|
|
3458
|
+
const normalized = bindings.map((binding) => ({
|
|
3459
|
+
targetId: binding.targetId,
|
|
3460
|
+
animatableId: binding.animatableId,
|
|
3461
|
+
component: binding.component ?? void 0,
|
|
3462
|
+
slotId: binding.slotId,
|
|
3463
|
+
slotAlias: binding.slotAlias,
|
|
3464
|
+
inputId: binding.inputId ?? null,
|
|
3465
|
+
remap: normalizeRemap2(binding.remap),
|
|
3466
|
+
expression: binding.expression,
|
|
3467
|
+
valueType: binding.valueType,
|
|
3468
|
+
nodeId: binding.nodeId,
|
|
3469
|
+
expressionNodeId: binding.expressionNodeId,
|
|
3470
|
+
issues: normalizeStringArray(binding.issues),
|
|
3471
|
+
metadata: cloneBindingMetadata(binding.metadata)
|
|
3472
|
+
}));
|
|
3473
|
+
normalized.sort((a, b) => bindingSortKey(a).localeCompare(bindingSortKey(b)));
|
|
3474
|
+
return normalized;
|
|
3475
|
+
}
|
|
3476
|
+
function cloneBindingMetadata(metadata) {
|
|
3477
|
+
if (!metadata) {
|
|
3478
|
+
return void 0;
|
|
3479
|
+
}
|
|
3480
|
+
return JSON.parse(JSON.stringify(metadata));
|
|
3481
|
+
}
|
|
3482
|
+
function normalizeIssues(issues) {
|
|
3483
|
+
const byTargetEntries = Object.entries(issues.byTarget ?? {}).map(
|
|
3484
|
+
([targetId, messages]) => [targetId, [...messages].sort()]
|
|
3485
|
+
);
|
|
3486
|
+
byTargetEntries.sort(([a], [b]) => a.localeCompare(b));
|
|
3487
|
+
const byTarget = {};
|
|
3488
|
+
byTargetEntries.forEach(([targetId, messages]) => {
|
|
3489
|
+
byTarget[targetId] = messages;
|
|
3490
|
+
});
|
|
3491
|
+
return {
|
|
3492
|
+
fatal: [...issues.fatal].sort(),
|
|
3493
|
+
byTarget
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
function normalizeIrGraph(graph) {
|
|
3497
|
+
return {
|
|
3498
|
+
id: graph.id,
|
|
3499
|
+
faceId: graph.faceId,
|
|
3500
|
+
nodes: normalizeIrNodes(graph.nodes),
|
|
3501
|
+
edges: normalizeIrEdges(graph.edges),
|
|
3502
|
+
constants: normalizeIrConstants(graph.constants),
|
|
3503
|
+
issues: normalizeIrIssues(graph.issues),
|
|
3504
|
+
summary: normalizeIrGraphSummary(graph.summary),
|
|
3505
|
+
metadata: normalizeIrMetadata(graph.metadata)
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
function normalizeIrNodes(nodes) {
|
|
3509
|
+
return [...nodes].map((node) => ({
|
|
3510
|
+
...node,
|
|
3511
|
+
inputDefaults: node.inputDefaults ? sortPlainObject(node.inputDefaults) : void 0,
|
|
3512
|
+
params: node.params ? sortPlainObject(node.params) : void 0,
|
|
3513
|
+
metadata: node.metadata ? sortPlainObject(node.metadata) : void 0,
|
|
3514
|
+
annotations: buildNodeAnnotations(node)
|
|
3515
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
3516
|
+
}
|
|
3517
|
+
function normalizeIrEdges(edges) {
|
|
3518
|
+
return [...edges].map((edge) => ({
|
|
3519
|
+
...edge,
|
|
3520
|
+
from: normalizePortRef(edge.from),
|
|
3521
|
+
to: normalizePortRef(edge.to),
|
|
3522
|
+
metadata: edge.metadata ? sortPlainObject(edge.metadata) : void 0
|
|
3523
|
+
})).sort((a, b) => edgeSortKey(a).localeCompare(edgeSortKey(b)));
|
|
3524
|
+
}
|
|
3525
|
+
function normalizePortRef(edgeRef) {
|
|
3526
|
+
return {
|
|
3527
|
+
nodeId: edgeRef.nodeId,
|
|
3528
|
+
portId: edgeRef.portId ?? void 0,
|
|
3529
|
+
component: edgeRef.component ?? void 0
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
function edgeSortKey(edge) {
|
|
3533
|
+
if (edge.id) {
|
|
3534
|
+
return edge.id;
|
|
3535
|
+
}
|
|
3536
|
+
const fromPort = edge.from.portId ?? "";
|
|
3537
|
+
const toPort = edge.to.portId ?? "";
|
|
3538
|
+
return `${edge.from.nodeId}:${fromPort}->${edge.to.nodeId}:${toPort}`;
|
|
3539
|
+
}
|
|
3540
|
+
function normalizeIrConstants(constants) {
|
|
3541
|
+
return [...constants].map((constant) => ({
|
|
3542
|
+
...constant,
|
|
3543
|
+
metadata: constant.metadata ? sortPlainObject(constant.metadata) : void 0
|
|
3544
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
3545
|
+
}
|
|
3546
|
+
function normalizeIrIssues(issues) {
|
|
3547
|
+
return [...issues].map((issue) => ({
|
|
3548
|
+
...issue,
|
|
3549
|
+
tags: normalizeStringArray(issue.tags),
|
|
3550
|
+
details: issue.details ? sortPlainObject(issue.details) : void 0
|
|
3551
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
3552
|
+
}
|
|
3553
|
+
function normalizeIrGraphSummary(summary) {
|
|
3554
|
+
return {
|
|
3555
|
+
faceId: summary.faceId,
|
|
3556
|
+
inputs: [...summary.inputs].sort(),
|
|
3557
|
+
outputs: [...summary.outputs].sort(),
|
|
3558
|
+
bindings: normalizeIrBindingSummaries(summary.bindings)
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
function normalizeIrBindingSummaries(bindings) {
|
|
3562
|
+
const normalized = bindings.map((binding) => ({
|
|
3563
|
+
...binding,
|
|
3564
|
+
remap: sortPlainObject(binding.remap),
|
|
3565
|
+
issues: normalizeStringArray(binding.issues)
|
|
3566
|
+
}));
|
|
3567
|
+
normalized.sort((a, b) => bindingSortKey(a).localeCompare(bindingSortKey(b)));
|
|
3568
|
+
return normalized;
|
|
3569
|
+
}
|
|
3570
|
+
function normalizeIrMetadata(metadata) {
|
|
3571
|
+
const normalized = {
|
|
3572
|
+
source: metadata.source ?? "unknown"
|
|
3573
|
+
};
|
|
3574
|
+
if (metadata.registryVersion) {
|
|
3575
|
+
normalized.registryVersion = metadata.registryVersion;
|
|
3576
|
+
}
|
|
3577
|
+
if (metadata.annotations) {
|
|
3578
|
+
const sorted = sortPlainObject(metadata.annotations);
|
|
3579
|
+
if (Object.keys(sorted).length > 0) {
|
|
3580
|
+
normalized.annotations = sorted;
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
return normalized;
|
|
3584
|
+
}
|
|
3585
|
+
function buildNodeAnnotations(node) {
|
|
3586
|
+
const signature = findRegistrySignature(node.type);
|
|
3587
|
+
if (!signature) {
|
|
3588
|
+
return void 0;
|
|
3589
|
+
}
|
|
3590
|
+
return {
|
|
3591
|
+
registry: normalizeRegistrySignature(signature)
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
function findRegistrySignature(typeId) {
|
|
3595
|
+
try {
|
|
3596
|
+
const signature = (0, import_metadata3.findNodeSignature)(typeId);
|
|
3597
|
+
if (!signature) {
|
|
3598
|
+
return void 0;
|
|
3599
|
+
}
|
|
3600
|
+
return signature;
|
|
3601
|
+
} catch {
|
|
3602
|
+
return void 0;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
function normalizeRegistrySignature(signature) {
|
|
3606
|
+
return {
|
|
3607
|
+
typeId: signature.type_id,
|
|
3608
|
+
name: signature.name,
|
|
3609
|
+
category: signature.category,
|
|
3610
|
+
doc: signature.doc,
|
|
3611
|
+
inputs: signature.inputs.map(normalizeRegistryPortSpec),
|
|
3612
|
+
variadicInputs: signature.variadic_inputs ? normalizeRegistryVariadicSpec(signature.variadic_inputs) : void 0,
|
|
3613
|
+
outputs: signature.outputs.map(normalizeRegistryPortSpec),
|
|
3614
|
+
variadicOutputs: signature.variadic_outputs ? normalizeRegistryVariadicSpec(signature.variadic_outputs) : void 0,
|
|
3615
|
+
params: signature.params.map(normalizeRegistryParamSpec)
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
function normalizeRegistryPortSpec(port) {
|
|
3619
|
+
const normalized = {
|
|
3620
|
+
id: port.id,
|
|
3621
|
+
label: port.label,
|
|
3622
|
+
type: port.ty ?? "unknown"
|
|
3623
|
+
};
|
|
3624
|
+
if (port.doc) {
|
|
3625
|
+
normalized.doc = port.doc;
|
|
3626
|
+
}
|
|
3627
|
+
if (port.optional) {
|
|
3628
|
+
normalized.optional = true;
|
|
3629
|
+
}
|
|
3630
|
+
return normalized;
|
|
3631
|
+
}
|
|
3632
|
+
function normalizeRegistryVariadicSpec(spec) {
|
|
3633
|
+
const normalized = {
|
|
3634
|
+
id: spec.id,
|
|
3635
|
+
label: spec.label,
|
|
3636
|
+
type: spec.ty ?? "unknown",
|
|
3637
|
+
min: spec.min
|
|
3638
|
+
};
|
|
3639
|
+
if (spec.doc) {
|
|
3640
|
+
normalized.doc = spec.doc;
|
|
3641
|
+
}
|
|
3642
|
+
if (typeof spec.max === "number") {
|
|
3643
|
+
normalized.max = spec.max;
|
|
3644
|
+
}
|
|
3645
|
+
return normalized;
|
|
3646
|
+
}
|
|
3647
|
+
function normalizeRegistryParamSpec(param) {
|
|
3648
|
+
const normalized = {
|
|
3649
|
+
id: param.id,
|
|
3650
|
+
label: param.label,
|
|
3651
|
+
type: param.ty ?? "unknown"
|
|
3652
|
+
};
|
|
3653
|
+
if (param.doc) {
|
|
3654
|
+
normalized.doc = param.doc;
|
|
3655
|
+
}
|
|
3656
|
+
if (param.default_json !== void 0) {
|
|
3657
|
+
normalized.defaultValue = normalizePlainValue(param.default_json);
|
|
3658
|
+
}
|
|
3659
|
+
if (typeof param.min === "number") {
|
|
3660
|
+
normalized.min = param.min;
|
|
3661
|
+
}
|
|
3662
|
+
if (typeof param.max === "number") {
|
|
3663
|
+
normalized.max = param.max;
|
|
3664
|
+
}
|
|
3665
|
+
return normalized;
|
|
3666
|
+
}
|
|
3667
|
+
function normalizeRemap2(remap) {
|
|
3668
|
+
const normalized = {};
|
|
3669
|
+
REMAP_KEYS.forEach((key) => {
|
|
3670
|
+
normalized[key] = remap[key];
|
|
3671
|
+
});
|
|
3672
|
+
return normalized;
|
|
3673
|
+
}
|
|
3674
|
+
function normalizeStringArray(values) {
|
|
3675
|
+
if (!values || values.length === 0) {
|
|
3676
|
+
return void 0;
|
|
3677
|
+
}
|
|
3678
|
+
const unique = Array.from(new Set(values));
|
|
3679
|
+
unique.sort();
|
|
3680
|
+
return unique;
|
|
3681
|
+
}
|
|
3682
|
+
function bindingSortKey(binding) {
|
|
3683
|
+
const component = binding.component ?? "";
|
|
3684
|
+
return `${binding.targetId}::${component}::${binding.slotId}::${binding.slotAlias}::${binding.animatableId}`;
|
|
3685
|
+
}
|
|
3686
|
+
function sortPlainObject(record) {
|
|
3687
|
+
const sorted = {};
|
|
3688
|
+
Object.keys(record).sort().forEach((key) => {
|
|
3689
|
+
sorted[key] = normalizePlainValue(record[key]);
|
|
3690
|
+
});
|
|
3691
|
+
return sorted;
|
|
3692
|
+
}
|
|
3693
|
+
function normalizePlainValue(value) {
|
|
3694
|
+
if (Array.isArray(value)) {
|
|
3695
|
+
return value.map((entry) => normalizePlainValue(entry));
|
|
3696
|
+
}
|
|
3697
|
+
if (isPlainObject(value)) {
|
|
3698
|
+
return sortPlainObject(value);
|
|
3699
|
+
}
|
|
3700
|
+
return value;
|
|
3701
|
+
}
|
|
3702
|
+
function isPlainObject(value) {
|
|
3703
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3704
|
+
}
|
|
3705
|
+
function diffValues(actual, expected, path, ctx) {
|
|
3706
|
+
if (ctx.differences.length >= ctx.limit) {
|
|
3707
|
+
ctx.limitReached = true;
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
if (Object.is(actual, expected)) {
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3713
|
+
if (Array.isArray(actual) && Array.isArray(expected)) {
|
|
3714
|
+
const compareLength = Math.min(actual.length, expected.length);
|
|
3715
|
+
for (let index = 0; index < compareLength; index += 1) {
|
|
3716
|
+
diffValues(actual[index], expected[index], pathIndex(path, index), ctx);
|
|
3717
|
+
if (ctx.differences.length >= ctx.limit) {
|
|
3718
|
+
ctx.limitReached = true;
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
if (actual.length > expected.length) {
|
|
3723
|
+
for (let index = compareLength; index < actual.length; index += 1) {
|
|
3724
|
+
pushDifference(ctx, {
|
|
3725
|
+
kind: "unexpected",
|
|
3726
|
+
path: pathIndex(path, index),
|
|
3727
|
+
actual: actual[index]
|
|
3728
|
+
});
|
|
3729
|
+
if (ctx.differences.length >= ctx.limit) {
|
|
3730
|
+
ctx.limitReached = true;
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
} else if (expected.length > actual.length) {
|
|
3735
|
+
for (let index = compareLength; index < expected.length; index += 1) {
|
|
3736
|
+
pushDifference(ctx, {
|
|
3737
|
+
kind: "missing",
|
|
3738
|
+
path: pathIndex(path, index),
|
|
3739
|
+
expected: expected[index]
|
|
3740
|
+
});
|
|
3741
|
+
if (ctx.differences.length >= ctx.limit) {
|
|
3742
|
+
ctx.limitReached = true;
|
|
3743
|
+
return;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
if (isPlainObject(actual) && isPlainObject(expected)) {
|
|
3750
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(actual), ...Object.keys(expected)]);
|
|
3751
|
+
const sortedKeys = Array.from(keys).sort();
|
|
3752
|
+
for (const key of sortedKeys) {
|
|
3753
|
+
if (!(key in actual)) {
|
|
3754
|
+
pushDifference(ctx, {
|
|
3755
|
+
kind: "missing",
|
|
3756
|
+
path: pathKey(path, key),
|
|
3757
|
+
expected: expected[key]
|
|
3758
|
+
});
|
|
3759
|
+
} else if (!(key in expected)) {
|
|
3760
|
+
pushDifference(ctx, {
|
|
3761
|
+
kind: "unexpected",
|
|
3762
|
+
path: pathKey(path, key),
|
|
3763
|
+
actual: actual[key]
|
|
3764
|
+
});
|
|
3765
|
+
} else {
|
|
3766
|
+
diffValues(actual[key], expected[key], pathKey(path, key), ctx);
|
|
3767
|
+
}
|
|
3768
|
+
if (ctx.differences.length >= ctx.limit) {
|
|
3769
|
+
ctx.limitReached = true;
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
return;
|
|
3774
|
+
}
|
|
3775
|
+
pushDifference(ctx, {
|
|
3776
|
+
kind: "mismatch",
|
|
3777
|
+
path,
|
|
3778
|
+
actual,
|
|
3779
|
+
expected
|
|
3780
|
+
});
|
|
3781
|
+
}
|
|
3782
|
+
function pathKey(base, key) {
|
|
3783
|
+
if (base === "") {
|
|
3784
|
+
return key;
|
|
3785
|
+
}
|
|
3786
|
+
if (base.endsWith("]")) {
|
|
3787
|
+
return `${base}.${key}`;
|
|
3788
|
+
}
|
|
3789
|
+
return `${base}.${key}`;
|
|
3790
|
+
}
|
|
3791
|
+
function pathIndex(base, index) {
|
|
3792
|
+
return `${base}[${index}]`;
|
|
3793
|
+
}
|
|
3794
|
+
function pushDifference(ctx, entry) {
|
|
3795
|
+
if (ctx.differences.length < ctx.limit) {
|
|
3796
|
+
ctx.differences.push(entry);
|
|
3797
|
+
if (ctx.differences.length === ctx.limit) {
|
|
3798
|
+
ctx.limitReached = true;
|
|
3799
|
+
}
|
|
3800
|
+
} else {
|
|
3801
|
+
ctx.limitReached = true;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
function normalizeDiffLimit(limit) {
|
|
3805
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
|
|
3806
|
+
return DEFAULT_DIFF_LIMIT;
|
|
3807
|
+
}
|
|
3808
|
+
return Math.floor(limit);
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
// src/cli/reportIr.ts
|
|
3812
|
+
function usage() {
|
|
3813
|
+
console.error(
|
|
3814
|
+
[
|
|
3815
|
+
"Usage: vizij-ir-report --input <path/to/buildGraphOptions.json> [options]",
|
|
3816
|
+
"",
|
|
3817
|
+
"Options:",
|
|
3818
|
+
" --dump <path|-> Write normalized machine-readable report (use '-' for stdout).",
|
|
3819
|
+
" --diff <path|-> Compare against a dumped report (use '-' or 'stdin').",
|
|
3820
|
+
" --diff-limit <number> Maximum diff entries to emit (default 50).",
|
|
3821
|
+
" -h, --help Show this message."
|
|
3822
|
+
].join("\n")
|
|
3823
|
+
);
|
|
3824
|
+
process.exit(1);
|
|
3825
|
+
throw new Error("Unreachable");
|
|
3826
|
+
}
|
|
3827
|
+
function parseArgs(argv) {
|
|
3828
|
+
const args = [...argv];
|
|
3829
|
+
let inputPath = "";
|
|
3830
|
+
let dumpPath;
|
|
3831
|
+
let diffPath;
|
|
3832
|
+
let diffLimit;
|
|
3833
|
+
while (args.length > 0) {
|
|
3834
|
+
const token = args.shift();
|
|
3835
|
+
if (!token) {
|
|
3836
|
+
break;
|
|
3837
|
+
}
|
|
3838
|
+
if (token === "--input" || token === "-i") {
|
|
3839
|
+
const value = args.shift();
|
|
3840
|
+
if (!value) {
|
|
3841
|
+
usage();
|
|
3842
|
+
}
|
|
3843
|
+
inputPath = value;
|
|
3844
|
+
} else if (token === "--help" || token === "-h") {
|
|
3845
|
+
usage();
|
|
3846
|
+
} else if (token === "--dump" || token === "--dump-json") {
|
|
3847
|
+
const value = args.shift();
|
|
3848
|
+
if (!value) {
|
|
3849
|
+
usage();
|
|
3850
|
+
}
|
|
3851
|
+
dumpPath = normalizeStreamToken(value);
|
|
3852
|
+
} else if (token === "--diff") {
|
|
3853
|
+
const value = args.shift();
|
|
3854
|
+
if (!value) {
|
|
3855
|
+
usage();
|
|
3856
|
+
}
|
|
3857
|
+
diffPath = normalizeStreamToken(value);
|
|
3858
|
+
} else if (token === "--diff-limit") {
|
|
3859
|
+
const value = args.shift();
|
|
3860
|
+
if (!value) {
|
|
3861
|
+
usage();
|
|
3862
|
+
}
|
|
3863
|
+
diffLimit = parsePositiveInteger(value, "--diff-limit");
|
|
3864
|
+
} else if (!token.startsWith("--") && !inputPath) {
|
|
3865
|
+
inputPath = token;
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
if (!inputPath) {
|
|
3869
|
+
usage();
|
|
3870
|
+
}
|
|
3871
|
+
return {
|
|
3872
|
+
inputPath: (0, import_node_path.resolve)(process.cwd(), inputPath),
|
|
3873
|
+
dumpPath: resolveOptionalPath(dumpPath),
|
|
3874
|
+
diffPath: resolveOptionalPath(diffPath),
|
|
3875
|
+
diffLimit
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
function loadCliInput(path) {
|
|
3879
|
+
const raw = JSON.parse((0, import_node_fs.readFileSync)(path, "utf-8"));
|
|
3880
|
+
const inputs = raw.inputs ?? [];
|
|
3881
|
+
const inputsById = new Map(inputs.map((input) => [input.id, input]));
|
|
3882
|
+
const inputMetadata = raw.inputMetadata ? new Map(
|
|
3883
|
+
raw.inputMetadata.map((entry) => ({
|
|
3884
|
+
id: entry.id,
|
|
3885
|
+
value: entry
|
|
3886
|
+
})).filter(
|
|
3887
|
+
(entry) => Boolean(entry.id)
|
|
3888
|
+
).map((entry) => [entry.id, entry.value])
|
|
3889
|
+
) : void 0;
|
|
3890
|
+
return {
|
|
3891
|
+
faceId: raw.faceId,
|
|
3892
|
+
animatables: raw.animatables,
|
|
3893
|
+
components: raw.components,
|
|
3894
|
+
bindings: raw.bindings,
|
|
3895
|
+
inputsById,
|
|
3896
|
+
inputBindings: raw.inputBindings ?? {},
|
|
3897
|
+
inputMetadata
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
function printIssues(result) {
|
|
3901
|
+
console.log(`
|
|
3902
|
+
IR Diagnostics for face "${result.summary.faceId}"`);
|
|
3903
|
+
if (result.issues.fatal.length === 0) {
|
|
3904
|
+
console.log("\u2022 No fatal binding issues detected.");
|
|
3905
|
+
} else {
|
|
3906
|
+
console.log("\u2022 Fatal issues:");
|
|
3907
|
+
result.issues.fatal.forEach((issue) => console.log(` - ${issue}`));
|
|
3908
|
+
}
|
|
3909
|
+
const targets = Object.entries(result.issues.byTarget);
|
|
3910
|
+
if (targets.length > 0) {
|
|
3911
|
+
console.log("\nPer-binding issues:");
|
|
3912
|
+
targets.forEach(([targetId, issues]) => {
|
|
3913
|
+
issues.forEach((issue) => {
|
|
3914
|
+
console.log(` - ${targetId}: ${issue}`);
|
|
3915
|
+
});
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3918
|
+
const irIssues = result.ir?.graph.issues ?? [];
|
|
3919
|
+
if (irIssues.length > 0) {
|
|
3920
|
+
console.log("\nIR issues:");
|
|
3921
|
+
irIssues.forEach((issue) => {
|
|
3922
|
+
console.log(
|
|
3923
|
+
` - (${issue.severity}) ${issue.message}${issue.targetId ? ` [${issue.targetId}]` : ""}`
|
|
3924
|
+
);
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
function printBindingSummary(result, _options) {
|
|
3929
|
+
console.log("\nBinding summary:");
|
|
3930
|
+
result.summary.bindings.forEach((binding) => {
|
|
3931
|
+
console.log(
|
|
3932
|
+
` - ${binding.targetId} (${binding.slotAlias}): expr="${binding.expression}"`
|
|
3933
|
+
);
|
|
3934
|
+
if (binding.issues && binding.issues.length > 0) {
|
|
3935
|
+
binding.issues.forEach((issue) => {
|
|
3936
|
+
console.log(` \u2022 ${issue}`);
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
async function main() {
|
|
3942
|
+
const { inputPath, dumpPath, diffPath, diffLimit } = parseArgs(
|
|
3943
|
+
process.argv.slice(2)
|
|
3944
|
+
);
|
|
3945
|
+
const options = loadCliInput(inputPath);
|
|
3946
|
+
const result = buildRigGraphSpec(options);
|
|
3947
|
+
const machineReport = buildMachineReport(result);
|
|
3948
|
+
if (diffPath && dumpPath && diffPath !== "-" && dumpPath !== "-" && diffPath === dumpPath) {
|
|
3949
|
+
throw new Error(
|
|
3950
|
+
"Cannot reuse the same file path for --dump and --diff. Provide separate files."
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3953
|
+
if (diffPath) {
|
|
3954
|
+
if (dumpPath === "-") {
|
|
3955
|
+
throw new Error("Cannot dump to stdout while running --diff.");
|
|
3956
|
+
}
|
|
3957
|
+
if (dumpPath) {
|
|
3958
|
+
writeMachineDump(machineReport, dumpPath);
|
|
3959
|
+
}
|
|
3960
|
+
const baseline = loadMachineDump(diffPath);
|
|
3961
|
+
validateReportVersion(baseline.reportVersion);
|
|
3962
|
+
const diffResult = diffMachineReports(machineReport, baseline, {
|
|
3963
|
+
limit: diffLimit
|
|
3964
|
+
});
|
|
3965
|
+
const diffPayload = {
|
|
3966
|
+
mode: "diff",
|
|
3967
|
+
reportVersion: MACHINE_REPORT_VERSION,
|
|
3968
|
+
faceId: machineReport.faceId,
|
|
3969
|
+
inputPath,
|
|
3970
|
+
baselinePath: diffPath,
|
|
3971
|
+
equal: diffResult.equal,
|
|
3972
|
+
differenceCount: diffResult.differences.length,
|
|
3973
|
+
limitReached: diffResult.limitReached,
|
|
3974
|
+
differences: diffResult.differences
|
|
3975
|
+
};
|
|
3976
|
+
console.log(JSON.stringify(diffPayload, null, 2));
|
|
3977
|
+
process.exitCode = diffResult.equal ? 0 : 1;
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
if (dumpPath) {
|
|
3981
|
+
writeMachineDump(machineReport, dumpPath);
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
console.log(
|
|
3985
|
+
`Loaded ${options.components.length} components / ${options.faceId} from ${inputPath}`
|
|
3986
|
+
);
|
|
3987
|
+
printIssues(result);
|
|
3988
|
+
printBindingSummary(result, options);
|
|
3989
|
+
console.log("\nDone.");
|
|
3990
|
+
}
|
|
3991
|
+
main().catch((error) => {
|
|
3992
|
+
console.error("[vizij-ir-report] Failed:", error);
|
|
3993
|
+
process.exit(1);
|
|
3994
|
+
});
|
|
3995
|
+
function resolveOptionalPath(path) {
|
|
3996
|
+
if (!path || path === "-") {
|
|
3997
|
+
return path;
|
|
3998
|
+
}
|
|
3999
|
+
return (0, import_node_path.resolve)(process.cwd(), path);
|
|
4000
|
+
}
|
|
4001
|
+
function normalizeStreamToken(value) {
|
|
4002
|
+
const lower = value.toLowerCase();
|
|
4003
|
+
if (lower === "-" || lower === "stdout" || lower === "stdin") {
|
|
4004
|
+
return "-";
|
|
4005
|
+
}
|
|
4006
|
+
return value;
|
|
4007
|
+
}
|
|
4008
|
+
function parsePositiveInteger(value, flag) {
|
|
4009
|
+
const parsed = Number.parseInt(value, 10);
|
|
4010
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4011
|
+
console.error(`Invalid value for ${flag}: ${value}`);
|
|
4012
|
+
usage();
|
|
4013
|
+
}
|
|
4014
|
+
return parsed;
|
|
4015
|
+
}
|
|
4016
|
+
function writeMachineDump(report, target) {
|
|
4017
|
+
const payload = `${JSON.stringify(report, null, 2)}
|
|
4018
|
+
`;
|
|
4019
|
+
if (target === "-") {
|
|
4020
|
+
process.stdout.write(payload);
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
(0, import_node_fs.writeFileSync)(target, payload, "utf-8");
|
|
4024
|
+
}
|
|
4025
|
+
function loadMachineDump(path) {
|
|
4026
|
+
const content = path === "-" ? (0, import_node_fs.readFileSync)(0, "utf-8") : (0, import_node_fs.readFileSync)(path, "utf-8");
|
|
4027
|
+
try {
|
|
4028
|
+
const parsed = JSON.parse(content);
|
|
4029
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.reportVersion !== "number") {
|
|
4030
|
+
throw new Error("Invalid machine report structure.");
|
|
4031
|
+
}
|
|
4032
|
+
return parsed;
|
|
4033
|
+
} catch (error) {
|
|
4034
|
+
if (error instanceof SyntaxError) {
|
|
4035
|
+
throw new Error("Failed to parse machine-readable report JSON.");
|
|
4036
|
+
}
|
|
4037
|
+
throw error;
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
function validateReportVersion(version) {
|
|
4041
|
+
if (version !== MACHINE_REPORT_VERSION) {
|
|
4042
|
+
throw new Error(
|
|
4043
|
+
`Machine report version mismatch (expected ${MACHINE_REPORT_VERSION}, received ${version}).`
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
4046
|
+
}
|