eyeling 1.19.2 → 1.19.4
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/HANDBOOK.md +14 -22
- package/eyeling.js +89 -145
- package/lib/builtins.js +89 -21
- package/package.json +1 -1
- package/test/api.test.js +27 -0
- package/test/builtin-contract.test.js +48 -40
- package/lib/builtin-contract.js +0 -121
- package/test/fixtures/builtins/bad-return.js +0 -5
package/HANDBOOK.md
CHANGED
|
@@ -2087,20 +2087,18 @@ const out = reason({ builtinModules: ['./hello-builtin.js'] }, n3Text);
|
|
|
2087
2087
|
|
|
2088
2088
|
### 16.2.1 Stability rule for `--builtin`
|
|
2089
2089
|
|
|
2090
|
-
Eyeling
|
|
2090
|
+
Eyeling keeps `--builtin` simple.
|
|
2091
2091
|
|
|
2092
|
-
|
|
2092
|
+
There is one small helper API passed into builtin modules. That helper object is frozen, its key set is regression-tested, and builtin modules must use one of the documented export forms.
|
|
2093
2093
|
|
|
2094
|
-
|
|
2094
|
+
In practice, this means:
|
|
2095
2095
|
|
|
2096
|
-
- builtin module loading accepts only the
|
|
2097
|
-
- the helper API exposed by `__buildBuiltinRegistrationApi()` has
|
|
2098
|
-
-
|
|
2099
|
-
-
|
|
2100
|
-
- builtin handlers must return an **array of substitution-delta objects**
|
|
2101
|
-
- any add/remove/rename of these contract elements is a **breaking change** and should bump the builtin API version
|
|
2096
|
+
- builtin module loading accepts only the documented export forms
|
|
2097
|
+
- the helper API exposed by `__buildBuiltinRegistrationApi()` has a fixed key set
|
|
2098
|
+
- builtin handlers should return an array of substitution objects
|
|
2099
|
+
- accidental helper drift is caught by `test/builtin-contract.test.js`
|
|
2102
2100
|
|
|
2103
|
-
|
|
2101
|
+
This is only meant to stop silent breakage. It is **not** a promise that Eyeling can never change the builtin API. If the helper surface ever needs to change, that change should be deliberate, documented, and called out in release notes.
|
|
2104
2102
|
|
|
2105
2103
|
### 16.3 What a builtin module may export
|
|
2106
2104
|
|
|
@@ -2167,7 +2165,7 @@ If none of those shapes match, Eyeling rejects the module with a descriptive err
|
|
|
2167
2165
|
|
|
2168
2166
|
### 16.4 The handler contract
|
|
2169
2167
|
|
|
2170
|
-
Builtin handlers are called with
|
|
2168
|
+
Builtin handlers are called with a context object containing:
|
|
2171
2169
|
|
|
2172
2170
|
- `iri` — the predicate IRI string
|
|
2173
2171
|
- `goal` — the current triple goal
|
|
@@ -2179,16 +2177,14 @@ Builtin handlers are called with an **exactly versioned** context object:
|
|
|
2179
2177
|
- `maxResults` — current result cap
|
|
2180
2178
|
- `api` — the same registration/helper API used by modules
|
|
2181
2179
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
A handler returns **an array of substitution-delta objects**:
|
|
2180
|
+
A handler should return an **array of substitution objects**:
|
|
2185
2181
|
|
|
2186
2182
|
- `[]` means failure / no solutions
|
|
2187
2183
|
- `[{}]` means success with no new bindings
|
|
2188
2184
|
- `[{ ...delta }]` means one successful continuation with bindings
|
|
2189
2185
|
- multiple objects mean a generator builtin
|
|
2190
2186
|
|
|
2191
|
-
Returning
|
|
2187
|
+
Returning something else is rejected at runtime.
|
|
2192
2188
|
|
|
2193
2189
|
In practice:
|
|
2194
2190
|
|
|
@@ -2201,13 +2197,9 @@ Custom builtin failures are wrapped so the predicate IRI appears in the thrown e
|
|
|
2201
2197
|
|
|
2202
2198
|
### 16.5 The helper API exposed to builtin modules
|
|
2203
2199
|
|
|
2204
|
-
Builtin modules do not need to import internal engine files directly. Eyeling passes a helper API into module registration, and that helper surface is
|
|
2205
|
-
|
|
2206
|
-
The current builtin API version is exposed as:
|
|
2207
|
-
|
|
2208
|
-
- `getBuiltinApiVersion()`
|
|
2200
|
+
Builtin modules do not need to import internal engine files directly. Eyeling passes a helper API into module registration, and that helper surface is kept intentionally small.
|
|
2209
2201
|
|
|
2210
|
-
The
|
|
2202
|
+
The current helper function set is:
|
|
2211
2203
|
|
|
2212
2204
|
- `registerBuiltin`, `unregisterBuiltin`, `listBuiltinIris`
|
|
2213
2205
|
- `internIri`, `internLiteral`, `literalParts`
|
|
@@ -2222,7 +2214,7 @@ The stable namespace bags are:
|
|
|
2222
2214
|
- `terms`: `Literal`, `Iri`, `Var`, `Blank`, `ListTerm`, `OpenListTerm`, `GraphTerm`, `Triple`, `Rule`
|
|
2223
2215
|
- `ns`: `RDF_NS`, `XSD_NS`, `CRYPTO_NS`, `MATH_NS`, `TIME_NS`, `LIST_NS`, `LOG_NS`, `STRING_NS`
|
|
2224
2216
|
|
|
2225
|
-
The
|
|
2217
|
+
The helper object is frozen and regression-tested so helper additions, removals, and renames do not slip in silently.
|
|
2226
2218
|
|
|
2227
2219
|
That API keeps the extension boundary explicit: custom builtins get the operations they need without reaching into Eyeling’s private module graph.
|
|
2228
2220
|
|
package/eyeling.js
CHANGED
|
@@ -9,130 +9,6 @@
|
|
|
9
9
|
const __cache = Object.create(null);
|
|
10
10
|
|
|
11
11
|
// ---- bundled modules ----
|
|
12
|
-
__modules["lib/builtin-contract.js"] = function(require, module, exports){
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
const CONTRACT = {
|
|
16
|
-
version: 1,
|
|
17
|
-
moduleExportForms: [
|
|
18
|
-
'function',
|
|
19
|
-
'object.register',
|
|
20
|
-
'object.builtins',
|
|
21
|
-
'plain-object-map',
|
|
22
|
-
'object.default-object-map',
|
|
23
|
-
],
|
|
24
|
-
api: {
|
|
25
|
-
functions: {
|
|
26
|
-
getBuiltinApiVersion: { arity: 0, required: true },
|
|
27
|
-
registerBuiltin: { arity: 2, required: true },
|
|
28
|
-
unregisterBuiltin: { arity: 1, required: true },
|
|
29
|
-
listBuiltinIris: { arity: 0, required: true },
|
|
30
|
-
internIri: { arity: 1, required: true },
|
|
31
|
-
internLiteral: { arity: 1, required: true },
|
|
32
|
-
literalParts: { arity: 1, required: true },
|
|
33
|
-
termToJsString: { arity: 1, required: true },
|
|
34
|
-
termToJsStringDecoded: { arity: 1, required: true },
|
|
35
|
-
termToN3: { arity: 2, required: true },
|
|
36
|
-
iriValue: { arity: 1, required: true },
|
|
37
|
-
unifyTerm: { arity: 3, required: true },
|
|
38
|
-
applySubstTerm: { arity: 2, required: true },
|
|
39
|
-
applySubstTriple: { arity: 2, required: true },
|
|
40
|
-
proveGoals: { arity: 9, required: true },
|
|
41
|
-
isGroundTerm: { arity: 1, required: true },
|
|
42
|
-
computeConclusionFromFormula: { arity: 1, required: true },
|
|
43
|
-
skolemIriFromGroundTerm: { arity: 1, required: true },
|
|
44
|
-
parseBooleanLiteralInfo: { arity: 1, required: true },
|
|
45
|
-
parseNumericLiteralInfo: { arity: 1, required: true },
|
|
46
|
-
parseXsdDecimalToBigIntScale: { arity: 1, required: true },
|
|
47
|
-
pow10n: { arity: 1, required: true },
|
|
48
|
-
normalizeLiteralForFastKey: { arity: 1, required: true },
|
|
49
|
-
literalsEquivalentAsXsdString: { arity: 2, required: true },
|
|
50
|
-
materializeRdfLists: { arity: 3, required: true },
|
|
51
|
-
},
|
|
52
|
-
namespaces: {
|
|
53
|
-
terms: ['Literal', 'Iri', 'Var', 'Blank', 'ListTerm', 'OpenListTerm', 'GraphTerm', 'Triple', 'Rule'],
|
|
54
|
-
ns: ['RDF_NS', 'XSD_NS', 'CRYPTO_NS', 'MATH_NS', 'TIME_NS', 'LIST_NS', 'LOG_NS', 'STRING_NS'],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
handler: {
|
|
58
|
-
ctxKeys: ['iri', 'goal', 'subst', 'facts', 'backRules', 'depth', 'varGen', 'maxResults', 'api'],
|
|
59
|
-
return: 'array-of-substitution-deltas',
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const EXACT_API_KEYS = new Set([...Object.keys(CONTRACT.api.functions), ...Object.keys(CONTRACT.api.namespaces)]);
|
|
64
|
-
|
|
65
|
-
function assertExactKeys(obj, expectedKeys, label) {
|
|
66
|
-
const got = Object.keys(obj).sort();
|
|
67
|
-
const exp = Array.from(expectedKeys).sort();
|
|
68
|
-
if (got.length !== exp.length || got.some((k, i) => k !== exp[i])) {
|
|
69
|
-
throw new Error(`${label} keys changed. expected: ${exp.join(', ')}; got: ${got.join(', ')}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function assertFunctionArity(name, fn, spec) {
|
|
74
|
-
if (typeof fn !== 'function') throw new TypeError(`Builtin API member ${name} must be a function`);
|
|
75
|
-
if (Number.isInteger(spec.arity) && fn.length !== spec.arity) {
|
|
76
|
-
throw new Error(`Builtin API member ${name} arity changed: expected ${spec.arity}, got ${fn.length}`);
|
|
77
|
-
}
|
|
78
|
-
if (Number.isInteger(spec.arityMin) && fn.length < spec.arityMin) {
|
|
79
|
-
throw new Error(`Builtin API member ${name} arity too small: expected >= ${spec.arityMin}, got ${fn.length}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function deepFreezeBuiltinApi(api) {
|
|
84
|
-
Object.freeze(api.terms);
|
|
85
|
-
Object.freeze(api.ns);
|
|
86
|
-
return Object.freeze(api);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function assertBuiltinApiShape(api) {
|
|
90
|
-
assertExactKeys(api, EXACT_API_KEYS, 'Builtin registration API');
|
|
91
|
-
|
|
92
|
-
for (const [name, spec] of Object.entries(CONTRACT.api.functions)) {
|
|
93
|
-
assertFunctionArity(name, api[name], spec);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
for (const name of CONTRACT.api.namespaces.terms) {
|
|
97
|
-
if (!api.terms || typeof api.terms[name] !== 'function') {
|
|
98
|
-
throw new TypeError(`Builtin API terms.${name} missing or invalid`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
for (const name of CONTRACT.api.namespaces.ns) {
|
|
103
|
-
if (!api.ns || typeof api.ns[name] !== 'string') {
|
|
104
|
-
throw new TypeError(`Builtin API ns.${name} missing or invalid`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return api;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function assertBuiltinCtxShape(ctx) {
|
|
112
|
-
assertExactKeys(ctx, new Set(CONTRACT.handler.ctxKeys), 'Builtin handler ctx');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function assertBuiltinResultShape(out, iri) {
|
|
116
|
-
if (out == null) return;
|
|
117
|
-
if (!Array.isArray(out)) {
|
|
118
|
-
throw new TypeError(`Custom builtin ${iri} must return an array of substitution deltas`);
|
|
119
|
-
}
|
|
120
|
-
for (const delta of out) {
|
|
121
|
-
if (delta === null || typeof delta !== 'object' || Array.isArray(delta)) {
|
|
122
|
-
throw new TypeError(`Custom builtin ${iri} returned a non-object substitution delta`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
module.exports = {
|
|
128
|
-
CONTRACT,
|
|
129
|
-
assertBuiltinApiShape,
|
|
130
|
-
assertBuiltinCtxShape,
|
|
131
|
-
assertBuiltinResultShape,
|
|
132
|
-
deepFreezeBuiltinApi,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
};
|
|
136
12
|
__modules["lib/builtin-sudoku.js"] = function(require, module, exports){
|
|
137
13
|
'use strict';
|
|
138
14
|
|
|
@@ -639,13 +515,6 @@ const { termToN3 } = require('./printing');
|
|
|
639
515
|
const trace = require('./trace');
|
|
640
516
|
const time = require('./time');
|
|
641
517
|
const deref = require('./deref');
|
|
642
|
-
const {
|
|
643
|
-
CONTRACT: BUILTIN_CONTRACT,
|
|
644
|
-
assertBuiltinApiShape,
|
|
645
|
-
assertBuiltinCtxShape,
|
|
646
|
-
assertBuiltinResultShape,
|
|
647
|
-
deepFreezeBuiltinApi,
|
|
648
|
-
} = require('./builtin-contract');
|
|
649
518
|
|
|
650
519
|
let nodeCrypto = null;
|
|
651
520
|
try {
|
|
@@ -690,18 +559,12 @@ function registerBuiltin(iri, handler) {
|
|
|
690
559
|
throw new TypeError(`Custom builtin ${iri} must be registered with a function handler`);
|
|
691
560
|
}
|
|
692
561
|
|
|
693
|
-
const wrapped = function
|
|
694
|
-
assertBuiltinCtxShape(ctx);
|
|
562
|
+
const wrapped = function builtinResultWrapper(ctx) {
|
|
695
563
|
const out = handler(ctx);
|
|
696
|
-
|
|
564
|
+
__assertBuiltinHandlerResult(iri, out);
|
|
697
565
|
return out;
|
|
698
566
|
};
|
|
699
567
|
|
|
700
|
-
Object.defineProperty(wrapped, '__builtinContractWrapped', {
|
|
701
|
-
value: true,
|
|
702
|
-
enumerable: false,
|
|
703
|
-
});
|
|
704
|
-
|
|
705
568
|
__customBuiltinHandlers.set(iri, wrapped);
|
|
706
569
|
return wrapped;
|
|
707
570
|
}
|
|
@@ -716,15 +579,28 @@ function listBuiltinIris() {
|
|
|
716
579
|
|
|
717
580
|
let __builtinApiSingleton = null;
|
|
718
581
|
|
|
719
|
-
function
|
|
720
|
-
|
|
582
|
+
function __freezeBuiltinApi(api) {
|
|
583
|
+
Object.freeze(api.terms);
|
|
584
|
+
Object.freeze(api.ns);
|
|
585
|
+
return Object.freeze(api);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function __assertBuiltinHandlerResult(iri, out) {
|
|
589
|
+
if (out == null) return;
|
|
590
|
+
if (!Array.isArray(out)) {
|
|
591
|
+
throw new TypeError(`Custom builtin ${iri} must return an array of substitution deltas`);
|
|
592
|
+
}
|
|
593
|
+
for (const delta of out) {
|
|
594
|
+
if (!delta || typeof delta !== 'object' || Array.isArray(delta)) {
|
|
595
|
+
throw new TypeError(`Custom builtin ${iri} must return an array of substitution deltas`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
721
598
|
}
|
|
722
599
|
|
|
723
600
|
function __buildBuiltinRegistrationApi() {
|
|
724
601
|
if (__builtinApiSingleton) return __builtinApiSingleton;
|
|
725
602
|
|
|
726
603
|
const api = {
|
|
727
|
-
getBuiltinApiVersion,
|
|
728
604
|
registerBuiltin,
|
|
729
605
|
unregisterBuiltin,
|
|
730
606
|
listBuiltinIris,
|
|
@@ -753,7 +629,7 @@ function __buildBuiltinRegistrationApi() {
|
|
|
753
629
|
ns: { RDF_NS, XSD_NS, CRYPTO_NS, MATH_NS, TIME_NS, LIST_NS, LOG_NS, STRING_NS },
|
|
754
630
|
};
|
|
755
631
|
|
|
756
|
-
__builtinApiSingleton =
|
|
632
|
+
__builtinApiSingleton = __freezeBuiltinApi(api);
|
|
757
633
|
return __builtinApiSingleton;
|
|
758
634
|
}
|
|
759
635
|
|
|
@@ -831,7 +707,7 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
831
707
|
try {
|
|
832
708
|
const out = handler(ctx);
|
|
833
709
|
if (out == null) return [];
|
|
834
|
-
|
|
710
|
+
__assertBuiltinHandlerResult(pv, out);
|
|
835
711
|
return out;
|
|
836
712
|
} catch (err) {
|
|
837
713
|
if (err && typeof err === 'object' && typeof err.message === 'string') {
|
|
@@ -2700,6 +2576,24 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2700
2576
|
|
|
2701
2577
|
// math:absoluteValue
|
|
2702
2578
|
if (pv === MATH_NS + 'absoluteValue') {
|
|
2579
|
+
const ai = parseIntLiteral(g.s);
|
|
2580
|
+
if (ai !== null) {
|
|
2581
|
+
const outVal = ai < 0n ? -ai : ai;
|
|
2582
|
+
const lit = makeNumericOutputLiteral(outVal, XSD_INTEGER_DT);
|
|
2583
|
+
|
|
2584
|
+
if (g.o instanceof Var) {
|
|
2585
|
+
const s2 = { ...subst };
|
|
2586
|
+
s2[g.o.name] = lit;
|
|
2587
|
+
return [s2];
|
|
2588
|
+
}
|
|
2589
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
2590
|
+
|
|
2591
|
+
const oi = parseIntLiteral(g.o);
|
|
2592
|
+
if (oi !== null && oi === outVal) return [{ ...subst }];
|
|
2593
|
+
if (numEqualTerm(g.o, Number(outVal))) return [{ ...subst }];
|
|
2594
|
+
return [];
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2703
2597
|
const a = parseNum(g.s);
|
|
2704
2598
|
if (a === null) return [];
|
|
2705
2599
|
|
|
@@ -2772,6 +2666,38 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2772
2666
|
|
|
2773
2667
|
// math:negation (inverse is itself)
|
|
2774
2668
|
if (pv === MATH_NS + 'negation') {
|
|
2669
|
+
const si = parseIntLiteral(g.s);
|
|
2670
|
+
if (si !== null) {
|
|
2671
|
+
const outVal = -si;
|
|
2672
|
+
const lit = makeNumericOutputLiteral(outVal, XSD_INTEGER_DT);
|
|
2673
|
+
if (g.o instanceof Var) {
|
|
2674
|
+
const s2 = { ...subst };
|
|
2675
|
+
s2[g.o.name] = lit;
|
|
2676
|
+
return [s2];
|
|
2677
|
+
}
|
|
2678
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
2679
|
+
const oi = parseIntLiteral(g.o);
|
|
2680
|
+
if (oi !== null && oi === outVal) return [{ ...subst }];
|
|
2681
|
+
if (numEqualTerm(g.o, Number(outVal))) return [{ ...subst }];
|
|
2682
|
+
return [];
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
const oi = parseIntLiteral(g.o);
|
|
2686
|
+
if (oi !== null) {
|
|
2687
|
+
const inVal = -oi;
|
|
2688
|
+
const lit = makeNumericOutputLiteral(inVal, XSD_INTEGER_DT);
|
|
2689
|
+
if (g.s instanceof Var) {
|
|
2690
|
+
const s2 = { ...subst };
|
|
2691
|
+
s2[g.s.name] = lit;
|
|
2692
|
+
return [s2];
|
|
2693
|
+
}
|
|
2694
|
+
if (g.s instanceof Blank) return [{ ...subst }];
|
|
2695
|
+
const si2 = parseIntLiteral(g.s);
|
|
2696
|
+
if (si2 !== null && si2 === inVal) return [{ ...subst }];
|
|
2697
|
+
if (numEqualTerm(g.s, Number(inVal))) return [{ ...subst }];
|
|
2698
|
+
return [];
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2775
2701
|
const neg = (x) => -x;
|
|
2776
2702
|
return evalUnaryMathRel(g, subst, neg, neg);
|
|
2777
2703
|
}
|
|
@@ -2830,6 +2756,25 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2830
2756
|
// Schema: $s+ math:rounded $o-
|
|
2831
2757
|
// Note: spec says $o is xsd:integer, but we also accept any numeric $o that equals the rounded value.
|
|
2832
2758
|
if (pv === MATH_NS + 'rounded') {
|
|
2759
|
+
const ai = parseIntLiteral(g.s);
|
|
2760
|
+
if (ai !== null) {
|
|
2761
|
+
const lit = makeNumericOutputLiteral(ai, XSD_INTEGER_DT);
|
|
2762
|
+
|
|
2763
|
+
if (g.o instanceof Var) {
|
|
2764
|
+
const s2 = { ...subst };
|
|
2765
|
+
s2[g.o.name] = lit;
|
|
2766
|
+
return [s2];
|
|
2767
|
+
}
|
|
2768
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
2769
|
+
|
|
2770
|
+
const oi = parseIntLiteral(g.o);
|
|
2771
|
+
if (oi !== null && oi === ai) return [{ ...subst }];
|
|
2772
|
+
if (numEqualTerm(g.o, Number(ai))) return [{ ...subst }];
|
|
2773
|
+
|
|
2774
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
2775
|
+
return s2 !== null ? [s2] : [];
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2833
2778
|
const a = parseNum(g.s);
|
|
2834
2779
|
if (a === null) return [];
|
|
2835
2780
|
if (Number.isNaN(a)) return [];
|
|
@@ -4626,7 +4571,6 @@ module.exports = {
|
|
|
4626
4571
|
registerBuiltinModule,
|
|
4627
4572
|
loadBuiltinModule,
|
|
4628
4573
|
listBuiltinIris,
|
|
4629
|
-
getBuiltinApiVersion,
|
|
4630
4574
|
__testBuildBuiltinApi: __buildBuiltinRegistrationApi,
|
|
4631
4575
|
// shared helpers used by engine/explain
|
|
4632
4576
|
parseBooleanLiteralInfo,
|
package/lib/builtins.js
CHANGED
|
@@ -35,13 +35,6 @@ const { termToN3 } = require('./printing');
|
|
|
35
35
|
const trace = require('./trace');
|
|
36
36
|
const time = require('./time');
|
|
37
37
|
const deref = require('./deref');
|
|
38
|
-
const {
|
|
39
|
-
CONTRACT: BUILTIN_CONTRACT,
|
|
40
|
-
assertBuiltinApiShape,
|
|
41
|
-
assertBuiltinCtxShape,
|
|
42
|
-
assertBuiltinResultShape,
|
|
43
|
-
deepFreezeBuiltinApi,
|
|
44
|
-
} = require('./builtin-contract');
|
|
45
38
|
|
|
46
39
|
let nodeCrypto = null;
|
|
47
40
|
try {
|
|
@@ -86,18 +79,12 @@ function registerBuiltin(iri, handler) {
|
|
|
86
79
|
throw new TypeError(`Custom builtin ${iri} must be registered with a function handler`);
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
const wrapped = function
|
|
90
|
-
assertBuiltinCtxShape(ctx);
|
|
82
|
+
const wrapped = function builtinResultWrapper(ctx) {
|
|
91
83
|
const out = handler(ctx);
|
|
92
|
-
|
|
84
|
+
__assertBuiltinHandlerResult(iri, out);
|
|
93
85
|
return out;
|
|
94
86
|
};
|
|
95
87
|
|
|
96
|
-
Object.defineProperty(wrapped, '__builtinContractWrapped', {
|
|
97
|
-
value: true,
|
|
98
|
-
enumerable: false,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
88
|
__customBuiltinHandlers.set(iri, wrapped);
|
|
102
89
|
return wrapped;
|
|
103
90
|
}
|
|
@@ -112,15 +99,28 @@ function listBuiltinIris() {
|
|
|
112
99
|
|
|
113
100
|
let __builtinApiSingleton = null;
|
|
114
101
|
|
|
115
|
-
function
|
|
116
|
-
|
|
102
|
+
function __freezeBuiltinApi(api) {
|
|
103
|
+
Object.freeze(api.terms);
|
|
104
|
+
Object.freeze(api.ns);
|
|
105
|
+
return Object.freeze(api);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function __assertBuiltinHandlerResult(iri, out) {
|
|
109
|
+
if (out == null) return;
|
|
110
|
+
if (!Array.isArray(out)) {
|
|
111
|
+
throw new TypeError(`Custom builtin ${iri} must return an array of substitution deltas`);
|
|
112
|
+
}
|
|
113
|
+
for (const delta of out) {
|
|
114
|
+
if (!delta || typeof delta !== 'object' || Array.isArray(delta)) {
|
|
115
|
+
throw new TypeError(`Custom builtin ${iri} must return an array of substitution deltas`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
function __buildBuiltinRegistrationApi() {
|
|
120
121
|
if (__builtinApiSingleton) return __builtinApiSingleton;
|
|
121
122
|
|
|
122
123
|
const api = {
|
|
123
|
-
getBuiltinApiVersion,
|
|
124
124
|
registerBuiltin,
|
|
125
125
|
unregisterBuiltin,
|
|
126
126
|
listBuiltinIris,
|
|
@@ -149,7 +149,7 @@ function __buildBuiltinRegistrationApi() {
|
|
|
149
149
|
ns: { RDF_NS, XSD_NS, CRYPTO_NS, MATH_NS, TIME_NS, LIST_NS, LOG_NS, STRING_NS },
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
-
__builtinApiSingleton =
|
|
152
|
+
__builtinApiSingleton = __freezeBuiltinApi(api);
|
|
153
153
|
return __builtinApiSingleton;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -227,7 +227,7 @@ function __evalRegisteredBuiltin(pv, goal, subst, facts, backRules, depth, varGe
|
|
|
227
227
|
try {
|
|
228
228
|
const out = handler(ctx);
|
|
229
229
|
if (out == null) return [];
|
|
230
|
-
|
|
230
|
+
__assertBuiltinHandlerResult(pv, out);
|
|
231
231
|
return out;
|
|
232
232
|
} catch (err) {
|
|
233
233
|
if (err && typeof err === 'object' && typeof err.message === 'string') {
|
|
@@ -2096,6 +2096,24 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2096
2096
|
|
|
2097
2097
|
// math:absoluteValue
|
|
2098
2098
|
if (pv === MATH_NS + 'absoluteValue') {
|
|
2099
|
+
const ai = parseIntLiteral(g.s);
|
|
2100
|
+
if (ai !== null) {
|
|
2101
|
+
const outVal = ai < 0n ? -ai : ai;
|
|
2102
|
+
const lit = makeNumericOutputLiteral(outVal, XSD_INTEGER_DT);
|
|
2103
|
+
|
|
2104
|
+
if (g.o instanceof Var) {
|
|
2105
|
+
const s2 = { ...subst };
|
|
2106
|
+
s2[g.o.name] = lit;
|
|
2107
|
+
return [s2];
|
|
2108
|
+
}
|
|
2109
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
2110
|
+
|
|
2111
|
+
const oi = parseIntLiteral(g.o);
|
|
2112
|
+
if (oi !== null && oi === outVal) return [{ ...subst }];
|
|
2113
|
+
if (numEqualTerm(g.o, Number(outVal))) return [{ ...subst }];
|
|
2114
|
+
return [];
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2099
2117
|
const a = parseNum(g.s);
|
|
2100
2118
|
if (a === null) return [];
|
|
2101
2119
|
|
|
@@ -2168,6 +2186,38 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2168
2186
|
|
|
2169
2187
|
// math:negation (inverse is itself)
|
|
2170
2188
|
if (pv === MATH_NS + 'negation') {
|
|
2189
|
+
const si = parseIntLiteral(g.s);
|
|
2190
|
+
if (si !== null) {
|
|
2191
|
+
const outVal = -si;
|
|
2192
|
+
const lit = makeNumericOutputLiteral(outVal, XSD_INTEGER_DT);
|
|
2193
|
+
if (g.o instanceof Var) {
|
|
2194
|
+
const s2 = { ...subst };
|
|
2195
|
+
s2[g.o.name] = lit;
|
|
2196
|
+
return [s2];
|
|
2197
|
+
}
|
|
2198
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
2199
|
+
const oi = parseIntLiteral(g.o);
|
|
2200
|
+
if (oi !== null && oi === outVal) return [{ ...subst }];
|
|
2201
|
+
if (numEqualTerm(g.o, Number(outVal))) return [{ ...subst }];
|
|
2202
|
+
return [];
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
const oi = parseIntLiteral(g.o);
|
|
2206
|
+
if (oi !== null) {
|
|
2207
|
+
const inVal = -oi;
|
|
2208
|
+
const lit = makeNumericOutputLiteral(inVal, XSD_INTEGER_DT);
|
|
2209
|
+
if (g.s instanceof Var) {
|
|
2210
|
+
const s2 = { ...subst };
|
|
2211
|
+
s2[g.s.name] = lit;
|
|
2212
|
+
return [s2];
|
|
2213
|
+
}
|
|
2214
|
+
if (g.s instanceof Blank) return [{ ...subst }];
|
|
2215
|
+
const si2 = parseIntLiteral(g.s);
|
|
2216
|
+
if (si2 !== null && si2 === inVal) return [{ ...subst }];
|
|
2217
|
+
if (numEqualTerm(g.s, Number(inVal))) return [{ ...subst }];
|
|
2218
|
+
return [];
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2171
2221
|
const neg = (x) => -x;
|
|
2172
2222
|
return evalUnaryMathRel(g, subst, neg, neg);
|
|
2173
2223
|
}
|
|
@@ -2226,6 +2276,25 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2226
2276
|
// Schema: $s+ math:rounded $o-
|
|
2227
2277
|
// Note: spec says $o is xsd:integer, but we also accept any numeric $o that equals the rounded value.
|
|
2228
2278
|
if (pv === MATH_NS + 'rounded') {
|
|
2279
|
+
const ai = parseIntLiteral(g.s);
|
|
2280
|
+
if (ai !== null) {
|
|
2281
|
+
const lit = makeNumericOutputLiteral(ai, XSD_INTEGER_DT);
|
|
2282
|
+
|
|
2283
|
+
if (g.o instanceof Var) {
|
|
2284
|
+
const s2 = { ...subst };
|
|
2285
|
+
s2[g.o.name] = lit;
|
|
2286
|
+
return [s2];
|
|
2287
|
+
}
|
|
2288
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
2289
|
+
|
|
2290
|
+
const oi = parseIntLiteral(g.o);
|
|
2291
|
+
if (oi !== null && oi === ai) return [{ ...subst }];
|
|
2292
|
+
if (numEqualTerm(g.o, Number(ai))) return [{ ...subst }];
|
|
2293
|
+
|
|
2294
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
2295
|
+
return s2 !== null ? [s2] : [];
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2229
2298
|
const a = parseNum(g.s);
|
|
2230
2299
|
if (a === null) return [];
|
|
2231
2300
|
if (Number.isNaN(a)) return [];
|
|
@@ -4022,7 +4091,6 @@ module.exports = {
|
|
|
4022
4091
|
registerBuiltinModule,
|
|
4023
4092
|
loadBuiltinModule,
|
|
4024
4093
|
listBuiltinIris,
|
|
4025
|
-
getBuiltinApiVersion,
|
|
4026
4094
|
__testBuildBuiltinApi: __buildBuiltinRegistrationApi,
|
|
4027
4095
|
// shared helpers used by engine/explain
|
|
4028
4096
|
parseBooleanLiteralInfo,
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -1498,6 +1498,33 @@ _:x :hates { _:foo :making :mess }.
|
|
|
1498
1498
|
],
|
|
1499
1499
|
},
|
|
1500
1500
|
|
|
1501
|
+
{
|
|
1502
|
+
name: '59c regression: integer-safe math absoluteValue, negation, and rounded preserve large integers',
|
|
1503
|
+
opt: { proofComments: false },
|
|
1504
|
+
input: `@prefix : <http://example.org/> .
|
|
1505
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
1506
|
+
@base <http://example.org/> .
|
|
1507
|
+
|
|
1508
|
+
{ 9999999999999999 math:absoluteValue ?X. ?X math:notEqualTo 9999999999999999. } => { :result :has :fail-abs. }.
|
|
1509
|
+
{ 9999999999999999 math:negation ?X. ?X math:negation ?Y. ?Y math:notEqualTo 9999999999999999. } => { :result :has :fail-neg. }.
|
|
1510
|
+
{ 9999999999999999 math:rounded ?X. ?X math:notEqualTo 9999999999999999. } => { :result :has :fail-round. }.
|
|
1511
|
+
|
|
1512
|
+
{ } => {
|
|
1513
|
+
:test :contains :fail-abs, :fail-neg, :fail-round.
|
|
1514
|
+
}.
|
|
1515
|
+
`,
|
|
1516
|
+
expect: [
|
|
1517
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-abs)\s*\./,
|
|
1518
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-neg)\s*\./,
|
|
1519
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-round)\s*\./,
|
|
1520
|
+
],
|
|
1521
|
+
notExpect: [
|
|
1522
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-abs)\s*\./,
|
|
1523
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-neg)\s*\./,
|
|
1524
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-round)\s*\./,
|
|
1525
|
+
],
|
|
1526
|
+
},
|
|
1527
|
+
|
|
1501
1528
|
{
|
|
1502
1529
|
name: '60 regression: log:includes must match quoted triples with variable predicates',
|
|
1503
1530
|
opt: { proofComments: false },
|
|
@@ -20,36 +20,65 @@ function fail(msg) {
|
|
|
20
20
|
|
|
21
21
|
const fixtures = path.join(__dirname, 'fixtures', 'builtins');
|
|
22
22
|
const builtins = require('../lib/builtins');
|
|
23
|
-
const { CONTRACT } = require('../lib/builtin-contract');
|
|
24
23
|
require('../lib/engine');
|
|
25
24
|
|
|
26
|
-
const expectedApiKeys = [
|
|
25
|
+
const expectedApiKeys = [
|
|
26
|
+
'registerBuiltin',
|
|
27
|
+
'unregisterBuiltin',
|
|
28
|
+
'listBuiltinIris',
|
|
29
|
+
'internIri',
|
|
30
|
+
'internLiteral',
|
|
31
|
+
'literalParts',
|
|
32
|
+
'termToJsString',
|
|
33
|
+
'termToJsStringDecoded',
|
|
34
|
+
'termToN3',
|
|
35
|
+
'iriValue',
|
|
36
|
+
'unifyTerm',
|
|
37
|
+
'applySubstTerm',
|
|
38
|
+
'applySubstTriple',
|
|
39
|
+
'proveGoals',
|
|
40
|
+
'isGroundTerm',
|
|
41
|
+
'computeConclusionFromFormula',
|
|
42
|
+
'skolemIriFromGroundTerm',
|
|
43
|
+
'parseBooleanLiteralInfo',
|
|
44
|
+
'parseNumericLiteralInfo',
|
|
45
|
+
'parseXsdDecimalToBigIntScale',
|
|
46
|
+
'pow10n',
|
|
47
|
+
'normalizeLiteralForFastKey',
|
|
48
|
+
'literalsEquivalentAsXsdString',
|
|
49
|
+
'materializeRdfLists',
|
|
50
|
+
'terms',
|
|
51
|
+
'ns',
|
|
52
|
+
].sort();
|
|
27
53
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
54
|
+
const expectedTermsKeys = [
|
|
55
|
+
'Literal',
|
|
56
|
+
'Iri',
|
|
57
|
+
'Var',
|
|
58
|
+
'Blank',
|
|
59
|
+
'ListTerm',
|
|
60
|
+
'OpenListTerm',
|
|
61
|
+
'GraphTerm',
|
|
62
|
+
'Triple',
|
|
63
|
+
'Rule',
|
|
64
|
+
].sort();
|
|
65
|
+
const expectedNsKeys = ['RDF_NS', 'XSD_NS', 'CRYPTO_NS', 'MATH_NS', 'TIME_NS', 'LIST_NS', 'LOG_NS', 'STRING_NS'].sort();
|
|
31
66
|
|
|
32
67
|
const cases = [
|
|
33
68
|
{
|
|
34
|
-
name: 'builtin API
|
|
69
|
+
name: 'builtin helper API stays stable and frozen',
|
|
35
70
|
run() {
|
|
36
71
|
const api = builtins.__testBuildBuiltinApi();
|
|
37
72
|
assert.deepEqual(Object.keys(api).sort(), expectedApiKeys);
|
|
38
73
|
assert.equal(Object.isFrozen(api), true);
|
|
39
74
|
assert.equal(Object.isFrozen(api.terms), true);
|
|
40
75
|
assert.equal(Object.isFrozen(api.ns), true);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (Number.isInteger(spec.arity)) assert.equal(api[name].length, spec.arity, `${name} arity drifted`);
|
|
44
|
-
if (Number.isInteger(spec.arityMin)) assert.ok(api[name].length >= spec.arityMin, `${name} arity drifted`);
|
|
45
|
-
}
|
|
46
|
-
assert.deepEqual(Object.keys(api.terms).sort(), CONTRACT.api.namespaces.terms.slice().sort());
|
|
47
|
-
assert.deepEqual(Object.keys(api.ns).sort(), CONTRACT.api.namespaces.ns.slice().sort());
|
|
48
|
-
assert.equal(api.getBuiltinApiVersion(), CONTRACT.version);
|
|
76
|
+
assert.deepEqual(Object.keys(api.terms).sort(), expectedTermsKeys);
|
|
77
|
+
assert.deepEqual(Object.keys(api.ns).sort(), expectedNsKeys);
|
|
49
78
|
},
|
|
50
79
|
},
|
|
51
80
|
{
|
|
52
|
-
name: 'registerBuiltinModule accepts
|
|
81
|
+
name: 'registerBuiltinModule accepts supported module export forms',
|
|
53
82
|
run() {
|
|
54
83
|
assert.doesNotThrow(() => builtins.registerBuiltinModule(require(path.join(fixtures, 'ok-map.js')), 'ok-map'));
|
|
55
84
|
assert.doesNotThrow(() =>
|
|
@@ -73,33 +102,13 @@ const cases = [
|
|
|
73
102
|
},
|
|
74
103
|
},
|
|
75
104
|
{
|
|
76
|
-
name: 'registered builtin handlers must return substitution
|
|
105
|
+
name: 'registered builtin handlers must return substitution arrays',
|
|
77
106
|
run() {
|
|
78
|
-
builtins.
|
|
79
|
-
assert.throws(() => {
|
|
80
|
-
const h = builtins.registerBuiltin('http://example.org/test#shape-check', () => ({ nope: true }));
|
|
81
|
-
h({
|
|
82
|
-
iri: 'http://example.org/test#shape-check',
|
|
83
|
-
goal: {},
|
|
84
|
-
subst: {},
|
|
85
|
-
facts: [],
|
|
86
|
-
backRules: [],
|
|
87
|
-
depth: 0,
|
|
88
|
-
varGen: 0,
|
|
89
|
-
maxResults: 1,
|
|
90
|
-
api: builtins.__testBuildBuiltinApi(),
|
|
91
|
-
});
|
|
92
|
-
}, /must return an array of substitution deltas/);
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: 'registered builtin handlers receive the exact stable ctx shape',
|
|
97
|
-
run() {
|
|
98
|
-
const wrapped = builtins.registerBuiltin('http://example.org/test#ctx-shape', ({ subst }) => [subst]);
|
|
107
|
+
const wrapped = builtins.registerBuiltin('http://example.org/test#shape-check', () => ({ nope: true }));
|
|
99
108
|
assert.throws(
|
|
100
109
|
() =>
|
|
101
110
|
wrapped({
|
|
102
|
-
iri: 'http://example.org/test#
|
|
111
|
+
iri: 'http://example.org/test#shape-check',
|
|
103
112
|
goal: {},
|
|
104
113
|
subst: {},
|
|
105
114
|
facts: [],
|
|
@@ -108,9 +117,8 @@ const cases = [
|
|
|
108
117
|
varGen: 0,
|
|
109
118
|
maxResults: 1,
|
|
110
119
|
api: builtins.__testBuildBuiltinApi(),
|
|
111
|
-
extra: true,
|
|
112
120
|
}),
|
|
113
|
-
/
|
|
121
|
+
/must return an array of substitution deltas/,
|
|
114
122
|
);
|
|
115
123
|
},
|
|
116
124
|
},
|
package/lib/builtin-contract.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const CONTRACT = {
|
|
4
|
-
version: 1,
|
|
5
|
-
moduleExportForms: [
|
|
6
|
-
'function',
|
|
7
|
-
'object.register',
|
|
8
|
-
'object.builtins',
|
|
9
|
-
'plain-object-map',
|
|
10
|
-
'object.default-object-map',
|
|
11
|
-
],
|
|
12
|
-
api: {
|
|
13
|
-
functions: {
|
|
14
|
-
getBuiltinApiVersion: { arity: 0, required: true },
|
|
15
|
-
registerBuiltin: { arity: 2, required: true },
|
|
16
|
-
unregisterBuiltin: { arity: 1, required: true },
|
|
17
|
-
listBuiltinIris: { arity: 0, required: true },
|
|
18
|
-
internIri: { arity: 1, required: true },
|
|
19
|
-
internLiteral: { arity: 1, required: true },
|
|
20
|
-
literalParts: { arity: 1, required: true },
|
|
21
|
-
termToJsString: { arity: 1, required: true },
|
|
22
|
-
termToJsStringDecoded: { arity: 1, required: true },
|
|
23
|
-
termToN3: { arity: 2, required: true },
|
|
24
|
-
iriValue: { arity: 1, required: true },
|
|
25
|
-
unifyTerm: { arity: 3, required: true },
|
|
26
|
-
applySubstTerm: { arity: 2, required: true },
|
|
27
|
-
applySubstTriple: { arity: 2, required: true },
|
|
28
|
-
proveGoals: { arity: 9, required: true },
|
|
29
|
-
isGroundTerm: { arity: 1, required: true },
|
|
30
|
-
computeConclusionFromFormula: { arity: 1, required: true },
|
|
31
|
-
skolemIriFromGroundTerm: { arity: 1, required: true },
|
|
32
|
-
parseBooleanLiteralInfo: { arity: 1, required: true },
|
|
33
|
-
parseNumericLiteralInfo: { arity: 1, required: true },
|
|
34
|
-
parseXsdDecimalToBigIntScale: { arity: 1, required: true },
|
|
35
|
-
pow10n: { arity: 1, required: true },
|
|
36
|
-
normalizeLiteralForFastKey: { arity: 1, required: true },
|
|
37
|
-
literalsEquivalentAsXsdString: { arity: 2, required: true },
|
|
38
|
-
materializeRdfLists: { arity: 3, required: true },
|
|
39
|
-
},
|
|
40
|
-
namespaces: {
|
|
41
|
-
terms: ['Literal', 'Iri', 'Var', 'Blank', 'ListTerm', 'OpenListTerm', 'GraphTerm', 'Triple', 'Rule'],
|
|
42
|
-
ns: ['RDF_NS', 'XSD_NS', 'CRYPTO_NS', 'MATH_NS', 'TIME_NS', 'LIST_NS', 'LOG_NS', 'STRING_NS'],
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
handler: {
|
|
46
|
-
ctxKeys: ['iri', 'goal', 'subst', 'facts', 'backRules', 'depth', 'varGen', 'maxResults', 'api'],
|
|
47
|
-
return: 'array-of-substitution-deltas',
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const EXACT_API_KEYS = new Set([...Object.keys(CONTRACT.api.functions), ...Object.keys(CONTRACT.api.namespaces)]);
|
|
52
|
-
|
|
53
|
-
function assertExactKeys(obj, expectedKeys, label) {
|
|
54
|
-
const got = Object.keys(obj).sort();
|
|
55
|
-
const exp = Array.from(expectedKeys).sort();
|
|
56
|
-
if (got.length !== exp.length || got.some((k, i) => k !== exp[i])) {
|
|
57
|
-
throw new Error(`${label} keys changed. expected: ${exp.join(', ')}; got: ${got.join(', ')}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function assertFunctionArity(name, fn, spec) {
|
|
62
|
-
if (typeof fn !== 'function') throw new TypeError(`Builtin API member ${name} must be a function`);
|
|
63
|
-
if (Number.isInteger(spec.arity) && fn.length !== spec.arity) {
|
|
64
|
-
throw new Error(`Builtin API member ${name} arity changed: expected ${spec.arity}, got ${fn.length}`);
|
|
65
|
-
}
|
|
66
|
-
if (Number.isInteger(spec.arityMin) && fn.length < spec.arityMin) {
|
|
67
|
-
throw new Error(`Builtin API member ${name} arity too small: expected >= ${spec.arityMin}, got ${fn.length}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function deepFreezeBuiltinApi(api) {
|
|
72
|
-
Object.freeze(api.terms);
|
|
73
|
-
Object.freeze(api.ns);
|
|
74
|
-
return Object.freeze(api);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function assertBuiltinApiShape(api) {
|
|
78
|
-
assertExactKeys(api, EXACT_API_KEYS, 'Builtin registration API');
|
|
79
|
-
|
|
80
|
-
for (const [name, spec] of Object.entries(CONTRACT.api.functions)) {
|
|
81
|
-
assertFunctionArity(name, api[name], spec);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
for (const name of CONTRACT.api.namespaces.terms) {
|
|
85
|
-
if (!api.terms || typeof api.terms[name] !== 'function') {
|
|
86
|
-
throw new TypeError(`Builtin API terms.${name} missing or invalid`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const name of CONTRACT.api.namespaces.ns) {
|
|
91
|
-
if (!api.ns || typeof api.ns[name] !== 'string') {
|
|
92
|
-
throw new TypeError(`Builtin API ns.${name} missing or invalid`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return api;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function assertBuiltinCtxShape(ctx) {
|
|
100
|
-
assertExactKeys(ctx, new Set(CONTRACT.handler.ctxKeys), 'Builtin handler ctx');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function assertBuiltinResultShape(out, iri) {
|
|
104
|
-
if (out == null) return;
|
|
105
|
-
if (!Array.isArray(out)) {
|
|
106
|
-
throw new TypeError(`Custom builtin ${iri} must return an array of substitution deltas`);
|
|
107
|
-
}
|
|
108
|
-
for (const delta of out) {
|
|
109
|
-
if (delta === null || typeof delta !== 'object' || Array.isArray(delta)) {
|
|
110
|
-
throw new TypeError(`Custom builtin ${iri} returned a non-object substitution delta`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = {
|
|
116
|
-
CONTRACT,
|
|
117
|
-
assertBuiltinApiShape,
|
|
118
|
-
assertBuiltinCtxShape,
|
|
119
|
-
assertBuiltinResultShape,
|
|
120
|
-
deepFreezeBuiltinApi,
|
|
121
|
-
};
|