@openmbee/opensysml 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +462 -0
- package/dist/browser/index.d.ts +12 -0
- package/dist/browser/index.js +34 -0
- package/dist/core/capabilities.d.ts +94 -0
- package/dist/core/capabilities.js +130 -0
- package/dist/core/connection.d.ts +79 -0
- package/dist/core/connection.js +110 -0
- package/dist/core/errors.d.ts +98 -0
- package/dist/core/errors.js +100 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +10 -0
- package/dist/core/model.d.ts +139 -0
- package/dist/core/model.js +339 -0
- package/dist/core/package.d.ts +4 -0
- package/dist/core/package.js +4 -0
- package/dist/core/status.d.ts +15 -0
- package/dist/core/status.js +94 -0
- package/dist/core/transport.d.ts +10 -0
- package/dist/core/transport.js +69 -0
- package/dist/core/values.d.ts +271 -0
- package/dist/core/values.js +687 -0
- package/dist/generated/sysml_pb.d.ts +5668 -0
- package/dist/generated/sysml_pb.js +837 -0
- package/dist/node/binary.d.ts +127 -0
- package/dist/node/binary.js +564 -0
- package/dist/node/index.d.ts +31 -0
- package/dist/node/index.js +101 -0
- package/dist/node/service.d.ts +31 -0
- package/dist/node/service.js +238 -0
- package/dist/node/signing.d.ts +55 -0
- package/dist/node/signing.js +167 -0
- package/package.json +77 -0
- package/release-digests.json +60 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
// Values, quantities and verdicts as discriminated unions: a caller switches on
|
|
2
|
+
// `kind` and the compiler checks the switch is exhaustive.
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import { ArraySchema, ComplexSchema, EnumLiteralSchema, FailureReason, FunctionSchema, MeasurementRefSchema, MetaobjectSchema, QuantitySchema, TensorQuantitySchema, UnitFactorSchema, UnitTermSchema, ValueSchema, ValueSequenceSchema, ValueSetSchema, VectorQuantitySchema, VectorSchema, } from "../generated/sysml_pb.js";
|
|
5
|
+
import { MalformedValueError } from "./errors.js";
|
|
6
|
+
/**
|
|
7
|
+
* Decodes a `sysml.Value` into the union.
|
|
8
|
+
*
|
|
9
|
+
* @throws {MalformedValueError} for a value that contradicts itself: an array
|
|
10
|
+
* whose elements do not fill its dimensions, a vector with a component that
|
|
11
|
+
* is not a number, a vector quantity with no components, a set listing a
|
|
12
|
+
* member twice, a tensor quantity whose components do not fill its
|
|
13
|
+
* dimensions, a quantity (alone or as a component) with no magnitude, a
|
|
14
|
+
* measurement reference naming no unit or a unit without its reduction, or a
|
|
15
|
+
* metaobject naming no element.
|
|
16
|
+
*/
|
|
17
|
+
export function decodeValue(value) {
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
return { kind: "absent" };
|
|
20
|
+
}
|
|
21
|
+
const kind = value.kind;
|
|
22
|
+
switch (kind.case) {
|
|
23
|
+
case "intValue":
|
|
24
|
+
return { kind: "int", value: kind.value };
|
|
25
|
+
case "realValue":
|
|
26
|
+
return { kind: "real", value: kind.value };
|
|
27
|
+
case "complex":
|
|
28
|
+
return { kind: "complex", value: { real: kind.value.real, imaginary: kind.value.imaginary } };
|
|
29
|
+
case "boolValue":
|
|
30
|
+
return { kind: "boolean", value: kind.value };
|
|
31
|
+
case "stringValue":
|
|
32
|
+
return { kind: "string", value: kind.value };
|
|
33
|
+
case "instanceId":
|
|
34
|
+
return { kind: "instance", id: kind.value };
|
|
35
|
+
case "sequence":
|
|
36
|
+
return { kind: "sequence", elements: kind.value.elements.map(decodeValue) };
|
|
37
|
+
case "quantity":
|
|
38
|
+
return { kind: "quantity", ...decodeQuantity(kind.value) };
|
|
39
|
+
case "measurementRef":
|
|
40
|
+
return { kind: "measurementRef", ...decodeMeasurementRef(kind.value) };
|
|
41
|
+
case "function":
|
|
42
|
+
return { kind: "function", ...decodeFunction(kind.value) };
|
|
43
|
+
case "enumLiteral":
|
|
44
|
+
return { kind: "enum", value: decodeEnumLiteral(kind.value) };
|
|
45
|
+
case "array":
|
|
46
|
+
return { kind: "array", ...decodeArray(kind.value) };
|
|
47
|
+
case "vector":
|
|
48
|
+
return { kind: "vector", components: decodeVector(kind.value) };
|
|
49
|
+
case "vectorQuantity":
|
|
50
|
+
return { kind: "vectorQuantity", components: decodeVectorQuantity(kind.value) };
|
|
51
|
+
case "set":
|
|
52
|
+
return { kind: "set", elements: decodeSet(kind.value) };
|
|
53
|
+
case "tensorQuantity":
|
|
54
|
+
return { kind: "tensorQuantity", ...decodeTensorQuantity(kind.value) };
|
|
55
|
+
case "metaobject":
|
|
56
|
+
return { kind: "metaobject", ...decodeMetaobject(kind.value) };
|
|
57
|
+
case "null":
|
|
58
|
+
return { kind: "null", reason: kind.value };
|
|
59
|
+
case "unset":
|
|
60
|
+
return { kind: "unset" };
|
|
61
|
+
case "undetermined":
|
|
62
|
+
return {
|
|
63
|
+
kind: "undetermined",
|
|
64
|
+
reason: kind.value.reason,
|
|
65
|
+
countLower: kind.value.count?.lower ?? "",
|
|
66
|
+
countUpper: kind.value.count?.upper ?? "",
|
|
67
|
+
};
|
|
68
|
+
case "infinity":
|
|
69
|
+
// Only an asserted arm carries the unbounded value.
|
|
70
|
+
if (!kind.value) {
|
|
71
|
+
throw new MalformedValueError("the infinity arm states no value unless it is true");
|
|
72
|
+
}
|
|
73
|
+
return { kind: "infinity" };
|
|
74
|
+
case undefined:
|
|
75
|
+
return { kind: "absent" };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Encodes the union as the `sysml.Value` the service decodes: the inverse of
|
|
80
|
+
* {@link decodeValue} for every kind but `absent`, which is no value at all.
|
|
81
|
+
*
|
|
82
|
+
* @throws {MalformedValueError} for an `absent` value, or a structured value
|
|
83
|
+
* the service would refuse (see {@link decodeValue}).
|
|
84
|
+
*/
|
|
85
|
+
export function encodeValue(value) {
|
|
86
|
+
switch (value.kind) {
|
|
87
|
+
case "int":
|
|
88
|
+
return create(ValueSchema, { kind: { case: "intValue", value: value.value } });
|
|
89
|
+
case "real":
|
|
90
|
+
return create(ValueSchema, { kind: { case: "realValue", value: value.value } });
|
|
91
|
+
case "complex":
|
|
92
|
+
return create(ValueSchema, {
|
|
93
|
+
kind: { case: "complex", value: create(ComplexSchema, value.value) },
|
|
94
|
+
});
|
|
95
|
+
case "boolean":
|
|
96
|
+
return create(ValueSchema, { kind: { case: "boolValue", value: value.value } });
|
|
97
|
+
case "string":
|
|
98
|
+
return create(ValueSchema, { kind: { case: "stringValue", value: value.value } });
|
|
99
|
+
case "instance":
|
|
100
|
+
return create(ValueSchema, { kind: { case: "instanceId", value: value.id } });
|
|
101
|
+
case "sequence":
|
|
102
|
+
return create(ValueSchema, {
|
|
103
|
+
kind: {
|
|
104
|
+
case: "sequence",
|
|
105
|
+
value: create(ValueSequenceSchema, { elements: value.elements.map(encodeValue) }),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
case "quantity":
|
|
109
|
+
return create(ValueSchema, { kind: { case: "quantity", value: encodeQuantity(value) } });
|
|
110
|
+
case "measurementRef":
|
|
111
|
+
return create(ValueSchema, {
|
|
112
|
+
kind: { case: "measurementRef", value: encodeMeasurementRef(value) },
|
|
113
|
+
});
|
|
114
|
+
case "function":
|
|
115
|
+
return create(ValueSchema, { kind: { case: "function", value: encodeFunction(value) } });
|
|
116
|
+
case "enum":
|
|
117
|
+
return create(ValueSchema, {
|
|
118
|
+
kind: { case: "enumLiteral", value: encodeEnumLiteral(value.value) },
|
|
119
|
+
});
|
|
120
|
+
case "array":
|
|
121
|
+
checkShape("an array", value.dimensions, value.elements.length);
|
|
122
|
+
return create(ValueSchema, {
|
|
123
|
+
kind: {
|
|
124
|
+
case: "array",
|
|
125
|
+
value: create(ArraySchema, {
|
|
126
|
+
dimensions: value.dimensions,
|
|
127
|
+
elements: value.elements.map(encodeValue),
|
|
128
|
+
}),
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
case "vector":
|
|
132
|
+
return create(ValueSchema, {
|
|
133
|
+
kind: {
|
|
134
|
+
case: "vector",
|
|
135
|
+
value: create(VectorSchema, { components: value.components.map(encodeMagnitude) }),
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
case "vectorQuantity":
|
|
139
|
+
if (value.components.length === 0) {
|
|
140
|
+
throw new MalformedValueError("a vector quantity has no components");
|
|
141
|
+
}
|
|
142
|
+
return create(ValueSchema, {
|
|
143
|
+
kind: {
|
|
144
|
+
case: "vectorQuantity",
|
|
145
|
+
value: create(VectorQuantitySchema, { components: value.components.map(encodeQuantity) }),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
case "set":
|
|
149
|
+
return create(ValueSchema, {
|
|
150
|
+
kind: {
|
|
151
|
+
case: "set",
|
|
152
|
+
value: create(ValueSetSchema, {
|
|
153
|
+
elements: uniqueMembers(value.elements).map(encodeValue),
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
case "tensorQuantity":
|
|
158
|
+
checkShape("a tensor quantity", value.dimensions, value.components.length);
|
|
159
|
+
return create(ValueSchema, {
|
|
160
|
+
kind: {
|
|
161
|
+
case: "tensorQuantity",
|
|
162
|
+
value: create(TensorQuantitySchema, {
|
|
163
|
+
dimensions: value.dimensions,
|
|
164
|
+
components: value.components.map(encodeQuantity),
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
case "metaobject":
|
|
169
|
+
return create(ValueSchema, { kind: { case: "metaobject", value: encodeMetaobject(value) } });
|
|
170
|
+
case "null":
|
|
171
|
+
return create(ValueSchema, { kind: { case: "null", value: value.reason } });
|
|
172
|
+
case "unset":
|
|
173
|
+
return create(ValueSchema, { kind: { case: "unset", value: true } });
|
|
174
|
+
case "undetermined":
|
|
175
|
+
throw new MalformedValueError("an undetermined result is something to read, not to send");
|
|
176
|
+
case "infinity":
|
|
177
|
+
return create(ValueSchema, { kind: { case: "infinity", value: true } });
|
|
178
|
+
case "absent":
|
|
179
|
+
throw new MalformedValueError("an absent value is no value at all, so it cannot be sent");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Decodes a `sysml.Verdict` into the union. */
|
|
183
|
+
export function decodeVerdict(verdict) {
|
|
184
|
+
const subject = {
|
|
185
|
+
kind: verdict.kind,
|
|
186
|
+
elementId: verdict.elementId,
|
|
187
|
+
element: verdict.element,
|
|
188
|
+
...(verdict.instanceId === 0n ? {} : { instanceId: verdict.instanceId }),
|
|
189
|
+
...(verdict.instanceTypeId === "" ? {} : { instanceTypeId: verdict.instanceTypeId }),
|
|
190
|
+
};
|
|
191
|
+
const standing = decodeStanding(verdict);
|
|
192
|
+
if (verdict.error !== "") {
|
|
193
|
+
return {
|
|
194
|
+
kind: "undecided",
|
|
195
|
+
subject,
|
|
196
|
+
error: verdict.error,
|
|
197
|
+
cause: failureCause(verdict.failureReason),
|
|
198
|
+
standing,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return verdict.holds
|
|
202
|
+
? { kind: "holds", subject, standing }
|
|
203
|
+
: { kind: "fails", subject, condition: verdict.condition, standing };
|
|
204
|
+
}
|
|
205
|
+
/** Reads the standing fields the `engines` capability adds to a verdict. */
|
|
206
|
+
export function decodeStanding(verdict) {
|
|
207
|
+
return {
|
|
208
|
+
engine: verdict.engine,
|
|
209
|
+
strength: verdict.strength,
|
|
210
|
+
bounds: verdict.bounds.map((b) => ({ name: b.name, limit: b.limit, reached: b.reached })),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Names the enum the service reports for a failure it could not answer through. */
|
|
214
|
+
export function failureCause(reason) {
|
|
215
|
+
switch (reason) {
|
|
216
|
+
case FailureReason.EVALUATION:
|
|
217
|
+
return "evaluation";
|
|
218
|
+
case FailureReason.WRONG_KIND:
|
|
219
|
+
return "wrong_kind";
|
|
220
|
+
case FailureReason.AMBIGUOUS_SUBJECT:
|
|
221
|
+
return "ambiguous_subject";
|
|
222
|
+
case FailureReason.UNSPECIFIED:
|
|
223
|
+
return "unspecified";
|
|
224
|
+
case FailureReason.UNDECIDED:
|
|
225
|
+
return "undecided";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** Renders a value the way the REPL prints one, for logs and error messages. */
|
|
229
|
+
export function formatValue(value) {
|
|
230
|
+
switch (value.kind) {
|
|
231
|
+
case "int":
|
|
232
|
+
return value.value.toString();
|
|
233
|
+
case "real":
|
|
234
|
+
return formatReal(value.value);
|
|
235
|
+
case "complex":
|
|
236
|
+
return formatComplex(value.value);
|
|
237
|
+
case "boolean":
|
|
238
|
+
return value.value ? "true" : "false";
|
|
239
|
+
case "string":
|
|
240
|
+
return JSON.stringify(value.value);
|
|
241
|
+
case "instance":
|
|
242
|
+
return `<instance ${value.id.toString()}>`;
|
|
243
|
+
case "sequence":
|
|
244
|
+
return `(${value.elements.map(formatValue).join(", ")})`;
|
|
245
|
+
case "quantity": {
|
|
246
|
+
const magnitude = formatMagnitude(value.magnitude);
|
|
247
|
+
return value.unit === "" ? magnitude : `${magnitude}[${value.unit}]`;
|
|
248
|
+
}
|
|
249
|
+
case "measurementRef":
|
|
250
|
+
return value.unit === "" ? formatUnitTerm(value.unitTerm) : value.unit;
|
|
251
|
+
case "function":
|
|
252
|
+
return value.calcId;
|
|
253
|
+
case "enum":
|
|
254
|
+
return value.value.name;
|
|
255
|
+
case "array":
|
|
256
|
+
return `Array(${value.dimensions.join(", ")})[${value.elements.map(formatValue).join(", ")}]`;
|
|
257
|
+
case "vector":
|
|
258
|
+
return `⟨${value.components.map(formatMagnitude).join(", ")}⟩`;
|
|
259
|
+
case "vectorQuantity":
|
|
260
|
+
return `⟨${value.components.map((c) => formatValue({ kind: "quantity", ...c })).join(", ")}⟩`;
|
|
261
|
+
case "set":
|
|
262
|
+
return `{${value.elements.map(formatValue).join(", ")}}`;
|
|
263
|
+
case "tensorQuantity":
|
|
264
|
+
return formatTensorQuantity(value);
|
|
265
|
+
case "metaobject":
|
|
266
|
+
return `meta(${value.elementId} : ${value.metaclassId})`;
|
|
267
|
+
case "null":
|
|
268
|
+
return value.reason === "" ? "null" : `null (${value.reason})`;
|
|
269
|
+
case "unset":
|
|
270
|
+
return "unset";
|
|
271
|
+
case "undetermined":
|
|
272
|
+
return "<undetermined>";
|
|
273
|
+
case "infinity":
|
|
274
|
+
return "*";
|
|
275
|
+
case "absent":
|
|
276
|
+
return "absent";
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function formatReal(value) {
|
|
280
|
+
return Number.isInteger(value) ? value.toFixed(1) : value.toString();
|
|
281
|
+
}
|
|
282
|
+
function formatMagnitude(magnitude) {
|
|
283
|
+
return magnitude.kind === "int" ? magnitude.value.toString() : formatReal(magnitude.value);
|
|
284
|
+
}
|
|
285
|
+
/** `Tensor(2, 2, 2)[1.0, …, 8.0][Pa]` when every component shares a unit; else each with its own. */
|
|
286
|
+
function formatTensorQuantity(tensor) {
|
|
287
|
+
const dims = tensor.dimensions.join(", ");
|
|
288
|
+
const units = new Set(tensor.components.map((c) => c.unit));
|
|
289
|
+
if (units.size === 1 && tensor.components[0]?.unit !== "") {
|
|
290
|
+
const body = tensor.components.map((c) => formatMagnitude(c.magnitude)).join(", ");
|
|
291
|
+
return `Tensor(${dims})[${body}][${tensor.components[0]?.unit ?? ""}]`;
|
|
292
|
+
}
|
|
293
|
+
const body = tensor.components.map((c) => formatValue({ kind: "quantity", ...c })).join(", ");
|
|
294
|
+
return `Tensor(${dims})[${body}]`;
|
|
295
|
+
}
|
|
296
|
+
/** `1.5 - 2.0i`, as the REPL prints a Complex; the sign is the imaginary part's. */
|
|
297
|
+
function formatComplex(value) {
|
|
298
|
+
const sign = value.imaginary < 0 || Object.is(value.imaginary, -0) ? "-" : "+";
|
|
299
|
+
return `${formatReal(value.real)} ${sign} ${formatReal(Math.abs(value.imaginary))}i`;
|
|
300
|
+
}
|
|
301
|
+
function decodeQuantity(quantity) {
|
|
302
|
+
let magnitude;
|
|
303
|
+
switch (quantity.magnitude.case) {
|
|
304
|
+
case "intMagnitude":
|
|
305
|
+
magnitude = { kind: "int", value: quantity.magnitude.value };
|
|
306
|
+
break;
|
|
307
|
+
case "realMagnitude":
|
|
308
|
+
magnitude = { kind: "real", value: quantity.magnitude.value };
|
|
309
|
+
break;
|
|
310
|
+
default:
|
|
311
|
+
throw new MalformedValueError(`a quantity in [${quantity.unit}] has no magnitude`);
|
|
312
|
+
}
|
|
313
|
+
const unitTerm = quantity.unitTerm === undefined ? undefined : decodeUnitTerm(quantity.unitTerm);
|
|
314
|
+
return {
|
|
315
|
+
magnitude,
|
|
316
|
+
unit: quantity.unit,
|
|
317
|
+
...(unitTerm === undefined ? {} : { unitTerm }),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function encodeQuantity(quantity) {
|
|
321
|
+
return create(QuantitySchema, {
|
|
322
|
+
magnitude: quantity.magnitude.kind === "int"
|
|
323
|
+
? { case: "intMagnitude", value: quantity.magnitude.value }
|
|
324
|
+
: { case: "realMagnitude", value: quantity.magnitude.value },
|
|
325
|
+
unit: quantity.unit,
|
|
326
|
+
...(quantity.unitTerm === undefined ? {} : { unitTerm: encodeUnitTerm(quantity.unitTerm) }),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function decodeMeasurementRef(ref) {
|
|
330
|
+
if (ref.unit === "" && ref.unitId === "" && ref.unitTerm === undefined) {
|
|
331
|
+
throw new MalformedValueError("a measurement reference names no unit");
|
|
332
|
+
}
|
|
333
|
+
if (ref.unitTerm === undefined) {
|
|
334
|
+
throw new MalformedValueError(`a measurement reference ${ref.unit || ref.unitId} has no reduction to base units`);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
unit: ref.unit,
|
|
338
|
+
unitTerm: decodeUnitTerm(ref.unitTerm),
|
|
339
|
+
...(ref.unitId === "" ? {} : { unitId: ref.unitId }),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function encodeMeasurementRef(ref) {
|
|
343
|
+
return create(MeasurementRefSchema, {
|
|
344
|
+
unit: ref.unit,
|
|
345
|
+
unitTerm: encodeUnitTerm(ref.unitTerm),
|
|
346
|
+
unitId: ref.unitId ?? "",
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function decodeFunction(fn) {
|
|
350
|
+
if (fn.calcId === "") {
|
|
351
|
+
throw new MalformedValueError("a function names no calc");
|
|
352
|
+
}
|
|
353
|
+
return { calcId: fn.calcId, ...(fn.selfId === 0n ? {} : { selfId: fn.selfId }) };
|
|
354
|
+
}
|
|
355
|
+
function encodeFunction(fn) {
|
|
356
|
+
if (fn.calcId === "") {
|
|
357
|
+
throw new MalformedValueError("a function names no calc");
|
|
358
|
+
}
|
|
359
|
+
return create(FunctionSchema, { calcId: fn.calcId, selfId: fn.selfId ?? 0n });
|
|
360
|
+
}
|
|
361
|
+
function decodeMetaobject(meta) {
|
|
362
|
+
if (meta.elementId === "") {
|
|
363
|
+
throw new MalformedValueError("a metaobject names no element");
|
|
364
|
+
}
|
|
365
|
+
return { elementId: meta.elementId, metaclassId: meta.metaclassId };
|
|
366
|
+
}
|
|
367
|
+
function encodeMetaobject(meta) {
|
|
368
|
+
if (meta.elementId === "") {
|
|
369
|
+
throw new MalformedValueError("a metaobject names no element");
|
|
370
|
+
}
|
|
371
|
+
return create(MetaobjectSchema, { elementId: meta.elementId, metaclassId: meta.metaclassId });
|
|
372
|
+
}
|
|
373
|
+
function encodeUnitTerm(term) {
|
|
374
|
+
return create(UnitTermSchema, {
|
|
375
|
+
scaleNum: term.scaleNum,
|
|
376
|
+
scaleDen: term.scaleDen,
|
|
377
|
+
factors: term.factors.map((factor) => create(UnitFactorSchema, factor)),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/** `1000·SI::metre·SI::second^-1`, for a unit that was never written down. */
|
|
381
|
+
function formatUnitTerm(term) {
|
|
382
|
+
const parts = [];
|
|
383
|
+
if (term.scaleNum !== 1 || term.scaleDen !== 1) {
|
|
384
|
+
parts.push(term.scaleDen === 1 ? `${term.scaleNum}` : `${term.scaleNum}/${term.scaleDen}`);
|
|
385
|
+
}
|
|
386
|
+
for (const factor of term.factors) {
|
|
387
|
+
parts.push(factor.exponent === 1 ? factor.unitId : `${factor.unitId}^${factor.exponent}`);
|
|
388
|
+
}
|
|
389
|
+
return parts.length === 0 ? "1" : parts.join("·");
|
|
390
|
+
}
|
|
391
|
+
function encodeMagnitude(magnitude) {
|
|
392
|
+
return magnitude.kind === "int"
|
|
393
|
+
? create(ValueSchema, { kind: { case: "intValue", value: magnitude.value } })
|
|
394
|
+
: create(ValueSchema, { kind: { case: "realValue", value: magnitude.value } });
|
|
395
|
+
}
|
|
396
|
+
/** The flattened size the dimensions demand, refusing a dimension that is not positive. */
|
|
397
|
+
function checkShape(what, dimensions, elementCount) {
|
|
398
|
+
let size = 1n;
|
|
399
|
+
for (const extent of dimensions) {
|
|
400
|
+
if (extent <= 0n) {
|
|
401
|
+
throw new MalformedValueError(`${what} dimension is ${extent.toString()}, not positive`);
|
|
402
|
+
}
|
|
403
|
+
size *= extent;
|
|
404
|
+
}
|
|
405
|
+
if (size !== BigInt(elementCount)) {
|
|
406
|
+
throw new MalformedValueError(`${what} of dimensions (${dimensions.join(", ")}) holds ${elementCount} element(s), want ${size.toString()}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function decodeArray(array) {
|
|
410
|
+
checkShape("an array", array.dimensions, array.elements.length);
|
|
411
|
+
return { dimensions: [...array.dimensions], elements: array.elements.map(decodeValue) };
|
|
412
|
+
}
|
|
413
|
+
function decodeSet(set) {
|
|
414
|
+
return uniqueMembers(set.elements.map(decodeValue));
|
|
415
|
+
}
|
|
416
|
+
/** Returns the members as given, refusing one listed twice by {@link valuesEqual}. */
|
|
417
|
+
function uniqueMembers(members) {
|
|
418
|
+
members.forEach((member, i) => {
|
|
419
|
+
if (members.slice(0, i).some((held) => valuesEqual(held, member))) {
|
|
420
|
+
throw new MalformedValueError(`a set lists a member twice: ${formatValue(member)}`);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
return members;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Whether two values are the same value to the model, as the service judges a
|
|
427
|
+
* set's membership: numbers by value, so a whole `real` is the `int` of its
|
|
428
|
+
* value and a `complex` on the real axis is its real part, exactly across the
|
|
429
|
+
* whole `int` range; a `sequence`'s order counts and a `set`'s does not, nor
|
|
430
|
+
* does a member it lists twice; a quantity is the same over its base units; a
|
|
431
|
+
* `measurementRef` is one reduction at one scale however spelt, except that a
|
|
432
|
+
* named unit of dimension one is only its own declaration (`rad` is not `sr`);
|
|
433
|
+
* an `enum` is its `literalId`, whatever else describes it; a `function` is
|
|
434
|
+
* its `calcId` read against its `selfId`; a `null` is the same whatever its
|
|
435
|
+
* reason, and the same as an empty `sequence` or `set` — the model's absent
|
|
436
|
+
* value however spelt.
|
|
437
|
+
*/
|
|
438
|
+
export function valuesEqual(a, b) {
|
|
439
|
+
if (isEmpty(a) || isEmpty(b)) {
|
|
440
|
+
return isEmpty(a) && isEmpty(b);
|
|
441
|
+
}
|
|
442
|
+
switch (a.kind) {
|
|
443
|
+
case "int":
|
|
444
|
+
case "real":
|
|
445
|
+
case "complex":
|
|
446
|
+
return numbersEqual(a, b);
|
|
447
|
+
case "boolean":
|
|
448
|
+
return b.kind === "boolean" && a.value === b.value;
|
|
449
|
+
case "string":
|
|
450
|
+
return b.kind === "string" && a.value === b.value;
|
|
451
|
+
case "instance":
|
|
452
|
+
return b.kind === "instance" && a.id === b.id;
|
|
453
|
+
case "sequence":
|
|
454
|
+
return b.kind === "sequence" && elementsEqual(a.elements, b.elements);
|
|
455
|
+
case "quantity":
|
|
456
|
+
return b.kind === "quantity" && quantitiesEqual(a, b);
|
|
457
|
+
case "measurementRef":
|
|
458
|
+
return b.kind === "measurementRef" && measurementRefsEqual(a, b);
|
|
459
|
+
case "enum":
|
|
460
|
+
return b.kind === "enum" && a.value.literalId === b.value.literalId;
|
|
461
|
+
case "function":
|
|
462
|
+
return (b.kind === "function" &&
|
|
463
|
+
a.calcId === b.calcId &&
|
|
464
|
+
a.selfId === b.selfId);
|
|
465
|
+
case "array":
|
|
466
|
+
return (b.kind === "array" &&
|
|
467
|
+
dimensionsEqual(a.dimensions, b.dimensions) &&
|
|
468
|
+
elementsEqual(a.elements, b.elements));
|
|
469
|
+
case "vector":
|
|
470
|
+
return b.kind === "vector" && magnitudesEqual(a.components, b.components);
|
|
471
|
+
case "vectorQuantity":
|
|
472
|
+
return (b.kind === "vectorQuantity" &&
|
|
473
|
+
componentsEqual(a.components, b.components));
|
|
474
|
+
case "set":
|
|
475
|
+
return (b.kind === "set" &&
|
|
476
|
+
subsetOf(a.elements, b.elements) &&
|
|
477
|
+
subsetOf(b.elements, a.elements));
|
|
478
|
+
case "tensorQuantity":
|
|
479
|
+
return (b.kind === "tensorQuantity" &&
|
|
480
|
+
dimensionsEqual(a.dimensions, b.dimensions) &&
|
|
481
|
+
componentsEqual(a.components, b.components));
|
|
482
|
+
case "metaobject":
|
|
483
|
+
// The element is the identity, whatever type each side was cast to.
|
|
484
|
+
return b.kind === "metaobject" && a.elementId === b.elementId;
|
|
485
|
+
case "undetermined":
|
|
486
|
+
return (b.kind === "undetermined" &&
|
|
487
|
+
a.reason === b.reason &&
|
|
488
|
+
a.countLower === b.countLower &&
|
|
489
|
+
a.countUpper === b.countUpper);
|
|
490
|
+
case "infinity":
|
|
491
|
+
case "null":
|
|
492
|
+
case "unset":
|
|
493
|
+
case "absent":
|
|
494
|
+
return b.kind === a.kind;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/** The model's absent value: a `null`, or a collection with no members. */
|
|
498
|
+
function isEmpty(value) {
|
|
499
|
+
switch (value.kind) {
|
|
500
|
+
case "null":
|
|
501
|
+
return true;
|
|
502
|
+
case "sequence":
|
|
503
|
+
case "set":
|
|
504
|
+
return value.elements.length === 0;
|
|
505
|
+
default:
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function elementsEqual(a, b) {
|
|
510
|
+
return (a.length === b.length &&
|
|
511
|
+
a.every((e, i) => valuesEqual(e, b[i])));
|
|
512
|
+
}
|
|
513
|
+
function subsetOf(members, of) {
|
|
514
|
+
return members.every((e) => of.some((o) => valuesEqual(e, o)));
|
|
515
|
+
}
|
|
516
|
+
function dimensionsEqual(a, b) {
|
|
517
|
+
return a.length === b.length && a.every((d, i) => d === b[i]);
|
|
518
|
+
}
|
|
519
|
+
function numbersEqual(a, b) {
|
|
520
|
+
if (a.kind === "complex") {
|
|
521
|
+
if (a.value.imaginary !== 0) {
|
|
522
|
+
return (b.kind === "complex" &&
|
|
523
|
+
a.value.real === b.value.real &&
|
|
524
|
+
a.value.imaginary === b.value.imaginary);
|
|
525
|
+
}
|
|
526
|
+
a = { kind: "real", value: a.value.real };
|
|
527
|
+
}
|
|
528
|
+
if (b.kind === "complex") {
|
|
529
|
+
if (b.value.imaginary !== 0) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
b = { kind: "real", value: b.value.real };
|
|
533
|
+
}
|
|
534
|
+
if (a.kind === "int") {
|
|
535
|
+
if (b.kind === "int")
|
|
536
|
+
return a.value === b.value;
|
|
537
|
+
return b.kind === "real" && realIsInt(b.value, a.value);
|
|
538
|
+
}
|
|
539
|
+
if (b.kind === "int")
|
|
540
|
+
return realIsInt(a.value, b.value);
|
|
541
|
+
return b.kind === "real" && a.value === b.value;
|
|
542
|
+
}
|
|
543
|
+
// Whether r is exactly the integer n, never rounding n.
|
|
544
|
+
function realIsInt(r, n) {
|
|
545
|
+
return Number.isInteger(r) && r >= -(2 ** 63) && r < 2 ** 63 && BigInt(r) === n;
|
|
546
|
+
}
|
|
547
|
+
function magnitudesEqual(a, b) {
|
|
548
|
+
return a.length === b.length && a.every((m, i) => numbersEqual(m, b[i]));
|
|
549
|
+
}
|
|
550
|
+
function componentsEqual(a, b) {
|
|
551
|
+
return (a.length === b.length &&
|
|
552
|
+
a.every((q, i) => quantitiesEqual(q, b[i])));
|
|
553
|
+
}
|
|
554
|
+
// Commensurable quantities are equal over their base units, as the service
|
|
555
|
+
// judges them; one carrying no reduction is compared in its unit as written.
|
|
556
|
+
function quantitiesEqual(a, b) {
|
|
557
|
+
if (a.unitTerm === undefined || b.unitTerm === undefined) {
|
|
558
|
+
return (numbersEqual(a.magnitude, b.magnitude) &&
|
|
559
|
+
a.unit === b.unit &&
|
|
560
|
+
unitTermsEqual(a.unitTerm, b.unitTerm));
|
|
561
|
+
}
|
|
562
|
+
if (!commensurable(a.unitTerm, b.unitTerm) ||
|
|
563
|
+
zeroScale(a.unitTerm) ||
|
|
564
|
+
zeroScale(b.unitTerm)) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
if (a.magnitude.kind === "int" &&
|
|
568
|
+
b.magnitude.kind === "int" &&
|
|
569
|
+
wholeScale(a.unitTerm) &&
|
|
570
|
+
wholeScale(b.unitTerm)) {
|
|
571
|
+
// m₁·n₁/d₁ = m₂·n₂/d₂ exactly, cross-multiplied in bigint.
|
|
572
|
+
return (a.magnitude.value * BigInt(a.unitTerm.scaleNum) * BigInt(b.unitTerm.scaleDen) ===
|
|
573
|
+
b.magnitude.value * BigInt(b.unitTerm.scaleNum) * BigInt(a.unitTerm.scaleDen));
|
|
574
|
+
}
|
|
575
|
+
return baseMagnitude(a.magnitude, a.unitTerm) === baseMagnitude(b.magnitude, b.unitTerm);
|
|
576
|
+
}
|
|
577
|
+
function baseMagnitude(magnitude, term) {
|
|
578
|
+
return (Number(magnitude.value) * term.scaleNum) / term.scaleDen;
|
|
579
|
+
}
|
|
580
|
+
// The reduction as base unit → exponent, repeated base units summed and
|
|
581
|
+
// cancelled ones dropped, so two reductions compare however they are listed.
|
|
582
|
+
function exponents(term) {
|
|
583
|
+
const totals = new Map();
|
|
584
|
+
for (const factor of term.factors) {
|
|
585
|
+
totals.set(factor.unitId, (totals.get(factor.unitId) ?? 0) + factor.exponent);
|
|
586
|
+
}
|
|
587
|
+
for (const [unitId, exponent] of totals) {
|
|
588
|
+
if (exponent === 0) {
|
|
589
|
+
totals.delete(unitId);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return totals;
|
|
593
|
+
}
|
|
594
|
+
function commensurable(a, b) {
|
|
595
|
+
const x = exponents(a);
|
|
596
|
+
const y = exponents(b);
|
|
597
|
+
return x.size === y.size && [...x].every(([unitId, exponent]) => y.get(unitId) === exponent);
|
|
598
|
+
}
|
|
599
|
+
function zeroScale(term) {
|
|
600
|
+
return term.scaleNum === 0 || term.scaleDen === 0;
|
|
601
|
+
}
|
|
602
|
+
/** One reduction: commensurable at one scale, however the ratio is written. */
|
|
603
|
+
function sameReduction(a, b) {
|
|
604
|
+
return (commensurable(a, b) &&
|
|
605
|
+
!zeroScale(a) &&
|
|
606
|
+
!zeroScale(b) &&
|
|
607
|
+
a.scaleNum * b.scaleDen === b.scaleNum * a.scaleDen);
|
|
608
|
+
}
|
|
609
|
+
// One reduction at one scale (`SI::'m/s'` is `m/s`, `km/m` is `m/mm`); a named
|
|
610
|
+
// unit reducing to nothing is only the declaration it names.
|
|
611
|
+
function measurementRefsEqual(a, b) {
|
|
612
|
+
if (!sameReduction(a.unitTerm, b.unitTerm)) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
if (exponents(a.unitTerm).size === 0 && (a.unitId !== undefined || b.unitId !== undefined)) {
|
|
616
|
+
return a.unitId === b.unitId;
|
|
617
|
+
}
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
function wholeScale(term) {
|
|
621
|
+
return Number.isInteger(term.scaleNum) && Number.isInteger(term.scaleDen);
|
|
622
|
+
}
|
|
623
|
+
function unitTermsEqual(a, b) {
|
|
624
|
+
if (a === undefined || b === undefined) {
|
|
625
|
+
return a === b;
|
|
626
|
+
}
|
|
627
|
+
return (a.scaleNum === b.scaleNum &&
|
|
628
|
+
a.scaleDen === b.scaleDen &&
|
|
629
|
+
a.factors.length === b.factors.length &&
|
|
630
|
+
a.factors.every((f, i) => f.unitId === b.factors[i]?.unitId &&
|
|
631
|
+
f.exponent === b.factors[i]?.exponent));
|
|
632
|
+
}
|
|
633
|
+
function decodeTensorQuantity(tensor) {
|
|
634
|
+
checkShape("a tensor quantity", tensor.dimensions, tensor.components.length);
|
|
635
|
+
return { dimensions: [...tensor.dimensions], components: tensor.components.map(decodeQuantity) };
|
|
636
|
+
}
|
|
637
|
+
function decodeVector(vector) {
|
|
638
|
+
return vector.components.map((component) => {
|
|
639
|
+
switch (component.kind.case) {
|
|
640
|
+
case "intValue":
|
|
641
|
+
return { kind: "int", value: component.kind.value };
|
|
642
|
+
case "realValue":
|
|
643
|
+
return { kind: "real", value: component.kind.value };
|
|
644
|
+
default:
|
|
645
|
+
throw new MalformedValueError(`a vector component is ${component.kind.case ?? "empty"}, not a number`);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
function decodeVectorQuantity(vector) {
|
|
650
|
+
if (vector.components.length === 0) {
|
|
651
|
+
throw new MalformedValueError("a vector quantity has no components");
|
|
652
|
+
}
|
|
653
|
+
return vector.components.map(decodeQuantity);
|
|
654
|
+
}
|
|
655
|
+
function decodeUnitTerm(term) {
|
|
656
|
+
return {
|
|
657
|
+
scaleNum: term.scaleNum,
|
|
658
|
+
scaleDen: term.scaleDen,
|
|
659
|
+
factors: term.factors.map((factor) => ({
|
|
660
|
+
unitId: factor.unitId,
|
|
661
|
+
exponent: factor.exponent,
|
|
662
|
+
})),
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function decodeEnumLiteral(literal) {
|
|
666
|
+
const decoded = {
|
|
667
|
+
name: literal.name,
|
|
668
|
+
literalId: literal.literalId,
|
|
669
|
+
enumerationId: literal.enumerationId,
|
|
670
|
+
};
|
|
671
|
+
if (literal.value !== undefined) {
|
|
672
|
+
const scalar = decodeValue(literal.value);
|
|
673
|
+
if (scalar.kind === "absent") {
|
|
674
|
+
throw new MalformedValueError("an enumeration literal's value, when present, states a scalar");
|
|
675
|
+
}
|
|
676
|
+
decoded.value = scalar;
|
|
677
|
+
}
|
|
678
|
+
return decoded;
|
|
679
|
+
}
|
|
680
|
+
function encodeEnumLiteral(literal) {
|
|
681
|
+
return create(EnumLiteralSchema, {
|
|
682
|
+
name: literal.name,
|
|
683
|
+
literalId: literal.literalId,
|
|
684
|
+
enumerationId: literal.enumerationId,
|
|
685
|
+
value: literal.value === undefined ? undefined : encodeValue(literal.value),
|
|
686
|
+
});
|
|
687
|
+
}
|