@telorun/cel 0.0.0-stage → 0.108.0
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/LICENSE +17 -0
- package/README.md +281 -2
- package/dist/activation.d.ts +27 -0
- package/dist/activation.d.ts.map +1 -0
- package/dist/activation.js +24 -0
- package/dist/backend-runtime.d.ts +184 -0
- package/dist/backend-runtime.d.ts.map +1 -0
- package/dist/backend-runtime.js +425 -0
- package/dist/bounded-cache.d.ts +21 -0
- package/dist/bounded-cache.d.ts.map +1 -0
- package/dist/bounded-cache.js +42 -0
- package/dist/catalog-runtime.d.ts +59 -0
- package/dist/catalog-runtime.d.ts.map +1 -0
- package/dist/catalog-runtime.js +786 -0
- package/dist/cel-expression.d.ts +32 -0
- package/dist/cel-expression.d.ts.map +1 -0
- package/dist/cel-expression.js +29 -0
- package/dist/cel-map-value.d.ts +47 -0
- package/dist/cel-map-value.d.ts.map +1 -0
- package/dist/cel-map-value.js +85 -0
- package/dist/cel-program.d.ts +44 -0
- package/dist/cel-program.d.ts.map +1 -0
- package/dist/cel-program.js +72 -0
- package/dist/cel-type.d.ts +131 -0
- package/dist/cel-type.d.ts.map +1 -0
- package/dist/cel-type.js +293 -0
- package/dist/cel-value.d.ts +166 -0
- package/dist/cel-value.d.ts.map +1 -0
- package/dist/cel-value.js +225 -0
- package/dist/check-diagnostic.d.ts +54 -0
- package/dist/check-diagnostic.d.ts.map +1 -0
- package/dist/check-diagnostic.js +66 -0
- package/dist/checker.d.ts +86 -0
- package/dist/checker.d.ts.map +1 -0
- package/dist/checker.js +806 -0
- package/dist/closure-backend.d.ts +21 -0
- package/dist/closure-backend.d.ts.map +1 -0
- package/dist/closure-backend.js +487 -0
- package/dist/comprehension-bindings.d.ts +33 -0
- package/dist/comprehension-bindings.d.ts.map +1 -0
- package/dist/comprehension-bindings.js +50 -0
- package/dist/comprehension-runtime.d.ts +44 -0
- package/dist/comprehension-runtime.d.ts.map +1 -0
- package/dist/comprehension-runtime.js +137 -0
- package/dist/declared-chain.d.ts +35 -0
- package/dist/declared-chain.d.ts.map +1 -0
- package/dist/declared-chain.js +36 -0
- package/dist/duration-value.d.ts +70 -0
- package/dist/duration-value.d.ts.map +1 -0
- package/dist/duration-value.js +149 -0
- package/dist/emitted-module.d.ts +207 -0
- package/dist/emitted-module.d.ts.map +1 -0
- package/dist/emitted-module.js +359 -0
- package/dist/engine-version.d.ts +3 -0
- package/dist/engine-version.d.ts.map +1 -0
- package/dist/engine-version.js +8 -0
- package/dist/environment-digest.d.ts +44 -0
- package/dist/environment-digest.d.ts.map +1 -0
- package/dist/environment-digest.js +98 -0
- package/dist/environment.d.ts +291 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +474 -0
- package/dist/function-catalog.d.ts +66 -0
- package/dist/function-catalog.d.ts.map +1 -0
- package/dist/function-catalog.js +77 -0
- package/dist/function-registry.d.ts +118 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +292 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/integer-arithmetic.d.ts +27 -0
- package/dist/integer-arithmetic.d.ts.map +1 -0
- package/dist/integer-arithmetic.js +58 -0
- package/dist/js-emitter.d.ts +133 -0
- package/dist/js-emitter.d.ts.map +1 -0
- package/dist/js-emitter.js +568 -0
- package/dist/json-schema-type.d.ts +182 -0
- package/dist/json-schema-type.d.ts.map +1 -0
- package/dist/json-schema-type.js +487 -0
- package/dist/json-text-scan.d.ts +28 -0
- package/dist/json-text-scan.d.ts.map +1 -0
- package/dist/json-text-scan.js +159 -0
- package/dist/lexer.d.ts +103 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +458 -0
- package/dist/macro-check.d.ts +41 -0
- package/dist/macro-check.d.ts.map +1 -0
- package/dist/macro-check.js +162 -0
- package/dist/macro-shape.d.ts +24 -0
- package/dist/macro-shape.d.ts.map +1 -0
- package/dist/macro-shape.js +55 -0
- package/dist/member-read.d.ts +89 -0
- package/dist/member-read.d.ts.map +1 -0
- package/dist/member-read.js +166 -0
- package/dist/namespace-resolution.d.ts +35 -0
- package/dist/namespace-resolution.d.ts.map +1 -0
- package/dist/namespace-resolution.js +160 -0
- package/dist/nominal-type.d.ts +63 -0
- package/dist/nominal-type.d.ts.map +1 -0
- package/dist/nominal-type.js +98 -0
- package/dist/nullable-access.d.ts +38 -0
- package/dist/nullable-access.d.ts.map +1 -0
- package/dist/nullable-access.js +93 -0
- package/dist/parse-limits.d.ts +26 -0
- package/dist/parse-limits.d.ts.map +1 -0
- package/dist/parse-limits.js +21 -0
- package/dist/parser.d.ts +48 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +503 -0
- package/dist/qualified-calls.d.ts +22 -0
- package/dist/qualified-calls.d.ts.map +1 -0
- package/dist/qualified-calls.js +27 -0
- package/dist/regular-expression.d.ts +48 -0
- package/dist/regular-expression.d.ts.map +1 -0
- package/dist/regular-expression.js +77 -0
- package/dist/reserved-words.d.ts +52 -0
- package/dist/reserved-words.d.ts.map +1 -0
- package/dist/reserved-words.js +77 -0
- package/dist/resolved-call.d.ts +46 -0
- package/dist/resolved-call.d.ts.map +1 -0
- package/dist/resolved-call.js +15 -0
- package/dist/root-references.d.ts +23 -0
- package/dist/root-references.d.ts.map +1 -0
- package/dist/root-references.js +120 -0
- package/dist/runtime-library.d.ts +67 -0
- package/dist/runtime-library.d.ts.map +1 -0
- package/dist/runtime-library.js +554 -0
- package/dist/serializer.d.ts +24 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +256 -0
- package/dist/sha256.d.ts +20 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +103 -0
- package/dist/signature.d.ts +72 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +61 -0
- package/dist/signatures/function-catalog.json +788 -0
- package/dist/signatures/standard-library.json +229 -0
- package/dist/standard-library.d.ts +41 -0
- package/dist/standard-library.d.ts.map +1 -0
- package/dist/standard-library.js +85 -0
- package/dist/syntax-diagnostic.d.ts +61 -0
- package/dist/syntax-diagnostic.d.ts.map +1 -0
- package/dist/syntax-diagnostic.js +28 -0
- package/dist/syntax-tree.d.ts +160 -0
- package/dist/syntax-tree.d.ts.map +1 -0
- package/dist/syntax-tree.js +58 -0
- package/dist/timestamp-value.d.ts +62 -0
- package/dist/timestamp-value.d.ts.map +1 -0
- package/dist/timestamp-value.js +238 -0
- package/dist/tree-equality.d.ts +15 -0
- package/dist/tree-equality.d.ts.map +1 -0
- package/dist/tree-equality.js +105 -0
- package/dist/type-expression.d.ts +42 -0
- package/dist/type-expression.d.ts.map +1 -0
- package/dist/type-expression.js +154 -0
- package/dist/value-equality.d.ts +38 -0
- package/dist/value-equality.d.ts.map +1 -0
- package/dist/value-equality.js +196 -0
- package/dist/value-text.d.ts +25 -0
- package/dist/value-text.d.ts.map +1 -0
- package/dist/value-text.js +44 -0
- package/dist/zoned-calendar.d.ts +61 -0
- package/dist/zoned-calendar.d.ts.map +1 -0
- package/dist/zoned-calendar.js +143 -0
- package/package.json +56 -3
- package/src/activation.ts +32 -0
- package/src/backend-runtime.ts +604 -0
- package/src/bounded-cache.ts +45 -0
- package/src/catalog-runtime.ts +921 -0
- package/src/cel-expression.ts +53 -0
- package/src/cel-map-value.ts +97 -0
- package/src/cel-program.ts +103 -0
- package/src/cel-type.ts +359 -0
- package/src/cel-value.ts +361 -0
- package/src/check-diagnostic.ts +104 -0
- package/src/checker.ts +1045 -0
- package/src/closure-backend.ts +547 -0
- package/src/comprehension-bindings.ts +66 -0
- package/src/comprehension-runtime.ts +157 -0
- package/src/declared-chain.ts +45 -0
- package/src/duration-value.ts +160 -0
- package/src/emitted-module.ts +494 -0
- package/src/engine-version.ts +9 -0
- package/src/environment-digest.ts +111 -0
- package/src/environment.ts +761 -0
- package/src/function-catalog.ts +140 -0
- package/src/function-registry.ts +341 -0
- package/src/index.ts +407 -0
- package/src/integer-arithmetic.ts +64 -0
- package/src/js-emitter.ts +721 -0
- package/src/json-schema-type.ts +664 -0
- package/src/json-text-scan.ts +163 -0
- package/src/lexer.ts +562 -0
- package/src/macro-check.ts +197 -0
- package/src/macro-shape.ts +66 -0
- package/src/member-read.ts +167 -0
- package/src/namespace-resolution.ts +167 -0
- package/src/nominal-type.ts +149 -0
- package/src/nullable-access.ts +95 -0
- package/src/parse-limits.ts +36 -0
- package/src/parser.ts +554 -0
- package/src/qualified-calls.ts +39 -0
- package/src/regular-expression.ts +101 -0
- package/src/reserved-words.ts +94 -0
- package/src/resolved-call.ts +47 -0
- package/src/root-references.ts +126 -0
- package/src/runtime-library.ts +639 -0
- package/src/serializer.ts +262 -0
- package/src/sha256.ts +112 -0
- package/src/signature.ts +127 -0
- package/src/signatures/function-catalog.json +788 -0
- package/src/signatures/standard-library.json +235 -0
- package/src/standard-library.ts +149 -0
- package/src/syntax-diagnostic.ts +72 -0
- package/src/syntax-tree.ts +229 -0
- package/src/timestamp-value.ts +310 -0
- package/src/tree-equality.ts +130 -0
- package/src/type-expression.ts +182 -0
- package/src/value-equality.ts +201 -0
- package/src/value-text.ts +45 -0
- package/src/zoned-calendar.ts +178 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An environment: what names, functions and types an expression is read against.
|
|
3
|
+
*
|
|
4
|
+
* **Everything is registered the same way, including the standard library.** There is
|
|
5
|
+
* no privileged door: a host can replace `duration(string)` with its own, or remove a
|
|
6
|
+
* standard function so a call to it is refused, because the library came in through the
|
|
7
|
+
* same `registerFunction` a host uses. An engine with a built-in library behind a
|
|
8
|
+
* private registration is an engine whose library cannot be changed — which is the
|
|
9
|
+
* concrete thing this package exists to fix.
|
|
10
|
+
*
|
|
11
|
+
* `clone()` inherits everything and then diverges: registering or removing on the clone
|
|
12
|
+
* leaves the parent as it was. That is what lets one base environment be built once and
|
|
13
|
+
* specialised per site.
|
|
14
|
+
*
|
|
15
|
+
* **Two seams, and only two, let a host's vocabulary in**: `registerType`, for a named
|
|
16
|
+
* type over a base with its own operators and members, and `resolveSchemaType`, asked at
|
|
17
|
+
* every node of a JSON Schema before the structural rules. Nothing in this package knows
|
|
18
|
+
* any host's type names.
|
|
19
|
+
*/
|
|
20
|
+
import { BoundedCache } from "./bounded-cache.js";
|
|
21
|
+
import { parseExpression, resolvedUnder } from "./cel-expression.js";
|
|
22
|
+
import { compileExpression } from "./cel-program.js";
|
|
23
|
+
import { emitModule, emitterRuntime, storedEmittedModule } from "./emitted-module.js";
|
|
24
|
+
import { environmentDigest } from "./environment-digest.js";
|
|
25
|
+
import { CEL_VALUE_KEYS } from "./cel-value.js";
|
|
26
|
+
import { DYN, formatType } from "./cel-type.js";
|
|
27
|
+
import { CelEngineError } from "./check-diagnostic.js";
|
|
28
|
+
import { checkExpression } from "./checker.js";
|
|
29
|
+
import { FunctionRegistry } from "./function-registry.js";
|
|
30
|
+
import { fieldMapType, schemaType } from "./json-schema-type.js";
|
|
31
|
+
import { normalizeNamespaces } from "./namespace-resolution.js";
|
|
32
|
+
import { buildRegisteredType, CelTypeRegistrationError, nominalOperators, nominalSignatures, } from "./nominal-type.js";
|
|
33
|
+
import { formatSignature, parseSignature } from "./signature.js";
|
|
34
|
+
import { registerStandardLibrary, standardConstants } from "./standard-library.js";
|
|
35
|
+
import { standardConstantValues } from "./runtime-library.js";
|
|
36
|
+
import { parseTypeExpression } from "./type-expression.js";
|
|
37
|
+
/** How many compiled expressions an environment keeps when nothing says otherwise. */
|
|
38
|
+
export const DEFAULT_COMPILED_CACHE_CAPACITY = 256;
|
|
39
|
+
export class CelEnvironment {
|
|
40
|
+
registry;
|
|
41
|
+
variables;
|
|
42
|
+
types;
|
|
43
|
+
namespaceFunctions;
|
|
44
|
+
/** Namespaces that declare only part of what they reach; see {@link NamespaceOptions.open}. */
|
|
45
|
+
openNamespaces;
|
|
46
|
+
constants;
|
|
47
|
+
/** Bounded: a source text is a key an author's input decides. */
|
|
48
|
+
compiled;
|
|
49
|
+
options;
|
|
50
|
+
constructor(options = {}, inherited) {
|
|
51
|
+
this.options = {
|
|
52
|
+
unlistedVariablesAreDyn: false,
|
|
53
|
+
homogeneousAggregateLiterals: false,
|
|
54
|
+
enableOptionalTypes: false,
|
|
55
|
+
...(inherited ? inherited.options : {}),
|
|
56
|
+
...options,
|
|
57
|
+
};
|
|
58
|
+
this.registry = new FunctionRegistry(inherited?.registry);
|
|
59
|
+
this.variables = new Map(inherited?.variables);
|
|
60
|
+
this.types = new Map(inherited?.types);
|
|
61
|
+
this.namespaceFunctions = new Map();
|
|
62
|
+
for (const [namespace, functions] of inherited?.namespaceFunctions ?? []) {
|
|
63
|
+
this.namespaceFunctions.set(namespace, new Map(functions));
|
|
64
|
+
}
|
|
65
|
+
this.openNamespaces = new Set(inherited?.openNamespaces);
|
|
66
|
+
this.constants = new Map(inherited?.constants);
|
|
67
|
+
this.compiled = new BoundedCache(this.options.compiledCacheCapacity ?? DEFAULT_COMPILED_CACHE_CAPACITY);
|
|
68
|
+
if (inherited)
|
|
69
|
+
return;
|
|
70
|
+
if (this.options.standardLibrary ?? true) {
|
|
71
|
+
for (const [name, value] of standardConstantValues(this.options.enableOptionalTypes)) {
|
|
72
|
+
this.constants.set(name, value);
|
|
73
|
+
}
|
|
74
|
+
registerStandardLibrary(this.registry, {
|
|
75
|
+
optionalTypes: this.options.enableOptionalTypes,
|
|
76
|
+
resolveNominal: this.nominalResolver,
|
|
77
|
+
});
|
|
78
|
+
for (const constant of standardConstants(this.options.enableOptionalTypes)) {
|
|
79
|
+
this.variables.set(constant.name, {
|
|
80
|
+
type: constant.type,
|
|
81
|
+
constant: true,
|
|
82
|
+
...(constant.description === undefined ? {} : { description: constant.description }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** A new environment inheriting everything, which may then diverge. */
|
|
88
|
+
clone(options = {}) {
|
|
89
|
+
return new CelEnvironment(options, this);
|
|
90
|
+
}
|
|
91
|
+
// --- registration -------------------------------------------------------
|
|
92
|
+
/**
|
|
93
|
+
* Registers a function, **replacing** any registration answering the same call.
|
|
94
|
+
*
|
|
95
|
+
* Every registration forgets what this environment compiled: a program holds the
|
|
96
|
+
* overloads its call sites resolved to, so serving a cached one after the library
|
|
97
|
+
* changed would run the function that was replaced — the precise failure the
|
|
98
|
+
* replaceable library exists to avoid.
|
|
99
|
+
*/
|
|
100
|
+
registerFunction(signature, metadata = {}) {
|
|
101
|
+
this.registry.register(this.signatureOf(signature), metadata);
|
|
102
|
+
this.compiled.clear();
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
/** Removes the one registration answering that call. Answers whether it was there. */
|
|
106
|
+
removeFunction(signature) {
|
|
107
|
+
const removed = this.registry.remove(this.signatureOf(signature));
|
|
108
|
+
this.compiled.clear();
|
|
109
|
+
return removed;
|
|
110
|
+
}
|
|
111
|
+
/** Removes every registration of a name, so a call to it is unknown. */
|
|
112
|
+
removeFunctionsNamed(name) {
|
|
113
|
+
const removed = this.registry.removeName(name);
|
|
114
|
+
this.compiled.clear();
|
|
115
|
+
return removed;
|
|
116
|
+
}
|
|
117
|
+
/** Registers an operator over the types given, replacing one over the same types. */
|
|
118
|
+
registerOperator(operator, parameters, returns, metadata = {}) {
|
|
119
|
+
this.registry.register({
|
|
120
|
+
name: operator,
|
|
121
|
+
form: "global",
|
|
122
|
+
parameters: parameters.map((text) => this.readType(text)),
|
|
123
|
+
returns: this.readType(returns),
|
|
124
|
+
}, metadata);
|
|
125
|
+
this.compiled.clear();
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
registerVariable(name, declaration, metadata = {}) {
|
|
129
|
+
this.variables.set(name, {
|
|
130
|
+
...this.typeOf(declaration),
|
|
131
|
+
constant: false,
|
|
132
|
+
...(metadata.description === undefined ? {} : { description: metadata.description }),
|
|
133
|
+
});
|
|
134
|
+
// A declared name changes which NAME a dotted chain reads: `splitDeclaredChain` is
|
|
135
|
+
// decided at compile time from the declarations, so a program compiled before this
|
|
136
|
+
// call resolved the chain against the activation instead. Serving it afterwards is
|
|
137
|
+
// the check/run divergence that split exists to prevent, arriving through the cache.
|
|
138
|
+
this.compiled.clear();
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
/** A name whose value never changes; it reads exactly as a variable does. */
|
|
142
|
+
registerConstant(name, declaration, metadata = {}) {
|
|
143
|
+
this.variables.set(name, {
|
|
144
|
+
...this.typeOf(declaration),
|
|
145
|
+
constant: true,
|
|
146
|
+
...(metadata.description === undefined ? {} : { description: metadata.description }),
|
|
147
|
+
});
|
|
148
|
+
this.compiled.clear();
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
hasVariable(name) {
|
|
152
|
+
return this.variables.has(name);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Every schema-typed registration whose schema this engine could not judge whole, with
|
|
156
|
+
* each node by JSON Pointer. Empty is every node judged, which is the normal answer.
|
|
157
|
+
*/
|
|
158
|
+
schemaReports() {
|
|
159
|
+
const reports = [];
|
|
160
|
+
for (const [name, held] of [...this.variables.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
|
|
161
|
+
const unjudged = held.unjudged ?? [];
|
|
162
|
+
const recursive = held.recursive ?? [];
|
|
163
|
+
if (unjudged.length === 0 && recursive.length === 0)
|
|
164
|
+
continue;
|
|
165
|
+
reports.push({ name, unjudged, recursive });
|
|
166
|
+
}
|
|
167
|
+
return reports;
|
|
168
|
+
}
|
|
169
|
+
/** Registers a named type, with every operator, conversion and member it declares. */
|
|
170
|
+
registerType(definition) {
|
|
171
|
+
// A value of a named type carries its name under the engine's own value key, so a
|
|
172
|
+
// name the engine already uses there would make two types one value.
|
|
173
|
+
if (CEL_VALUE_KEYS.includes(definition.name)) {
|
|
174
|
+
throw new CelTypeRegistrationError(`${JSON.stringify(definition.name)} is the type key of a value this engine builds`);
|
|
175
|
+
}
|
|
176
|
+
const registered = buildRegisteredType(definition, this.nominalResolver);
|
|
177
|
+
this.types.set(definition.name, registered);
|
|
178
|
+
const resolver = (name, args) => name === "Self" ? { ...registered.self, args: args.length > 0 ? args : registered.self.args } : this.nominalResolver(name, args);
|
|
179
|
+
for (const signature of nominalSignatures(definition)) {
|
|
180
|
+
this.registry.register(parseSignature(signature, resolver), {
|
|
181
|
+
deterministic: true,
|
|
182
|
+
origin: `type:${definition.name}`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
for (const operator of nominalOperators(definition)) {
|
|
186
|
+
this.registry.register({
|
|
187
|
+
name: operator.operator,
|
|
188
|
+
form: "global",
|
|
189
|
+
parameters: operator.parameters.map((text) => parseTypeExpression(text, resolver)),
|
|
190
|
+
returns: parseTypeExpression(operator.returns, resolver),
|
|
191
|
+
}, { deterministic: true, origin: `type:${definition.name}` });
|
|
192
|
+
}
|
|
193
|
+
this.compiled.clear();
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Registers a namespace and the functions it declares. A namespace is a name that
|
|
198
|
+
* denotes a module rather than a value, which is what turns `Alias.fn(x)` into a
|
|
199
|
+
* qualified call rather than a method on something.
|
|
200
|
+
*/
|
|
201
|
+
registerNamespace(name, functions = [], options = {}) {
|
|
202
|
+
normalizeNamespaces([name]);
|
|
203
|
+
const declared = new Map();
|
|
204
|
+
for (const declaration of functions) {
|
|
205
|
+
const entry = typeof declaration === "string" ? { signature: declaration } : declaration;
|
|
206
|
+
const flags = {
|
|
207
|
+
...(entry.deterministic === undefined ? {} : { deterministic: entry.deterministic }),
|
|
208
|
+
...(entry.hostBacked === undefined ? {} : { hostBacked: entry.hostBacked }),
|
|
209
|
+
...(entry.throws === undefined ? {} : { throws: entry.throws }),
|
|
210
|
+
};
|
|
211
|
+
// A name this environment registers no type under is RECORDED and read as `dyn`,
|
|
212
|
+
// never thrown: a namespace declaration is a host's own data — a module function's
|
|
213
|
+
// declared result out of a manifest — so a typo there would otherwise be a crash
|
|
214
|
+
// with no line, and the consumer that knows where it was written is the one that
|
|
215
|
+
// can anchor the diagnostic. The checker reports it at each call.
|
|
216
|
+
const unregistered = [];
|
|
217
|
+
const resolver = (named, args) => {
|
|
218
|
+
const resolved = this.nominalResolver(named, args);
|
|
219
|
+
if (resolved)
|
|
220
|
+
return resolved;
|
|
221
|
+
if (!unregistered.includes(named))
|
|
222
|
+
unregistered.push(named);
|
|
223
|
+
return DYN;
|
|
224
|
+
};
|
|
225
|
+
const recorded = () => (unregistered.length > 0 ? { unregisteredTypes: [...unregistered] } : {});
|
|
226
|
+
if (entry.signature !== undefined) {
|
|
227
|
+
const signature = parseSignature(entry.signature, resolver);
|
|
228
|
+
if (signature.form !== "global") {
|
|
229
|
+
throw new CelTypeRegistrationError(`a namespaced function is declared without a receiver: ${JSON.stringify(entry.signature)}`);
|
|
230
|
+
}
|
|
231
|
+
declared.set(signature.name, {
|
|
232
|
+
name: signature.name,
|
|
233
|
+
returns: signature.returns,
|
|
234
|
+
parameters: signature.parameters,
|
|
235
|
+
signature: formatSignature(signature),
|
|
236
|
+
...recorded(),
|
|
237
|
+
...flags,
|
|
238
|
+
});
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
declared.set(entry.name, {
|
|
242
|
+
name: entry.name,
|
|
243
|
+
returns: typeof entry.returns === "string" ? parseTypeExpression(entry.returns, resolver) : entry.returns,
|
|
244
|
+
...recorded(),
|
|
245
|
+
...flags,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
this.namespaceFunctions.set(name, declared);
|
|
249
|
+
if (options.open === true)
|
|
250
|
+
this.openNamespaces.add(name);
|
|
251
|
+
else
|
|
252
|
+
this.openNamespaces.delete(name);
|
|
253
|
+
this.compiled.clear();
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
/** Every namespace registered, in canonical order. */
|
|
257
|
+
namespaces() {
|
|
258
|
+
return [...this.namespaceFunctions.keys()].sort();
|
|
259
|
+
}
|
|
260
|
+
// --- reading ------------------------------------------------------------
|
|
261
|
+
/** Reads an expression against this environment's namespaces. */
|
|
262
|
+
parse(source) {
|
|
263
|
+
return parseExpression(source, {
|
|
264
|
+
namespaces: this.namespaces(),
|
|
265
|
+
optionalSyntax: this.options.enableOptionalTypes,
|
|
266
|
+
...(this.options.limits ? { limits: this.options.limits } : {}),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Checks an expression. A tree resolved under a different namespace set is **refused**
|
|
271
|
+
* rather than checked: its qualified calls are not the ones this environment would
|
|
272
|
+
* have found, so every answer about it would be about a different expression.
|
|
273
|
+
*/
|
|
274
|
+
check(source) {
|
|
275
|
+
const expression = typeof source === "string" ? this.parse(source) : source;
|
|
276
|
+
if (typeof source !== "string" && !resolvedUnder(expression, this.namespaces())) {
|
|
277
|
+
throw new CelEngineError("namespaces_mismatch", `the expression was resolved under [${expression.namespaces.join(", ")}] and this environment has [${this.namespaces().join(", ")}]`);
|
|
278
|
+
}
|
|
279
|
+
return checkExpression(expression, {
|
|
280
|
+
registry: this.registry,
|
|
281
|
+
variable: (name) => this.variables.get(name)?.type,
|
|
282
|
+
declaredVariableNames: () => [...this.variables.keys()],
|
|
283
|
+
namespaceFunction: (namespace, name) => this.namespaceFunctions.get(namespace)?.get(name),
|
|
284
|
+
namespaceIsOpen: (namespace) => this.openNamespaces.has(namespace),
|
|
285
|
+
options: {
|
|
286
|
+
unlistedVariablesAreDyn: this.options.unlistedVariablesAreDyn,
|
|
287
|
+
homogeneousAggregateLiterals: this.options.homogeneousAggregateLiterals,
|
|
288
|
+
enableOptionalTypes: this.options.enableOptionalTypes,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Compiles an expression into a program. Nothing is type-checked here — the checker
|
|
294
|
+
* decides every verdict, and a consumer that wants one asks for it — so compiling
|
|
295
|
+
* refuses only what cannot be compiled at all: a source that did not read whole.
|
|
296
|
+
*
|
|
297
|
+
* A compile from text is memoized in a bounded cache; a tree handed over directly is
|
|
298
|
+
* compiled each time, because a tree has no identity to key on.
|
|
299
|
+
*/
|
|
300
|
+
compile(source) {
|
|
301
|
+
if (typeof source !== "string")
|
|
302
|
+
return this.compileExpressionNow(source);
|
|
303
|
+
const held = this.compiled.get(source);
|
|
304
|
+
if (held)
|
|
305
|
+
return held;
|
|
306
|
+
const program = this.compileExpressionNow(this.parse(source));
|
|
307
|
+
this.compiled.set(source, program);
|
|
308
|
+
return program;
|
|
309
|
+
}
|
|
310
|
+
/** Compiles and runs an expression against an activation. */
|
|
311
|
+
evaluate(source, activation, options) {
|
|
312
|
+
return this.compile(source).evaluate(activation, options);
|
|
313
|
+
}
|
|
314
|
+
// --- the emitter ---------------------------------------------------------
|
|
315
|
+
/**
|
|
316
|
+
* The JavaScript module for a set of expressions, emitted now: its text, its key and its
|
|
317
|
+
* integrity header. The same expressions in the same order against the same environment
|
|
318
|
+
* emit byte-identically, so a host may compare two emissions instead of trusting one.
|
|
319
|
+
*
|
|
320
|
+
* The host stores and loads the text; this package touches no filesystem and exports no
|
|
321
|
+
* loader. `emitterRuntime()` is what the loaded module's factory takes, and
|
|
322
|
+
* `programsFromEmittedModule` verifies its header before anything runs.
|
|
323
|
+
*/
|
|
324
|
+
emit(sources) {
|
|
325
|
+
return emitModule(this.target(), this.digest(), this.expressionsOf(sources));
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* The module for a set of expressions, read from the store where it holds a copy whose
|
|
329
|
+
* header matches and emitted and written where it does not — so a corrupted, stale or
|
|
330
|
+
* foreign stored copy causes a recompile rather than a run.
|
|
331
|
+
*/
|
|
332
|
+
emittedModule(sources, store) {
|
|
333
|
+
// The trees are handed over as a thunk: a hit reads a header and a digest, and parsing
|
|
334
|
+
// every expression to find out whether it needed to is work with no answer attached.
|
|
335
|
+
return storedEmittedModule(this.target(), this.digest(), sources, () => this.expressionsOf(sources), store);
|
|
336
|
+
}
|
|
337
|
+
/** The runtime support library an emitted module's factory is handed. */
|
|
338
|
+
emitterRuntime() {
|
|
339
|
+
return emitterRuntime(this.target());
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* This environment's digest: the order-independent hash of its resolved listing, which
|
|
343
|
+
* the key and the header are both over. A host that caches by key never needs it; a host
|
|
344
|
+
* explaining a recompile does.
|
|
345
|
+
*/
|
|
346
|
+
digest() {
|
|
347
|
+
return environmentDigest(this);
|
|
348
|
+
}
|
|
349
|
+
expressionsOf(sources) {
|
|
350
|
+
return sources.map((source) => ({ source, root: this.readyExpression(this.parse(source)).root }));
|
|
351
|
+
}
|
|
352
|
+
/** What the backends need of this environment, built one way for both of them. */
|
|
353
|
+
target() {
|
|
354
|
+
return {
|
|
355
|
+
registry: this.registry,
|
|
356
|
+
constants: this.constants,
|
|
357
|
+
nominalArity: (name) => this.types.get(name)?.parameters.length,
|
|
358
|
+
// The same declarations the checker splits a dotted chain on.
|
|
359
|
+
declares: (name) => this.variables.has(name),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* An expression a backend may compile: read whole, and resolved under this
|
|
364
|
+
* environment's own namespaces. Nothing is type-checked — the checker decides every
|
|
365
|
+
* verdict and a consumer that wants one asks for it.
|
|
366
|
+
*/
|
|
367
|
+
readyExpression(expression) {
|
|
368
|
+
if (expression.diagnostics.length > 0) {
|
|
369
|
+
throw new CelEngineError("unreadable_expression", `${JSON.stringify(expression.source)} could not be read: ${expression.diagnostics[0].message}`);
|
|
370
|
+
}
|
|
371
|
+
if (!resolvedUnder(expression, this.namespaces())) {
|
|
372
|
+
throw new CelEngineError("namespaces_mismatch", `the expression was resolved under [${expression.namespaces.join(", ")}] and this environment has [${this.namespaces().join(", ")}]`);
|
|
373
|
+
}
|
|
374
|
+
return expression;
|
|
375
|
+
}
|
|
376
|
+
compileExpressionNow(expression) {
|
|
377
|
+
return compileExpression(this.readyExpression(expression), this.target());
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Whether a value of this type can be converted to text by the environment's own
|
|
381
|
+
* `string()`. A consumer that renders a value into text asks this before it tries;
|
|
382
|
+
* `dyn` passes, because only the runtime knows what it holds.
|
|
383
|
+
*/
|
|
384
|
+
convertsToString(type) {
|
|
385
|
+
if (type.kind === "dyn")
|
|
386
|
+
return true;
|
|
387
|
+
return "resolved" in this.registry.resolve("string", "global", [type]);
|
|
388
|
+
}
|
|
389
|
+
/** Every registration, for a listing a host prints or compares. */
|
|
390
|
+
definitions() {
|
|
391
|
+
return {
|
|
392
|
+
variables: [...this.variables.entries()]
|
|
393
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
394
|
+
.map(([name, held]) => ({
|
|
395
|
+
name,
|
|
396
|
+
type: held.type,
|
|
397
|
+
typeName: formatType(held.type, true),
|
|
398
|
+
constant: held.constant,
|
|
399
|
+
...(held.description === undefined ? {} : { description: held.description }),
|
|
400
|
+
})),
|
|
401
|
+
functions: this.registry.list().map((entry) => ({
|
|
402
|
+
name: entry.signature.name,
|
|
403
|
+
signature: formatSignature(entry.signature),
|
|
404
|
+
receiverType: entry.signature.receiver ? formatType(entry.signature.receiver) : null,
|
|
405
|
+
parameters: entry.signature.parameters.map((type) => formatType(type)),
|
|
406
|
+
returns: formatType(entry.signature.returns),
|
|
407
|
+
deterministic: entry.metadata.deterministic ?? true,
|
|
408
|
+
hostBacked: entry.metadata.hostBacked ?? false,
|
|
409
|
+
...(entry.metadata.throws === undefined ? {} : { throws: entry.metadata.throws }),
|
|
410
|
+
...(entry.metadata.description === undefined ? {} : { description: entry.metadata.description }),
|
|
411
|
+
...(entry.metadata.origin === undefined ? {} : { origin: entry.metadata.origin }),
|
|
412
|
+
})),
|
|
413
|
+
types: [...this.types.values()].map((registered) => ({
|
|
414
|
+
name: registered.definition.name,
|
|
415
|
+
base: formatType(registered.base),
|
|
416
|
+
parameters: registered.parameters,
|
|
417
|
+
...(registered.definition.description === undefined
|
|
418
|
+
? {}
|
|
419
|
+
: { description: registered.definition.description }),
|
|
420
|
+
})),
|
|
421
|
+
namespaces: this.namespaces().map((name) => ({
|
|
422
|
+
name,
|
|
423
|
+
open: this.openNamespaces.has(name),
|
|
424
|
+
// A declaration whose parameter list is withheld has no signature text, so it is
|
|
425
|
+
// listed by what it DOES declare — its name and its result. Printing an invented
|
|
426
|
+
// empty parameter list would read as a function of no arguments.
|
|
427
|
+
functions: [...this.namespaceFunctions.get(name).values()].map((held) => held.signature ?? `${held.name}(…): ${formatType(held.returns)}`),
|
|
428
|
+
})),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
// --- types --------------------------------------------------------------
|
|
432
|
+
nominalResolver = (name, args) => {
|
|
433
|
+
const registered = this.types.get(name);
|
|
434
|
+
if (!registered)
|
|
435
|
+
return undefined;
|
|
436
|
+
if (args.length === 0)
|
|
437
|
+
return registered.self;
|
|
438
|
+
if (args.length !== registered.parameters.length) {
|
|
439
|
+
throw new CelTypeRegistrationError(`${name} takes ${registered.parameters.length} type argument${registered.parameters.length === 1 ? "" : "s"}`);
|
|
440
|
+
}
|
|
441
|
+
return { ...registered.self, args };
|
|
442
|
+
};
|
|
443
|
+
readType(text) {
|
|
444
|
+
return parseTypeExpression(text, this.nominalResolver);
|
|
445
|
+
}
|
|
446
|
+
signatureOf(signature) {
|
|
447
|
+
return typeof signature === "string" ? parseSignature(signature, this.nominalResolver) : signature;
|
|
448
|
+
}
|
|
449
|
+
/** The type a declaration names: a type expression, a field map, or a schema. */
|
|
450
|
+
typeOf(declaration) {
|
|
451
|
+
if (typeof declaration === "string")
|
|
452
|
+
return { type: this.readType(declaration) };
|
|
453
|
+
if ("kind" in declaration)
|
|
454
|
+
return { type: declaration };
|
|
455
|
+
if ("fields" in declaration) {
|
|
456
|
+
return { type: fieldMapType(declaration.fields, (text) => this.readType(text)) };
|
|
457
|
+
}
|
|
458
|
+
if ("schema" in declaration) {
|
|
459
|
+
const converted = schemaType({
|
|
460
|
+
node: declaration.schema,
|
|
461
|
+
...(declaration.document === undefined ? {} : { root: declaration.document }),
|
|
462
|
+
}, {
|
|
463
|
+
...(this.options.resolveSchemaType ? { resolveSchemaType: this.options.resolveSchemaType } : {}),
|
|
464
|
+
lookupNamedType: (name, args) => this.nominalResolver(name, args.map((text) => this.readType(text))),
|
|
465
|
+
});
|
|
466
|
+
return {
|
|
467
|
+
type: converted.type,
|
|
468
|
+
...(converted.unjudged.length === 0 ? {} : { unjudged: converted.unjudged }),
|
|
469
|
+
...(converted.recursive.length === 0 ? {} : { recursive: converted.recursive }),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
throw new CelTypeRegistrationError("a declaration is a type expression, a field map or a schema");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading the function catalog into an environment.
|
|
3
|
+
*
|
|
4
|
+
* The catalog is the **dialect**: the functions a manifest may call beside CEL's own
|
|
5
|
+
* library. Like the standard library it is **data** (`signatures/function-catalog.json`)
|
|
6
|
+
* read through the same registration surface a host uses — here literally the public one,
|
|
7
|
+
* `CelEnvironment.registerFunction`, with no privileged door of any kind. A host may
|
|
8
|
+
* register the catalog, leave it out, replace one of its signatures or remove a name, and
|
|
9
|
+
* nothing in the engine behaves differently for it.
|
|
10
|
+
*
|
|
11
|
+
* **One surface, read by everything that lists the catalog.** `functionCatalog()` answers
|
|
12
|
+
* each function with its display signature, its category, its summary and its two flags,
|
|
13
|
+
* which is exactly what a `functions` listing prints; a consumer never reconstructs that
|
|
14
|
+
* from registrations.
|
|
15
|
+
*
|
|
16
|
+
* What a function DOES is beside it, per runtime — `catalog-runtime.ts`, keyed by the same
|
|
17
|
+
* dispatch key the registry resolves on. A declared signature with no behaviour there is
|
|
18
|
+
* refused at registration rather than at evaluation, which is the failure class the two
|
|
19
|
+
* artifacts exist to remove.
|
|
20
|
+
*/
|
|
21
|
+
import type { CelCatalogHandlers } from "./catalog-runtime.js";
|
|
22
|
+
import type { CelEnvironment } from "./environment.js";
|
|
23
|
+
export declare const FUNCTION_CATALOG_GENERATION: number;
|
|
24
|
+
/** The categories the catalog groups its functions under, for a grouped listing. */
|
|
25
|
+
export declare function catalogCategories(): readonly string[];
|
|
26
|
+
/** One function of the catalog, as a listing prints it. */
|
|
27
|
+
export interface CatalogFunction {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
/**
|
|
30
|
+
* The signature a human reads, which is not always one registration: an optional
|
|
31
|
+
* parameter is written `fn(string?): string` and registers one signature per arity.
|
|
32
|
+
*/
|
|
33
|
+
readonly signature: string;
|
|
34
|
+
/** Every signature the function registers, in registration order. */
|
|
35
|
+
readonly signatures: readonly string[];
|
|
36
|
+
readonly category: string;
|
|
37
|
+
readonly summary: string;
|
|
38
|
+
/** False where two calls with the same arguments may answer differently. */
|
|
39
|
+
readonly deterministic: boolean;
|
|
40
|
+
/** Whether the implementation is the host's rather than the engine's. */
|
|
41
|
+
readonly hostBacked: boolean;
|
|
42
|
+
/** Whether the function refuses an argument written as a literal, at check. */
|
|
43
|
+
readonly checksLiteralArguments: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** The catalog, as the one surface a listing reads. */
|
|
46
|
+
export declare function functionCatalog(): readonly CatalogFunction[];
|
|
47
|
+
/** Every signature the catalog registers, for a validator and for a docs listing. */
|
|
48
|
+
export declare function catalogSignatures(): readonly string[];
|
|
49
|
+
export interface RegisterCatalogOptions {
|
|
50
|
+
/**
|
|
51
|
+
* The implementations of the nine host-backed functions. Each may be left out — the
|
|
52
|
+
* registration is still made, so an expression calling it still type-checks, and
|
|
53
|
+
* evaluating one answers an `unbound_function` error naming the function. That is what
|
|
54
|
+
* a consumer which only ever CHECKS (an analyzer, an editor) needs.
|
|
55
|
+
*/
|
|
56
|
+
readonly handlers?: Partial<CelCatalogHandlers>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Registers the catalog onto an environment, through the public registration surface.
|
|
60
|
+
*
|
|
61
|
+
* A signature the runtime table does not answer for is refused here: a declaration with
|
|
62
|
+
* no behaviour type-checks and then fails at evaluation, which is precisely the failure
|
|
63
|
+
* the declaration/behaviour split exists to catch at registration instead.
|
|
64
|
+
*/
|
|
65
|
+
export declare function registerFunctionCatalog(environment: CelEnvironment, options?: RegisterCatalogOptions): void;
|
|
66
|
+
//# sourceMappingURL=function-catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"function-catalog.d.ts","sourceRoot":"","sources":["../src/function-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAwBvD,eAAO,MAAM,2BAA2B,QAAkB,CAAC;AAE3D,oFAAoF;AACpF,wBAAgB,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAErD;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C;AAED,uDAAuD;AACvD,wBAAgB,eAAe,IAAI,SAAS,eAAe,EAAE,CAW5D;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAErD;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACjD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,cAAc,EAC3B,OAAO,GAAE,sBAA2B,GACnC,IAAI,CAyBN"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading the function catalog into an environment.
|
|
3
|
+
*
|
|
4
|
+
* The catalog is the **dialect**: the functions a manifest may call beside CEL's own
|
|
5
|
+
* library. Like the standard library it is **data** (`signatures/function-catalog.json`)
|
|
6
|
+
* read through the same registration surface a host uses — here literally the public one,
|
|
7
|
+
* `CelEnvironment.registerFunction`, with no privileged door of any kind. A host may
|
|
8
|
+
* register the catalog, leave it out, replace one of its signatures or remove a name, and
|
|
9
|
+
* nothing in the engine behaves differently for it.
|
|
10
|
+
*
|
|
11
|
+
* **One surface, read by everything that lists the catalog.** `functionCatalog()` answers
|
|
12
|
+
* each function with its display signature, its category, its summary and its two flags,
|
|
13
|
+
* which is exactly what a `functions` listing prints; a consumer never reconstructs that
|
|
14
|
+
* from registrations.
|
|
15
|
+
*
|
|
16
|
+
* What a function DOES is beside it, per runtime — `catalog-runtime.ts`, keyed by the same
|
|
17
|
+
* dispatch key the registry resolves on. A declared signature with no behaviour there is
|
|
18
|
+
* refused at registration rather than at evaluation, which is the failure class the two
|
|
19
|
+
* artifacts exist to remove.
|
|
20
|
+
*/
|
|
21
|
+
import data from "./signatures/function-catalog.json" with { type: "json" };
|
|
22
|
+
import { catalogImplementation, catalogLiteralCheck } from "./catalog-runtime.js";
|
|
23
|
+
import { parseSignature, signatureKey } from "./signature.js";
|
|
24
|
+
const DATA = data;
|
|
25
|
+
export const FUNCTION_CATALOG_GENERATION = DATA.generation;
|
|
26
|
+
/** The categories the catalog groups its functions under, for a grouped listing. */
|
|
27
|
+
export function catalogCategories() {
|
|
28
|
+
return DATA.categories;
|
|
29
|
+
}
|
|
30
|
+
/** The catalog, as the one surface a listing reads. */
|
|
31
|
+
export function functionCatalog() {
|
|
32
|
+
return DATA.functions.map((entry) => ({
|
|
33
|
+
name: entry.name,
|
|
34
|
+
signature: entry.signature,
|
|
35
|
+
signatures: entry.signatures,
|
|
36
|
+
category: entry.category,
|
|
37
|
+
summary: entry.summary,
|
|
38
|
+
deterministic: entry.deterministic,
|
|
39
|
+
hostBacked: entry.hostBacked,
|
|
40
|
+
checksLiteralArguments: entry.checksLiteralArguments ?? false,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/** Every signature the catalog registers, for a validator and for a docs listing. */
|
|
44
|
+
export function catalogSignatures() {
|
|
45
|
+
return DATA.functions.flatMap((entry) => entry.signatures);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Registers the catalog onto an environment, through the public registration surface.
|
|
49
|
+
*
|
|
50
|
+
* A signature the runtime table does not answer for is refused here: a declaration with
|
|
51
|
+
* no behaviour type-checks and then fails at evaluation, which is precisely the failure
|
|
52
|
+
* the declaration/behaviour split exists to catch at registration instead.
|
|
53
|
+
*/
|
|
54
|
+
export function registerFunctionCatalog(environment, options = {}) {
|
|
55
|
+
const handlers = options.handlers ?? {};
|
|
56
|
+
for (const entry of DATA.functions) {
|
|
57
|
+
const check = entry.checksLiteralArguments ? catalogLiteralCheck(entry.name) : undefined;
|
|
58
|
+
if (entry.checksLiteralArguments && check === undefined) {
|
|
59
|
+
throw new Error(`the catalog declares that ${entry.name} checks its literal arguments, and no guard answers for it`);
|
|
60
|
+
}
|
|
61
|
+
for (const signature of entry.signatures) {
|
|
62
|
+
const key = signatureKey(parseSignature(signature));
|
|
63
|
+
const implementation = catalogImplementation(key, handlers);
|
|
64
|
+
if (implementation === undefined) {
|
|
65
|
+
throw new Error(`the catalog declares ${signature} and nothing implements ${key}`);
|
|
66
|
+
}
|
|
67
|
+
environment.registerFunction(signature, {
|
|
68
|
+
implementation,
|
|
69
|
+
deterministic: entry.deterministic,
|
|
70
|
+
hostBacked: entry.hostBacked,
|
|
71
|
+
description: entry.summary,
|
|
72
|
+
origin: "function-catalog",
|
|
73
|
+
...(check === undefined ? {} : { checkArguments: check }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|