@calcit/procs 0.28.0 → 0.29.0-alpha.10
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/lib/calcit-data.mjs +47 -10
- package/lib/calcit.procs.d.mts +4 -1
- package/lib/calcit.procs.mjs +98 -5
- package/lib/js-cirru.mjs +4 -2
- package/lib/js-trait.d.mts +4 -1
- package/lib/js-trait.mjs +18 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
package/lib/calcit-data.mjs
CHANGED
|
@@ -225,6 +225,9 @@ let defaultHash_struct = valueHash("struct:");
|
|
|
225
225
|
let defaultHash_enum = valueHash("enum:");
|
|
226
226
|
let defaultHash_cirru_quote = valueHash("cirru-quote:");
|
|
227
227
|
let defaultHash_unknown = valueHash("unknown:");
|
|
228
|
+
let defaultHash_buffer = valueHash("buffer:");
|
|
229
|
+
/** Identity hashes for host values, kept outside the objects so frozen or DOM objects are never mutated. */
|
|
230
|
+
let hostValueHashes = new WeakMap();
|
|
228
231
|
let fnHashCounter = 0;
|
|
229
232
|
let jsObjectHashCounter = 0;
|
|
230
233
|
export let hashFunction = (x) => {
|
|
@@ -265,15 +268,20 @@ export let hashFunction = (x) => {
|
|
|
265
268
|
if (typeof x === "function") {
|
|
266
269
|
// method values are closures created on the fly (see invoke_method_closure);
|
|
267
270
|
// hash by method name so equal methods share the same hash, matching isEqual
|
|
271
|
+
// cache in a WeakMap instead of writing onto the function, so frozen functions hash safely
|
|
272
|
+
const cachedFn = hostValueHashes.get(x);
|
|
273
|
+
if (cachedFn != null) {
|
|
274
|
+
return cachedFn;
|
|
275
|
+
}
|
|
268
276
|
const methodName = x.__calcitMethodName;
|
|
269
277
|
if (methodName != null) {
|
|
270
278
|
let h = mergeValueHash(defaultHash_fn, methodName);
|
|
271
|
-
x
|
|
279
|
+
hostValueHashes.set(x, h);
|
|
272
280
|
return h;
|
|
273
281
|
}
|
|
274
282
|
fnHashCounter = fnHashCounter + 1;
|
|
275
283
|
let h = mergeValueHash(defaultHash_fn, fnHashCounter);
|
|
276
|
-
x
|
|
284
|
+
hostValueHashes.set(x, h);
|
|
277
285
|
return h;
|
|
278
286
|
}
|
|
279
287
|
if (x instanceof CalcitRef) {
|
|
@@ -397,15 +405,28 @@ export let hashFunction = (x) => {
|
|
|
397
405
|
base = hashCirru(base, x.value);
|
|
398
406
|
return base;
|
|
399
407
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
408
|
+
if (x instanceof Uint8Array) {
|
|
409
|
+
// Buffers are values: equal bytes must hash equally, matching native Buffer equality.
|
|
410
|
+
let base = mergeValueHash(defaultHash_buffer, x.length);
|
|
411
|
+
for (let idx = 0; idx < x.length; idx++) {
|
|
412
|
+
base = mergeValueHash(base, x[idx]);
|
|
413
|
+
}
|
|
414
|
+
return base;
|
|
415
|
+
}
|
|
416
|
+
if (typeof x === "object" || typeof x === "function") {
|
|
417
|
+
// Host values compare by identity, so they hash by identity as well.
|
|
418
|
+
let cached = hostValueHashes.get(x);
|
|
419
|
+
if (cached != null) {
|
|
420
|
+
return cached;
|
|
421
|
+
}
|
|
422
|
+
jsObjectHashCounter = jsObjectHashCounter + 1;
|
|
423
|
+
let hashHostValue = mergeValueHash(defaultHash_unknown, jsObjectHashCounter);
|
|
424
|
+
hostValueHashes.set(x, hashHostValue);
|
|
425
|
+
return hashHostValue;
|
|
426
|
+
}
|
|
427
|
+
console.warn(`[warn] calcit-js has no method for hashing this: ${String(x)}`);
|
|
405
428
|
jsObjectHashCounter = jsObjectHashCounter + 1;
|
|
406
|
-
|
|
407
|
-
x[calcit_dirty_hash_key] = hashJsObject;
|
|
408
|
-
return hashJsObject;
|
|
429
|
+
return mergeValueHash(defaultHash_unknown, jsObjectHashCounter);
|
|
409
430
|
};
|
|
410
431
|
/// traverse Cirru tree to make unique hash
|
|
411
432
|
let hashCirru = (base, x) => {
|
|
@@ -826,6 +847,22 @@ export let _$n__$e_ = (x, y) => {
|
|
|
826
847
|
}
|
|
827
848
|
return false;
|
|
828
849
|
}
|
|
850
|
+
if (x instanceof Uint8Array) {
|
|
851
|
+
if (!(y instanceof Uint8Array) || x.length !== y.length) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
for (let idx = 0; idx < x.length; idx++) {
|
|
855
|
+
if (x[idx] !== y[idx]) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return true;
|
|
860
|
+
}
|
|
861
|
+
if (tx === "object") {
|
|
862
|
+
// Host objects (DOM nodes, JS class instances, BufList) compare by identity;
|
|
863
|
+
// identical references were accepted at the top of this function.
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
829
866
|
throw new Error("Missing handler for this type");
|
|
830
867
|
};
|
|
831
868
|
// overwrite internary comparator of ternary-tree
|
package/lib/calcit.procs.d.mts
CHANGED
|
@@ -87,7 +87,9 @@ import { CalcitSet } from "./js-set.mjs";
|
|
|
87
87
|
import { CalcitEnumValue } from "./js-enum-value.mjs";
|
|
88
88
|
import { CalcitCirruQuote } from "./js-cirru.mjs";
|
|
89
89
|
export declare let type_of: (x: any) => CalcitTag;
|
|
90
|
-
|
|
90
|
+
/** Check host member shape without invoking methods or claiming their signature is verified. */
|
|
91
|
+
export declare const js_cast: (value: unknown, trait: string, fields: readonly string[], methods: readonly string[]) => object;
|
|
92
|
+
export declare let _$n_trait_$o__$o_new: (name: CalcitValue, methods: CalcitValue, parents?: CalcitValue) => CalcitTrait;
|
|
91
93
|
export declare let _$n_assert_traits: (value: CalcitValue, traitDef: CalcitValue) => CalcitValue;
|
|
92
94
|
export declare let defstruct: (name: CalcitValue, ...entries: CalcitValue[]) => CalcitStructDef;
|
|
93
95
|
export declare let defenum: (name: CalcitValue, ...variants: CalcitValue[]) => CalcitEnumDef;
|
|
@@ -186,6 +188,7 @@ export declare let _$n__GT_: (a: number, b: number) => boolean;
|
|
|
186
188
|
export declare let _$n__LT_: (a: number, b: number) => boolean;
|
|
187
189
|
export declare let _$n__: (a: number, b: number) => number;
|
|
188
190
|
export declare let _$n__SLSH_: (a: number, b: number) => number;
|
|
191
|
+
/** Truncated remainder over safe integers, matching native and WASM; the result is never `-0`. */
|
|
189
192
|
export declare let _$n_number_$o_rem: (a: number, b: number) => number;
|
|
190
193
|
export declare let _$n_number_$o_fits_$q_: (value: number, target: CalcitTag) => boolean;
|
|
191
194
|
export declare let round_$q_: (a: number) => boolean;
|
package/lib/calcit.procs.mjs
CHANGED
|
@@ -104,6 +104,52 @@ export let type_of = (x) => {
|
|
|
104
104
|
}
|
|
105
105
|
throw new Error(`Unknown data ${x}`);
|
|
106
106
|
};
|
|
107
|
+
// The intrinsic getter reads typed-array slots across realms, ignoring a forged Symbol.toStringTag.
|
|
108
|
+
const typedArrayTag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), Symbol.toStringTag).get;
|
|
109
|
+
/** Check host member shape without invoking methods or claiming their signature is verified. */
|
|
110
|
+
export const js_cast = (value, trait, fields, methods) => {
|
|
111
|
+
const fail = (detail, cause) => {
|
|
112
|
+
const error = new TypeError(`js-cast ${trait}: ${detail}`);
|
|
113
|
+
Object.defineProperty(error, "cause", { value: cause, configurable: true });
|
|
114
|
+
throw error;
|
|
115
|
+
};
|
|
116
|
+
// Calcit objects (including Buffers) are not interchangeable with external handles.
|
|
117
|
+
// Keep the runtime's own value classification authoritative instead of another registry.
|
|
118
|
+
let host;
|
|
119
|
+
try {
|
|
120
|
+
host = value !== null && typeof value === "object"
|
|
121
|
+
&& !(ArrayBuffer.isView(value) && typedArrayTag.call(value) === "Uint8Array")
|
|
122
|
+
&& !(value instanceof CalcitRecur) && type_of(value).value === "js-object";
|
|
123
|
+
}
|
|
124
|
+
catch (cause) {
|
|
125
|
+
return fail("host classification failed", cause);
|
|
126
|
+
}
|
|
127
|
+
if (!host || value === null || typeof value !== "object")
|
|
128
|
+
return fail("expected a JavaScript host object");
|
|
129
|
+
for (const field of fields) {
|
|
130
|
+
let present;
|
|
131
|
+
try {
|
|
132
|
+
present = field in value;
|
|
133
|
+
}
|
|
134
|
+
catch (cause) {
|
|
135
|
+
return fail(`field ${JSON.stringify(field)} lookup failed`, cause);
|
|
136
|
+
}
|
|
137
|
+
if (!present)
|
|
138
|
+
return fail(`missing field ${JSON.stringify(field)}`);
|
|
139
|
+
}
|
|
140
|
+
for (const method of methods) {
|
|
141
|
+
let member;
|
|
142
|
+
try {
|
|
143
|
+
member = Reflect.get(value, method);
|
|
144
|
+
}
|
|
145
|
+
catch (cause) {
|
|
146
|
+
return fail(`method ${JSON.stringify(method)} lookup failed`, cause);
|
|
147
|
+
}
|
|
148
|
+
if (typeof member !== "function")
|
|
149
|
+
return fail(`method ${JSON.stringify(method)} is not callable`);
|
|
150
|
+
}
|
|
151
|
+
return value;
|
|
152
|
+
};
|
|
107
153
|
const list_items = (item) => {
|
|
108
154
|
if (item instanceof CalcitList || item instanceof CalcitSliceList) {
|
|
109
155
|
return Array.from(item.items());
|
|
@@ -161,9 +207,9 @@ const trimDataDefinitionEntries = (entries) => {
|
|
|
161
207
|
}
|
|
162
208
|
return entries.slice(start);
|
|
163
209
|
};
|
|
164
|
-
export let _$n_trait_$o__$o_new = function (name, methods) {
|
|
165
|
-
if (arguments.length !== 2)
|
|
166
|
-
throw new Error("&trait::new expected 2 arguments");
|
|
210
|
+
export let _$n_trait_$o__$o_new = function (name, methods, parents) {
|
|
211
|
+
if (arguments.length !== 2 && arguments.length !== 3)
|
|
212
|
+
throw new Error("&trait::new expected 2 or 3 arguments");
|
|
167
213
|
const items = list_items(methods);
|
|
168
214
|
const methodNames = [];
|
|
169
215
|
const methodTypes = [];
|
|
@@ -175,7 +221,39 @@ export let _$n_trait_$o__$o_new = function (name, methods) {
|
|
|
175
221
|
methodNames.push(pair[0]);
|
|
176
222
|
methodTypes.push(pair[1]);
|
|
177
223
|
}
|
|
178
|
-
|
|
224
|
+
const requires = parents == null ? [] : list_items(parents);
|
|
225
|
+
for (const parent of requires) {
|
|
226
|
+
if (!(parent instanceof CalcitTrait)) {
|
|
227
|
+
throw new Error(`trait ${toString(name, false)}: each requires entry must be a trait, but received: ${toString(parent, true)}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const traitDef = new CalcitTrait(name, methodNames, methodTypes, requires);
|
|
231
|
+
const owners = new Map();
|
|
232
|
+
for (const reachable of traitDef.reachable()) {
|
|
233
|
+
for (const member of reachable.methods) {
|
|
234
|
+
const owner = owners.get(member.value);
|
|
235
|
+
if (owner != null && owner !== reachable) {
|
|
236
|
+
throw new Error(`member \`${member.value}\` is declared by both ${owner.name.toString()} and ${reachable.name.toString()}; keep it on one trait`);
|
|
237
|
+
}
|
|
238
|
+
owners.set(member.value, reachable);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return traitDef;
|
|
242
|
+
};
|
|
243
|
+
/** Every attached impl needs impls of all traits its origin requires on the same type. */
|
|
244
|
+
let checkRequiredTraitImpls = (impls, procName) => {
|
|
245
|
+
for (const impl of impls) {
|
|
246
|
+
const origin = impl?.origin;
|
|
247
|
+
if (origin == null)
|
|
248
|
+
continue;
|
|
249
|
+
for (const required of origin.reachable()) {
|
|
250
|
+
if (required === origin)
|
|
251
|
+
continue;
|
|
252
|
+
if (!impls.some((candidate) => candidate?.origin === required)) {
|
|
253
|
+
throw new Error(`${procName}: an impl of trait ${origin.name.toString()} requires an impl of trait ${required.name.toString()} on the same type; attach one before or with it`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
179
257
|
};
|
|
180
258
|
export let _$n_assert_traits = function (value, traitDef) {
|
|
181
259
|
if (arguments.length !== 2)
|
|
@@ -205,6 +283,10 @@ export let _$n_assert_traits = function (value, traitDef) {
|
|
|
205
283
|
if (missing.length > 0) {
|
|
206
284
|
throw new Error(`assert-traits failed: impl ${selected.name.toString()} for trait ${traitDef.name.toString()} is incomplete. Missing: ${missing.join(" ")}`);
|
|
207
285
|
}
|
|
286
|
+
const unmet = traitDef.reachable().find((required) => required !== traitDef && !impls.some((impl) => impl?.origin === required));
|
|
287
|
+
if (unmet != null) {
|
|
288
|
+
throw new Error(`assert-traits failed: ${toString(value, true)} implements ${traitDef.name.toString()} but not its required trait ${unmet.name.toString()}`);
|
|
289
|
+
}
|
|
208
290
|
return value;
|
|
209
291
|
};
|
|
210
292
|
export let defstruct = (name, ...entries) => {
|
|
@@ -597,6 +679,7 @@ export let _$n_enum_$o_impl_traits = function (x, ...traits) {
|
|
|
597
679
|
if (!(x instanceof CalcitEnumValue))
|
|
598
680
|
throw new Error("&enum:impl-traits expects an enum value");
|
|
599
681
|
const impls = traits.map((trait) => coerce_impl(trait, "&enum:impl-traits"));
|
|
682
|
+
checkRequiredTraitImpls([...(x.enumPrototype?.impls ?? []), ...impls], "&enum:impl-traits");
|
|
600
683
|
let proto = x.enumPrototype;
|
|
601
684
|
if (proto == null) {
|
|
602
685
|
const tagName = x.tag instanceof CalcitTag ? x.tag : newTag("tag");
|
|
@@ -733,6 +816,7 @@ export let _$n_struct_$o_impl_traits = function (xs, ...traits) {
|
|
|
733
816
|
if (!(xs instanceof CalcitStructValue))
|
|
734
817
|
throw new Error("&struct:impl-traits expected a struct value");
|
|
735
818
|
const impls = traits.map((trait) => coerce_impl(trait, "&struct:impl-traits"));
|
|
819
|
+
checkRequiredTraitImpls(xs.structRef.impls.concat(impls), "&struct:impl-traits");
|
|
736
820
|
const nextStruct = new CalcitStructDef(xs.name, xs.fields, xs.structRef.fieldTypes, xs.structRef.impls.concat(impls), xs.structRef.definitionRef);
|
|
737
821
|
return new CalcitStructValue(xs.name, xs.fields, xs.values, nextStruct);
|
|
738
822
|
};
|
|
@@ -743,6 +827,7 @@ export let _$n_struct_def_$o_impl_traits = function (xs, ...traits) {
|
|
|
743
827
|
throw new Error("&struct-def:impl-traits expected a struct definition");
|
|
744
828
|
const addedImpls = traits.map((trait) => coerce_impl(trait, "&struct-def:impl-traits"));
|
|
745
829
|
const baseImpls = xs.impls ?? [];
|
|
830
|
+
checkRequiredTraitImpls(baseImpls.concat(addedImpls), "&struct-def:impl-traits");
|
|
746
831
|
return new CalcitStructDef(xs.name, xs.fields, xs.fieldTypes, baseImpls.concat(addedImpls), xs.definitionRef);
|
|
747
832
|
};
|
|
748
833
|
export let _$n_enum_def_$o_impl_traits = function (xs, ...traits) {
|
|
@@ -750,6 +835,7 @@ export let _$n_enum_def_$o_impl_traits = function (xs, ...traits) {
|
|
|
750
835
|
throw new Error("&enum-def:impl-traits takes 2+ arguments");
|
|
751
836
|
const addedImpls = traits.map((trait) => coerce_impl(trait, "&enum-def:impl-traits"));
|
|
752
837
|
if (xs instanceof CalcitEnumDef) {
|
|
838
|
+
checkRequiredTraitImpls([...(xs.impls ?? []), ...addedImpls], "&enum-def:impl-traits");
|
|
753
839
|
return xs.withImpls(addedImpls);
|
|
754
840
|
}
|
|
755
841
|
throw new Error("&enum-def:impl-traits expected an enum definition");
|
|
@@ -1114,8 +1200,15 @@ export let _$n__ = (a, b) => {
|
|
|
1114
1200
|
export let _$n__SLSH_ = (a, b) => {
|
|
1115
1201
|
return a / b;
|
|
1116
1202
|
};
|
|
1203
|
+
/** Truncated remainder over safe integers, matching native and WASM; the result is never `-0`. */
|
|
1117
1204
|
export let _$n_number_$o_rem = (a, b) => {
|
|
1118
|
-
|
|
1205
|
+
if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b)) {
|
|
1206
|
+
throw new Error(`&number:rem requires safe integers, but received: ${toString(a, false)} ${toString(b, false)}`);
|
|
1207
|
+
}
|
|
1208
|
+
if (b === 0) {
|
|
1209
|
+
throw new Error("&number:rem divisor must not be zero");
|
|
1210
|
+
}
|
|
1211
|
+
return (a % b) + 0;
|
|
1119
1212
|
};
|
|
1120
1213
|
export let _$n_number_$o_fits_$q_ = (value, target) => {
|
|
1121
1214
|
if (!(target instanceof CalcitTag)) {
|
package/lib/js-cirru.mjs
CHANGED
|
@@ -555,7 +555,6 @@ const extract_cirru_edn_inner = (x, options, preserveSourceEntries) => {
|
|
|
555
555
|
return atom(extract_cirru_edn_inner(x[1], options, preserveSourceEntries));
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
|
-
console.error(x);
|
|
559
558
|
throw new Error(`Unexpected data from EDN: ${x}`);
|
|
560
559
|
};
|
|
561
560
|
export let extract_cirru_edn = (x, options) => {
|
|
@@ -565,6 +564,9 @@ export let extract_cirru_edn_for_typed = (x, options) => {
|
|
|
565
564
|
return extract_cirru_edn_inner(x, options, true);
|
|
566
565
|
};
|
|
567
566
|
export let format_cirru_edn = (data, useInline = true) => {
|
|
567
|
+
if (typeof useInline !== "boolean") {
|
|
568
|
+
throw new Error("format-cirru-edn requires a boolean inline option");
|
|
569
|
+
}
|
|
568
570
|
if (data == null) {
|
|
569
571
|
return "\ndo nil" + "\n";
|
|
570
572
|
}
|
|
@@ -572,7 +574,7 @@ export let format_cirru_edn = (data, useInline = true) => {
|
|
|
572
574
|
let quoted = writeCirruCode([[to_cirru_edn(data)]], { useInline: useInline }).trim();
|
|
573
575
|
return "\ndo " + quoted + "\n";
|
|
574
576
|
}
|
|
575
|
-
if (typeof data === "boolean") {
|
|
577
|
+
if (typeof data === "boolean" || typeof data === "number") {
|
|
576
578
|
return "\ndo " + to_cirru_edn(data) + "\n";
|
|
577
579
|
}
|
|
578
580
|
if (data instanceof CalcitSymbol) {
|
package/lib/js-trait.d.mts
CHANGED
|
@@ -4,6 +4,9 @@ export declare class CalcitTrait {
|
|
|
4
4
|
name: CalcitTag;
|
|
5
5
|
methods: CalcitTag[];
|
|
6
6
|
methodTypes: CalcitValue[];
|
|
7
|
-
|
|
7
|
+
requires: CalcitTrait[];
|
|
8
|
+
constructor(name: CalcitValue, methods: CalcitValue[], methodTypes: CalcitValue[], requires?: CalcitTrait[]);
|
|
9
|
+
/** This trait and every transitively required trait, each origin once. */
|
|
10
|
+
reachable(): CalcitTrait[];
|
|
8
11
|
toString(disableJsDataWarning?: boolean): string;
|
|
9
12
|
}
|
package/lib/js-trait.mjs
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import { castTag } from "./calcit-data.mjs";
|
|
2
2
|
export class CalcitTrait {
|
|
3
|
-
constructor(name, methods, methodTypes) {
|
|
3
|
+
constructor(name, methods, methodTypes, requires = []) {
|
|
4
4
|
this.name = castTag(name);
|
|
5
5
|
this.methods = methods.map(castTag);
|
|
6
6
|
this.methodTypes = methodTypes;
|
|
7
|
+
this.requires = requires;
|
|
8
|
+
}
|
|
9
|
+
/** This trait and every transitively required trait, each origin once. */
|
|
10
|
+
reachable() {
|
|
11
|
+
const output = [];
|
|
12
|
+
const visit = (current, active) => {
|
|
13
|
+
if (active.includes(current)) {
|
|
14
|
+
throw new Error(`trait requires cycle: ${[...active, current].map((item) => item.name.toString()).join(" -> ")}`);
|
|
15
|
+
}
|
|
16
|
+
if (output.includes(current))
|
|
17
|
+
return;
|
|
18
|
+
for (const required of current.requires)
|
|
19
|
+
visit(required, [...active, current]);
|
|
20
|
+
output.push(current);
|
|
21
|
+
};
|
|
22
|
+
visit(this, []);
|
|
23
|
+
return output;
|
|
7
24
|
}
|
|
8
25
|
toString(disableJsDataWarning = false) {
|
|
9
26
|
const parts = ["(trait ", this.name.toString()];
|
package/lib/package.json
CHANGED