@telorun/cel 0.0.0-stage → 0.107.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 +146 -0
- package/dist/backend-runtime.d.ts.map +1 -0
- package/dist/backend-runtime.js +322 -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 +785 -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 +34 -0
- package/dist/cel-map-value.d.ts.map +1 -0
- package/dist/cel-map-value.js +74 -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 +159 -0
- package/dist/cel-value.d.ts.map +1 -0
- package/dist/cel-value.js +225 -0
- package/dist/check-diagnostic.d.ts +53 -0
- package/dist/check-diagnostic.d.ts.map +1 -0
- package/dist/check-diagnostic.js +66 -0
- package/dist/checker.d.ts +77 -0
- package/dist/checker.d.ts.map +1 -0
- package/dist/checker.js +721 -0
- package/dist/closure-backend.d.ts +21 -0
- package/dist/closure-backend.d.ts.map +1 -0
- package/dist/closure-backend.js +436 -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 +59 -0
- package/dist/duration-value.d.ts.map +1 -0
- package/dist/duration-value.js +135 -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 +283 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +459 -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 +78 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +189 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -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 +132 -0
- package/dist/js-emitter.d.ts.map +1 -0
- package/dist/js-emitter.js +562 -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 +33 -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 +52 -0
- package/dist/member-read.d.ts.map +1 -0
- package/dist/member-read.js +125 -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 +33 -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 +56 -0
- package/dist/runtime-library.d.ts.map +1 -0
- package/dist/runtime-library.js +545 -0
- package/dist/serializer.d.ts +24 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +240 -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 +53 -0
- package/dist/timestamp-value.d.ts.map +1 -0
- package/dist/timestamp-value.js +223 -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 +26 -0
- package/dist/type-expression.d.ts.map +1 -0
- package/dist/type-expression.js +134 -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 +454 -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 +86 -0
- package/src/cel-program.ts +103 -0
- package/src/cel-type.ts +359 -0
- package/src/cel-value.ts +353 -0
- package/src/check-diagnostic.ts +102 -0
- package/src/checker.ts +932 -0
- package/src/closure-backend.ts +502 -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 +145 -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 +740 -0
- package/src/function-catalog.ts +140 -0
- package/src/function-registry.ts +229 -0
- package/src/index.ts +386 -0
- package/src/integer-arithmetic.ts +64 -0
- package/src/js-emitter.ts +713 -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 +191 -0
- package/src/macro-shape.ts +66 -0
- package/src/member-read.ts +126 -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 +34 -0
- package/src/root-references.ts +126 -0
- package/src/runtime-library.ts +615 -0
- package/src/serializer.ts +246 -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 +294 -0
- package/src/tree-equality.ts +130 -0
- package/src/type-expression.ts +160 -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,740 @@
|
|
|
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
|
+
|
|
21
|
+
import type { CelActivation } from "./activation.js";
|
|
22
|
+
import { BoundedCache } from "./bounded-cache.js";
|
|
23
|
+
import type { CelExpression } from "./cel-expression.js";
|
|
24
|
+
import { parseExpression, resolvedUnder } from "./cel-expression.js";
|
|
25
|
+
import type { CelProgram, EvaluateOptions } from "./cel-program.js";
|
|
26
|
+
import { compileExpression } from "./cel-program.js";
|
|
27
|
+
import type { CompileTarget } from "./backend-runtime.js";
|
|
28
|
+
import type {
|
|
29
|
+
EmittedExpression,
|
|
30
|
+
EmittedModule,
|
|
31
|
+
EmittedModuleStore,
|
|
32
|
+
EmittedRuntime,
|
|
33
|
+
StoredEmittedModule,
|
|
34
|
+
} from "./emitted-module.js";
|
|
35
|
+
import { emitModule, emitterRuntime, storedEmittedModule } from "./emitted-module.js";
|
|
36
|
+
import { environmentDigest } from "./environment-digest.js";
|
|
37
|
+
import type { CelValue } from "./cel-value.js";
|
|
38
|
+
import { CEL_VALUE_KEYS } from "./cel-value.js";
|
|
39
|
+
import type { CelType } from "./cel-type.js";
|
|
40
|
+
import { formatType } from "./cel-type.js";
|
|
41
|
+
import { CelEngineError } from "./check-diagnostic.js";
|
|
42
|
+
import type { CheckResult, NamespaceFunction } from "./checker.js";
|
|
43
|
+
import { checkExpression } from "./checker.js";
|
|
44
|
+
import { FunctionRegistry } from "./function-registry.js";
|
|
45
|
+
import type {
|
|
46
|
+
FieldDeclaration,
|
|
47
|
+
JsonSchemaNode,
|
|
48
|
+
RecursiveSchemaReference,
|
|
49
|
+
SchemaTypeResolver,
|
|
50
|
+
UnjudgedSchemaNode,
|
|
51
|
+
} from "./json-schema-type.js";
|
|
52
|
+
import { fieldMapType, schemaType } from "./json-schema-type.js";
|
|
53
|
+
import { normalizeNamespaces } from "./namespace-resolution.js";
|
|
54
|
+
import type { NominalTypeDefinition, RegisteredType } from "./nominal-type.js";
|
|
55
|
+
import {
|
|
56
|
+
buildRegisteredType,
|
|
57
|
+
CelTypeRegistrationError,
|
|
58
|
+
nominalOperators,
|
|
59
|
+
nominalSignatures,
|
|
60
|
+
} from "./nominal-type.js";
|
|
61
|
+
import type { CelParseLimits } from "./parse-limits.js";
|
|
62
|
+
import type { CelSignature, FunctionMetadata } from "./signature.js";
|
|
63
|
+
import { formatSignature, parseSignature } from "./signature.js";
|
|
64
|
+
import { registerStandardLibrary, standardConstants } from "./standard-library.js";
|
|
65
|
+
import { standardConstantValues } from "./runtime-library.js";
|
|
66
|
+
import type { NominalResolver } from "./type-expression.js";
|
|
67
|
+
import { parseTypeExpression } from "./type-expression.js";
|
|
68
|
+
|
|
69
|
+
export interface CelEnvironmentOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Whether a name nothing declares reads as `dyn` instead of being reported.
|
|
72
|
+
*
|
|
73
|
+
* The default is CEL's own answer — report it. A host that types only part of what an
|
|
74
|
+
* expression may read turns it on, and a host that declares everything legal at a site
|
|
75
|
+
* leaves it off so a misspelled name is caught.
|
|
76
|
+
*/
|
|
77
|
+
readonly unlistedVariablesAreDyn?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Whether a list or map literal must hold one type. The default is off, which is
|
|
80
|
+
* CEL's: a heterogeneous literal is a `list<dyn>`, not a mistake.
|
|
81
|
+
*/
|
|
82
|
+
readonly homogeneousAggregateLiterals?: boolean;
|
|
83
|
+
/** Whether `.?`, `[?]` and the `optional` members exist. Off by default, as in CEL. */
|
|
84
|
+
readonly enableOptionalTypes?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Asked at every schema node, with the document the node belongs to, before its
|
|
87
|
+
* structure is read. It answers a registered type name with its arguments, or the
|
|
88
|
+
* document to read in place of the node — which is how a reference leaving the document
|
|
89
|
+
* is resolved, the host owning what a reference outside this document is based on.
|
|
90
|
+
*/
|
|
91
|
+
readonly resolveSchemaType?: SchemaTypeResolver;
|
|
92
|
+
readonly limits?: Partial<CelParseLimits>;
|
|
93
|
+
/** Whether the standard library is registered. On by default. */
|
|
94
|
+
readonly standardLibrary?: boolean;
|
|
95
|
+
/** How many compiled expressions this environment keeps. */
|
|
96
|
+
readonly compiledCacheCapacity?: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type TypeDeclaration =
|
|
100
|
+
| string
|
|
101
|
+
| CelType
|
|
102
|
+
| { readonly fields: Readonly<Record<string, FieldDeclaration>> }
|
|
103
|
+
| {
|
|
104
|
+
readonly schema: JsonSchemaNode;
|
|
105
|
+
/**
|
|
106
|
+
* The document the schema belongs to — what a `#/…` reference inside it resolves
|
|
107
|
+
* against. Absent where the schema **is** the document, which is the common case.
|
|
108
|
+
*/
|
|
109
|
+
readonly document?: JsonSchemaNode;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export interface VariableDefinition {
|
|
113
|
+
readonly name: string;
|
|
114
|
+
readonly type: CelType;
|
|
115
|
+
readonly typeName: string;
|
|
116
|
+
readonly description?: string;
|
|
117
|
+
readonly constant: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface FunctionDefinition {
|
|
121
|
+
readonly name: string;
|
|
122
|
+
readonly signature: string;
|
|
123
|
+
/** How the call is written: the receiver type, or null for a global call. */
|
|
124
|
+
readonly receiverType: string | null;
|
|
125
|
+
readonly parameters: readonly string[];
|
|
126
|
+
readonly returns: string;
|
|
127
|
+
readonly deterministic: boolean;
|
|
128
|
+
readonly hostBacked: boolean;
|
|
129
|
+
readonly throws?: readonly string[];
|
|
130
|
+
readonly description?: string;
|
|
131
|
+
readonly origin?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface TypeDefinitionListing {
|
|
135
|
+
readonly name: string;
|
|
136
|
+
readonly base: string;
|
|
137
|
+
readonly parameters: readonly string[];
|
|
138
|
+
readonly description?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface NamespaceListing {
|
|
142
|
+
readonly name: string;
|
|
143
|
+
readonly functions: readonly string[];
|
|
144
|
+
/** Whether names beyond the listed ones are reachable; see {@link NamespaceOptions.open}. */
|
|
145
|
+
readonly open: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface Definitions {
|
|
149
|
+
readonly variables: readonly VariableDefinition[];
|
|
150
|
+
readonly functions: readonly FunctionDefinition[];
|
|
151
|
+
readonly types: readonly TypeDefinitionListing[];
|
|
152
|
+
readonly namespaces: readonly NamespaceListing[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* What converting one registration's schema could not judge.
|
|
157
|
+
*
|
|
158
|
+
* A schema node this engine has no rule for is reported rather than refused: the schema
|
|
159
|
+
* is usually a third party's data, so throwing at registration would turn someone else's
|
|
160
|
+
* schema into a crash, and typing it `dyn` quietly is the hole the report exists to close.
|
|
161
|
+
* The consumer that knows where the schema was written is the one that can anchor a
|
|
162
|
+
* diagnostic at it.
|
|
163
|
+
*/
|
|
164
|
+
export interface SchemaRegistrationReport {
|
|
165
|
+
/** The name registered under. */
|
|
166
|
+
readonly name: string;
|
|
167
|
+
readonly unjudged: readonly UnjudgedSchemaNode[];
|
|
168
|
+
/** Every reference re-entered on the descent: a recursive schema, deliberately `dyn`. */
|
|
169
|
+
readonly recursive: readonly RecursiveSchemaReference[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* A namespaced function a host declares, in one of two forms.
|
|
174
|
+
*
|
|
175
|
+
* **`signature`** declares it whole, in global form (`total(double, int): double`), and this
|
|
176
|
+
* engine judges the call's arity and its argument types against it.
|
|
177
|
+
*
|
|
178
|
+
* **`name` + `returns`** declares the function's identity and its RESULT and **withholds the
|
|
179
|
+
* parameter list**, so arity and arguments are judged by nobody here. A host whose own
|
|
180
|
+
* signature grammar is richer than CEL's — an optional trailing parameter, a declared JSON
|
|
181
|
+
* Schema per parameter — judges them itself and strictly better. The two forms are exclusive
|
|
182
|
+
* by construction rather than by a flag: a declaration that carried parameters and asked for
|
|
183
|
+
* them to be ignored would hold a list nothing reads, which no reader can tell from a list
|
|
184
|
+
* that is simply wrong.
|
|
185
|
+
*/
|
|
186
|
+
export type NamespaceFunctionDeclaration = (
|
|
187
|
+
| { readonly signature: string; readonly name?: never; readonly returns?: never }
|
|
188
|
+
| { readonly name: string; readonly returns: string | CelType; readonly signature?: never }
|
|
189
|
+
) & {
|
|
190
|
+
readonly deterministic?: boolean;
|
|
191
|
+
readonly hostBacked?: boolean;
|
|
192
|
+
readonly throws?: readonly string[];
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/** What a host says about a namespace as a whole. */
|
|
196
|
+
export interface NamespaceOptions {
|
|
197
|
+
/**
|
|
198
|
+
* Whether names beyond the declared ones are reachable through it. **Open** means a name
|
|
199
|
+
* this namespace did not declare types `dyn` and is reported by nobody: the host resolves
|
|
200
|
+
* it against vocabulary this engine does not hold — an export gate, a capability, a
|
|
201
|
+
* re-export chain — and words that verdict itself. Closed (the default) makes such a name
|
|
202
|
+
* `FUNCTION_UNRESOLVED`.
|
|
203
|
+
*/
|
|
204
|
+
readonly open?: boolean;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface VariableRecord {
|
|
208
|
+
readonly type: CelType;
|
|
209
|
+
readonly description?: string;
|
|
210
|
+
readonly constant: boolean;
|
|
211
|
+
/** What the schema it was typed from could not judge, where it was typed from one. */
|
|
212
|
+
readonly unjudged?: readonly UnjudgedSchemaNode[];
|
|
213
|
+
readonly recursive?: readonly RecursiveSchemaReference[];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** A type a declaration named, beside what converting it could not judge. */
|
|
217
|
+
interface DeclaredType {
|
|
218
|
+
readonly type: CelType;
|
|
219
|
+
readonly unjudged?: readonly UnjudgedSchemaNode[];
|
|
220
|
+
readonly recursive?: readonly RecursiveSchemaReference[];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** How many compiled expressions an environment keeps when nothing says otherwise. */
|
|
224
|
+
export const DEFAULT_COMPILED_CACHE_CAPACITY = 256;
|
|
225
|
+
|
|
226
|
+
export class CelEnvironment {
|
|
227
|
+
private readonly registry: FunctionRegistry;
|
|
228
|
+
private readonly variables: Map<string, VariableRecord>;
|
|
229
|
+
private readonly types: Map<string, RegisteredType>;
|
|
230
|
+
private readonly namespaceFunctions: Map<string, Map<string, NamespaceFunction>>;
|
|
231
|
+
/** Namespaces that declare only part of what they reach; see {@link NamespaceOptions.open}. */
|
|
232
|
+
private readonly openNamespaces: Set<string>;
|
|
233
|
+
private readonly constants: Map<string, CelValue>;
|
|
234
|
+
/** Bounded: a source text is a key an author's input decides. */
|
|
235
|
+
private readonly compiled: BoundedCache<string, CelProgram>;
|
|
236
|
+
readonly options: Required<
|
|
237
|
+
Pick<
|
|
238
|
+
CelEnvironmentOptions,
|
|
239
|
+
"unlistedVariablesAreDyn" | "homogeneousAggregateLiterals" | "enableOptionalTypes"
|
|
240
|
+
>
|
|
241
|
+
> &
|
|
242
|
+
CelEnvironmentOptions;
|
|
243
|
+
|
|
244
|
+
constructor(options: CelEnvironmentOptions = {}, inherited?: CelEnvironment) {
|
|
245
|
+
this.options = {
|
|
246
|
+
unlistedVariablesAreDyn: false,
|
|
247
|
+
homogeneousAggregateLiterals: false,
|
|
248
|
+
enableOptionalTypes: false,
|
|
249
|
+
...(inherited ? inherited.options : {}),
|
|
250
|
+
...options,
|
|
251
|
+
};
|
|
252
|
+
this.registry = new FunctionRegistry(inherited?.registry);
|
|
253
|
+
this.variables = new Map(inherited?.variables);
|
|
254
|
+
this.types = new Map(inherited?.types);
|
|
255
|
+
this.namespaceFunctions = new Map();
|
|
256
|
+
for (const [namespace, functions] of inherited?.namespaceFunctions ?? []) {
|
|
257
|
+
this.namespaceFunctions.set(namespace, new Map(functions));
|
|
258
|
+
}
|
|
259
|
+
this.openNamespaces = new Set(inherited?.openNamespaces);
|
|
260
|
+
this.constants = new Map(inherited?.constants);
|
|
261
|
+
this.compiled = new BoundedCache(
|
|
262
|
+
this.options.compiledCacheCapacity ?? DEFAULT_COMPILED_CACHE_CAPACITY,
|
|
263
|
+
);
|
|
264
|
+
if (inherited) return;
|
|
265
|
+
if (this.options.standardLibrary ?? true) {
|
|
266
|
+
for (const [name, value] of standardConstantValues(this.options.enableOptionalTypes)) {
|
|
267
|
+
this.constants.set(name, value);
|
|
268
|
+
}
|
|
269
|
+
registerStandardLibrary(this.registry, {
|
|
270
|
+
optionalTypes: this.options.enableOptionalTypes,
|
|
271
|
+
resolveNominal: this.nominalResolver,
|
|
272
|
+
});
|
|
273
|
+
for (const constant of standardConstants(this.options.enableOptionalTypes)) {
|
|
274
|
+
this.variables.set(constant.name, {
|
|
275
|
+
type: constant.type,
|
|
276
|
+
constant: true,
|
|
277
|
+
...(constant.description === undefined ? {} : { description: constant.description }),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** A new environment inheriting everything, which may then diverge. */
|
|
284
|
+
clone(options: CelEnvironmentOptions = {}): CelEnvironment {
|
|
285
|
+
return new CelEnvironment(options, this);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// --- registration -------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Registers a function, **replacing** any registration answering the same call.
|
|
292
|
+
*
|
|
293
|
+
* Every registration forgets what this environment compiled: a program holds the
|
|
294
|
+
* overloads its call sites resolved to, so serving a cached one after the library
|
|
295
|
+
* changed would run the function that was replaced — the precise failure the
|
|
296
|
+
* replaceable library exists to avoid.
|
|
297
|
+
*/
|
|
298
|
+
registerFunction(signature: string | CelSignature, metadata: FunctionMetadata = {}): this {
|
|
299
|
+
this.registry.register(this.signatureOf(signature), metadata);
|
|
300
|
+
this.compiled.clear();
|
|
301
|
+
return this;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Removes the one registration answering that call. Answers whether it was there. */
|
|
305
|
+
removeFunction(signature: string | CelSignature): boolean {
|
|
306
|
+
const removed = this.registry.remove(this.signatureOf(signature));
|
|
307
|
+
this.compiled.clear();
|
|
308
|
+
return removed;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Removes every registration of a name, so a call to it is unknown. */
|
|
312
|
+
removeFunctionsNamed(name: string): number {
|
|
313
|
+
const removed = this.registry.removeName(name);
|
|
314
|
+
this.compiled.clear();
|
|
315
|
+
return removed;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Registers an operator over the types given, replacing one over the same types. */
|
|
319
|
+
registerOperator(
|
|
320
|
+
operator: string,
|
|
321
|
+
parameters: readonly string[],
|
|
322
|
+
returns: string,
|
|
323
|
+
metadata: FunctionMetadata = {},
|
|
324
|
+
): this {
|
|
325
|
+
this.registry.register(
|
|
326
|
+
{
|
|
327
|
+
name: operator,
|
|
328
|
+
form: "global",
|
|
329
|
+
parameters: parameters.map((text) => this.readType(text)),
|
|
330
|
+
returns: this.readType(returns),
|
|
331
|
+
},
|
|
332
|
+
metadata,
|
|
333
|
+
);
|
|
334
|
+
this.compiled.clear();
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
registerVariable(
|
|
339
|
+
name: string,
|
|
340
|
+
declaration: TypeDeclaration,
|
|
341
|
+
metadata: { readonly description?: string } = {},
|
|
342
|
+
): this {
|
|
343
|
+
this.variables.set(name, {
|
|
344
|
+
...this.typeOf(declaration),
|
|
345
|
+
constant: false,
|
|
346
|
+
...(metadata.description === undefined ? {} : { description: metadata.description }),
|
|
347
|
+
});
|
|
348
|
+
// A declared name changes which NAME a dotted chain reads: `splitDeclaredChain` is
|
|
349
|
+
// decided at compile time from the declarations, so a program compiled before this
|
|
350
|
+
// call resolved the chain against the activation instead. Serving it afterwards is
|
|
351
|
+
// the check/run divergence that split exists to prevent, arriving through the cache.
|
|
352
|
+
this.compiled.clear();
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** A name whose value never changes; it reads exactly as a variable does. */
|
|
357
|
+
registerConstant(
|
|
358
|
+
name: string,
|
|
359
|
+
declaration: TypeDeclaration,
|
|
360
|
+
metadata: { readonly description?: string } = {},
|
|
361
|
+
): this {
|
|
362
|
+
this.variables.set(name, {
|
|
363
|
+
...this.typeOf(declaration),
|
|
364
|
+
constant: true,
|
|
365
|
+
...(metadata.description === undefined ? {} : { description: metadata.description }),
|
|
366
|
+
});
|
|
367
|
+
this.compiled.clear();
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
hasVariable(name: string): boolean {
|
|
372
|
+
return this.variables.has(name);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Every schema-typed registration whose schema this engine could not judge whole, with
|
|
377
|
+
* each node by JSON Pointer. Empty is every node judged, which is the normal answer.
|
|
378
|
+
*/
|
|
379
|
+
schemaReports(): readonly SchemaRegistrationReport[] {
|
|
380
|
+
const reports: SchemaRegistrationReport[] = [];
|
|
381
|
+
for (const [name, held] of [...this.variables.entries()].sort(([left], [right]) =>
|
|
382
|
+
left < right ? -1 : left > right ? 1 : 0,
|
|
383
|
+
)) {
|
|
384
|
+
const unjudged = held.unjudged ?? [];
|
|
385
|
+
const recursive = held.recursive ?? [];
|
|
386
|
+
if (unjudged.length === 0 && recursive.length === 0) continue;
|
|
387
|
+
reports.push({ name, unjudged, recursive });
|
|
388
|
+
}
|
|
389
|
+
return reports;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Registers a named type, with every operator, conversion and member it declares. */
|
|
393
|
+
registerType(definition: NominalTypeDefinition): this {
|
|
394
|
+
// A value of a named type carries its name under the engine's own value key, so a
|
|
395
|
+
// name the engine already uses there would make two types one value.
|
|
396
|
+
if ((CEL_VALUE_KEYS as readonly string[]).includes(definition.name)) {
|
|
397
|
+
throw new CelTypeRegistrationError(
|
|
398
|
+
`${JSON.stringify(definition.name)} is the type key of a value this engine builds`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const registered = buildRegisteredType(definition, this.nominalResolver);
|
|
402
|
+
this.types.set(definition.name, registered);
|
|
403
|
+
const resolver: NominalResolver = (name, args) =>
|
|
404
|
+
name === "Self" ? { ...registered.self, args: args.length > 0 ? args : registered.self.args } : this.nominalResolver(name, args);
|
|
405
|
+
for (const signature of nominalSignatures(definition)) {
|
|
406
|
+
this.registry.register(parseSignature(signature, resolver), {
|
|
407
|
+
deterministic: true,
|
|
408
|
+
origin: `type:${definition.name}`,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
for (const operator of nominalOperators(definition)) {
|
|
412
|
+
this.registry.register(
|
|
413
|
+
{
|
|
414
|
+
name: operator.operator,
|
|
415
|
+
form: "global",
|
|
416
|
+
parameters: operator.parameters.map((text) => parseTypeExpression(text, resolver)),
|
|
417
|
+
returns: parseTypeExpression(operator.returns, resolver),
|
|
418
|
+
},
|
|
419
|
+
{ deterministic: true, origin: `type:${definition.name}` },
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
this.compiled.clear();
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Registers a namespace and the functions it declares. A namespace is a name that
|
|
428
|
+
* denotes a module rather than a value, which is what turns `Alias.fn(x)` into a
|
|
429
|
+
* qualified call rather than a method on something.
|
|
430
|
+
*/
|
|
431
|
+
registerNamespace(
|
|
432
|
+
name: string,
|
|
433
|
+
functions: readonly (string | NamespaceFunctionDeclaration)[] = [],
|
|
434
|
+
options: NamespaceOptions = {},
|
|
435
|
+
): this {
|
|
436
|
+
normalizeNamespaces([name]);
|
|
437
|
+
const declared = new Map<string, NamespaceFunction>();
|
|
438
|
+
for (const declaration of functions) {
|
|
439
|
+
const entry: NamespaceFunctionDeclaration =
|
|
440
|
+
typeof declaration === "string" ? { signature: declaration } : declaration;
|
|
441
|
+
const flags = {
|
|
442
|
+
...(entry.deterministic === undefined ? {} : { deterministic: entry.deterministic }),
|
|
443
|
+
...(entry.hostBacked === undefined ? {} : { hostBacked: entry.hostBacked }),
|
|
444
|
+
...(entry.throws === undefined ? {} : { throws: entry.throws }),
|
|
445
|
+
};
|
|
446
|
+
if (entry.signature !== undefined) {
|
|
447
|
+
const signature = parseSignature(entry.signature, this.nominalResolver);
|
|
448
|
+
if (signature.form !== "global") {
|
|
449
|
+
throw new CelTypeRegistrationError(
|
|
450
|
+
`a namespaced function is declared without a receiver: ${JSON.stringify(entry.signature)}`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
declared.set(signature.name, {
|
|
454
|
+
name: signature.name,
|
|
455
|
+
returns: signature.returns,
|
|
456
|
+
parameters: signature.parameters,
|
|
457
|
+
signature: formatSignature(signature),
|
|
458
|
+
...flags,
|
|
459
|
+
});
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
declared.set(entry.name, {
|
|
463
|
+
name: entry.name,
|
|
464
|
+
returns:
|
|
465
|
+
typeof entry.returns === "string"
|
|
466
|
+
? parseTypeExpression(entry.returns, this.nominalResolver)
|
|
467
|
+
: entry.returns,
|
|
468
|
+
...flags,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
this.namespaceFunctions.set(name, declared);
|
|
472
|
+
if (options.open === true) this.openNamespaces.add(name);
|
|
473
|
+
else this.openNamespaces.delete(name);
|
|
474
|
+
this.compiled.clear();
|
|
475
|
+
return this;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Every namespace registered, in canonical order. */
|
|
479
|
+
namespaces(): readonly string[] {
|
|
480
|
+
return [...this.namespaceFunctions.keys()].sort();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// --- reading ------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
/** Reads an expression against this environment's namespaces. */
|
|
486
|
+
parse(source: string): CelExpression {
|
|
487
|
+
return parseExpression(source, {
|
|
488
|
+
namespaces: this.namespaces(),
|
|
489
|
+
optionalSyntax: this.options.enableOptionalTypes,
|
|
490
|
+
...(this.options.limits ? { limits: this.options.limits } : {}),
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Checks an expression. A tree resolved under a different namespace set is **refused**
|
|
496
|
+
* rather than checked: its qualified calls are not the ones this environment would
|
|
497
|
+
* have found, so every answer about it would be about a different expression.
|
|
498
|
+
*/
|
|
499
|
+
check(source: string | CelExpression): CheckResult {
|
|
500
|
+
const expression = typeof source === "string" ? this.parse(source) : source;
|
|
501
|
+
if (typeof source !== "string" && !resolvedUnder(expression, this.namespaces())) {
|
|
502
|
+
throw new CelEngineError(
|
|
503
|
+
"namespaces_mismatch",
|
|
504
|
+
`the expression was resolved under [${expression.namespaces.join(", ")}] and this environment has [${this.namespaces().join(", ")}]`,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
return checkExpression(expression, {
|
|
508
|
+
registry: this.registry,
|
|
509
|
+
variable: (name) => this.variables.get(name)?.type,
|
|
510
|
+
declaredVariableNames: () => [...this.variables.keys()],
|
|
511
|
+
namespaceFunction: (namespace, name) => this.namespaceFunctions.get(namespace)?.get(name),
|
|
512
|
+
namespaceIsOpen: (namespace) => this.openNamespaces.has(namespace),
|
|
513
|
+
options: {
|
|
514
|
+
unlistedVariablesAreDyn: this.options.unlistedVariablesAreDyn,
|
|
515
|
+
homogeneousAggregateLiterals: this.options.homogeneousAggregateLiterals,
|
|
516
|
+
enableOptionalTypes: this.options.enableOptionalTypes,
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Compiles an expression into a program. Nothing is type-checked here — the checker
|
|
523
|
+
* decides every verdict, and a consumer that wants one asks for it — so compiling
|
|
524
|
+
* refuses only what cannot be compiled at all: a source that did not read whole.
|
|
525
|
+
*
|
|
526
|
+
* A compile from text is memoized in a bounded cache; a tree handed over directly is
|
|
527
|
+
* compiled each time, because a tree has no identity to key on.
|
|
528
|
+
*/
|
|
529
|
+
compile(source: string | CelExpression): CelProgram {
|
|
530
|
+
if (typeof source !== "string") return this.compileExpressionNow(source);
|
|
531
|
+
const held = this.compiled.get(source);
|
|
532
|
+
if (held) return held;
|
|
533
|
+
const program = this.compileExpressionNow(this.parse(source));
|
|
534
|
+
this.compiled.set(source, program);
|
|
535
|
+
return program;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Compiles and runs an expression against an activation. */
|
|
539
|
+
evaluate(
|
|
540
|
+
source: string | CelExpression,
|
|
541
|
+
activation?: CelActivation,
|
|
542
|
+
options?: EvaluateOptions,
|
|
543
|
+
): CelValue {
|
|
544
|
+
return this.compile(source).evaluate(activation, options);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// --- the emitter ---------------------------------------------------------
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* The JavaScript module for a set of expressions, emitted now: its text, its key and its
|
|
551
|
+
* integrity header. The same expressions in the same order against the same environment
|
|
552
|
+
* emit byte-identically, so a host may compare two emissions instead of trusting one.
|
|
553
|
+
*
|
|
554
|
+
* The host stores and loads the text; this package touches no filesystem and exports no
|
|
555
|
+
* loader. `emitterRuntime()` is what the loaded module's factory takes, and
|
|
556
|
+
* `programsFromEmittedModule` verifies its header before anything runs.
|
|
557
|
+
*/
|
|
558
|
+
emit(sources: readonly string[]): EmittedModule {
|
|
559
|
+
return emitModule(this.target(), this.digest(), this.expressionsOf(sources));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* The module for a set of expressions, read from the store where it holds a copy whose
|
|
564
|
+
* header matches and emitted and written where it does not — so a corrupted, stale or
|
|
565
|
+
* foreign stored copy causes a recompile rather than a run.
|
|
566
|
+
*/
|
|
567
|
+
emittedModule(sources: readonly string[], store: EmittedModuleStore): StoredEmittedModule {
|
|
568
|
+
// The trees are handed over as a thunk: a hit reads a header and a digest, and parsing
|
|
569
|
+
// every expression to find out whether it needed to is work with no answer attached.
|
|
570
|
+
return storedEmittedModule(
|
|
571
|
+
this.target(),
|
|
572
|
+
this.digest(),
|
|
573
|
+
sources,
|
|
574
|
+
() => this.expressionsOf(sources),
|
|
575
|
+
store,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** The runtime support library an emitted module's factory is handed. */
|
|
580
|
+
emitterRuntime(): EmittedRuntime {
|
|
581
|
+
return emitterRuntime(this.target());
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* This environment's digest: the order-independent hash of its resolved listing, which
|
|
586
|
+
* the key and the header are both over. A host that caches by key never needs it; a host
|
|
587
|
+
* explaining a recompile does.
|
|
588
|
+
*/
|
|
589
|
+
digest(): string {
|
|
590
|
+
return environmentDigest(this);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private expressionsOf(sources: readonly string[]): readonly EmittedExpression[] {
|
|
594
|
+
return sources.map((source) => ({ source, root: this.readyExpression(this.parse(source)).root }));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** What the backends need of this environment, built one way for both of them. */
|
|
598
|
+
private target(): CompileTarget {
|
|
599
|
+
return {
|
|
600
|
+
registry: this.registry,
|
|
601
|
+
constants: this.constants,
|
|
602
|
+
nominalArity: (name) => this.types.get(name)?.parameters.length,
|
|
603
|
+
// The same declarations the checker splits a dotted chain on.
|
|
604
|
+
declares: (name) => this.variables.has(name),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* An expression a backend may compile: read whole, and resolved under this
|
|
610
|
+
* environment's own namespaces. Nothing is type-checked — the checker decides every
|
|
611
|
+
* verdict and a consumer that wants one asks for it.
|
|
612
|
+
*/
|
|
613
|
+
private readyExpression(expression: CelExpression): CelExpression {
|
|
614
|
+
if (expression.diagnostics.length > 0) {
|
|
615
|
+
throw new CelEngineError(
|
|
616
|
+
"unreadable_expression",
|
|
617
|
+
`${JSON.stringify(expression.source)} could not be read: ${expression.diagnostics[0]!.message}`,
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
if (!resolvedUnder(expression, this.namespaces())) {
|
|
621
|
+
throw new CelEngineError(
|
|
622
|
+
"namespaces_mismatch",
|
|
623
|
+
`the expression was resolved under [${expression.namespaces.join(", ")}] and this environment has [${this.namespaces().join(", ")}]`,
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
return expression;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private compileExpressionNow(expression: CelExpression): CelProgram {
|
|
630
|
+
return compileExpression(this.readyExpression(expression), this.target());
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Whether a value of this type can be converted to text by the environment's own
|
|
635
|
+
* `string()`. A consumer that renders a value into text asks this before it tries;
|
|
636
|
+
* `dyn` passes, because only the runtime knows what it holds.
|
|
637
|
+
*/
|
|
638
|
+
convertsToString(type: CelType): boolean {
|
|
639
|
+
if (type.kind === "dyn") return true;
|
|
640
|
+
return "resolved" in this.registry.resolve("string", "global", [type]);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Every registration, for a listing a host prints or compares. */
|
|
644
|
+
definitions(): Definitions {
|
|
645
|
+
return {
|
|
646
|
+
variables: [...this.variables.entries()]
|
|
647
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
648
|
+
.map(([name, held]) => ({
|
|
649
|
+
name,
|
|
650
|
+
type: held.type,
|
|
651
|
+
typeName: formatType(held.type, true),
|
|
652
|
+
constant: held.constant,
|
|
653
|
+
...(held.description === undefined ? {} : { description: held.description }),
|
|
654
|
+
})),
|
|
655
|
+
functions: this.registry.list().map((entry) => ({
|
|
656
|
+
name: entry.signature.name,
|
|
657
|
+
signature: formatSignature(entry.signature),
|
|
658
|
+
receiverType: entry.signature.receiver ? formatType(entry.signature.receiver) : null,
|
|
659
|
+
parameters: entry.signature.parameters.map((type) => formatType(type)),
|
|
660
|
+
returns: formatType(entry.signature.returns),
|
|
661
|
+
deterministic: entry.metadata.deterministic ?? true,
|
|
662
|
+
hostBacked: entry.metadata.hostBacked ?? false,
|
|
663
|
+
...(entry.metadata.throws === undefined ? {} : { throws: entry.metadata.throws }),
|
|
664
|
+
...(entry.metadata.description === undefined ? {} : { description: entry.metadata.description }),
|
|
665
|
+
...(entry.metadata.origin === undefined ? {} : { origin: entry.metadata.origin }),
|
|
666
|
+
})),
|
|
667
|
+
types: [...this.types.values()].map((registered) => ({
|
|
668
|
+
name: registered.definition.name,
|
|
669
|
+
base: formatType(registered.base),
|
|
670
|
+
parameters: registered.parameters,
|
|
671
|
+
...(registered.definition.description === undefined
|
|
672
|
+
? {}
|
|
673
|
+
: { description: registered.definition.description }),
|
|
674
|
+
})),
|
|
675
|
+
namespaces: this.namespaces().map((name) => ({
|
|
676
|
+
name,
|
|
677
|
+
open: this.openNamespaces.has(name),
|
|
678
|
+
// A declaration whose parameter list is withheld has no signature text, so it is
|
|
679
|
+
// listed by what it DOES declare — its name and its result. Printing an invented
|
|
680
|
+
// empty parameter list would read as a function of no arguments.
|
|
681
|
+
functions: [...this.namespaceFunctions.get(name)!.values()].map(
|
|
682
|
+
(held) => held.signature ?? `${held.name}(…): ${formatType(held.returns)}`,
|
|
683
|
+
),
|
|
684
|
+
})),
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// --- types --------------------------------------------------------------
|
|
689
|
+
|
|
690
|
+
private readonly nominalResolver: NominalResolver = (name, args) => {
|
|
691
|
+
const registered = this.types.get(name);
|
|
692
|
+
if (!registered) return undefined;
|
|
693
|
+
if (args.length === 0) return registered.self;
|
|
694
|
+
if (args.length !== registered.parameters.length) {
|
|
695
|
+
throw new CelTypeRegistrationError(
|
|
696
|
+
`${name} takes ${registered.parameters.length} type argument${registered.parameters.length === 1 ? "" : "s"}`,
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
return { ...registered.self, args };
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
private readType(text: string): CelType {
|
|
703
|
+
return parseTypeExpression(text, this.nominalResolver);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
private signatureOf(signature: string | CelSignature): CelSignature {
|
|
707
|
+
return typeof signature === "string" ? parseSignature(signature, this.nominalResolver) : signature;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** The type a declaration names: a type expression, a field map, or a schema. */
|
|
711
|
+
private typeOf(declaration: TypeDeclaration): DeclaredType {
|
|
712
|
+
if (typeof declaration === "string") return { type: this.readType(declaration) };
|
|
713
|
+
if ("kind" in declaration) return { type: declaration };
|
|
714
|
+
if ("fields" in declaration) {
|
|
715
|
+
return { type: fieldMapType(declaration.fields, (text) => this.readType(text)) };
|
|
716
|
+
}
|
|
717
|
+
if ("schema" in declaration) {
|
|
718
|
+
const converted = schemaType(
|
|
719
|
+
{
|
|
720
|
+
node: declaration.schema,
|
|
721
|
+
...(declaration.document === undefined ? {} : { root: declaration.document }),
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
...(this.options.resolveSchemaType ? { resolveSchemaType: this.options.resolveSchemaType } : {}),
|
|
725
|
+
lookupNamedType: (name, args) =>
|
|
726
|
+
this.nominalResolver(
|
|
727
|
+
name,
|
|
728
|
+
args.map((text) => this.readType(text)),
|
|
729
|
+
),
|
|
730
|
+
},
|
|
731
|
+
);
|
|
732
|
+
return {
|
|
733
|
+
type: converted.type,
|
|
734
|
+
...(converted.unjudged.length === 0 ? {} : { unjudged: converted.unjudged }),
|
|
735
|
+
...(converted.recursive.length === 0 ? {} : { recursive: converted.recursive }),
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
throw new CelTypeRegistrationError("a declaration is a type expression, a field map or a schema");
|
|
739
|
+
}
|
|
740
|
+
}
|