@formaui-net/nexo 0.0.0 → 1.0.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +148 -6
  4. package/package.json +23 -3
  5. package/src/common/ArrayUtils.mjs +16 -0
  6. package/src/common/ObjectUtils.mjs +33 -0
  7. package/src/language/DefinitionLocations.mjs +41 -0
  8. package/src/language/DefinitionValidator.mjs +1265 -0
  9. package/src/language/DefinitionWriter.mjs +475 -0
  10. package/src/language/Dictionary.mjs +52 -0
  11. package/src/language/Evaluator.mjs +3480 -0
  12. package/src/language/ImportedVocabulary.mjs +137 -0
  13. package/src/language/Imports.mjs +141 -0
  14. package/src/language/Index.mjs +29 -0
  15. package/src/language/LanguageResolver.mjs +4068 -0
  16. package/src/language/Limits.mjs +78 -0
  17. package/src/language/LineStructure.mjs +385 -0
  18. package/src/language/Lowering.mjs +453 -0
  19. package/src/language/MemberSelector.mjs +89 -0
  20. package/src/language/Names.mjs +65 -0
  21. package/src/language/NexoError.mjs +145 -0
  22. package/src/language/Pair.mjs +21 -0
  23. package/src/language/ParameterBindings.mjs +361 -0
  24. package/src/language/ParsedTexts.mjs +62 -0
  25. package/src/language/Parser.mjs +1817 -0
  26. package/src/language/Range.mjs +51 -0
  27. package/src/language/RecentlyUsed.mjs +50 -0
  28. package/src/language/ResolvedDefinition.mjs +219 -0
  29. package/src/language/ResultPath.mjs +101 -0
  30. package/src/language/ScopeLocals.mjs +102 -0
  31. package/src/language/StructureTypes.mjs +48 -0
  32. package/src/language/SyntaxTree.mjs +203 -0
  33. package/src/language/TextLocations.mjs +185 -0
  34. package/src/language/TextPositions.mjs +23 -0
  35. package/src/language/Tokenizer.mjs +917 -0
  36. package/src/language/Type.mjs +401 -0
  37. package/src/language/TypeChecker.mjs +449 -0
  38. package/src/language/ValueCapture.mjs +315 -0
  39. package/src/language/Versions.mjs +14 -0
  40. package/src/language/vocabulary/Declarations.mjs +64 -0
  41. package/src/language/vocabulary/ItemPositions.mjs +48 -0
  42. package/src/language/vocabulary/ListOrdering.mjs +145 -0
  43. package/src/language/vocabulary/MemberFailure.mjs +21 -0
  44. package/src/language/vocabulary/NexoModule.mjs +1119 -0
  45. package/src/language/vocabulary/NexoRegistry.mjs +1540 -0
  46. package/src/language/vocabulary/TypeNameScope.mjs +63 -0
  47. package/src/language/vocabulary/Vocabulary.mjs +640 -0
  48. package/src/nexo.d.ts +743 -0
  49. package/src/nexo.js +338 -0
@@ -0,0 +1,3480 @@
1
+ /**
2
+ * Evaluator — evaluates a resolved script against its registry for the host that asks, answering detached data: a script's value as it reads now, and for a script that ends in a host call, the call prepared for its host and each value its host supplies projected through it
3
+ */
4
+
5
+ import { ArrayUtils } from '../common/ArrayUtils.mjs';
6
+ import { ObjectUtils } from '../common/ObjectUtils.mjs';
7
+ import { Dictionary } from './Dictionary.mjs';
8
+ import { Index } from './Index.mjs';
9
+ import { NexoErrors } from './NexoError.mjs';
10
+ import { Pair } from './Pair.mjs';
11
+ import { ParameterBindings } from './ParameterBindings.mjs';
12
+ import { Range } from './Range.mjs';
13
+ import { StructureTypes } from './StructureTypes.mjs';
14
+ import { Type } from './Type.mjs';
15
+ import { ValueCapture } from './ValueCapture.mjs';
16
+ import { ItemPositions } from './vocabulary/ItemPositions.mjs';
17
+ import { Limits } from './Limits.mjs';
18
+ import { RecentlyUsed } from './RecentlyUsed.mjs';
19
+ import { NexoModule } from './vocabulary/NexoModule.mjs';
20
+ import { ListOrdering } from './vocabulary/ListOrdering.mjs';
21
+ import { MemberFailure } from './vocabulary/MemberFailure.mjs';
22
+ import { Vocabulary } from './vocabulary/Vocabulary.mjs';
23
+
24
+ /** @typedef {Record<string, unknown>} Environment What the host gives an evaluation that does not belong in the expression, passed to every implementation. */
25
+
26
+ /**
27
+ * What a member's invocation is, as the hooks receive it: the declaration that carries it out, its receiver, its arguments by parameter name, and where it stands in the definition.
28
+ * @typedef {{ declaration: object, receiver: unknown, args: Record<string, unknown>, pointer: string }} InvocationRequest
29
+ */
30
+
31
+ /**
32
+ * How an evaluation ended, as `afterEvaluation` receives it: completed, with its value; pending, where a sample a member read was pending, with its value; or failed, with the failure.
33
+ * @typedef {{ kind: 'completed' | 'pending', value: unknown } | { kind: 'failed', error: unknown }} EvaluationOutcome
34
+ */
35
+
36
+ /**
37
+ * What an evaluation tells of a value it answers, beside the value and never part of it: the origin, a token for the expression that produced it, the same token exactly where one expression of one definition, through one declaration and with one candidate selected, produced two values;
38
+ * the tolerance the producing member declares, applying to every number within the value but a part with a record of its own; the record of each field of an object an object expression built and of each item of a list a projection built;
39
+ * the input each item of a list a projection built came from, a list no projection built having no sources; and, beside them, each item's provenance, the source the earliest projection it descends from recorded for it.
40
+ * A part with no record inherits its parent's origin and tolerance.
41
+ * @typedef {{ origin: object, tolerance: number | null, objectFields?: Record<string, ResultMetadata>, items?: ResultMetadata[], sources?: readonly ProjectionSource[], provenance?: readonly ProjectionSource[] }} ResultMetadata
42
+ */
43
+
44
+ /**
45
+ * The input a projected item came from, of the kind its established type gives it: a reference value as the host holds it, data as Nexo's own frozen copy, or null.
46
+ * @typedef {{ kind: 'reference', value: unknown } | { kind: 'data', value: unknown } | { kind: 'null' }} ProjectionSource
47
+ */
48
+
49
+ /**
50
+ * The hooks a host gives an evaluation, each optional.
51
+ * @typedef {object} EvaluationHooks
52
+ * @property {(request: InvocationRequest) => unknown[] | { pending: true } | undefined} [beforeInvoke] Called before a member's implementation is invoked: answers the samples of a member that reads maintained values, that one is pending, or nothing.
53
+ * @property {(request: InvocationRequest, value: unknown) => void} [afterInvoke] Called after an implementation answered a value of its member's result type.
54
+ * @property {(outcome: EvaluationOutcome) => void} [afterEvaluation] Called once, as the evaluation ends.
55
+ */
56
+
57
+ /**
58
+ * The names one scope declares, as it runs, inside the frame of the scope enclosing it: an application of an object expression, whose locals are evaluated at their first use, a run of a body, whose locals it evaluates in order,
59
+ * a call of a local function, its parameters holding its arguments, or a field the fields after it read, holding its value.
60
+ * @typedef {object} LocalsFrame
61
+ * @property {readonly string[]} names The names the scope declares, never evaluating anything to find them: a body's or an object expression's locals, a call's parameters, or the one name of a binding, an option or a field.
62
+ * @property {{ locals: { name: string, value: object }[] }} [node] An object expression's frame alone: the node whose locals are evaluated at their first use, which a read finds the initializer in. Every other frame holds each
63
+ * name's value from the start, so it has none.
64
+ * @property {unknown} context The implicit parameter the scope runs against, which a local evaluated at its first use is evaluated against.
65
+ * @property {unknown[]} values Each name's value evaluated so far, at the name's place among the names, a call's own arguments as they were given; undefined where an object expression's local is yet to be evaluated.
66
+ * @property {readonly (Map<object, object | string> | null | undefined)[]} selections The selections each name's evaluation made, at the same place, undefined or null where it made none.
67
+ * @property {LocalsFrame | null} enclosing The frame of the scope enclosing this one, or null for the outermost.
68
+ */
69
+
70
+ /** The selections of a frame whose names' values made none, as a call's parameters and a binding's name. */
71
+ const NO_SELECTIONS = Object.freeze([]);
72
+
73
+ /** The arguments of a call that takes none. */
74
+ const NO_ARGUMENTS = Object.freeze({});
75
+
76
+ /** The kinds of expression whose value is the value of one of the expressions it holds, which it selected as it was evaluated. */
77
+ const SELECTING_KINDS = new Set(['conditional', 'coalescing', 'and', 'or']);
78
+
79
+ /** How many of the records an object expression gave last it keeps for objects whose fields give the same records to share. */
80
+ const RECENT_FIELD_RECORDS = 4;
81
+
82
+ /** The most selections a deferred access keeps, of the actual types it met most recently, so a value of Any meeting objects of ever new shapes keeps a bounded number. */
83
+ const MAX_KEPT_SELECTIONS = 32;
84
+
85
+ /** The samples of a call that reads none. */
86
+ const NO_SAMPLES = Object.freeze([]);
87
+
88
+ /** What a message's description marks a number no finite number holds with, as a string JSON writes escaped. */
89
+ const NON_FINITE_MARKER = '\u0000non-finite\u0000';
90
+
91
+ /** A marked number as JSON writes it, quoted and escaped, which the description writes as its word. */
92
+ const NON_FINITE_WRITTEN = /"\\u0000non-finite\\u0000(-?Infinity|NaN)"/g;
93
+
94
+ /** What each arithmetic operator answers for its operands: `%` the remainder with the sign of the dividend, as `-5 % 3` is `-2`. */
95
+ const ARITHMETIC = Object.freeze({
96
+ '+': (a, b) => a + b,
97
+ '-': (a, b) => a - b,
98
+ '*': (a, b) => a * b,
99
+ '/': (a, b) => a / b,
100
+ '%': (a, b) => a % b,
101
+ });
102
+
103
+ /** What each comparison operator answers for its operands. */
104
+ const COMPARISONS = Object.freeze({
105
+ '<': (a, b) => a < b,
106
+ '<=': (a, b) => a <= b,
107
+ '>': (a, b) => a > b,
108
+ '>=': (a, b) => a >= b,
109
+ });
110
+
111
+ /** What an evaluation answers, frozen: its value and whether a member it read was pending, and the metadata that describes the value, described from the identities its evaluation left where it is first read. */
112
+ class Evaluation {
113
+ /** The function that creates the metadata at its first read, null once it has. */
114
+ #createMetadata;
115
+ #metadata;
116
+
117
+ constructor(value, isPending, createMetadata) {
118
+ this.value = value;
119
+ this.isPending = isPending;
120
+ this.#createMetadata = createMetadata;
121
+ Object.freeze(this);
122
+ }
123
+
124
+ /** The metadata of the value, frozen, the same each time it is read. */
125
+ get metadata() {
126
+ if (this.#createMetadata !== null) {
127
+ this.#metadata = this.#createMetadata();
128
+ this.#createMetadata = null;
129
+ }
130
+
131
+ return this.#metadata;
132
+ }
133
+ }
134
+
135
+ export class Evaluator {
136
+ /**
137
+ * The function each node of a resolved definition compiles to, by the definition and then the node, with the registry they were compiled against: built once, it holds what the node's evaluation needs that no evaluation changes, and takes the evaluator, which holds what one does, and the implicit parameter.
138
+ * Beside them, the function each member's arguments compile to, by the member's resolution, for the evaluations that read the member without compiling it.
139
+ * @type {WeakMap<object, { registry: object, functions: Map<object, (evaluator: Evaluator, context: unknown) => unknown>, arguments: Map<object, (evaluator: Evaluator, context: unknown) => object | null> }>}
140
+ */
141
+ static #compilationCache = new WeakMap();
142
+
143
+ /**
144
+ * The record of each expression that produces a value itself, holding its origin token, indexed resolved definition → expression node → selected candidate, the name a computed property access selected or the declaration a deferred access selected, or null → record. Keyed weakly by the resolved definition, a record
145
+ * lives exactly as long as its resolution, and every evaluation of the resolution shares it. @type {WeakMap<object, Map<object, Map<string | object | null, object>>>}
146
+ */
147
+ static #expressionRecordCache = new WeakMap();
148
+
149
+ /**
150
+ * The origin token of each expression of a definition, indexed definition → pointer → declaration its resolution selects there → selected candidate, or null → token, so every resolution of one definition, one made again
151
+ * after a registration made the first stale among them, gives the values one expression produces through one declaration the same origin. Keyed weakly by the definition and by the declaration, a token lives as long
152
+ * as both. @type {WeakMap<object, Map<string, { byDeclaration: WeakMap<object, Map<string | object | null, object>>, undeclared: Map<string | object | null, object> }>>}
153
+ */
154
+ static #originCache = new WeakMap();
155
+
156
+ /** The record a part with none of its own takes, indexed whole record → inherited record, made once for each whole and released with it. @type {WeakMap<object, object>} */
157
+ static #inheritedRecordCache = new WeakMap();
158
+
159
+
160
+ /**
161
+ * The evaluator that compiles this evaluator's nodes, an evaluator of the compilation's own, which holds empty bindings and an empty environment, of the kinds an evaluator holds, so the functions it compiles, which the compilation keeps for every
162
+ * evaluator of its resolved definition, keep no host's values alive with it. It compiles for itself.
163
+ * @type {Evaluator}
164
+ */
165
+ #compiler;
166
+
167
+ /** The functions this evaluator's nodes compile to. @type {Map<object, (evaluator: Evaluator, context: unknown) => unknown>} */
168
+ #compiledFunctions;
169
+
170
+ /**
171
+ * The records each object expression gave last in the metadata being described, the latest first, with the implicit parameter's record and the records of the fields each was made of, so an object whose fields give
172
+ * the same records shares one, as items that alternate between branches do. It lasts as long as one description of a value's metadata, so a record, and a host's value a source in it holds, outlives neither.
173
+ * @type {WeakMap<object, { contextRecord: object | null, fields: object[], record: object }[]> | null}
174
+ */
175
+ #recentFieldRecords = null;
176
+
177
+ /** The functions the arguments of this evaluator's members compile to, by each member's resolution. @type {Map<object, (evaluator: Evaluator, context: unknown) => object | null>} */
178
+ #compiledArguments;
179
+
180
+ /** The compiled invocations of the calls resolution selected, by each declaration and then by the text of each result type it answers, and whether its answer is relied on; a deferred access keeps its own. @type {Map<object, Map<string, object>>} */
181
+ #compiledInvocations;
182
+
183
+ /** Whether the result's type holds a value of Any, which is data or not as it is evaluated, so each result is checked to be data. */
184
+ #checksResultData;
185
+
186
+ #registry;
187
+
188
+ /** Captures a value its host supplies as the type its call declares. */
189
+ #valueCapture;
190
+
191
+ /** The limits its host set, which `sequence` and `concat` are given as they are invoked. @type {{ items: number | null }} */
192
+ #limits;
193
+
194
+ #resolvedDefinition;
195
+ #parameterBindings;
196
+ #environment;
197
+ #locations;
198
+
199
+ /** The source of each item of a list an object expression projected, by the list it produced, for the evaluation in progress. @type {WeakMap<unknown[], readonly ProjectionSource[]>} */
200
+ #sourcesByList = new WeakMap();
201
+
202
+ /**
203
+ * The provenance of each item of a list an object expression projected, by the list it produced, for the evaluation in progress: the source the earliest projection the item descends from recorded for it, its own
204
+ * source where the list it projected carries none. @type {WeakMap<unknown[], readonly ProjectionSource[]>}
205
+ */
206
+ #provenanceByList = new WeakMap();
207
+
208
+ /**
209
+ * The selections each field of an object made, as `#selections` holds them, by the object and then the field's name: every completed field of an outermost object, which a read through a reference to it reuses as it is built, and
210
+ * each field that made any of another object an object expression built. Each object is built anew by the evaluation that reads it, so a store is made for each evaluation, and for each prepared host call, whose projections
211
+ * share it, since they read the objects its locals built. A store is released with the evaluation, or with its prepared call; one an evaluator kept for good would grow with every evaluation's objects until a collection,
212
+ * and recording in it measured a tenth of the account summary's evaluation.
213
+ * Each object holds a list of each field's name beside its selections, as `#select` records them, a field that made none left out, which reads as one recorded with none.
214
+ * @type {WeakMap<object, unknown[]>}
215
+ */
216
+ #objectFieldSelections = new WeakMap();
217
+
218
+ /**
219
+ * The selections made for the value being evaluated at its place in the result, by the node that made each: the branch a conditional or a coalescing selected, as its node, the name a bracket property access selected, and the declaration a deferred access selected; null where none has been made.
220
+ * A result's identities hold them as their `selections`.
221
+ * Each node stands beside its selection in one list, as `#select` records them.
222
+ * @type {unknown[] | null}
223
+ */
224
+ #selections = null;
225
+
226
+ /** The frame of the innermost scope that declares names, with the value of each evaluated so far, inside those of the scopes enclosing it; null outside every one. @type {LocalsFrame | null} */
227
+ #localsFrame = null;
228
+
229
+ /** The outermost object being built, which `root`, the object root, refers to, filled field by field; null outside every object expression. @type {object | null} */
230
+ #objectRoot = null;
231
+
232
+ /** The hooks of the evaluation in progress, or null where it was given none. @type {EvaluationHooks | null} */
233
+ #hooks = null;
234
+
235
+ /**
236
+ * The reads of the evaluation in progress whose samples its host answered pending, each request beside the node that made it, in one flat list, or null where none was: kept only where a read is pending, so an evaluation
237
+ * that reads none allocates nothing for them, and described into its metadata's pending reads only where a host reads that metadata.
238
+ * @type {unknown[] | null}
239
+ */
240
+ #pendingReads = null;
241
+
242
+ /**
243
+ * The host call's node and the value its host supplied, while the call's result projection is evaluated against it.
244
+ * The value is what the implicit parameter stands for, and the call's node answers it; null outside a projection.
245
+ * @type {{ node: object, value: unknown } | null}
246
+ */
247
+ #projectionContext = null;
248
+
249
+ /**
250
+ * The deferred access whose member last answered null, which a deferred access it is the receiver of reads, and clears, to tell that null from one an optional access before it met; null where none is to be read.
251
+ * @type {object | null}
252
+ */
253
+ #nullAnswer = null;
254
+
255
+ /**
256
+ * @param {import('./vocabulary/NexoRegistry.mjs').NexoRegistry} registry The vocabulary the request was resolved against.
257
+ * @param {import('./ResolvedDefinition.mjs').ResolvedDefinition} resolvedDefinition The resolved definition to read.
258
+ * @param {import('./ParameterBindings.mjs').ParameterBindings} parameterBindings What the request's parameters are bound to.
259
+ * @param {Environment} environment What the host gives the evaluation, passed to every implementation.
260
+ * @param {import('./TextLocations.mjs').TextLocations | null} locations Where in a text each part of the definition came from.
261
+ * @param {{ items: number | null }} [limits] The limits its host sets on the lists Nexo's own members make, which each of them reads as it is invoked.
262
+ */
263
+ constructor(registry, resolvedDefinition, parameterBindings, environment, locations, limits = Limits.none) {
264
+ this.#registry = registry;
265
+ this.#limits = limits;
266
+ this.#valueCapture = new ValueCapture(registry);
267
+ this.#resolvedDefinition = resolvedDefinition;
268
+ this.#parameterBindings = parameterBindings;
269
+ this.#environment = environment;
270
+ this.#locations = locations;
271
+
272
+ let compilation = Evaluator.#compilationCache.get(resolvedDefinition);
273
+
274
+ if (compilation === undefined || compilation.registry !== registry) {
275
+ const { type } = resolvedDefinition;
276
+
277
+ compilation = { registry, functions: new Map(), arguments: new Map(), invocations: new Map(), checksResultData: type !== null && !registry.isData(type), compiler: null };
278
+ Evaluator.#compilationCache.set(resolvedDefinition, compilation);
279
+ // The compiler is made once the compilation is kept, so it finds the compilation and compiles for itself.
280
+ compilation.compiler = new Evaluator(registry, resolvedDefinition, new ParameterBindings(registry), {}, locations);
281
+ }
282
+
283
+ this.#compiler = compilation.compiler ?? this;
284
+
285
+ this.#compiledFunctions = compilation.functions;
286
+ this.#compiledArguments = compilation.arguments;
287
+ this.#compiledInvocations = compilation.invocations;
288
+ this.#checksResultData = compilation.checksResultData;
289
+ }
290
+
291
+ /**
292
+ * Evaluates the expression once, synchronously, with the hooks given, answering the detached result, the metadata that describes it, and whether a member it read was pending.
293
+ * Without hooks, or without a `beforeInvoke`, a member that reads maintained values fails the evaluation, since nothing answers its samples.
294
+ * @param {EvaluationHooks | null} [hooks] The hooks its host gives the evaluation.
295
+ */
296
+ evaluate(hooks = null) {
297
+ this.#objectFieldSelections = new WeakMap();
298
+
299
+ return this.#evaluateForHost(hooks, () => this.#evaluateScript());
300
+ }
301
+
302
+ /**
303
+ * Prepares the host call the script ends in for its host: evaluates the locals before it, each body's along the script's terminal position in order, then its receiver and its arguments, once, without hooks, as `evaluate` given none evaluates a script.
304
+ * It answers a prepared call, with its declaration, its receiver, its arguments by parameter name and its definition pointer, as an invocation request names its call, whose `project` projects each value its host supplies where the call declares a result; or a skipped call where the
305
+ * receiver, or an argument whose parameter propagates null, is null, or the call has no declaration yet, its receiver's type unknown, so there is nothing to invoke. Each prepared call keeps the locals it was prepared with, so the prepared
306
+ * calls of one evaluator stay apart. A function argument is a kept function its host calls as long as it keeps the call, which `dispose` ends.
307
+ * @returns {{ kind: 'prepared', declaration: object, receiver: unknown, args: Record<string, unknown>, pointer: string, isDeferred: false, dispose: () => void, project?: (value: unknown, hooks?: EvaluationHooks | null) => Evaluation } | { kind: 'skipped', pointer: string }}
308
+ */
309
+ prepareHostCall() {
310
+ const { hostCall } = this.#resolvedDefinition;
311
+
312
+ if (hostCall === null) {
313
+ this.#fail('NEXO5014', 'The script gives a value and ends in no host call, so there is nothing to prepare; evaluate it', '/expression');
314
+ }
315
+
316
+ const { node, pointer, declaration } = hostCall;
317
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
318
+ const objectFieldSelections = new WeakMap();
319
+ // The lineage of the lists the locals before the call make, which each projection reads as an evaluation reads its own, and adds the lineage of the lists it makes to, each released with its list.
320
+ const sourcesByList = new WeakMap();
321
+ const provenanceByList = new WeakMap();
322
+
323
+ this.#objectFieldSelections = objectFieldSelections;
324
+ this.#sourcesByList = sourcesByList;
325
+ this.#provenanceByList = provenanceByList;
326
+
327
+ const { terminalNode, localsFrame } = this.#evaluateLocalsBefore();
328
+ // A qualified form's receiver is its first argument, and a module member has none, so nothing it lacks skips it.
329
+ const takesReceiver = declaration === null || this.#registry.takesReceiver(declaration);
330
+ const enclosingSelections = this.#selections;
331
+ let receiver;
332
+ let args;
333
+
334
+ // The receiver and the arguments are evaluated within the locals' frame, and no result records the selections they make.
335
+ let kept;
336
+
337
+ try {
338
+ ({ receiver, args } = this.#evaluateWithin(localsFrame, this.#objectRoot, evaluator => {
339
+ const evaluatedReceiver = declaration === null ? null : takesReceiver ? evaluator.#evaluateNode(resolution.receiverNode, null) : undefined;
340
+
341
+ return { receiver: evaluatedReceiver, args: evaluatedReceiver === null ? null : evaluator.#evaluateArguments(resolution, null) };
342
+ }, null));
343
+ } finally {
344
+ this.#selections = enclosingSelections;
345
+
346
+ // The lifetimes of the functions the call's arguments gave its host, which no invocation closes: the host keeps them until it disposes the call.
347
+ kept = this.#givenLifetimes ?? [];
348
+ this.#givenLifetimes = null;
349
+ }
350
+
351
+ if (args === null) {
352
+ return Object.freeze({ kind: 'skipped', pointer });
353
+ }
354
+
355
+ for (const lifetime of kept) {
356
+ lifetime.isKept = true;
357
+ }
358
+
359
+ const dispose = () => {
360
+ for (const lifetime of kept) {
361
+ Evaluator.#closeLifetime(lifetime);
362
+ }
363
+ };
364
+
365
+ // Resolution selects every host call, so none is deferred.
366
+ const call = takesReceiver ? { kind: 'prepared', declaration, receiver, args, pointer, isDeferred: false, dispose } : { kind: 'prepared', declaration, args, pointer, isDeferred: false, dispose };
367
+
368
+ // A call whose host supplies nothing has nothing to project.
369
+ if (declaration.result !== undefined) {
370
+ call.project = (value, hooks = null) => {
371
+ this.#objectFieldSelections = objectFieldSelections;
372
+ this.#sourcesByList = sourcesByList;
373
+ this.#provenanceByList = provenanceByList;
374
+
375
+ return this.#evaluateForHost(hooks, () => this.#project(localsFrame, terminalNode, value));
376
+ };
377
+ }
378
+
379
+ return Object.freeze(call);
380
+ }
381
+
382
+ /**
383
+ * Evaluates the locals of each body along the script's terminal position into a frame of its own, in order, as a run of each body does, each local's value and the selections it made kept in its entry of the frame. It answers
384
+ * the node the terminal position holds after its bodies, the result projection, the optional chain or the host call itself, as `terminalNode`, and the innermost frame as `localsFrame`; the frame in scope and the selections
385
+ * are restored as they were, whether it completes or fails.
386
+ * @returns {{ terminalNode: object, localsFrame: LocalsFrame | null }}
387
+ */
388
+ #evaluateLocalsBefore() {
389
+ const enclosingLocalsFrame = this.#localsFrame;
390
+ const enclosingSelections = this.#selections;
391
+ let node = this.#resolvedDefinition.definition.expression;
392
+
393
+ try {
394
+ while (node.kind === 'body' || node.kind === 'do') {
395
+ if (node.kind === 'body') {
396
+ const frame = { names: node.locals.map(local => local.name), context: null, values: [], selections: [], enclosing: this.#localsFrame };
397
+
398
+ this.#localsFrame = frame;
399
+
400
+ for (const local of node.locals) {
401
+ this.#selections = null;
402
+ frame.values.push(this.#getCompiledFunction(local.value)(this, null));
403
+ frame.selections.push(this.#selections);
404
+ }
405
+ }
406
+
407
+ node = node.kind === 'body' ? node.result : node.body;
408
+ }
409
+
410
+ return { terminalNode: node, localsFrame: this.#localsFrame };
411
+ } finally {
412
+ this.#localsFrame = enclosingLocalsFrame;
413
+ this.#selections = enclosingSelections;
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Projects one value the host supplied for a prepared call: captures it as the type the call declares, then evaluates the terminal node against it as an evaluation of its own, the result projection or the call itself, which
419
+ * answers the captured value, within the frame of the locals the call was prepared with, which it reads and never evaluates again.
420
+ */
421
+ #project(frame, terminalNode, value) {
422
+ const { node, pointer, declaration } = this.#resolvedDefinition.hostCall;
423
+ const { type: resultType } = this.#resolvedDefinition.resolutionOf(node);
424
+
425
+ // A part of the value is named by its path within it, the value itself by none.
426
+ const fail = (path, part, partType, reason) => {
427
+ if (reason === 'cycle') {
428
+ this.#fail('NEXO5015', `The host call '${NexoModule.writtenNameOf(declaration)}' supplied a value that reaches itself${path === '' ? '' : ` at '${path}'`}, which data never does`, pointer);
429
+ }
430
+
431
+ if (reason === 'duplicate') {
432
+ this.#fail('NEXO5015', `The host call '${NexoModule.writtenNameOf(declaration)}' supplied a dictionary that holds a key twice, at '${path}', which a dictionary never does`, pointer);
433
+ }
434
+
435
+ if (reason === 'depth') {
436
+ this.#fail('NEXO5015', `The host call '${NexoModule.writtenNameOf(declaration)}' supplied a value nesting deeper than ${Limits.dataNesting} levels, the most data nests${path === '' ? '' : `, at '${path}'`}`, pointer);
437
+ }
438
+
439
+ const supplied = path === '' ? Evaluator.#describe(part) : part === undefined ? `a value without '${path}'` : `a value whose '${path}' is ${Evaluator.#describe(part)}`;
440
+
441
+ this.#fail('NEXO5015', `The host call '${NexoModule.writtenNameOf(declaration)}' supplied ${supplied}, and ${path === '' ? 'it declares' : `'${path}' is declared`} ${partType}`, pointer);
442
+ };
443
+
444
+ const captured = this.#valueCapture.capture(value, resultType, fail);
445
+ const enclosingLocalsFrame = this.#localsFrame;
446
+
447
+ this.#localsFrame = frame;
448
+ this.#projectionContext = { node, value: captured };
449
+
450
+ try {
451
+ return this.#evaluateResult(terminalNode);
452
+ } finally {
453
+ this.#projectionContext = null;
454
+ this.#localsFrame = enclosingLocalsFrame;
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Evaluates once for the host, as `evaluate` and a prepared call's `project` each ask: holds the hooks for the invocations the evaluation reaches and clears the pending mark, answers what the evaluation produced, whether
460
+ * a member it read was pending and the metadata, described when it is first read, and calls its `afterEvaluation` hook once as it ends: a failure of that hook after a failure is recorded on the failure as its `suppressed`,
461
+ * which stays the one thrown, and after a success fails the evaluation itself.
462
+ */
463
+ #evaluateForHost(hooks, produce) {
464
+ this.#hooks = hooks;
465
+ this.#pendingReads = null;
466
+ let outcome = null;
467
+
468
+ // The outcome is made only where an `afterEvaluation` hook is told it, and the metadata only where it is read.
469
+ const isTold = hooks?.afterEvaluation !== undefined;
470
+
471
+ try {
472
+ const result = produce();
473
+ const pendingReads = this.#pendingReads;
474
+ const isPending = pendingReads !== null;
475
+
476
+ if (isTold) {
477
+ outcome = { kind: isPending ? 'pending' : 'completed', value: result.value };
478
+ }
479
+
480
+ return new Evaluation(result.value, isPending, Evaluator.#describerOf(this.#compiler, result, pendingReads));
481
+ } catch (error) {
482
+ if (isTold) {
483
+ outcome = { kind: 'failed', error };
484
+ }
485
+
486
+ throw error;
487
+ } finally {
488
+ // The reads the result holds are its own, so the evaluator keeps none past the evaluation.
489
+ this.#hooks = null;
490
+ this.#pendingReads = null;
491
+
492
+ if (isTold) {
493
+ try {
494
+ hooks.afterEvaluation(Object.freeze(outcome));
495
+ } catch (cleanupError) {
496
+ if (outcome.kind !== 'failed') {
497
+ // A failure as the evaluation ends fails it, as a hook's failure does: one Nexo raised as it is, and anything else the hook threw, a NexoError it built among them, as the cause of Nexo's own.
498
+ // The finally block's throw replaces the completed result.
499
+ if (NexoErrors.isRaisedByNexo(cleanupError)) {
500
+ throw cleanupError;
501
+ }
502
+
503
+ this.#fail('NEXO5009', `The host failed after the evaluation: ${Evaluator.#messageOf(cleanupError)}`, '/expression', { cause: cleanupError });
504
+ }
505
+
506
+ try {
507
+ Object.defineProperty(outcome.error, 'suppressed', { value: cleanupError, configurable: true });
508
+ } catch {
509
+ // A failure that cannot record it keeps its own.
510
+ }
511
+ }
512
+ }
513
+ }
514
+ }
515
+
516
+ /** The detached result, and the metadata that describes it, which the result itself never holds. */
517
+ #evaluateScript() {
518
+ const { hostCall } = this.#resolvedDefinition;
519
+
520
+ if (hostCall !== null) {
521
+ this.#fail('NEXO5023', `The script ends in the host call '${hostCall.name}', which its host invokes, so it is never evaluated; prepare it`, hostCall.pointer);
522
+ }
523
+
524
+ return this.#evaluateResult(this.#resolvedDefinition.definition.expression);
525
+ }
526
+
527
+ #evaluateNode(node, context) {
528
+ return this.#getCompiledFunction(node)(this, context);
529
+ }
530
+
531
+ /** The function the node compiles to, built the first time it is asked for and kept for every evaluation of its resolved definition. */
532
+ #getCompiledFunction(node) {
533
+ let compiledFunction = this.#compiledFunctions.get(node);
534
+
535
+ if (compiledFunction === undefined) {
536
+ compiledFunction = this.#compiler.#compileNode(node);
537
+ this.#compiledFunctions.set(node, compiledFunction);
538
+ }
539
+
540
+ return compiledFunction;
541
+ }
542
+
543
+ /** The node as a function of the evaluator and the implicit parameter, each kind evaluated as its section of the specification sets out, the nodes it holds compiled with it. */
544
+ #compileNode(node) {
545
+ switch (node.kind) {
546
+ case 'literal': {
547
+ const { value } = node;
548
+
549
+ return () => value;
550
+ }
551
+ case 'scriptParameter':
552
+ return evaluator => evaluator.#parameterBindings.readValue(node.name);
553
+ case 'root':
554
+ return evaluator => evaluator.#objectRoot;
555
+ case 'it':
556
+ return (evaluator, context) => context;
557
+ case 'local':
558
+ return evaluator => evaluator.#evaluateLocal(node);
559
+ case 'memberAccess':
560
+ case 'indexerAccess':
561
+ return this.#compileMember(node);
562
+ case 'equality': {
563
+ const left = this.#getCompiledFunction(node.left);
564
+ const right = this.#getCompiledFunction(node.right);
565
+
566
+ if (this.#resolvedDefinition.resolutionOf(node)?.isChecked === true) {
567
+ return (evaluator, context) => {
568
+ const leftValue = left(evaluator, context);
569
+ const rightValue = right(evaluator, context);
570
+
571
+ evaluator.#requireComparable(node, leftValue, rightValue);
572
+
573
+ return leftValue === rightValue;
574
+ };
575
+ }
576
+
577
+ return (evaluator, context) => {
578
+ const value = left(evaluator, context);
579
+
580
+ return value === right(evaluator, context);
581
+ };
582
+ }
583
+ case 'conditional': {
584
+ const consequent = this.#getCompiledFunction(node.consequent);
585
+ const alternative = this.#getCompiledFunction(node.alternative);
586
+
587
+ if (node.binding !== undefined) {
588
+ const evaluateBindingValue = this.#getCompiledFunction(node.binding.value);
589
+ const bindingNames = [node.binding.name];
590
+
591
+ // The value is evaluated once; where it is truthy, the consequent runs in a frame holding it by the name it is bound to, which a function made within it keeps as it keeps every frame around it, and the branch
592
+ // taken is recorded for the value's place in the result.
593
+ return (evaluator, context) => {
594
+ const value = evaluateBindingValue(evaluator, context);
595
+ const isTruthy = Evaluator.#isTruthy(value);
596
+
597
+ evaluator.#selections = Evaluator.#select(evaluator.#selections, node, isTruthy ? node.consequent : node.alternative);
598
+
599
+ if (!isTruthy) {
600
+ return alternative(evaluator, context);
601
+ }
602
+
603
+ const enclosingLocalsFrame = evaluator.#localsFrame;
604
+ const { name } = node.binding;
605
+
606
+ evaluator.#localsFrame = { names: bindingNames, context, values: [value], selections: NO_SELECTIONS, enclosing: enclosingLocalsFrame };
607
+
608
+ try {
609
+ return consequent(evaluator, context);
610
+ } finally {
611
+ evaluator.#localsFrame = enclosingLocalsFrame;
612
+ }
613
+ };
614
+ }
615
+
616
+ const condition = this.#getCompiledFunction(node.condition);
617
+
618
+ // The branch the condition selects is recorded for the value's place in the result.
619
+ return (evaluator, context) => {
620
+ const isTrue = Evaluator.#isTruthy(condition(evaluator, context));
621
+
622
+ evaluator.#selections = Evaluator.#select(evaluator.#selections, node, isTrue ? node.consequent : node.alternative);
623
+
624
+ return (isTrue ? consequent : alternative)(evaluator, context);
625
+ };
626
+ }
627
+ case 'optionalChain':
628
+ // An access that meets null answers null, and so does everything after it in its chain, which is evaluated no further.
629
+ return this.#getCompiledFunction(node.chain);
630
+ case 'coalescing': {
631
+ const value = this.#getCompiledFunction(node.value);
632
+ const fallback = this.#getCompiledFunction(node.fallback);
633
+
634
+ return (evaluator, context) => {
635
+ const answer = value(evaluator, context);
636
+
637
+ evaluator.#selections = Evaluator.#select(evaluator.#selections, node, answer === null ? node.fallback : node.value);
638
+
639
+ return answer === null ? fallback(evaluator, context) : answer;
640
+ };
641
+ }
642
+ case 'and':
643
+ case 'or': {
644
+ const operands = node.operands.map(operand => this.#getCompiledFunction(operand));
645
+ const decidesWhenTruthy = node.kind === 'or';
646
+ const last = operands.length - 1;
647
+
648
+ // The first operand that decides it, falsey for `and` and truthy for `or`, or else the last, is its value as it is, and the operand it is is recorded for the value's place in the result.
649
+ return (evaluator, context) => {
650
+ for (let index = 0; ; index++) {
651
+ const value = operands[index](evaluator, context);
652
+
653
+ if (index === last || Evaluator.#isTruthy(value) === decidesWhenTruthy) {
654
+ evaluator.#selections = Evaluator.#select(evaluator.#selections, node, node.operands[index]);
655
+
656
+ return value;
657
+ }
658
+ }
659
+ };
660
+ }
661
+ case 'not': {
662
+ const operand = this.#getCompiledFunction(node.operand);
663
+
664
+ return (evaluator, context) => !Evaluator.#isTruthy(operand(evaluator, context));
665
+ }
666
+ case 'function':
667
+ // A function evaluates to a reference to a script function, which holds its captured scope: all three of the locals' frame, the implicit parameter and the object root where it is declared, which each call evaluates its body in.
668
+ return (evaluator, context) => ({ function: node, capturedFrame: evaluator.#localsFrame, context, objectRoot: evaluator.#objectRoot });
669
+ case 'call': {
670
+ // A call of a member is compiled as any member is; a call of the function a name holds reads the local its callee names.
671
+ if (node.callee.kind !== 'local') {
672
+ return this.#compileMember(node);
673
+ }
674
+
675
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
676
+
677
+ // A call of one a reference to a module's function alone is its qualified form's call, and the reference it is reads nothing; a receiver of unknown type selects no declaration, as the qualified form's selects none.
678
+ if (resolution.functions === undefined) {
679
+ return resolution.candidates !== undefined ? this.#compileOnUnknownType(resolution) : this.#compileInvocation(node, resolution);
680
+ }
681
+
682
+ const functions = this.#compileFunctions(resolution.functions);
683
+ const evaluateArguments = node.arguments.map(argument => this.#getCompiledFunction(argument));
684
+
685
+ // The callee is evaluated once, and then the arguments, once each, left to right, in the caller's scope, and the body of the function it is in its captured scope, so no local of the caller's is visible in it;
686
+ // where it may be one of several, the one it is is recorded for the value's place in the result. The receiver argument of a reference to a module's function is evaluated first, and a null one answers null before the others are;
687
+ // a reference bound to its receiver captured it, so every argument the call writes is one of its own.
688
+ return (evaluator, context) => {
689
+ const callee = evaluator.#evaluateLocal(resolution.callee);
690
+ const functionKey = Evaluator.#getFunctionKey(callee);
691
+
692
+ if (functions.size > 1) {
693
+ evaluator.#selections = Evaluator.#select(evaluator.#selections, node, functionKey);
694
+ }
695
+
696
+ if (callee.isBound !== true && functions.get(functionKey).invocation?.takesReceiver === true) {
697
+ const receiver = evaluateArguments[0](evaluator, context);
698
+
699
+ if (receiver === null) {
700
+ return null;
701
+ }
702
+
703
+ return evaluator.#callFunction(callee, [receiver, ...evaluateArguments.slice(1).map(evaluateArgument => evaluateArgument(evaluator, context))], functions, node);
704
+ }
705
+
706
+ return evaluator.#callFunction(callee, evaluateArguments.map(evaluateArgument => evaluateArgument(evaluator, context)), functions, node);
707
+ };
708
+ }
709
+ case 'do':
710
+ return this.#getCompiledFunction(node.body);
711
+ case 'body': {
712
+ const locals = node.locals.map(local => ({ name: local.name, evaluate: this.#getCompiledFunction(local.value) }));
713
+ const localNames = locals.map(local => local.name);
714
+ const result = this.#getCompiledFunction(node.result);
715
+
716
+ // Each local is evaluated once, in order, whenever the body runs, an unused one included, each seeing those before it, and then the result, all against the body's implicit parameter.
717
+ return (evaluator, context) => {
718
+ const frame = { names: localNames, context, values: [], selections: [], enclosing: evaluator.#localsFrame };
719
+ const enclosingSelections = evaluator.#selections;
720
+
721
+ evaluator.#localsFrame = frame;
722
+
723
+ try {
724
+ for (const local of locals) {
725
+ evaluator.#selections = null;
726
+
727
+ frame.values.push(local.evaluate(evaluator, context));
728
+ frame.selections.push(evaluator.#selections);
729
+ }
730
+
731
+ evaluator.#selections = enclosingSelections;
732
+
733
+ return result(evaluator, context);
734
+ } finally {
735
+ evaluator.#localsFrame = frame.enclosing;
736
+ }
737
+ };
738
+ }
739
+ case 'interpolatedString': {
740
+ const parts = node.parts.map(part => (part.kind === 'text' ? part.value : { expression: part.expression, evaluate: this.#getCompiledFunction(part.expression) }));
741
+
742
+ // Each interpolation is evaluated once, from left to right, and its value inserted as its text: a string as it is and a number as the built-in conversion writes it, negative zero as "0".
743
+ return (evaluator, context) => {
744
+ let text = '';
745
+
746
+ for (const part of parts) {
747
+ if (typeof part === 'string') {
748
+ text += part;
749
+ continue;
750
+ }
751
+
752
+ const value = part.evaluate(evaluator, context);
753
+
754
+ if (typeof value !== 'string' && typeof value !== 'number') {
755
+ evaluator.#fail('NEXO5500', `An interpolation inserts a string or a number, and is given ${Evaluator.#describe(value)}`, evaluator.#pointerOf(part.expression));
756
+ }
757
+
758
+ text += String(value);
759
+ }
760
+
761
+ return text;
762
+ };
763
+ }
764
+ case 'comparison': {
765
+ const operands = node.operands.map(operand => this.#getCompiledFunction(operand));
766
+ const compares = node.operators.map(operator => COMPARISONS[operator]);
767
+
768
+ // Each operand is evaluated once, left to right, the one between two operators compared with both, and the first comparison that is false answers false without evaluating the operands after it.
769
+ return (evaluator, context) => {
770
+ let left = operands[0](evaluator, context);
771
+
772
+ for (let index = 0; index < compares.length; index++) {
773
+ const right = operands[index + 1](evaluator, context);
774
+
775
+ if (typeof left !== 'number' || typeof right !== 'number') {
776
+ evaluator.#fail('NEXO5501', `'${node.operators[index]}' compares numbers, and is given ${Evaluator.#describe(typeof left !== 'number' ? left : right)}`, evaluator.#pointerOf(node));
777
+ }
778
+
779
+ if (!compares[index](left, right)) {
780
+ return false;
781
+ }
782
+
783
+ left = right;
784
+ }
785
+
786
+ return true;
787
+ };
788
+ }
789
+ case 'arithmetic': {
790
+ // The left operand is evaluated, then the right; arithmetic does not short-circuit, and an operand that fails stops it.
791
+ const left = this.#getCompiledFunction(node.left);
792
+ const right = this.#getCompiledFunction(node.right);
793
+ const operate = ARITHMETIC[node.operator];
794
+
795
+ return (evaluator, context) => {
796
+ const leftValue = left(evaluator, context);
797
+ const rightValue = right(evaluator, context);
798
+
799
+ return evaluator.#arithmeticResult(node, operate(leftValue, rightValue), leftValue, rightValue);
800
+ };
801
+ }
802
+ case 'arithmeticNegation': {
803
+ const operand = this.#getCompiledFunction(node.operand);
804
+
805
+ return (evaluator, context) => {
806
+ const value = operand(evaluator, context);
807
+
808
+ return evaluator.#arithmeticResult(node, -value, value, 0);
809
+ };
810
+ }
811
+ case 'fromEndIndex': {
812
+ const offset = this.#getCompiledFunction(node.offset);
813
+
814
+ return (evaluator, context) => new Index(evaluator.#requireOffset(node, offset(evaluator, context), false));
815
+ }
816
+ case 'range': {
817
+ // Its start is evaluated before its end, each where the range holds it.
818
+ const [start, end] = ['start', 'end'].map(boundary => (node[boundary] === undefined ? null : this.#getCompiledFunction(node[boundary])));
819
+
820
+ return (evaluator, context) => {
821
+ const startValue = start === null ? null : evaluator.#requireOffset(node, start(evaluator, context), true);
822
+ const endValue = end === null ? null : evaluator.#requireOffset(node, end(evaluator, context), true);
823
+
824
+ return new Range(startValue, endValue);
825
+ };
826
+ }
827
+ case 'cast':
828
+ return this.#compileCast(node, false);
829
+ case 'conversion':
830
+ return this.#compileConversion(node, false);
831
+ case 'try':
832
+ return node.operand.kind === 'cast' ? this.#compileCast(node.operand, true) : this.#compileConversion(node.operand, true);
833
+ case 'object':
834
+ return this.#compileObject(node);
835
+ case 'list': {
836
+ // A list's items are evaluated once each, left to right, into a frozen list.
837
+ const items = node.items.map(item => this.#getCompiledFunction(item));
838
+
839
+ return (evaluator, context) => Object.freeze(items.map(item => item(evaluator, context)));
840
+ }
841
+ case 'dictionary': {
842
+ // A dictionary's values are evaluated once each, left to right, in the order its entries are written, which validation found of distinct keys.
843
+ const entries = node.entries.map(entry => ({ key: entry.key, value: this.#getCompiledFunction(entry.value) }));
844
+
845
+ return (evaluator, context) => new Dictionary(entries.map(({ key, value }) => [key, value(evaluator, context)]));
846
+ }
847
+ }
848
+ }
849
+
850
+ /**
851
+ * A cast, with its operand evaluated once: its operand itself where it belongs to the type, by the type's membership and its supertypes', and null where it is null, its cast operator, where one casts across
852
+ * hierarchies, called first where it has an implementation, which answers its operand or rejects it. An operand that doesn't belong, and one the operator rejects, fails the evaluation of a cast, naming the type its
853
+ * operand is of, which is never asked again, or the operator's reason, and an attempted one answers null instead; an operator failing otherwise is a fault, which an attempt does not catch.
854
+ */
855
+ #compileCast(node, isAttempted) {
856
+ const evaluateOperand = this.#getCompiledFunction(node.operand);
857
+ const { type: targetType, sourceType, declaration } = this.#resolvedDefinition.resolutionOf(node);
858
+ const asked = `the cast to ${targetType}`;
859
+ // A value of a type above the cast's is one that isn't of the cast's type, and a value of the cast's own type, or of one beneath it, is one its supertypes refuse.
860
+ const what = this.#registry.typeChecker.isAssignable(targetType, sourceType) && !this.#registry.typeChecker.isAssignable(sourceType, targetType) ? `${Evaluator.#getNameWithArticle(sourceType)} that isn't ${Evaluator.#getNameWithArticle(targetType)}` : Evaluator.#getNameWithArticle(sourceType);
861
+ const implementation = declaration === null ? undefined : this.#registry.getImplementation(declaration);
862
+ const name = declaration === null ? null : this.#registry.formatOperator(declaration);
863
+
864
+ return (evaluator, context) => {
865
+ const value = evaluateOperand(evaluator, context);
866
+
867
+ if (value === null) {
868
+ return null;
869
+ }
870
+
871
+ const outcome = implementation === undefined ? { isAccepted: true, value } : Evaluator.#invokeOperator(evaluator, implementation, value, node, `The cast operator ${name}`, { failure: 'NEXO5004', rejection: 'NEXO5004' });
872
+
873
+ if (outcome.isAccepted && evaluator.#registry.belongsTo(outcome.value, targetType, asked)) {
874
+ return outcome.value;
875
+ }
876
+
877
+ if (isAttempted) {
878
+ return null;
879
+ }
880
+
881
+ return evaluator.#fail('NEXO5001', `The cast to ${targetType} rejected its value: ${outcome.isAccepted ? `it is ${what}` : outcome.reason}`, evaluator.#pointerOf(node));
882
+ };
883
+ }
884
+
885
+ /** The type's name with its indefinite article, as a message writes it: `a Light`, `an Outlet`. */
886
+ static #getNameWithArticle(type) {
887
+ const name = String(type);
888
+
889
+ return /^[AEIOU]/.test(name) ? `an ${name}` : `a ${name}`;
890
+ }
891
+
892
+ /**
893
+ * A conversion, with its operand evaluated once and its conversion carried out once. Null converts to null, and a value already of the type to itself, without a conversion;
894
+ * a conversion that rejects its value fails the evaluation, and an attempted one answers null instead. A fault fails the evaluation of either, as a failure of its operand does.
895
+ */
896
+ #compileConversion(node, isAttempted) {
897
+ const evaluateOperand = this.#getCompiledFunction(node.operand);
898
+ const { declaration, type, candidates } = this.#resolvedDefinition.resolutionOf(node);
899
+
900
+ if (declaration === null) {
901
+ return evaluateOperand;
902
+ }
903
+
904
+ const registry = this.#registry;
905
+ // A conversion of a value of Any selects its operator by the value's actual type as it is evaluated, each operator's attempt compiled the first time it is selected and kept with the compiled conversion.
906
+ const attempts = new Map();
907
+ const attemptOf = selected => {
908
+ let attempt = attempts.get(selected);
909
+
910
+ if (attempt === undefined) {
911
+ attempt = Evaluator.#compileConversionAttempt(selected, type, registry);
912
+ attempts.set(selected, attempt);
913
+ }
914
+
915
+ return attempt;
916
+ };
917
+
918
+ return (evaluator, context) => {
919
+ const value = evaluateOperand(evaluator, context);
920
+
921
+ if (value === null) {
922
+ return null;
923
+ }
924
+
925
+ let selected = declaration;
926
+
927
+ if (candidates !== undefined) {
928
+ const fail = message => evaluator.#fail('NEXO5053', `'to ${type}' converts a value of Any whose type ${message}`, evaluator.#pointerOf(node));
929
+ const actual = evaluator.#getActualType(value, `the conversion to ${type}`, fail);
930
+
931
+ if (actual.equals(type)) {
932
+ return value;
933
+ }
934
+
935
+ const applying = registry.typeChecker.mostSpecificOf(candidates, actual);
936
+
937
+ if (applying.length !== 1) {
938
+ evaluator.#fail('NEXO5053', applying.length === 0 ? `No conversion from ${actual} to ${type} is available, so a value of Any that is one converts to nothing of it` : `The conversions from ${applying.map(operator => registry.formatOperator(operator)).join(' and ')} all apply to a value of Any that is ${actual}, and none is more specific`, evaluator.#pointerOf(node));
939
+ }
940
+
941
+ [selected] = applying;
942
+ }
943
+
944
+ const name = registry.formatOperator(selected);
945
+ const outcome = attemptOf(selected)(evaluator, value, node);
946
+
947
+ if (!outcome.isAccepted) {
948
+ if (isAttempted) {
949
+ return null;
950
+ }
951
+
952
+ evaluator.#fail('NEXO5016', `The conversion from ${name} rejected its value: ${outcome.reason}`, evaluator.#pointerOf(node));
953
+ }
954
+
955
+ return outcome.value;
956
+ };
957
+ }
958
+
959
+ /**
960
+ * The conversion of a value that is not null, attempted or not: its converted value where it accepts the value, or the reason it rejected it with.
961
+ * A rejection is the signal `reject` throws for this attempt alone; any other exception, a reason that is no string, a rejection the conversion caught and answered past, and a value not of the type are faults, which fail the evaluation, attempted or not.
962
+ */
963
+ static #compileConversionAttempt(declaration, type, registry) {
964
+ const name = registry.formatOperator(declaration);
965
+ const convert = registry.getImplementation(declaration);
966
+ const conforms = Evaluator.#compileTypeCheck(type, registry, `the conversion from ${name}`);
967
+ const copyStructure = Evaluator.#compileStructureCopy(type, registry);
968
+
969
+ return (evaluator, value, node) => {
970
+ if (convert === undefined) {
971
+ evaluator.#fail('NEXO4001', `The module '${registry.getDeclaringModuleName(declaration)}' gives the conversion from ${name} no implementation`, evaluator.#pointerOf(node));
972
+ }
973
+
974
+ const outcome = Evaluator.#invokeOperator(evaluator, convert, value, node, `The conversion from ${name}`, { failure: 'NEXO5003', rejection: 'NEXO5018' });
975
+
976
+ if (!outcome.isAccepted) {
977
+ return outcome;
978
+ }
979
+
980
+ if (!conforms(outcome.value)) {
981
+ evaluator.#fail('NEXO5019', `The conversion from ${name} answered ${Evaluator.#describe(outcome.value)}, and it converts to ${type}`, evaluator.#pointerOf(node));
982
+ }
983
+
984
+ return { isAccepted: true, value: copyStructure === null ? outcome.value : copyStructure(outcome.value) };
985
+ };
986
+ }
987
+
988
+ /**
989
+ * An operator's implementation called with a value, never null, and `reject`: what it answers where it accepts the value, or the reason it rejected it with. A rejection is the signal `reject` throws for this call
990
+ * alone; any other exception, the codes' `failure`, and a reason that is no string or a rejection the operator caught and answered past, their `rejection`, are faults, which fail the evaluation, attempted or not.
991
+ * @param {string} subject How a message names the operator, as `The conversion from String to Number`.
992
+ * @param {{ failure: string, rejection: string }} codes The codes of the operator's faults.
993
+ */
994
+ static #invokeOperator(evaluator, implementation, value, node, subject, codes) {
995
+ const rejection = { isRejected: false, reason: null, isClosed: false };
996
+ // `reject` answers this call alone, so one an operator keeps and calls once it has returned refuses the call, rather than throwing Nexo's signal into its host's code.
997
+ const reject = reason => {
998
+ if (rejection.isClosed) {
999
+ evaluator.#fail(codes.rejection, `${subject} rejected a value after it answered, which it rejects only while it is called`, evaluator.#pointerOf(node));
1000
+ }
1001
+
1002
+ rejection.isRejected = true;
1003
+ rejection.reason = reason;
1004
+
1005
+ throw rejection;
1006
+ };
1007
+ let answered;
1008
+
1009
+ try {
1010
+ answered = implementation({ value, reject });
1011
+ } catch (error) {
1012
+ rejection.isClosed = true;
1013
+
1014
+ // A failure Nexo raised keeps its own code and place; anything else the operator threw but its rejection, a NexoError it built among them, is the cause of Nexo's own, exactly as thrown.
1015
+ if (NexoErrors.isRaisedByNexo(error)) {
1016
+ throw error;
1017
+ }
1018
+
1019
+ if (error !== rejection) {
1020
+ evaluator.#fail(codes.failure, `${subject} failed: ${Evaluator.#messageOf(error)}`, evaluator.#pointerOf(node), { cause: error });
1021
+ }
1022
+
1023
+ if (typeof rejection.reason !== 'string') {
1024
+ evaluator.#fail(codes.rejection, `${subject} rejected its value with a reason that is no string`, evaluator.#pointerOf(node));
1025
+ }
1026
+
1027
+ return { isAccepted: false, reason: rejection.reason };
1028
+ }
1029
+
1030
+ rejection.isClosed = true;
1031
+
1032
+ if (rejection.isRejected) {
1033
+ evaluator.#fail(codes.rejection, `${subject} rejected its value and then answered one`, evaluator.#pointerOf(node));
1034
+ }
1035
+
1036
+ return { isAccepted: true, value: answered };
1037
+ }
1038
+
1039
+ /**
1040
+ * A property access, a call or an indexer, with its resolution, its function, its arguments and the check of its result made once.
1041
+ * A computed property access, and a member of a receiver of unknown type, which answers null without reading anything, are evaluated as the node itself says.
1042
+ */
1043
+ #compileMember(node) {
1044
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
1045
+
1046
+ // A reference to a module's function is the same for every read of it, and reads nothing.
1047
+ if (resolution?.kind === 'functionReference') {
1048
+ const reference = Evaluator.#getModuleFunctionReference(resolution.declaration);
1049
+
1050
+ return () => reference;
1051
+ }
1052
+
1053
+ if (resolution?.kind === 'boundFunctionReference') {
1054
+ return this.#compileBoundFunctionReference(node, resolution);
1055
+ }
1056
+
1057
+ if (resolution?.kind === 'deferred') {
1058
+ return this.#compileDeferredAccess(node, resolution);
1059
+ }
1060
+
1061
+ if (resolution?.kind === 'invocation' && resolution.candidates !== undefined) {
1062
+ return this.#compileOnUnknownType(resolution);
1063
+ }
1064
+
1065
+ if (resolution === undefined || resolution.kind === 'computedPropertyAccess') {
1066
+ return (evaluator, context) => evaluator.#evaluateMember(node, context);
1067
+ }
1068
+
1069
+ if (resolution.kind === 'invocation') {
1070
+ return this.#compileInvocation(node, resolution);
1071
+ }
1072
+
1073
+ const evaluateReceiver = this.#getCompiledFunction(node.receiver);
1074
+
1075
+ if (resolution.kind === 'objectRootField') {
1076
+ const { name } = resolution;
1077
+
1078
+ return (evaluator, context) => evaluator.#readStoredValue(evaluateReceiver(evaluator, context), name);
1079
+ }
1080
+
1081
+ const { name } = resolution;
1082
+
1083
+ // A stored value, an object field or a property of a structured type's data, read alike, which a projection's host call never is.
1084
+ return (evaluator, context) => {
1085
+ if (evaluator.#projectionContext !== null && node === evaluator.#projectionContext.node) {
1086
+ return evaluator.#projectionContext.value;
1087
+ }
1088
+
1089
+ return evaluator.#readStoredValue(evaluateReceiver(evaluator, context), name);
1090
+ };
1091
+ }
1092
+
1093
+ /**
1094
+ * An invocation of a member's declaration, with its function, its arguments and the check of its result made once: its receiver evaluated first, the node its resolution records, the access's receiver, the receiver of the access a named
1095
+ * indexer's name is written in, a qualified call's first argument, or the receiver a reference bound to it captured, which the local a call's callee names holds, a null one answering null before its own arguments are
1096
+ * evaluated, and a module member's none; then its arguments, null where one makes the call answer null.
1097
+ */
1098
+ #compileInvocation(node, resolution) {
1099
+ const { type: resultType, declaration, receiverNode } = resolution;
1100
+ const checksReceiver = resolution.checksReceiver === true;
1101
+ const evaluateNode = receiverNode === undefined ? null : this.#getCompiledFunction(receiverNode);
1102
+ const evaluateReceiver = resolution.readsBoundReceiver === true ? (evaluator, context) => evaluateNode(evaluator, context).receiver : evaluateNode;
1103
+ // A local that may hold several functions of one contract invokes the one it holds, each with its own result type, the arguments the contract read named by that function's own parameters.
1104
+ const alternatives = resolution.alternatives === undefined ? null : new Map(resolution.alternatives.map(alternative => [alternative.declaration, { invocation: this.#getCompiledInvocation(alternative.declaration, alternative.type), parameterNames: alternative.parameterNames }]));
1105
+ const evaluateCallee = alternatives === null ? null : this.#getCompiledFunction(node.callee);
1106
+ const parameterNames = declaration.parameters?.map(parameter => parameter.name) ?? [];
1107
+ const invocation = this.#getCompiledInvocation(declaration, resultType, alternatives === null && this.#reliesOnAnswer(declaration, resultType));
1108
+ const evaluateArguments = this.#compileArguments(resolution);
1109
+
1110
+ return (evaluator, context) => {
1111
+ // A projection stands the value its host supplied where the host call is, so the call is never evaluated.
1112
+ if (evaluator.#projectionContext !== null && node === evaluator.#projectionContext.node) {
1113
+ return evaluator.#projectionContext.value;
1114
+ }
1115
+
1116
+ const receiver = evaluateReceiver === null ? undefined : evaluateReceiver(evaluator, context);
1117
+
1118
+ if (receiver === null) {
1119
+ return checksReceiver ? evaluator.#answerNullReceiver(node, resolution) : null;
1120
+ }
1121
+
1122
+ if (checksReceiver) {
1123
+ evaluator.#requireReceiverOf(node, declaration, receiver);
1124
+ }
1125
+
1126
+ const args = evaluateArguments(evaluator, context);
1127
+ const lifetimes = evaluator.#givenLifetimes;
1128
+
1129
+ if (args === null) {
1130
+ return null;
1131
+ }
1132
+
1133
+ if (alternatives === null) {
1134
+ return evaluator.#invokeImplementation(invocation, receiver, args, node, false, lifetimes);
1135
+ }
1136
+
1137
+ const callee = evaluateCallee(evaluator, context);
1138
+ const chosen = alternatives.get(callee.reference);
1139
+
1140
+ evaluator.#selections = Evaluator.#select(evaluator.#selections, node, callee.reference);
1141
+
1142
+ return evaluator.#invokeImplementation(chosen.invocation, receiver, Evaluator.#renameArguments(args, parameterNames, chosen.parameterNames), node, false, lifetimes);
1143
+ };
1144
+ }
1145
+
1146
+ /** The arguments, named by one declaration's parameters, named by another's of the same positions, as functions of one contract name them apart. */
1147
+ static #renameArguments(args, names, ownNames) {
1148
+ if (names.every((name, index) => name === ownNames[index])) {
1149
+ return args;
1150
+ }
1151
+
1152
+ return Object.fromEntries(names.filter(name => Object.hasOwn(args, name)).map(name => [ownNames[names.indexOf(name)], args[name]]));
1153
+ }
1154
+
1155
+ /**
1156
+ * The invocation of the declaration whose implementation answers a value of the result type, as the call it serves resolved it: its implementation, whether it takes a receiver, the capture, the check and the copy of
1157
+ * its answer, and whether the call relies on its answer as the language's and of its type, made once for each declaration, result type and reliance and shared by every evaluator of the resolution, so one generic
1158
+ * declaration answering two types has two. The result type is the declaration's own, its type parameters bound for the call, which a null the call propagates or the types of other functions it may call never make wider.
1159
+ */
1160
+ #getCompiledInvocation(declaration, resultType, reliesOnAnswer = false) {
1161
+ if (this.#compiler !== this) {
1162
+ return this.#compiler.#getCompiledInvocation(declaration, resultType, reliesOnAnswer);
1163
+ }
1164
+
1165
+ let invocations = this.#compiledInvocations.get(declaration);
1166
+
1167
+ if (invocations === undefined) {
1168
+ invocations = new Map();
1169
+ this.#compiledInvocations.set(declaration, invocations);
1170
+ }
1171
+
1172
+ // A type's qualified text names it alone, as a resolution key does, so a lookup compares no types.
1173
+ const key = `${resultType.toString(true)}|${reliesOnAnswer}`;
1174
+ let invocation = invocations.get(key);
1175
+
1176
+ if (invocation === undefined) {
1177
+ invocation = this.#compileDeclarationInvocation(declaration, resultType, reliesOnAnswer);
1178
+ invocations.set(key, invocation);
1179
+ }
1180
+
1181
+ return invocation;
1182
+ }
1183
+
1184
+ /**
1185
+ * The invocation of a declaration a deferred access selected, for a result type: kept by the access, as many as its selections, and never among the invocations resolution's calls share, so a value of Any of ever new
1186
+ * shapes grows none of them further.
1187
+ * @param {Map<object, RecentlyUsed>} invocations The access's invocations, by their declaration and then by their result type's text.
1188
+ */
1189
+ #getDeferredInvocation(declaration, resultType, invocations) {
1190
+ let byType = invocations.get(declaration);
1191
+
1192
+ if (byType === undefined) {
1193
+ byType = new RecentlyUsed(MAX_KEPT_SELECTIONS);
1194
+ invocations.set(declaration, byType);
1195
+ }
1196
+
1197
+ const key = resultType.toString(true);
1198
+ let invocation = byType.get(key);
1199
+
1200
+ if (invocation === undefined) {
1201
+ invocation = this.#compiler.#compileDeclarationInvocation(declaration, resultType, false);
1202
+ byType.offer(key, invocation);
1203
+ }
1204
+
1205
+ return invocation;
1206
+ }
1207
+
1208
+ /** Compiles the invocation of a declaration for a result type: what the protocol does with its answer, decided once for the declaration and the type rather than at each invocation. */
1209
+ #compileDeclarationInvocation(declaration, resultType, reliesOnAnswer) {
1210
+ const takesReceiver = this.#registry.takesReceiver(declaration);
1211
+
1212
+ return Object.freeze({
1213
+ declaration,
1214
+ implementation: this.#registry.getImplementation(declaration),
1215
+ takesReceiver,
1216
+ // What the protocol does with the member's answer, and whether it reads samples, decided once for the declaration rather than at each invocation.
1217
+ answersReceiverItems: takesReceiver && Vocabulary.answersReceiverItems(declaration),
1218
+ projectsItems: takesReceiver && Vocabulary.projectsItems(declaration),
1219
+ readsMaintained: declaration.updates?.reads === 'maintained',
1220
+ // The members of Nexo's own that make a list of a size their receiver doesn't give are given the limits the host set, which no other member is.
1221
+ takesLimits: Vocabulary.takesLimits(declaration),
1222
+ resultType,
1223
+ reliesOnAnswer,
1224
+ // An answer relied on holds the language's collections already, which is all a capture makes.
1225
+ captureCollections: reliesOnAnswer ? null : Evaluator.#compileCollectionCapture(resultType),
1226
+ isOfResultType: Evaluator.#compileTypeCheck(resultType, this.#registry, `the answer of '${NexoModule.writtenNameOf(declaration)}'`),
1227
+ copyStructure: Evaluator.#compileStructureCopy(resultType, this.#registry),
1228
+ });
1229
+ }
1230
+
1231
+ /**
1232
+ * Whether the answer of a call needs neither a capture nor a check: the member is one of Nexo's own, which resolution selected, and its result type may hold no reference, nor a value of Any. Its implementation
1233
+ * answers the language's own values, built of its receiver's items, its arguments, its functions' results and the numbers it computes, each of the type resolution established for it, so the answer is of the type its
1234
+ * declaration gives the call; data shared with a host stays as it was checked. A reference's membership is its type's to answer, so an answer that may hold one is checked again, and so is any other member's.
1235
+ */
1236
+ #reliesOnAnswer(declaration, resultType) {
1237
+ return this.#registry.getDeclaringModuleName(declaration) === Vocabulary.moduleName && !this.#registry.mayHoldReference(resultType);
1238
+ }
1239
+
1240
+ /**
1241
+ * Whether the argument a function is called with needs no check of its own: the member is one of Nexo's own that calls the functions it is given with its receiver's items alone, the function takes one argument,
1242
+ * the receiver's type resolution established holds items of that argument's type, and that type may hold no reference. Each item was checked as that type where the receiver was made, or is of it as an answer of Nexo's own is,
1243
+ * since data shared with a host stays as it was.
1244
+ */
1245
+ #givesCheckedItems({ declaration, receiverType }, parameterTypes) {
1246
+ if (receiverType === undefined || parameterTypes.length !== 1 || !Vocabulary.givesReceiverItems(declaration)) {
1247
+ return false;
1248
+ }
1249
+
1250
+ const itemType = receiverType.toNonNullable().itemContextType;
1251
+
1252
+ return itemType !== null && itemType.equals(parameterTypes[0]) && !this.#registry.mayHoldReference(itemType);
1253
+ }
1254
+
1255
+ /**
1256
+ * A function an extension access reads without a call: its receiver evaluated once, as the reference is created, and a reference to the function bound to it, which a call gives the receiver it captured before its own
1257
+ * arguments. A null receiver, which only an optional access admits, answers null, and a value of Any is checked against the extension's target first.
1258
+ */
1259
+ #compileBoundFunctionReference(node, resolution) {
1260
+ const { declaration, receiverNode } = resolution;
1261
+ const evaluateReceiver = this.#getCompiledFunction(receiverNode);
1262
+ const checksReceiver = resolution.checksReceiver === true;
1263
+
1264
+ return (evaluator, context) => {
1265
+ const receiver = evaluateReceiver(evaluator, context);
1266
+
1267
+ if (receiver === null) {
1268
+ return checksReceiver ? evaluator.#answerNullReceiver(node, resolution) : null;
1269
+ }
1270
+
1271
+ if (checksReceiver) {
1272
+ evaluator.#requireReceiverOf(node, declaration, receiver);
1273
+ }
1274
+
1275
+ return Object.freeze({ reference: declaration, isBound: true, receiver });
1276
+ };
1277
+ }
1278
+
1279
+ /**
1280
+ * What an extension access on a value of Any answers where that value is null: null where the access is optional or an optional access before it in its chain met null, which ends the chain, and otherwise the
1281
+ * failure of a direct access on null (NEXO5022), as a deferred access fails.
1282
+ */
1283
+ #answerNullReceiver(node, { receiverNode, isOptional }) {
1284
+ const isAnswered = this.#nullAnswer === receiverNode;
1285
+
1286
+ this.#nullAnswer = null;
1287
+
1288
+ if (isOptional === true || Evaluator.#accessOf(node).optional === true || (Evaluator.#followsOptionalAccess(receiverNode) && !isAnswered)) {
1289
+ return null;
1290
+ }
1291
+
1292
+ this.#fail('NEXO5022', `'${Evaluator.#writtenNameOf(node)}' is read through an extension on a value of Any, and its receiver is null: write '?:' before the extension to read it where its receiver is not null`, this.#pointerOf(node));
1293
+ }
1294
+
1295
+ /** Checks a value of Any an extension access is given against the extension's target, before anything is invoked, a value outside it failing the access (NEXO5043), and one of no type the vocabulary knows (NEXO5045). */
1296
+ #requireReceiverOf(node, declaration, receiver) {
1297
+ const written = Evaluator.#writtenNameOf(node);
1298
+ const actualType = this.#getActualType(receiver, `the access '${written}'`, this.#failSelectionOf(node));
1299
+
1300
+ if (!this.#registry.typeChecker.matchesReceiver(declaration, actualType)) {
1301
+ this.#fail('NEXO5043', `'${written}' is read through an extension of ${this.#registry.getReceiverTypes(declaration).join(', ')}, and its receiver is ${actualType}, which does not satisfy it`, this.#pointerOf(node));
1302
+ }
1303
+ }
1304
+
1305
+ /**
1306
+ * A member of a receiver of unknown type, the type null or a never, which selects no declaration among its candidates: its receiver is evaluated once and is null, so it answers null, invoking nothing and evaluating
1307
+ * none of its arguments, as an access of null does; a never is never evaluated, since no value has that type.
1308
+ */
1309
+ #compileOnUnknownType(resolution) {
1310
+ const receiver = this.#getCompiledFunction(resolution.receiverNode);
1311
+
1312
+ return (evaluator, context) => {
1313
+ receiver(evaluator, context);
1314
+
1315
+ return null;
1316
+ };
1317
+ }
1318
+
1319
+ /**
1320
+ * An access on a value of Any, its member selected as it is evaluated, by the actual type of its receiver's value, as deferred member selection sets out. A null receiver answers null where the access is optional, or where an
1321
+ * optional access before it in its chain met null, which ends the chain, and fails the access otherwise (NEXO5022), its arguments never evaluated. A plain object's own object field is read by its name; any other access
1322
+ * is selected among the candidates its resolution kept, and is read or invoked as its selection says, its arguments evaluated after its receiver in the order they are written.
1323
+ */
1324
+ #compileDeferredAccess(node, resolution) {
1325
+ const { receiverNode } = resolution;
1326
+ const evaluateReceiver = this.#getCompiledFunction(receiverNode);
1327
+ const evaluateArguments = resolution.arguments.map(argument => this.#getCompiledFunction(argument.node));
1328
+ const followsOptionalAccess = Evaluator.#followsOptionalAccess(receiverNode);
1329
+ const written = Evaluator.#writtenNameOf(node);
1330
+ // The selection made for each actual type the access meets, by the type's qualified text. It is kept with the compilation, which its resolved definition and its registry hold, and released with the resolved
1331
+ // definition. It stays valid while that resolution is current: its candidates were fixed at resolution and no registration changes a type's hierarchy, so only a module declaring a `typeOf`, which changes
1332
+ // the actual types values are found of, could change a selection, and such a registration makes the resolution stale, an access on a value of Any always recognizing. It saves selecting, binding the type parameters the receiver writes and compiling the parameters' checks at every evaluation.
1333
+ // It keeps the selections of the types the access met most recently, as many as `MAX_KEPT_SELECTIONS`, a type entering once that many are kept at its second miss, so the objects of ever new shapes a value of Any
1334
+ // can hold grow it no further and displace none of the types met again: another type is selected each time it is met. The invocations its selections make are kept alike, by their declaration and result type.
1335
+ const selections = new RecentlyUsed(MAX_KEPT_SELECTIONS);
1336
+ const invocations = new Map();
1337
+
1338
+ return (evaluator, context) => {
1339
+ const receiver = evaluateReceiver(evaluator, context);
1340
+
1341
+ if (receiver === null) {
1342
+ // A deferred access that answered null itself met no null before it, so no optional access ended its chain.
1343
+ const isAnswered = evaluator.#nullAnswer === receiverNode;
1344
+
1345
+ evaluator.#nullAnswer = null;
1346
+
1347
+ if (resolution.isOptional === true || Evaluator.#accessOf(node).optional === true || (followsOptionalAccess && !isAnswered)) {
1348
+ return null;
1349
+ }
1350
+
1351
+ evaluator.#fail('NEXO5022', `'${written}' is read on a value of Any, and its receiver is null: write '${resolution.readsExtensionMembers === true ? '?:' : '?.'}' before it to read it where its receiver is not null`, evaluator.#pointerOf(node));
1352
+ }
1353
+
1354
+ const receiverType = evaluator.#getActualType(receiver, `the access '${written}'`, evaluator.#failSelectionOf(node));
1355
+ // A plain object's object fields are its data, found on each object, since objects of one type need not hold the same fields; an extension access reads extension members alone.
1356
+ const isObjectField = node.kind === 'memberAccess' && resolution.readsExtensionMembers !== true && receiverType.kind === 'object' && Object.hasOwn(receiver, node.name);
1357
+ const value = isObjectField ? evaluator.#readStoredValue(receiver, node.name) : evaluator.#evaluateDeferredSelection(node, resolution.candidates, receiver, receiverType, selections, invocations, evaluateArguments, context);
1358
+
1359
+ if (value === null) {
1360
+ evaluator.#nullAnswer = node;
1361
+ }
1362
+
1363
+ return value;
1364
+ };
1365
+ }
1366
+
1367
+ /**
1368
+ * Carries out a deferred access on a receiver of its actual type, as the selection made at the access's first meeting of that type says, the selection kept for every later one: a property of a structured type's data is
1369
+ * read from the data, and any other member is invoked with the values its arguments give, its request marked deferred. A selection that fails, fails each evaluation that meets it.
1370
+ */
1371
+ #evaluateDeferredSelection(node, candidates, receiver, receiverType, selections, invocations, evaluateArguments, context) {
1372
+ const key = receiverType.toString(true);
1373
+ let selection = selections.get(key);
1374
+
1375
+ if (selection === undefined) {
1376
+ selection = this.#selectDeferred(node, candidates, receiverType, invocations);
1377
+ selections.offer(key, selection);
1378
+ }
1379
+
1380
+ if (selection.kind === 'failure') {
1381
+ this.#fail(selection.code, selection.message, this.#pointerOf(node));
1382
+ }
1383
+
1384
+ if (selection.kind === 'dataProperty') {
1385
+ return this.#readStoredValue(receiver, selection.name);
1386
+ }
1387
+
1388
+ const values = evaluateArguments.map(evaluate => evaluate(this, context));
1389
+ // An indexer of several signatures takes the one its key's value is of, as resolution takes the one its key's type is, and otherwise its first, which refuses the key.
1390
+ const chosen = selection.kind === 'signatures' ? (selection.selections.find(signature => signature.parameters[0]?.isOfType?.(values[0]) === true) ?? selection.selections[0]) : selection;
1391
+ const args = this.#bindDeferredArguments(node, chosen, receiverType, values, invocations);
1392
+
1393
+ if (args === null) {
1394
+ return null;
1395
+ }
1396
+
1397
+ // The declaration it selects describes the value it answers, as a member resolution selects does, so the value's record is read from its selection.
1398
+ this.#selections = Evaluator.#select(this.#selections, node, chosen.declaration);
1399
+
1400
+ return this.#invokeImplementation(args.invocation, receiver, args.values, node, true);
1401
+ }
1402
+
1403
+ /**
1404
+ * The selection a deferred access makes on a receiver of the type, through the selector, as an access its receiver's type selects during resolution: a property of a structured type's data, by its name; a member to
1405
+ * invoke, with the type arguments its receiver binds, each parameter's declared type, the check of each a type parameter of its own leaves unwritten, and its invocation where it declares none; or the failure of a type
1406
+ * that has no applicable candidate, NEXO5012, or several none is more specific than, NEXO5044, of a host call, NEXO5013, which resolution alone selects, and of a member comparing items of no key type by key
1407
+ * equality, NEXO5042.
1408
+ */
1409
+ #selectDeferred(node, candidates, receiverType, invocations) {
1410
+ const written = Evaluator.#writtenNameOf(node);
1411
+ const outcome = this.#registry.memberSelector.select(candidates, receiverType);
1412
+
1413
+ if (outcome.kind === 'ambiguous') {
1414
+ const modules = outcome.declarations.map(declaration => `'${this.#registry.getDeclaringModuleName(declaration)}'`);
1415
+
1416
+ return { kind: 'failure', code: 'NEXO5044', message: `'${written}' on ${receiverType}, the type its receiver's value is of, is declared by ${modules.join(' and ')}, none of them its owner and none more specific than the others` };
1417
+ }
1418
+
1419
+ if (outcome.kind !== 'selected') {
1420
+ return { kind: 'failure', code: 'NEXO5012', message: `'${written}' is not available on ${receiverType}, the type its receiver's value is of` };
1421
+ }
1422
+
1423
+ const { declaration } = outcome;
1424
+
1425
+ if (NexoModule.isInvokedByHost(declaration)) {
1426
+ return {
1427
+ kind: 'failure',
1428
+ code: 'NEXO5013',
1429
+ message: `'${written}' on ${receiverType} is a host call, which its host invokes and resolution alone selects: make its receiver known, as a cast does, or select its extension explicitly`,
1430
+ };
1431
+ }
1432
+
1433
+ if (this.#registry.isDataProperty(declaration)) {
1434
+ return { kind: 'dataProperty', name: declaration.name };
1435
+ }
1436
+
1437
+ // A member comparing the receiver's items by key equality takes items of a key type alone, as resolution checks a receiver whose type it knows.
1438
+ if (Vocabulary.getKeyTypeParameter(declaration) === 'T') {
1439
+ const itemType = this.#registry.typeChecker.inferReceiverTypeArguments(declaration, receiverType)?.get('T');
1440
+
1441
+ if (itemType !== undefined && !this.#registry.isKeyType(itemType)) {
1442
+ return {
1443
+ kind: 'failure',
1444
+ code: 'NEXO5042',
1445
+ message: `'${written}' compares the items of ${receiverType}, the type its receiver's value is of, by key equality, which compares numbers, strings, Booleans, references and null alone: compare them by a key`,
1446
+ };
1447
+ }
1448
+ }
1449
+
1450
+ const signatures = this.#registry.getIndexerSignatures(declaration);
1451
+
1452
+ return signatures.length > 1 ? { kind: 'signatures', selections: signatures.map(signature => this.#prepareDeferredInvocation(signature, receiverType, written, invocations)) } : this.#prepareDeferredInvocation(declaration, receiverType, written, invocations);
1453
+ }
1454
+
1455
+ /** The invocation a deferred access makes of the declaration on a receiver of the type: the type arguments its receiver binds, each parameter's declared type and its check, and its invocation where it declares no type parameters of its own. */
1456
+ #prepareDeferredInvocation(declaration, receiverType, written, invocations) {
1457
+ const typeArguments = this.#registry.typeChecker.inferReceiverTypeArguments(declaration, receiverType) ?? new Map();
1458
+ const hasOwnTypeParameters = (declaration.typeParameters ?? []).length > 0;
1459
+ const parameters = (declaration.parameters ?? []).map(parameter => {
1460
+ const declaredType = this.#registry.getDeclaredType(declaration, parameter.type).resolveTypeParameters(typeArguments);
1461
+ const isOfType = hasOwnTypeParameters ? null : Evaluator.#compileTypeCheck(declaredType, this.#registry, `the argument '${parameter.name}' of '${written}'`);
1462
+ const copyToType = hasOwnTypeParameters ? null : Evaluator.#compileStructureCopy(declaredType, this.#registry);
1463
+
1464
+ return { parameter, declaredType, isOfType, copyToType };
1465
+ });
1466
+ const resultType = this.#registry.getDeclaredType(declaration, declaration.result).resolveTypeParameters(typeArguments);
1467
+
1468
+ return { kind: 'invocation', declaration, written, typeArguments, parameters, invocation: hasOwnTypeParameters ? null : this.#getDeferredInvocation(declaration, resultType, invocations) };
1469
+ }
1470
+
1471
+ /**
1472
+ * The arguments a deferred call gives the member its selection invokes, by parameter name, with the invocation of the result type they bind: the values written in order, or the object fields of the one plain object a
1473
+ * member taking options is given, each parameter left out taking its default. It answers null where a null value's parameter propagates null. A value whose parameter it doesn't satisfy fails the call (NEXO5021),
1474
+ * and so do too many values (NEXO5046), a parameter none supplies that requires one (NEXO5047), a value that is no object where the member takes options (NEXO5048), an object field it has no parameter of
1475
+ * (NEXO5049), and a parameter a value never satisfies, a function's, an expression's, a predicate's or a fixed one's (NEXO5050).
1476
+ */
1477
+ /**
1478
+ * A position's offset as an index or a range takes it: a nonnegative safe integer, never rounded, `-0` as `0`, or, for a range's boundary, an index; any other value fails where the index or the range stands
1479
+ * (NEXO5032), a value of Any's among them.
1480
+ */
1481
+ #requireOffset(node, value, allowsIndex) {
1482
+ if (allowsIndex && value instanceof Index) {
1483
+ return value;
1484
+ }
1485
+
1486
+ if (Number.isSafeInteger(value) && value >= 0) {
1487
+ return value === 0 ? 0 : value;
1488
+ }
1489
+
1490
+ const what = node.kind === 'range' ? "A range's boundary" : "An index's offset";
1491
+
1492
+ this.#fail('NEXO5032', `${what} is a nonnegative whole number${allowsIndex ? ' or an index' : ''}, and this one is ${Evaluator.#describe(value)}`, this.#pointerOf(node));
1493
+ }
1494
+
1495
+ #bindDeferredArguments(node, selection, receiverType, values, invocations) {
1496
+ const { declaration, written, parameters } = selection;
1497
+ const pointer = this.#pointerOf(node);
1498
+ const on = `'${written}' on ${receiverType}`;
1499
+ const supplied = new Map();
1500
+
1501
+ if (declaration.arguments === 'options' && values.length > 0) {
1502
+ const [options] = values;
1503
+
1504
+ if (values.length > 1) {
1505
+ this.#fail('NEXO5046', `${on} takes its arguments as the object fields of one object, and is given ${values.length} values`, pointer);
1506
+ }
1507
+
1508
+ if (!ObjectUtils.isPlainObject(options)) {
1509
+ this.#fail('NEXO5048', `${on} takes its arguments as the object fields of one object, and is given ${Evaluator.#describe(options)}`, pointer);
1510
+ }
1511
+
1512
+ for (const [name, value] of Object.entries(options)) {
1513
+ if (!parameters.some(({ parameter }) => parameter.name === name)) {
1514
+ this.#fail('NEXO5049', `${on} has no parameter '${name}'; it takes ${parameters.map(({ parameter }) => parameter.name).join(', ')}`, pointer);
1515
+ }
1516
+
1517
+ supplied.set(name, value);
1518
+ }
1519
+ } else {
1520
+ if (values.length > parameters.length) {
1521
+ this.#fail('NEXO5046', `${on} takes ${parameters.some(({ parameter }) => !parameter.required) ? 'at most ' : ''}${parameters.length === 1 ? 'one argument' : `${parameters.length} arguments`}, and is given ${values.length}`, pointer);
1522
+ }
1523
+
1524
+ values.forEach((value, index) => supplied.set(parameters[index].parameter.name, value));
1525
+ }
1526
+
1527
+ const args = {};
1528
+ const typeArguments = new Map(selection.typeArguments);
1529
+
1530
+ for (const { parameter, declaredType } of parameters) {
1531
+ if (!supplied.has(parameter.name)) {
1532
+ if (parameter.required) {
1533
+ this.#fail('NEXO5047', `${on} requires the argument '${parameter.name}'`, pointer);
1534
+ }
1535
+
1536
+ args[parameter.name] = parameter.default;
1537
+ continue;
1538
+ }
1539
+
1540
+ if (parameter.kind === 'expression' || parameter.kind === 'predicate' || parameter.kind === 'function' || parameter.fixed === true) {
1541
+ const taken = parameter.fixed === true ? 'a fixed value, which selects its result type' : parameter.kind === 'function' ? 'a function' : `an ${parameter.kind}`;
1542
+
1543
+ this.#fail('NEXO5050', `${on} takes ${taken} as '${parameter.name}', and a call selected as it is evaluated gives values alone`, pointer);
1544
+ }
1545
+
1546
+ const value = supplied.get(parameter.name);
1547
+
1548
+ if (value === null && parameter.nulls === 'propagate') {
1549
+ return null;
1550
+ }
1551
+
1552
+ // A type parameter of the member's own is bound by the values it is given, as resolution binds it by their types.
1553
+ if (selection.invocation === null && value !== null) {
1554
+ const bound = this.#registry.typeChecker.inferTypeArguments(declaredType, this.#getActualType(value, `the access '${written}'`, this.#failSelectionOf(node)), typeArguments);
1555
+
1556
+ bound?.forEach((type, name) => typeArguments.set(name, type));
1557
+ }
1558
+
1559
+ args[parameter.name] = value;
1560
+ }
1561
+
1562
+ for (const { parameter, declaredType, isOfType, copyToType } of parameters) {
1563
+ if (!supplied.has(parameter.name)) {
1564
+ continue;
1565
+ }
1566
+
1567
+ const parameterType = isOfType === null ? declaredType.resolveTypeParameters(typeArguments) : declaredType;
1568
+ const isAdmitted = isOfType ?? Evaluator.#compileTypeCheck(parameterType, this.#registry, `the argument '${parameter.name}' of '${written}'`);
1569
+
1570
+ if (!isAdmitted(args[parameter.name])) {
1571
+ this.#fail('NEXO5021', `The argument '${parameter.name}' of ${on} is declared ${parameterType}, and is given ${Evaluator.#describe(args[parameter.name])}`, pointer);
1572
+ }
1573
+
1574
+ // An argument enters a structure its parameter holds as its data shape alone, as a value of Any does wherever a type is declared.
1575
+ const copy = isOfType === null ? Evaluator.#compileStructureCopy(parameterType, this.#registry) : copyToType;
1576
+
1577
+ if (copy !== null) {
1578
+ args[parameter.name] = copy(args[parameter.name]);
1579
+ }
1580
+ }
1581
+
1582
+ const invocation = selection.invocation ?? this.#getDeferredInvocation(declaration, this.#registry.getDeclaredType(declaration, declaration.result).resolveTypeParameters(typeArguments), invocations);
1583
+
1584
+ return { invocation, values: args };
1585
+ }
1586
+
1587
+ /**
1588
+ * The actual type of a value a deferred access meets, as binding types a value, without copying it: a string, a finite number and a Boolean their scalar type; a reference the type its modules recognize it as, the one
1589
+ * beneath every other; a structured value the type whose answer gave it; a list the type its items share, Any where they share none, the empty list's being never; and an object the object type of its fields' types.
1590
+ * A value of no type, a function, an instance no module recognizes, a list with a hole and data that reaches itself among them, is given to `fail` with the reason it has none, as is a reference several modules recognize
1591
+ * as types none of which is beneath the others.
1592
+ * @param {unknown} value
1593
+ * @param {string} asked What asked for the value's type, as a message names it.
1594
+ * @param {(reason: string) => never} fail
1595
+ */
1596
+ #getActualType(value, asked, fail, ancestors = new Set()) {
1597
+ if (value === null) {
1598
+ return Type.null;
1599
+ }
1600
+
1601
+ if (value instanceof Index || value instanceof Range) {
1602
+ return Type.createNamedType(value instanceof Index ? 'Index' : 'Range');
1603
+ }
1604
+
1605
+ if (typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value))) {
1606
+ return Type.createPrimitiveType(value);
1607
+ }
1608
+
1609
+ if (value === undefined || typeof value !== 'object') {
1610
+ fail(`${Evaluator.#describe(value)} has none the vocabulary knows`);
1611
+ }
1612
+
1613
+ const recognized = this.#registry.recognize(value, asked);
1614
+
1615
+ if (recognized.length > 0) {
1616
+ const { typeChecker } = this.#registry;
1617
+ const specific = recognized.find(type => recognized.every(other => typeChecker.isAssignable(type, other)));
1618
+
1619
+ if (specific === undefined) {
1620
+ fail(`it is recognized as ${recognized.map(type => type.toString(true)).join(' and ')}, of which none is beneath the others`);
1621
+ }
1622
+
1623
+ return specific;
1624
+ }
1625
+
1626
+ const structured = StructureTypes.getType(value, this.#registry);
1627
+
1628
+ if (structured !== undefined) {
1629
+ return structured;
1630
+ }
1631
+
1632
+ if (ancestors.has(value)) {
1633
+ fail('it reaches itself, which data never does');
1634
+ }
1635
+
1636
+ if (ancestors.size >= Limits.dataNesting) {
1637
+ fail(`it nests deeper than ${Limits.dataNesting} levels, the most data nests`);
1638
+ }
1639
+
1640
+ ancestors.add(value);
1641
+
1642
+ try {
1643
+ if (Array.isArray(value) && ArrayUtils.findMissingIndex(value) === -1) {
1644
+ let itemType = Type.never;
1645
+
1646
+ for (const item of value) {
1647
+ const actual = this.#getActualType(item, asked, fail, ancestors);
1648
+ const isNullable = itemType.isNullable || actual.isNullable;
1649
+
1650
+ itemType = this.#registry.typeChecker.merge(itemType, actual) ?? (isNullable ? Type.any.toNullable() : Type.any);
1651
+ }
1652
+
1653
+ return Type.createListType(itemType);
1654
+ }
1655
+
1656
+ // A dictionary of the types its keys and its values share, the empty one's never, and a pair of its parts'; a dictionary is the language's, so its keys share one already.
1657
+ if (value instanceof Dictionary) {
1658
+ const merged = parts => parts.reduce((shared, part) => this.#registry.typeChecker.merge(shared, this.#getActualType(part, asked, fail, ancestors)) ?? Type.any, Type.never);
1659
+
1660
+ return Type.createDictionaryType(merged(value.keys), merged(value.values));
1661
+ }
1662
+
1663
+ if (value instanceof Pair) {
1664
+ return Type.createPairType(this.#getActualType(value.key, asked, fail, ancestors), this.#getActualType(value.value, asked, fail, ancestors));
1665
+ }
1666
+
1667
+ if (ObjectUtils.isPlainObject(value)) {
1668
+ return Type.createObjectType(new Map(Object.entries(value).map(([name, field]) => [name, this.#getActualType(field, asked, fail, ancestors)])));
1669
+ }
1670
+ } finally {
1671
+ ancestors.delete(value);
1672
+ }
1673
+
1674
+ return fail(`${Evaluator.#describe(value)} has none the vocabulary knows`);
1675
+ }
1676
+
1677
+ /** How a deferred access fails where its receiver's value, or an argument's, has no actual type, which it is selected by (NEXO5045). */
1678
+ #failSelectionOf(node) {
1679
+ return reason => this.#fail('NEXO5045', `'${Evaluator.#writtenNameOf(node)}' is selected by the types of the values it is given, and ${reason}`, this.#pointerOf(node));
1680
+ }
1681
+
1682
+ /** The name a message gives an access, its member's name, a call's its callee's, or `[…]` for an unnamed indexer. */
1683
+ static #writtenNameOf(node) {
1684
+ return node.kind === 'indexerAccess' && node.name === undefined ? '[…]' : Evaluator.#accessOf(node).name;
1685
+ }
1686
+
1687
+ /** The access a node of a member makes: a property access and an indexer are their own, and a call of a member the member access its callee is. */
1688
+ static #accessOf(node) {
1689
+ return node.kind === 'call' ? node.callee : node;
1690
+ }
1691
+
1692
+ /** Whether an access of the chain the node ends, the node among them, is optional, so a null the node answers can be one an optional access met, which ends the chain. */
1693
+ static #followsOptionalAccess(node) {
1694
+ for (let current = node; current?.kind === 'memberAccess' || current?.kind === 'indexerAccess' || (current?.kind === 'call' && current.callee.kind === 'memberAccess'); current = Evaluator.#accessOf(current).receiver) {
1695
+ if (Evaluator.#accessOf(current).optional === true) {
1696
+ return true;
1697
+ }
1698
+ }
1699
+
1700
+ return false;
1701
+ }
1702
+
1703
+ /** The reference to each module's function, by its declaration: the same for every evaluation, since it holds nothing an evaluation bound. @type {WeakMap<object, { reference: object }>} */
1704
+ static #moduleFunctionReferenceCache = new WeakMap();
1705
+
1706
+ /** The reference to the declaration's function, made at its first request and the same at every later one. */
1707
+ static #getModuleFunctionReference(declaration) {
1708
+ let reference = Evaluator.#moduleFunctionReferenceCache.get(declaration);
1709
+
1710
+ if (reference === undefined) {
1711
+ reference = Object.freeze({ reference: declaration });
1712
+ Evaluator.#moduleFunctionReferenceCache.set(declaration, reference);
1713
+ }
1714
+
1715
+ return reference;
1716
+ }
1717
+
1718
+ /**
1719
+ * The arguments a call is given, in the order they are written, each argument's plan made once: its default or fixed value, a callback the member calls, an expression evaluated against its argument or the function a value gives,
1720
+ * or a value, null where it makes the call answer null.
1721
+ * An options object's written values are its fields: each is in scope after its evaluation for the options after it that read it, holding its value and the selections it made, and one no other object encloses
1722
+ * builds the outermost object, which `root` refers to while its options are evaluated, each written value a field of it after its evaluation; a default is none.
1723
+ */
1724
+ #compileArguments(resolution) {
1725
+ if (resolution.arguments.length === 0) {
1726
+ return evaluator => {
1727
+ evaluator.#givenLifetimes = null;
1728
+
1729
+ return NO_ARGUMENTS;
1730
+ };
1731
+ }
1732
+
1733
+ const { buildsObjectRoot } = resolution;
1734
+ const isOptions = resolution.declaration.arguments === 'options';
1735
+ const isReadByLater = isOptions ? Evaluator.#readByLater(resolution.arguments.map(argument => argument.parameter.name), resolution.arguments.map(argument => (argument.isDefault ? null : argument.node))) : null;
1736
+ const argumentPlans = resolution.arguments.map((argument, index) => {
1737
+ const { parameter } = argument;
1738
+ const isOption = isOptions && !argument.isDefault && parameter.kind !== 'expression' && parameter.kind !== 'predicate' && argument.callback?.isBody !== true;
1739
+ const isReadLater = isOption && isReadByLater[index];
1740
+ const isField = isOption && buildsObjectRoot === true;
1741
+
1742
+ if (argument.isDefault || parameter.fixed) {
1743
+ return { name: parameter.name, kind: 'constant', value: argument.isDefault ? parameter.default : argument.value, isReadLater, isField };
1744
+ }
1745
+
1746
+ // A value of Any is checked against its parameter's type as it is evaluated, and enters a structure it holds as its data shape alone.
1747
+ const isOfCheckedType = argument.checkedType === undefined ? null : Evaluator.#compileTypeCheck(argument.checkedType, this.#registry, `the argument '${parameter.name}' of '${NexoModule.writtenNameOf(resolution.declaration)}'`);
1748
+ const copyToCheckedType = argument.checkedType === undefined ? null : Evaluator.#compileStructureCopy(argument.checkedType, this.#registry);
1749
+ const plan = { name: parameter.name, kind: parameter.kind, evaluate: this.#getCompiledFunction(argument.node), propagatesNull: parameter.nulls === 'propagate', isOption, isReadLater, isField, checkedType: argument.checkedType ?? null, isOfCheckedType, copyToCheckedType, pointer: argument.pointer };
1750
+
1751
+ if (argument.callback === undefined) {
1752
+ return plan;
1753
+ }
1754
+
1755
+ // The member calls the function it is given with the arguments the callback contract declares, each checked as the contract's types say, the checks compiled once for every call.
1756
+ const { functions, parameterTypes, resultType, isBody } = argument.callback;
1757
+ const asked = `the function given as '${parameter.name}' of '${NexoModule.writtenNameOf(resolution.declaration)}'`;
1758
+ // An argument the member gives from its receiver's items is checked as the receiver's items were, null standing for no check of its own.
1759
+ const givesCheckedItems = this.#givesCheckedItems(resolution, parameterTypes);
1760
+ const isOfParameterType = parameterTypes.map(parameterType => (givesCheckedItems ? null : Evaluator.#compileTypeCheck(parameterType, this.#registry, asked)));
1761
+ const isOfResultType = resultType === null ? null : Evaluator.#compileTypeCheck(resultType, this.#registry, asked);
1762
+ // A module's implementation calls the function with values of its own, which enter a structure as its data shape alone, where Nexo's own members give the script's values; and a function whose
1763
+ // result may be of Any gives a value that enters the contract's structure so too.
1764
+ const isOwnMember = this.#registry.getDeclaringModuleName(resolution.declaration) === Vocabulary.moduleName;
1765
+ const copyToParameterType = parameterTypes.map(parameterType => (isOwnMember || givesCheckedItems ? null : Evaluator.#compileStructureCopy(parameterType, this.#registry)));
1766
+ const mayGiveAny = isBody !== true && functions.some(target => target.resultType !== undefined && Evaluator.#mayHoldAny(target.resultType));
1767
+ const copyToResultType = resultType === null || !mayGiveAny ? null : Evaluator.#compileStructureCopy(resultType, this.#registry);
1768
+
1769
+ return {
1770
+ ...plan,
1771
+ kind: 'callback',
1772
+ propagatesNull: false,
1773
+ isBody: isBody === true,
1774
+ isPredicate: parameter.kind === 'predicate',
1775
+ // A body of each item reads its item as a value that is not null, as resolution types it, so a null item answers null and the body is never evaluated against it, unless its parameter accepts null items.
1776
+ skipsNullItem: isBody === true && (parameter.kind === 'expression' || parameter.kind === 'predicate') && parameter.nulls !== 'accept',
1777
+ functions: isBody === true ? null : this.#compileFunctions(functions),
1778
+ memberName: NexoModule.writtenNameOf(resolution.declaration),
1779
+ parameterTypes,
1780
+ resultType,
1781
+ isOfParameterType,
1782
+ isOfResultType,
1783
+ copyToParameterType,
1784
+ copyToResultType,
1785
+ pointer: argument.pointer,
1786
+ node: argument.node,
1787
+ };
1788
+ });
1789
+
1790
+ return (evaluator, context) => {
1791
+ const args = {};
1792
+ // The lifetime of each function the call gives its member, which the invocation closes, handed to it as the arguments are made.
1793
+ let lifetimes = null;
1794
+ const enclosingObjectRoot = evaluator.#objectRoot;
1795
+ const enclosingLocalsFrame = evaluator.#localsFrame;
1796
+ const objectRoot = buildsObjectRoot === true ? {} : null;
1797
+ let rootSelections = null;
1798
+
1799
+ if (objectRoot !== null) {
1800
+ evaluator.#objectRoot = objectRoot;
1801
+ }
1802
+
1803
+ try {
1804
+ for (const plan of argumentPlans) {
1805
+ let selections = null;
1806
+ let argumentValue;
1807
+
1808
+ if (plan.kind === 'constant') {
1809
+ args[plan.name] = plan.value;
1810
+ argumentValue = plan.value;
1811
+ } else if (plan.kind === 'callback' && plan.isBody) {
1812
+ // The member calls it when it chooses, after its options are evaluated, each call evaluating the expression anew against its argument, with the names and the object root of the place it is written.
1813
+ const { evaluate } = plan;
1814
+ const capturedFrame = evaluator.#localsFrame;
1815
+ const capturedObjectRoot = evaluator.#objectRoot;
1816
+
1817
+ const lifetime = { isOpen: true };
1818
+
1819
+ (lifetimes ??= []).push(lifetime);
1820
+ args[plan.name] = evaluator.#createCallback(plan, lifetime, argumentValues => (plan.skipsNullItem && argumentValues[0] === null ? null : evaluator.#evaluateWithin(capturedFrame, capturedObjectRoot, evaluate, argumentValues[0])));
1821
+ } else {
1822
+ let value;
1823
+
1824
+ if (plan.isOption) {
1825
+ ({ value, selections } = evaluator.#evaluateOption(plan, context));
1826
+ } else {
1827
+ value = plan.evaluate(evaluator, context);
1828
+ }
1829
+
1830
+ if (value === null && plan.propagatesNull) {
1831
+ return null;
1832
+ }
1833
+
1834
+ if (plan.isOfCheckedType !== null && !plan.isOfCheckedType(value)) {
1835
+ evaluator.#fail('NEXO5021', `The argument '${plan.name}' of '${NexoModule.writtenNameOf(resolution.declaration)}' is declared ${plan.checkedType}, and is given ${Evaluator.#describe(value)}`, plan.pointer);
1836
+ }
1837
+
1838
+ if (plan.copyToCheckedType !== null) {
1839
+ value = plan.copyToCheckedType(value);
1840
+ }
1841
+
1842
+ // A function reference is evaluated where it is written, once, and goes to the member as a callback it calls, while the expression's own names and fields hold the value itself.
1843
+ argumentValue = value;
1844
+
1845
+ if (plan.kind === 'callback') {
1846
+ const lifetime = { isOpen: true };
1847
+
1848
+ (lifetimes ??= []).push(lifetime);
1849
+ args[plan.name] = evaluator.#createCallback(plan, lifetime, argumentValues => evaluator.#callFunction(value, argumentValues, plan.functions, plan.node));
1850
+ } else {
1851
+ args[plan.name] = value;
1852
+ }
1853
+ }
1854
+
1855
+ // A field of the outermost object is recorded where it made any selection, as it completes, for `root` to read, so the list recorded is added to in place.
1856
+ if (plan.isField) {
1857
+ objectRoot[plan.name] = argumentValue;
1858
+
1859
+ if (selections !== null) {
1860
+ const isFirst = rootSelections === null;
1861
+
1862
+ rootSelections = Evaluator.#select(rootSelections, plan.name, selections);
1863
+
1864
+ if (isFirst) {
1865
+ evaluator.#objectFieldSelections.set(objectRoot, rootSelections);
1866
+ }
1867
+ }
1868
+ }
1869
+
1870
+ if (plan.isReadLater) {
1871
+ evaluator.#localsFrame = { names: [plan.name], context, values: [argumentValue], selections: [selections], enclosing: evaluator.#localsFrame };
1872
+ }
1873
+ }
1874
+
1875
+ return args;
1876
+ } finally {
1877
+ evaluator.#givenLifetimes = lifetimes;
1878
+ evaluator.#objectRoot = enclosingObjectRoot;
1879
+ evaluator.#localsFrame = enclosingLocalsFrame;
1880
+ }
1881
+ };
1882
+ }
1883
+
1884
+ /**
1885
+ * The functions a call or a callback contract may call, each compiled once for it and found by its function key: a function the script declares, its parameters and the compiled copy of its body resolved for this call;
1886
+ * or a reference to a module's function, its argument map and its invocation, of the result type resolved for this call. Each call compiles its own, so one function called at two places evaluates each place's body or specialization.
1887
+ * @returns {Map<object, { parameters: string[], evaluateBody: Function } | { argumentMap: object, invocation: object }>}
1888
+ */
1889
+ #compileFunctions(functions) {
1890
+ return new Map(functions.map(entry => [
1891
+ Evaluator.#getFunctionKey(entry),
1892
+ entry.reference === undefined ? { parameters: entry.parameters, evaluateBody: this.#getCompiledFunction(entry.body) } : { argumentMap: entry.argumentMap, invocation: this.#getCompiledInvocation(entry.reference, entry.resultType) },
1893
+ ]));
1894
+ }
1895
+
1896
+ /**
1897
+ * The key a call finds a function's compiled form by: the declaration a reference to a module's function names, or the node of a function the script declares. Two references to one function share it, whatever each captured, which the callee
1898
+ * itself still holds.
1899
+ */
1900
+ static #getFunctionKey(callee) {
1901
+ return callee.reference ?? callee.function;
1902
+ }
1903
+
1904
+ /**
1905
+ * Calls the callee with its arguments' values, which the caller evaluated, as the call's compiled functions say: a function the script declares, its body evaluated in the callee's captured scope, its locals, implicit parameter and object root,
1906
+ * its parameters bound to the values in order; or a reference to a module's function, its declaration invoked as its qualified form is, its receiver the first value or, for one bound to its receiver, the receiver it captured, a null one answering null without invoking the implementation, and its own
1907
+ * arguments bound by its argument map, a null one whose parameter propagates null answering null without invoking it, each parameter the map leaves out taking its default. Its failures are named at the node that called it.
1908
+ */
1909
+ #callFunction(callee, values, functions, node) {
1910
+ const compiled = functions.get(Evaluator.#getFunctionKey(callee));
1911
+ // A reference bound to its receiver gives the receiver it captured before the values it is called with.
1912
+ const argumentValues = callee.isBound === true ? [callee.receiver, ...values] : values;
1913
+
1914
+ if (compiled.invocation === undefined) {
1915
+ const { parameters } = compiled;
1916
+ const enclosingLocalsFrame = this.#localsFrame;
1917
+ const enclosingObjectRoot = this.#objectRoot;
1918
+
1919
+ // The parameters hold the arguments in order, as the list the call was given, which nothing changes.
1920
+ this.#localsFrame = { names: parameters, context: callee.context, values: argumentValues, selections: NO_SELECTIONS, enclosing: callee.capturedFrame };
1921
+ this.#objectRoot = callee.objectRoot;
1922
+
1923
+ try {
1924
+ return compiled.evaluateBody(this, callee.context);
1925
+ } finally {
1926
+ this.#localsFrame = enclosingLocalsFrame;
1927
+ this.#objectRoot = enclosingObjectRoot;
1928
+ }
1929
+ }
1930
+
1931
+ const { argumentMap, invocation } = compiled;
1932
+ const receiver = invocation.takesReceiver ? argumentValues[0] : undefined;
1933
+
1934
+ if (receiver === null) {
1935
+ return null;
1936
+ }
1937
+
1938
+ const own = invocation.takesReceiver ? argumentValues.slice(1) : argumentValues;
1939
+ const args = {};
1940
+
1941
+ for (const [index, { name, propagatesNull }] of argumentMap.supplied.entries()) {
1942
+ // A null argument whose parameter propagates null makes the call answer null without invoking the implementation.
1943
+ if (own[index] === null && propagatesNull) {
1944
+ return null;
1945
+ }
1946
+
1947
+ args[name] = own[index];
1948
+ }
1949
+
1950
+ for (const { name, value } of argumentMap.defaults) {
1951
+ args[name] = value;
1952
+ }
1953
+
1954
+ return this.#invokeImplementation(invocation, receiver, args, node);
1955
+ }
1956
+
1957
+ /**
1958
+ * Creates the function a member is given for a parameter that takes one, open while its lifetime is, which `#invokeImplementation` closes when the member's invocation ends, however it ends: each call checks the member's arguments
1959
+ * against the callback contract's parameter types, gives them to the body, which evaluates the argument's expression against the first or calls the function the argument gave, checks the body's result against the contract's
1960
+ * result type, and answers it, or its truthiness for a predicate. A call after the invocation fails, while the function the body calls stays callable wherever else it is given or called.
1961
+ */
1962
+ #createCallback(plan, lifetime, body) {
1963
+ // The body and the evaluator are held by the lifetime alone, which closing lets go of, so a function its holder keeps past its lifetime holds nothing of the evaluation: neither the bindings, nor the environment,
1964
+ // nor the locals it read. Where it stands in the text is the definition's, which it keeps to say so.
1965
+ lifetime.body = body;
1966
+ lifetime.evaluator = this;
1967
+
1968
+ return Evaluator.#createLifetimeCallback(plan, lifetime, this.#resolvedDefinition, this.#locations);
1969
+ }
1970
+
1971
+ /** The callback of a lifetime, made apart from any evaluator, so it reaches one only through the lifetime while it is open. */
1972
+ static #createLifetimeCallback(plan, lifetime, resolvedDefinition, locations) {
1973
+ const { name, memberName, parameterTypes, resultType, isOfParameterType, isOfResultType, copyToParameterType, copyToResultType, isPredicate, pointer } = plan;
1974
+ const fail = (code, message) => {
1975
+ const definitionPointer = resolvedDefinition.resolveDefinitionPointer(pointer);
1976
+
1977
+ throw NexoErrors.evaluation(code, message, locations?.locate(definitionPointer) ?? { pointer: definitionPointer });
1978
+ };
1979
+
1980
+ return (...argumentValues) => {
1981
+ const { evaluator, body } = lifetime;
1982
+
1983
+ try {
1984
+ if (!lifetime.isOpen) {
1985
+ if (lifetime.isKept === true) {
1986
+ fail('NEXO5051', `The function given as '${name}' of '${memberName}' is called after its host disposed the call, and a host calls the functions a call gives it only while it keeps the call`);
1987
+ }
1988
+
1989
+ fail('NEXO5008', `The function given as '${name}' of '${memberName}' is called after '${memberName}' returned, and a member calls the functions it is given only while its own invocation lasts`);
1990
+ }
1991
+
1992
+ if (argumentValues.length !== parameterTypes.length) {
1993
+ fail('NEXO5503', `The function given as '${name}' of '${memberName}' is called with ${argumentValues.length === 1 ? 'one argument' : `${argumentValues.length} arguments`}, and its contract declares ${parameterTypes.length}`);
1994
+ }
1995
+
1996
+ for (let index = 0; index < argumentValues.length; index++) {
1997
+ if (isOfParameterType[index] !== null && !isOfParameterType[index](argumentValues[index])) {
1998
+ fail('NEXO5503', `The function given as '${name}' of '${memberName}' is called with ${Evaluator.#describe(argumentValues[index])}, and its contract declares ${parameterTypes[index]}`);
1999
+ }
2000
+ }
2001
+
2002
+ const given = copyToParameterType.some(copy => copy !== null) ? argumentValues.map((argument, index) => (copyToParameterType[index] === null ? argument : copyToParameterType[index](argument))) : argumentValues;
2003
+ const value = body(given);
2004
+
2005
+ if (isOfResultType !== null && !isOfResultType(value)) {
2006
+ fail('NEXO5504', `The function given as '${name}' of '${memberName}' gave ${Evaluator.#describe(value)}, and its contract declares ${resultType}`);
2007
+ }
2008
+
2009
+ // A predicate's contract takes any value, and the member receives its truthiness.
2010
+ return isPredicate ? Evaluator.#isTruthy(value) : copyToResultType === null ? value : copyToResultType(value);
2011
+ } catch (error) {
2012
+ // A thrown value that is no object is the member's to report, as anything it throws is; a lifetime closed has no evaluation to report it to.
2013
+ if (evaluator !== null && evaluator !== undefined && error !== null && typeof error === 'object') {
2014
+ evaluator.#callbackErrors.add(error);
2015
+ }
2016
+
2017
+ throw error;
2018
+ }
2019
+ };
2020
+ }
2021
+
2022
+ /** Closes a lifetime: the functions it gave are called no more, and it lets go of the body and the evaluator each held. */
2023
+ static #closeLifetime(lifetime) {
2024
+ lifetime.isOpen = false;
2025
+ lifetime.body = null;
2026
+ lifetime.evaluator = null;
2027
+ }
2028
+
2029
+ /**
2030
+ * The lifetimes of the functions the arguments made last give their member, or null where they give none, which the caller reads as the arguments are made, before evaluating anything else, and hands to the
2031
+ * invocation, which closes them. A host call's are kept, `isKept`, and its prepared call's `dispose` closes them; one given to an invocation an argument's failure prevented keeps its lifetime as it is. An index of
2032
+ * every open function, which each invocation searched by its arguments, measured a few hundredths of an evaluation.
2033
+ * @type {{ isOpen: boolean, isKept?: boolean, body?: Function | null, evaluator?: Evaluator | null }[] | null}
2034
+ */
2035
+ #givenLifetimes = null;
2036
+
2037
+ /** The errors a function given to a member raised, which reach the expression as they are, not as the member's failure. @type {WeakSet<object>} */
2038
+ #callbackErrors = new WeakSet();
2039
+
2040
+ /** What the compiled function gives for the implicit parameter with the frame of names and the object root in scope, those of the place it was written, both restored after it. */
2041
+ #evaluateWithin(frame, objectRoot, evaluate, context) {
2042
+ const enclosingLocalsFrame = this.#localsFrame;
2043
+ const enclosingObjectRoot = this.#objectRoot;
2044
+
2045
+ this.#localsFrame = frame;
2046
+ this.#objectRoot = objectRoot;
2047
+
2048
+ try {
2049
+ return evaluate(this, context);
2050
+ } finally {
2051
+ this.#localsFrame = enclosingLocalsFrame;
2052
+ this.#objectRoot = enclosingObjectRoot;
2053
+ }
2054
+ }
2055
+
2056
+ /** The value of a written option and the selections its evaluation made, which are made where the option is evaluated as well. */
2057
+ #evaluateOption(plan, context) {
2058
+ const enclosingSelections = this.#selections;
2059
+
2060
+ this.#selections = null;
2061
+
2062
+ try {
2063
+ const value = plan.evaluate(this, context);
2064
+
2065
+ return { value, selections: this.#selections };
2066
+ } finally {
2067
+ const selections = this.#selections;
2068
+
2069
+ this.#selections = enclosingSelections;
2070
+ this.#selections = Evaluator.#joinSelections(this.#selections, selections);
2071
+ }
2072
+ }
2073
+
2074
+ /** An object expression, its context, its fields' names and values and whether it holds locals found once; applied to each item of a list it follows, and to the value it follows otherwise. */
2075
+ #compileObject(node) {
2076
+ const hasTarget = node.context !== undefined;
2077
+ const evaluateSource = hasTarget ? this.#getCompiledFunction(node.context) : null;
2078
+ // An item's source is of the kind its established type gives it, a reference where its items are of a reference type, whatever the value itself is.
2079
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
2080
+ const isOfReferences = resolution?.kind === 'projection' && this.#registry.isReferenceType(resolution.itemType.toNonNullable());
2081
+ const localNames = node.locals === undefined ? null : node.locals.map(local => local.name);
2082
+ // A field the entries after it read is in scope for them, by the name the definition gives it.
2083
+ const isReadByLater = Evaluator.#readByLater(node.objectFields.map(field => field.name), node.objectFields.map(field => field.value));
2084
+ const fields = node.objectFields.map((field, index) => ({
2085
+ name: field.name,
2086
+ isReadLater: isReadByLater[index],
2087
+ evaluate: this.#getCompiledFunction(field.value),
2088
+ }));
2089
+
2090
+ if (resolution?.kind === 'construction') {
2091
+ return this.#compileConstruction(node, resolution, fields, localNames);
2092
+ }
2093
+
2094
+ // It applies to each item only where its resolution projects a list; a value of Any is one value, a list it holds among them, as its resolution typed it.
2095
+ const isProjection = resolution?.kind === 'projection';
2096
+
2097
+ return (evaluator, context) => {
2098
+ const value = hasTarget ? evaluateSource(evaluator, context) : context;
2099
+
2100
+ if (!(hasTarget && isProjection)) {
2101
+ return value === null && hasTarget ? null : evaluator.#applyFields(node, fields, localNames, value);
2102
+ }
2103
+
2104
+ const items = value.map(item => (item === null ? null : evaluator.#applyFields(node, fields, localNames, item)));
2105
+
2106
+ evaluator.#recordLineage(value, items, isOfReferences);
2107
+
2108
+ return items;
2109
+ };
2110
+ }
2111
+
2112
+ /**
2113
+ * An object expression constructing a structure: its fields evaluated as an object's are, in the order written, then the structure of its data shape, a property it leaves out null, each in the shape's order,
2114
+ * frozen. A value of Any resolution couldn't check is checked here against its property's type (NEXO5038).
2115
+ */
2116
+ #compileConstruction(node, { type, checks }, fields, localNames) {
2117
+ const shape = [...this.#registry.getDataShape(type).keys()];
2118
+ const asked = `constructing ${type}`;
2119
+ const checked = [...checks].map(([name, propertyType]) => ({ name, propertyType, conforms: Evaluator.#compileTypeCheck(propertyType, this.#registry, asked), copy: Evaluator.#compileStructureCopy(propertyType, this.#registry) }));
2120
+ const pointer = this.#pointerOf(node);
2121
+
2122
+ return (evaluator, context) => {
2123
+ const object = evaluator.#applyFields(node, fields, localNames, context);
2124
+
2125
+ for (const { name, propertyType, conforms, copy } of checked) {
2126
+ if (!conforms(object[name])) {
2127
+ evaluator.#fail('NEXO5038', `The property '${name}' of the ${type} written here is ${propertyType}, and is given ${Evaluator.#describe(object[name])}`, pointer);
2128
+ }
2129
+
2130
+ // A value of Any enters a structure its property holds as its data shape alone.
2131
+ if (copy !== null) {
2132
+ object[name] = copy(object[name]);
2133
+ }
2134
+ }
2135
+
2136
+ const structure = Object.fromEntries(shape.map(name => [name, Object.hasOwn(object, name) ? object[name] : null]));
2137
+
2138
+ StructureTypes.recordType(structure, evaluator.#registry, type);
2139
+
2140
+ return Object.freeze(structure);
2141
+ };
2142
+ }
2143
+
2144
+ /**
2145
+ * Records the lineage of a list projected item by item from the input: each item's source, the input item it came from, of the kind the input's established type gives it, and the provenance it inherits, the input's
2146
+ * where a projection built the input and its own source otherwise. Two inputs can project to the same data, and one input can stand more than once, each occurrence keeping its own.
2147
+ */
2148
+ #recordLineage(input, projected, isOfReferences) {
2149
+ const inherited = this.#provenanceByList.get(input) ?? null;
2150
+ const sources = input.map(item => Object.freeze(item === null ? { kind: 'null' } : isOfReferences ? { kind: 'reference', value: item } : { kind: 'data', value: Evaluator.#frozenDataOf(item) }));
2151
+
2152
+ this.#sourcesByList.set(projected, Object.freeze(sources));
2153
+ this.#provenanceByList.set(projected, Object.freeze(sources.map((source, index) => (inherited === null ? source : inherited[index]))));
2154
+ }
2155
+
2156
+ /** The data as Nexo holds it, frozen throughout: the value itself where it is frozen already or is no object, and a frozen copy of its lists and objects otherwise. */
2157
+ static #frozenDataOf(value) {
2158
+ if (value === null || typeof value !== 'object' || Object.isFrozen(value)) {
2159
+ return value;
2160
+ }
2161
+
2162
+ if (Array.isArray(value)) {
2163
+ return Object.freeze(value.map(item => Evaluator.#frozenDataOf(item)));
2164
+ }
2165
+
2166
+ const copy = {};
2167
+
2168
+ for (const [name, field] of Object.entries(value)) {
2169
+ ObjectUtils.defineDataProperty(copy, name, Evaluator.#frozenDataOf(field));
2170
+ }
2171
+
2172
+ return Object.freeze(copy);
2173
+ }
2174
+
2175
+ /**
2176
+ * Whether each entry's name, a field's or an option's, is read by an entry after it: by a local or a field of the name read, or the function it holds called, anywhere within one, which an entry left out, null,
2177
+ * never is. Each entry is walked once, from the last back, gathering the names it reads, rather than each entry's later ones walked again for each name.
2178
+ */
2179
+ static #readByLater(names, nodes) {
2180
+ const read = new Set();
2181
+ const isRead = new Array(names.length);
2182
+
2183
+ for (let index = names.length - 1; index >= 0; index--) {
2184
+ isRead[index] = read.has(names[index]);
2185
+
2186
+ if (nodes[index] !== null) {
2187
+ Evaluator.#gatherLocalsRead(nodes[index], read);
2188
+ }
2189
+ }
2190
+
2191
+ return isRead;
2192
+ }
2193
+
2194
+ /** Adds to the names the name of each local read anywhere beneath the part, a scope beneath it that hides one of its names among them. */
2195
+ static #gatherLocalsRead(part, names) {
2196
+ if (Array.isArray(part)) {
2197
+ for (const item of part) {
2198
+ Evaluator.#gatherLocalsRead(item, names);
2199
+ }
2200
+ } else if (part !== null && typeof part === 'object') {
2201
+ if (part.kind === 'local') {
2202
+ names.add(part.name);
2203
+ }
2204
+
2205
+ for (const key of Object.keys(part)) {
2206
+ Evaluator.#gatherLocalsRead(part[key], names);
2207
+ }
2208
+ }
2209
+ }
2210
+
2211
+ /**
2212
+ * The object the fields give the context, the value the expression follows or an item it projects, evaluated against it in order with the expression's locals in scope, the names in `localNames`, with the selections made for each field; a field's name is a name, so it is assigned as it is.
2213
+ * A field the entries after it read is in scope for them from its evaluation on, holding its value and the selections it made, so a read of it evaluates nothing again.
2214
+ */
2215
+ #applyFields(node, fields, localNames, context) {
2216
+ const object = {};
2217
+ const enclosingSelections = this.#selections;
2218
+ const enclosingLocalsFrame = this.#localsFrame;
2219
+ const enclosingObjectRoot = this.#objectRoot;
2220
+ // An application no other encloses builds the outermost object, this object, which `root` refers to in its body; any other builds within that object.
2221
+ const isRoot = enclosingObjectRoot === null;
2222
+ let fieldSelections = null;
2223
+
2224
+ if (node.locals !== undefined) {
2225
+ this.#localsFrame = { names: localNames, node, context, values: [], selections: [], enclosing: enclosingLocalsFrame };
2226
+ }
2227
+
2228
+ if (isRoot) {
2229
+ this.#objectRoot = object;
2230
+ }
2231
+
2232
+ try {
2233
+ for (const field of fields) {
2234
+ this.#selections = null;
2235
+
2236
+ const value = field.evaluate(this, context);
2237
+
2238
+ object[field.name] = value;
2239
+
2240
+ // A field is recorded where it made any selection, the outermost object's as it completes, for `root` to read, so the list recorded is added to in place; a field recorded with none reads alike.
2241
+ if (this.#selections !== null) {
2242
+ const isFirst = fieldSelections === null;
2243
+
2244
+ fieldSelections = Evaluator.#select(fieldSelections, field.name, this.#selections);
2245
+
2246
+ if (isFirst && isRoot) {
2247
+ this.#objectFieldSelections.set(object, fieldSelections);
2248
+ }
2249
+ }
2250
+
2251
+ if (field.isReadLater) {
2252
+ this.#localsFrame = { names: [field.name], context, values: [value], selections: [this.#selections], enclosing: this.#localsFrame };
2253
+ }
2254
+ }
2255
+ } finally {
2256
+ this.#selections = enclosingSelections;
2257
+ this.#localsFrame = enclosingLocalsFrame;
2258
+ this.#objectRoot = enclosingObjectRoot;
2259
+ }
2260
+
2261
+ if (fieldSelections !== null && !isRoot) {
2262
+ this.#objectFieldSelections.set(object, fieldSelections);
2263
+ }
2264
+
2265
+ return object;
2266
+ }
2267
+
2268
+ /** Whether a value of the type may be of Any where it stands, or hold one in a collection or an object: a structure's own fields are its declaration's, and a function's what it is given. */
2269
+ static #mayHoldAny(type) {
2270
+ if (type.kind === 'any') {
2271
+ return true;
2272
+ }
2273
+
2274
+ if (type.isList) {
2275
+ return Evaluator.#mayHoldAny(type.itemType);
2276
+ }
2277
+
2278
+ if (type.isDictionary || type.isPair) {
2279
+ return type.typeArguments.some(typeArgument => Evaluator.#mayHoldAny(typeArgument));
2280
+ }
2281
+
2282
+ return type.fieldTypes !== null && [...type.fieldTypes.values()].some(fieldType => Evaluator.#mayHoldAny(fieldType));
2283
+ }
2284
+
2285
+ /**
2286
+ * The copy a type makes of a value it admits: a declared structure as its declared fields alone, each read as the value offers it, a getter's included, so its declaration is the shape a result holds,
2287
+ * and a list item by item; null for a type that holds no declared structure, whose values stand as they are. A structure's fields are found at its first copy, so a structure that holds itself is copied as deep as its value goes.
2288
+ */
2289
+ static #compileStructureCopy(type, registry) {
2290
+ if (type.isList) {
2291
+ const copyItem = Evaluator.#compileStructureCopy(type.itemType, registry);
2292
+
2293
+ return copyItem === null ? null : value => (value === null ? null : value.map(item => (item === null ? null : copyItem(item))));
2294
+ }
2295
+
2296
+ // A dictionary's keys and values, and a pair's, are copied as their types declare them, where either is a structure.
2297
+ if (type.isDictionary || type.isPair) {
2298
+ const [copyKey, copyValue] = type.typeArguments.map(typeArgument => Evaluator.#compileStructureCopy(typeArgument, registry));
2299
+ const copy = (part, copyPart) => (copyPart === null || part === null ? part : copyPart(part));
2300
+
2301
+ if (copyKey === null && copyValue === null) {
2302
+ return null;
2303
+ }
2304
+
2305
+ return type.isPair
2306
+ ? value => (value === null ? null : new Pair(copy(value.key, copyKey), copy(value.value, copyValue)))
2307
+ : value => (value === null ? null : new Dictionary(value.entries.map(entry => [copy(entry.key, copyKey), copy(entry.value, copyValue)])));
2308
+ }
2309
+
2310
+ switch (type.kind) {
2311
+ case 'named': {
2312
+ const isStructured = registry.getTypeDeclaration(type)?.kind === 'structured';
2313
+
2314
+ if (!isStructured) {
2315
+ return null;
2316
+ }
2317
+
2318
+ // It holds the registry alone, never an evaluator, so it holds nothing an evaluation bound.
2319
+ let fields = null;
2320
+ // The type each copy keeps through a value of Any.
2321
+ const structured = type.toNonNullable();
2322
+
2323
+ return value => {
2324
+ if (value === null) {
2325
+ return null;
2326
+ }
2327
+
2328
+ // A structure Nexo made of the type is its declared shape already, its own fields and the structures they hold, since every value of a structured type holds its data shape alone; a host's own is copied.
2329
+ if (StructureTypes.getType(value, registry)?.equals(structured) === true) {
2330
+ return value;
2331
+ }
2332
+
2333
+ if (fields === null) {
2334
+ fields = [...registry.getDataShape(type)].map(([name, fieldType]) => [name, Evaluator.#compileStructureCopy(fieldType, registry)]);
2335
+ }
2336
+
2337
+ const copy = {};
2338
+
2339
+ for (const [name, copyField] of fields) {
2340
+ const field = value[name];
2341
+
2342
+ // A field's name is a name, so it is assigned as it is.
2343
+ copy[name] = copyField === null || field === null ? field : copyField(field);
2344
+ }
2345
+
2346
+ StructureTypes.recordType(copy, registry, structured);
2347
+
2348
+ return copy;
2349
+ };
2350
+ }
2351
+ default:
2352
+ return null;
2353
+ }
2354
+ }
2355
+
2356
+ /**
2357
+ * The capture of a value a host gives where the type is declared, as the language's collection: a set where a list is, its items in their order, a map where a dictionary is, its entries in their order, and an object
2358
+ * of a key and a value alone where a pair is, each item, key and value captured in turn; null where the type holds no collection. A value of the language's form is kept, and one of any other form left for its type's
2359
+ * check to refuse, so a capture never makes a value of a type a check would refuse.
2360
+ */
2361
+ static #compileCollectionCapture(type) {
2362
+ if (type.isList) {
2363
+ const captureItem = Evaluator.#compileCollectionCapture(type.itemType);
2364
+
2365
+ return value => {
2366
+ const items = value instanceof Set ? [...value] : value;
2367
+
2368
+ return captureItem === null || !Array.isArray(items) ? items : items.map(item => (item === null ? null : captureItem(item)));
2369
+ };
2370
+ }
2371
+
2372
+ if (!type.isDictionary && !type.isPair) {
2373
+ return null;
2374
+ }
2375
+
2376
+ const [captureKey, captureValue] = type.typeArguments.map(typeArgument => Evaluator.#compileCollectionCapture(typeArgument));
2377
+ const capture = (part, capturePart) => (capturePart === null || part === null ? part : capturePart(part));
2378
+
2379
+ if (type.isPair) {
2380
+ return value => (ObjectUtils.isPlainObject(value) && Object.keys(value).length === 2 && Object.hasOwn(value, 'key') && Object.hasOwn(value, 'value') ? new Pair(capture(value.key, captureKey), capture(value.value, captureValue)) : value);
2381
+ }
2382
+
2383
+ return value => (value instanceof Map ? new Dictionary([...value].map(([key, entry]) => [capture(key, captureKey), capture(entry, captureValue)])) : value);
2384
+ }
2385
+
2386
+ /** Whether a value is one the type admits, with the type's structure found once and each declared structure's fields at its first check. */
2387
+ static #compileTypeCheck(type, registry, asked) {
2388
+ const admits = Evaluator.#compileNonNullTypeCheck(type, registry, asked);
2389
+
2390
+ // A collection, an object, a structure, a scalar and a position refuse null and nothing in their own checks, so where the type is not nullable its check is theirs alone; any other's, a reference's
2391
+ // membership among them, is never asked of either.
2392
+ if (!type.isNullable && Evaluator.#refusesAbsence(type, registry)) {
2393
+ return admits;
2394
+ }
2395
+
2396
+ const { isNullable } = type;
2397
+
2398
+ return value => (value === undefined ? false : value === null ? isNullable : admits(value));
2399
+ }
2400
+
2401
+ /** Whether the type's own check refuses null and nothing: a list's, a dictionary's, a pair's, an object's and a named type's that no module declares as a reference type, which no check of `Any` or a type parameter does. */
2402
+ static #refusesAbsence(type, registry) {
2403
+ if (type.isList || type.isDictionary || type.isPair) {
2404
+ return true;
2405
+ }
2406
+
2407
+ return type.kind === 'object' || (type.kind === 'named' && !registry.isReferenceType(type));
2408
+ }
2409
+
2410
+ /** A compiled function holds the registry alone, never an evaluator, so it holds nothing an evaluation bound; a structure that holds itself is checked as deep as its value goes. */
2411
+ static #compileNonNullTypeCheck(type, registry, asked) {
2412
+ if (type.isList) {
2413
+ const checkItem = Evaluator.#compileTypeCheck(type.itemType, registry, asked);
2414
+
2415
+ return value => Array.isArray(value) && ArrayUtils.findMissingIndex(value) === -1 && value.every(checkItem);
2416
+ }
2417
+
2418
+ // A dictionary is the language's, each key of its key type and each value of its value type, and a pair each of its parts.
2419
+ if (type.isDictionary || type.isPair) {
2420
+ const [checkKey, checkValue] = type.typeArguments.map(typeArgument => Evaluator.#compileTypeCheck(typeArgument, registry, asked));
2421
+
2422
+ return type.isPair
2423
+ ? value => value instanceof Pair && checkKey(value.key) && checkValue(value.value)
2424
+ : value => value instanceof Dictionary && value.keys.every(checkKey) && value.values.every(checkValue);
2425
+ }
2426
+
2427
+ switch (type.kind) {
2428
+ // A value of Any is of any type, and is checked where a type is declared for it.
2429
+ case 'any':
2430
+ case 'typeParameter':
2431
+ return () => true;
2432
+ case 'object': {
2433
+ const names = [...type.fieldTypes.keys()];
2434
+ const checks = [...type.fieldTypes.values()].map(fieldType => Evaluator.#compileTypeCheck(fieldType, registry, asked));
2435
+
2436
+ return value => ObjectUtils.isNonArrayObject(value) && Evaluator.#fieldsConform(value, names, checks);
2437
+ }
2438
+ default: {
2439
+ const { name } = type;
2440
+ const isStructured = registry.getTypeDeclaration(type)?.kind === 'structured';
2441
+
2442
+ if (registry.isReferenceType(type)) {
2443
+ return value => registry.belongsTo(value, type, asked);
2444
+ }
2445
+
2446
+ if (type.moduleName === null && NexoModule.scalarTypeNames.includes(name)) {
2447
+ return ValueCapture.getScalarCheck(name);
2448
+ }
2449
+
2450
+ // A position is the language's own value of its kind.
2451
+ if (type.moduleName === null && (name === 'Index' || name === 'Range')) {
2452
+ const kind = name === 'Index' ? Index : Range;
2453
+
2454
+ return value => value instanceof kind;
2455
+ }
2456
+
2457
+ if (!isStructured) {
2458
+ return () => false;
2459
+ }
2460
+
2461
+ let names = null;
2462
+ let checks = null;
2463
+
2464
+ return value => {
2465
+ if (!ObjectUtils.isNonArrayObject(value)) {
2466
+ return false;
2467
+ }
2468
+
2469
+ if (names === null) {
2470
+ const shape = registry.getDataShape(type);
2471
+
2472
+ names = [...shape.keys()];
2473
+ checks = [...shape.values()].map(fieldType => Evaluator.#compileTypeCheck(fieldType, registry, asked));
2474
+ }
2475
+
2476
+ return Evaluator.#fieldsConform(value, names, checks);
2477
+ };
2478
+ }
2479
+ }
2480
+ }
2481
+
2482
+ /** Whether each field the names name holds a value its check, at the same place, admits, in order, the first that doesn't ending the checks. */
2483
+ static #fieldsConform(value, names, checks) {
2484
+ for (let index = 0; index < names.length; index++) {
2485
+ if (!checks[index](value[names[index]])) {
2486
+ return false;
2487
+ }
2488
+ }
2489
+
2490
+ return true;
2491
+ }
2492
+
2493
+ /**
2494
+ * The value of the local the node names: evaluated at its first use in the application of the object expression that holds it, against that application's implicit parameter and the locals in scope where it is written, and the same value at every later use there.
2495
+ * The selections its evaluation made are recorded at every place in the result it is used.
2496
+ */
2497
+ #evaluateLocal(node) {
2498
+ let frame = this.#localsFrame;
2499
+
2500
+ let index = frame.names.indexOf(node.name);
2501
+
2502
+ // A frame is an object expression's, whose locals are evaluated at their first use, or a body's, whose bindings it evaluated in order before its result.
2503
+ while (index === -1) {
2504
+ frame = frame.enclosing;
2505
+ index = frame.names.indexOf(node.name);
2506
+ }
2507
+
2508
+ // An object expression's local is evaluated at its first use; no value the language holds is undefined, so undefined marks one yet to be.
2509
+ if (frame.node !== undefined && frame.values[index] === undefined) {
2510
+ const enclosingLocalsFrame = this.#localsFrame;
2511
+ const enclosingSelections = this.#selections;
2512
+
2513
+ this.#localsFrame = frame;
2514
+ this.#selections = null;
2515
+
2516
+ try {
2517
+ frame.values[index] = this.#evaluateNode(frame.node.locals[index].value, frame.context);
2518
+ frame.selections[index] = this.#selections;
2519
+ } finally {
2520
+ this.#localsFrame = enclosingLocalsFrame;
2521
+ this.#selections = enclosingSelections;
2522
+ }
2523
+ }
2524
+
2525
+ const localSelections = frame.selections[index] ?? null;
2526
+
2527
+ if (localSelections !== null) {
2528
+ this.#selections = Evaluator.#joinSelections(this.#selections, localSelections);
2529
+ }
2530
+
2531
+ return frame.values[index];
2532
+ }
2533
+
2534
+ /** Evaluates a member whose resolution is selected as it is evaluated, a computed property access's by the name it computes, its receiver the node it targets. */
2535
+ #evaluateMember(node, context) {
2536
+ // A projection stands the value its host supplied where the host call is, so the call is never evaluated.
2537
+ if (node === this.#projectionContext?.node) {
2538
+ return this.#projectionContext.value;
2539
+ }
2540
+
2541
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
2542
+ const receiver = this.#evaluateNode(node.receiver, context);
2543
+
2544
+ if (receiver === null) {
2545
+ return null;
2546
+ }
2547
+
2548
+ if (resolution.kind !== 'computedPropertyAccess') {
2549
+ return this.#evaluateResolvedAccess(resolution, receiver, node, context);
2550
+ }
2551
+
2552
+ // A computed property access computes its name once and reads the property it selects, which is recorded for the value's place in the result.
2553
+ const name = this.#evaluateNode(node.name, context);
2554
+
2555
+ this.#selections = Evaluator.#select(this.#selections, node, name);
2556
+
2557
+ return this.#evaluateResolvedAccess(resolution.resolutionsByName.get(name), receiver, node, context);
2558
+ }
2559
+
2560
+ /**
2561
+ * Evaluates the access the resolution names on the receiver: reads a value it stores, or a completed field of the object root, or evaluates the arguments the declaration is given and invokes it with them, answering null
2562
+ * without invoking it where a null argument propagates.
2563
+ */
2564
+ #evaluateResolvedAccess(resolution, receiver, node, context) {
2565
+ if (resolution.kind === 'objectField' || resolution.kind === 'dataProperty' || resolution.kind === 'objectRootField') {
2566
+ return this.#readStoredValue(receiver, resolution.name);
2567
+ }
2568
+
2569
+ const args = this.#evaluateArguments(resolution, context);
2570
+
2571
+ return args === null ? null : this.#invokeImplementation(this.#getCompiledInvocation(resolution.declaration, resolution.type), receiver, args, node, false, this.#givenLifetimes);
2572
+ }
2573
+
2574
+ /**
2575
+ * A value the receiver stores under the name, an object field or a property of a structured type's data, a completed field of the outermost object read through a reference to it among them, as its evaluation gave it:
2576
+ * the selections that evaluation made, where an object expression of this evaluation built the receiver, recorded where it is read; null for no receiver, where a receiver that can be null is. Its metadata is traced by its
2577
+ * resolution's kind, which tells a field of the root, which follows its initializer, from any other.
2578
+ */
2579
+ #readStoredValue(receiver, name) {
2580
+ if (receiver === null) {
2581
+ return null;
2582
+ }
2583
+
2584
+ const selections = Evaluator.#selectionOf(this.#objectFieldSelections.get(receiver) ?? null, name) ?? null;
2585
+
2586
+ if (selections !== null) {
2587
+ this.#selections = Evaluator.#joinSelections(this.#selections, selections);
2588
+ }
2589
+
2590
+ return receiver[name] ?? null;
2591
+ }
2592
+
2593
+ /**
2594
+ * Refuses an equality whose operands, one of Any among them, are found as they are evaluated to be of types it never compares, as resolution refuses where it knows them: a structured value, a list or an object
2595
+ * compared with a value that isn't null (NEXO5025), and values of two types that fit neither way (NEXO5024). A null operand is compared with any value.
2596
+ */
2597
+ #requireComparable(node, leftValue, rightValue) {
2598
+ if (leftValue === null || rightValue === null) {
2599
+ return;
2600
+ }
2601
+
2602
+ const { operator } = this.#resolvedDefinition.resolutionOf(node);
2603
+ const pointer = this.#pointerOf(node);
2604
+ const typeOf = value => this.#getActualType(value, `'${operator}'`, reason => this.#fail('NEXO5025', `'${operator}' compares strings, numbers, Booleans and references, and ${reason}`, pointer));
2605
+ const leftType = typeOf(leftValue);
2606
+ const rightType = typeOf(rightValue);
2607
+ const { typeChecker } = this.#registry;
2608
+
2609
+ for (const type of [leftType, rightType]) {
2610
+ if (!typeChecker.isComparable(type)) {
2611
+ this.#fail('NEXO5025', `'${operator}' compares a value of ${type} with null alone, since only strings, numbers, Booleans and references are compared with each other`, pointer);
2612
+ }
2613
+ }
2614
+
2615
+ if (!typeChecker.fitsEitherWay(leftType, rightType)) {
2616
+ this.#fail('NEXO5024', `'${operator}' compares values whose types fit one way or the other, and its operands were found to be of ${leftType} and ${rightType}, which fit neither way`, pointer);
2617
+ }
2618
+ }
2619
+
2620
+ /**
2621
+ * The result of an arithmetic operation, refused where an operand is no number, which a binding of no number can still give, where `/` or `%` divides by zero,
2622
+ * and where the result is no finite number, so neither infinity nor NaN reaches a result; each is an evaluation error at the operation.
2623
+ */
2624
+ #arithmeticResult(node, result, left, right) {
2625
+ const operator = node.kind === 'arithmeticNegation' ? '-' : node.operator;
2626
+
2627
+ if (typeof left !== 'number' || typeof right !== 'number') {
2628
+ this.#fail('NEXO5505', `'${operator}' takes numbers, and is given ${typeof left !== 'number' ? Evaluator.#describe(left) : Evaluator.#describe(right)}`, this.#pointerOf(node));
2629
+ }
2630
+
2631
+ if ((operator === '/' || operator === '%') && right === 0 && node.kind === 'arithmetic') {
2632
+ this.#fail('NEXO5005', `'${operator}' divides by zero`, this.#pointerOf(node));
2633
+ }
2634
+
2635
+ if (!Number.isFinite(result)) {
2636
+ this.#fail('NEXO5020', `The result of '${operator}' is no finite number`, this.#pointerOf(node));
2637
+ }
2638
+
2639
+ return result;
2640
+ }
2641
+
2642
+ /** The arguments by parameter name, or null where a null argument makes the call answer null, evaluated as a compiled member's are, their plan made once for the member's resolution. */
2643
+ #evaluateArguments(resolution, context) {
2644
+ let evaluateArguments = this.#compiledArguments.get(resolution);
2645
+
2646
+ if (evaluateArguments === undefined) {
2647
+ evaluateArguments = this.#compiler.#compileArguments(resolution);
2648
+ this.#compiledArguments.set(resolution, evaluateArguments);
2649
+ }
2650
+
2651
+ return evaluateArguments(this, context);
2652
+ }
2653
+
2654
+ /**
2655
+ * Carries out a compiled invocation with its receiver and its arguments by parameter name, the one protocol every invocation of an implementation follows: it fails a declaration without an implementation, asks the host's
2656
+ * `beforeInvoke` for the samples a maintained member reads, answering null where one is pending, gives the implementation one object it reads by name, closes the functions it was given once it returns, checks its answer
2657
+ * against the invocation's result type, tells the host's `afterInvoke`, and answers a copy of the structures the answer holds. Its failures are located at the node that invoked it. Its request says whether the member was
2658
+ * selected as it is evaluated.
2659
+ */
2660
+ #invokeImplementation(invocation, receiver, args, node, isDeferred = false, lifetimes = null) {
2661
+ const { declaration, implementation, takesReceiver, answersReceiverItems, projectsItems, readsMaintained, takesLimits, resultType, reliesOnAnswer, captureCollections, isOfResultType, copyStructure } = invocation;
2662
+
2663
+ if (implementation === undefined) {
2664
+ this.#fail('NEXO4001', `The module '${this.#registry.getDeclaringModuleName(declaration)}' gives '${NexoModule.writtenNameOf(declaration)}' no implementation`, this.#pointerOf(node));
2665
+ }
2666
+
2667
+ // A request names its call by where it stands in the definition, so a call within a script function's body names the part of the body it is, whichever call of the function reached it. A module member is read and
2668
+ // called through its namespace, and has no receiver.
2669
+ const pointer = this.#hooks === null ? null : this.#resolvedDefinition.resolveDefinitionPointer(this.#pointerOf(node));
2670
+ const request = this.#hooks === null ? null : takesReceiver ? { declaration, receiver, args, pointer, isDeferred } : { declaration, args, pointer, isDeferred };
2671
+ let value;
2672
+
2673
+ try {
2674
+ const samples = this.#beforeInvoke(request, declaration, node, readsMaintained);
2675
+
2676
+ if (samples === null) {
2677
+ return null;
2678
+ }
2679
+
2680
+ try {
2681
+ // The input names the receiver, the arguments, the environment and a maintained member's samples, in that order.
2682
+ const input = takesReceiver ? { receiver, args, env: this.#environment } : { args, env: this.#environment };
2683
+
2684
+ if (readsMaintained) {
2685
+ input.samples = samples;
2686
+ }
2687
+
2688
+ if (takesLimits) {
2689
+ input.limits = this.#limits;
2690
+ }
2691
+
2692
+ value = implementation(input);
2693
+ } catch (error) {
2694
+ // A failure Nexo raised, within a function the member was given or a nested evaluation among them, keeps its own code and place; anything else the implementation threw, a NexoError it built among them,
2695
+ // is the cause of Nexo's own, exactly as thrown.
2696
+ if (NexoErrors.isRaisedByNexo(error) || this.#callbackErrors.has(error)) {
2697
+ throw error;
2698
+ }
2699
+
2700
+ // One of Nexo's own members refuses the values it is given with a failure of the language's, at the call.
2701
+ if (error instanceof MemberFailure && this.#registry.getDeclaringModuleName(declaration) === Vocabulary.moduleName) {
2702
+ this.#fail(error.code, error.message, this.#pointerOf(node));
2703
+ }
2704
+
2705
+ this.#fail('NEXO5007', `${this.#subjectOf(declaration, node)} failed: ${Evaluator.#messageOf(error)}`, this.#pointerOf(node), { cause: error });
2706
+ }
2707
+ } finally {
2708
+ // A function the member was given is called only while its invocation lasts, however it ends: answered, pending, or failed in the host's hook or in the implementation.
2709
+ if (lifetimes !== null) {
2710
+ for (const lifetime of lifetimes) {
2711
+ Evaluator.#closeLifetime(lifetime);
2712
+ }
2713
+ }
2714
+ }
2715
+
2716
+ // A member answering its receiver's items hands over the position each stood at there, and an ordering keeps the keys that ordered them with the list it answered, which a capture and a copy can replace with another,
2717
+ // so both are read from that list as the member returns.
2718
+ const implemented = value;
2719
+ const positions = answersReceiverItems ? ItemPositions.takePositions(implemented) : undefined;
2720
+
2721
+ // A collection a host gives in a form of its own, a set, a map or a pair's object, is the language's where the member declares one.
2722
+ if (captureCollections !== null) {
2723
+ value = captureCollections(value);
2724
+ }
2725
+
2726
+ if (!reliesOnAnswer && !isOfResultType(value)) {
2727
+ this.#fail('NEXO5006', `${this.#subjectOf(declaration, node)} answered ${Evaluator.#describe(value)}, and it declares ${resultType}`, this.#pointerOf(node));
2728
+ }
2729
+
2730
+ if (request !== null && this.#hooks.afterInvoke !== undefined) {
2731
+ try {
2732
+ this.#hooks.afterInvoke(request, value);
2733
+ } catch (error) {
2734
+ this.#failHook(error, declaration, node, 'after its invocation');
2735
+ }
2736
+ }
2737
+
2738
+ const answered = copyStructure === null ? value : copyStructure(value);
2739
+
2740
+ // An ordered list's capture and copy keep the keys that ordered it, its items in the same places, so a `thenBy` given it refines it.
2741
+ if (answered !== implemented) {
2742
+ ListOrdering.carry(implemented, answered);
2743
+ }
2744
+
2745
+ // A member answering its receiver's items keeps the lineage each occurrence had there, as each item's own record already follows it; one answering a value for each item, as a projection does, records each item as its source.
2746
+ if (answersReceiverItems) {
2747
+ this.#realignLineage(receiver, positions, answered);
2748
+ } else if (projectsItems) {
2749
+ const itemType = this.#resolvedDefinition.resolutionOf(node)?.receiverType?.itemType ?? null;
2750
+
2751
+ this.#recordLineage(receiver, answered, itemType !== null && this.#registry.isReferenceType(itemType.toNonNullable()));
2752
+ }
2753
+
2754
+ return answered;
2755
+ }
2756
+
2757
+ /**
2758
+ * Gives the list a member answered of its receiver's items, its copy where its items were copied, the source and the provenance of each occurrence it answered, by the position the occurrence stood at in the
2759
+ * receiver, so a filter, a partition, a reordering and a taking of distinct items alike realign, and items equal to each other keep their own occurrences' lineage. A receiver without either records nothing.
2760
+ */
2761
+ #realignLineage(receiver, positions, answered) {
2762
+ const sources = this.#sourcesByList.get(receiver) ?? null;
2763
+ const provenance = this.#provenanceByList.get(receiver) ?? null;
2764
+
2765
+ if (sources !== null) {
2766
+ this.#sourcesByList.set(answered, Object.freeze(positions.map(at => sources[at])));
2767
+ }
2768
+
2769
+ if (provenance !== null) {
2770
+ this.#provenanceByList.set(answered, Object.freeze(positions.map(at => provenance[at])));
2771
+ }
2772
+ }
2773
+
2774
+ /** What a message names as carrying a member out. */
2775
+ #subjectOf(declaration) {
2776
+ // An unnamed indexer has no name, and is named by its kind and its module.
2777
+ return declaration.name === undefined ? `The unnamed indexer of the module '${this.#registry.getDeclaringModuleName(declaration)}'` : `The member '${declaration.name}'`;
2778
+ }
2779
+
2780
+ /**
2781
+ * The evaluator's step before an invocation, at the point the host's hook is named for: calls the host's `beforeInvoke` where there is one and answers the samples a member's invocation reads, or null where one is pending,
2782
+ * marking the evaluation pending, so the member answers null. A failure of the hook fails the evaluation at the member. Without the hook, it refuses a member that reads maintained values, since nothing answers its samples.
2783
+ */
2784
+ #beforeInvoke(request, declaration, node, readsMaintained) {
2785
+ if (request === null || this.#hooks.beforeInvoke === undefined) {
2786
+ if (readsMaintained) {
2787
+ this.#fail('NEXO5011', `${this.#subjectOf(declaration, node)} is maintained by an observation, and reading it waits for the first sample, which a synchronous evaluation cannot`, this.#pointerOf(node));
2788
+ }
2789
+
2790
+ return NO_SAMPLES;
2791
+ }
2792
+
2793
+ let answer;
2794
+
2795
+ try {
2796
+ answer = this.#hooks.beforeInvoke(request);
2797
+ } catch (error) {
2798
+ this.#failHook(error, declaration, node, 'before its invocation');
2799
+ }
2800
+
2801
+ // The hook answers a member's samples, that it is pending, which only a member reading maintained values is, or nothing; anything else is the host's failure, as a throw is.
2802
+ if (answer === undefined || answer === null) {
2803
+ return NO_SAMPLES;
2804
+ }
2805
+
2806
+ if (Array.isArray(answer)) {
2807
+ return answer;
2808
+ }
2809
+
2810
+ if (answer.pending === true && readsMaintained) {
2811
+ // The request is the read's record, kept as the host was given it: frozen, since nothing invokes the member, so the arguments it holds are given to nothing else.
2812
+ Object.freeze(request.args);
2813
+ (this.#pendingReads ??= []).push(Object.freeze(request), node);
2814
+
2815
+ return null;
2816
+ }
2817
+
2818
+ const answered = answer.pending === true ? 'that it is pending, which only a member reading maintained values is' : `${Evaluator.#describe(answer)}, which is neither its samples, a list, nor { pending: true }`;
2819
+
2820
+ this.#fail('NEXO5009', `The host answered before the invocation of ${this.#subjectOf(declaration, node).replace(/^The /, 'the ').replace(/,$/, '')} ${answered}`, this.#pointerOf(node), { cause: answer });
2821
+ }
2822
+
2823
+ /**
2824
+ * Fails the evaluation at the member for what a hook around its invocation threw, a NexoError the hook built among them, a failure Nexo raised keeping its own place;
2825
+ * the hooks are called directly, since they run on every invocation.
2826
+ */
2827
+ #failHook(error, declaration, node, moment) {
2828
+ if (NexoErrors.isRaisedByNexo(error) || this.#callbackErrors.has(error)) {
2829
+ throw error;
2830
+ }
2831
+
2832
+ this.#fail('NEXO5009', `The host failed ${moment} of ${this.#subjectOf(declaration, node).replace(/^The /, 'the ').replace(/,$/, '')}: ${Evaluator.#messageOf(error)}`, this.#pointerOf(node), { cause: error });
2833
+ }
2834
+
2835
+ /** What a thrown value says, for the message of the failure it causes: an error's message, or the value written as text, whatever it is. */
2836
+ static #messageOf(error) {
2837
+ try {
2838
+ return error?.message ?? String(error);
2839
+ } catch {
2840
+ return Object.prototype.toString.call(error);
2841
+ }
2842
+ }
2843
+
2844
+ /**
2845
+ * Raises an error of the code and the message at the source of the pointer: a module error for a code of the module range, NEXO4…, and an evaluation error for any other, which carries the options given as they are,
2846
+ * the failure that caused it among them. Either is raised through Nexo's own factories, which check its code's range and mark it as Nexo's.
2847
+ */
2848
+ #fail(code, message, pointer, options) {
2849
+ const location = this.#locate(pointer);
2850
+
2851
+ throw code.startsWith('NEXO4') ? NexoErrors.module(code, message, location) : NexoErrors.evaluation(code, message, location, options);
2852
+ }
2853
+
2854
+ /** Where the node stands in the definition, as a JSON Pointer. */
2855
+ #pointerOf(node) {
2856
+ return this.#resolvedDefinition.pointerOf(node);
2857
+ }
2858
+
2859
+ #locate(pointer) {
2860
+ const definitionPointer = this.#resolvedDefinition.resolveDefinitionPointer(pointer);
2861
+
2862
+ return this.#locations?.locate(definitionPointer) ?? { pointer: definitionPointer };
2863
+ }
2864
+
2865
+ /** Whether a value is truthy: every value is but null and false. */
2866
+ static #isTruthy(value) {
2867
+ return value !== null && value !== false;
2868
+ }
2869
+
2870
+ /** What the value is, for a message, its type never asked: nothing, or its data where it has any, a number no finite number holds written as `Infinity`, `-Infinity` or `NaN` wherever it stands. */
2871
+ static #describe(value) {
2872
+ if (value === undefined) {
2873
+ return 'nothing';
2874
+ }
2875
+
2876
+ const hole = Array.isArray(value) ? ArrayUtils.findMissingIndex(value) : -1;
2877
+
2878
+ if (hole !== -1) {
2879
+ return `a list with a hole at index ${hole}`;
2880
+ }
2881
+
2882
+ // A promise and an instance of a class are named as they are, since what they would be written as says nothing of them.
2883
+ if (typeof value?.then === 'function') {
2884
+ return 'a promise';
2885
+ }
2886
+
2887
+ if (value !== null && typeof value === 'object' && !Array.isArray(value) && !ObjectUtils.isPlainObject(value)) {
2888
+ return `an instance of ${value.constructor?.name || 'a class'}`;
2889
+ }
2890
+
2891
+ // JSON writes a number no finite number holds as null, so each is marked as a string JSON escapes and then written as its own word; a string holding the marker's NUL characters is no realistic value.
2892
+ const nonFinite = (key, part) => (typeof part === 'number' && !Number.isFinite(part) ? `${NON_FINITE_MARKER}${part}` : part);
2893
+
2894
+ try {
2895
+ return JSON.stringify(value, nonFinite)?.replace(NON_FINITE_WRITTEN, '$1') ?? String(value);
2896
+ } catch {
2897
+ return Object.prototype.toString.call(value);
2898
+ }
2899
+ }
2900
+
2901
+ /**
2902
+ * The node evaluated and detached, the script's expression or a host call's result projection, with the metadata that describes it, built from the identities the copy answers beside it, which the evaluator hands over and never
2903
+ * keeps, so it holds no source between evaluations.
2904
+ */
2905
+ #evaluateResult(node) {
2906
+ // An evaluation starts its lineage afresh, and a projection continues the lineage its call was prepared with.
2907
+ if (this.#projectionContext === null) {
2908
+ this.#sourcesByList = new WeakMap();
2909
+ this.#provenanceByList = new WeakMap();
2910
+ }
2911
+
2912
+ this.#selections = null;
2913
+
2914
+ try {
2915
+ const result = this.#evaluateNode(node, null);
2916
+
2917
+ this.#resultNode = node;
2918
+
2919
+ // Resolution proved every other part of the result data.
2920
+ if (this.#checksResultData && !this.#isData(result)) {
2921
+ this.#fail('NEXO5028', 'The result holds a value of Any that is no data, as a reference or a function is; read the facts it needs through an object expression', this.#pointerOf(node));
2922
+ }
2923
+
2924
+ const value = this.#copyResult(result);
2925
+ const copiedIdentities = this.#copiedIdentities;
2926
+
2927
+ // The identities go with the result they describe, so the evaluator keeps none of a host's values past it.
2928
+ this.#copiedIdentities = null;
2929
+
2930
+ // The metadata is described where it is first read, from identities no later evaluation changes, so a caller that reads the value alone pays nothing for it.
2931
+ return { node, value, identities: Evaluator.#withSelections(copiedIdentities, this.#selections) };
2932
+ } finally {
2933
+ this.#selections = null;
2934
+ }
2935
+ }
2936
+
2937
+ /** The node whose result is being checked and copied, which a result nesting deeper than data nests fails at. @type {object | null} */
2938
+ #resultNode = null;
2939
+
2940
+ /** Refuses a result nesting deeper than `Limits.dataNesting`, which only data a member answered as a value of Any can, at the node that gave it. */
2941
+ #failNesting() {
2942
+ this.#fail('NEXO5052', `The result nests deeper than ${Limits.dataNesting} levels, the most data nests`, this.#pointerOf(this.#resultNode));
2943
+ }
2944
+
2945
+ /** Whether the value is data at any depth: null, a string, a Boolean, a finite number, a list holding data at every index, or a plain object of data no module recognizes as a reference. */
2946
+ #isData(value, depth = 0) {
2947
+ if (depth > Limits.dataNesting) {
2948
+ this.#failNesting();
2949
+ }
2950
+
2951
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') {
2952
+ return true;
2953
+ }
2954
+
2955
+ if (typeof value === 'number') {
2956
+ return Number.isFinite(value);
2957
+ }
2958
+
2959
+ if (Array.isArray(value)) {
2960
+ return ArrayUtils.findMissingIndex(value) === -1 && value.every(item => this.#isData(item, depth + 1));
2961
+ }
2962
+
2963
+ // A dictionary is data where every key and every value is, and a pair where both its parts are.
2964
+ if (value instanceof Dictionary) {
2965
+ return value.keys.every(key => this.#isData(key, depth + 1)) && value.values.every(entry => this.#isData(entry, depth + 1));
2966
+ }
2967
+
2968
+ if (value instanceof Pair) {
2969
+ return this.#isData(value.key, depth + 1) && this.#isData(value.value, depth + 1);
2970
+ }
2971
+
2972
+ return ObjectUtils.isPlainObject(value) && this.#registry.recognize(value, 'the result').length === 0 && Object.values(value).every(field => this.#isData(field, depth + 1));
2973
+ }
2974
+
2975
+ /**
2976
+ * The identities the last `#copyResult` left beside its copy, which its caller reads as it returns, before copying anything else: for a list projected from elements, the element each item came from, for an object
2977
+ * field, the selections made for its value, as `selections`, and null where nothing beneath holds either. Handing them over this way spares a pair for each part copied, a scalar among them.
2978
+ * @type {object | null}
2979
+ */
2980
+ #copiedIdentities = null;
2981
+
2982
+ /**
2983
+ * Copies the data, from one traversal, into a frozen copy sharing no structure with the original, which it answers, and the identities that follow its structure, which it leaves in `#copiedIdentities`. Each
2984
+ * call leaves its own, so a caller reads the identities of the part it copied.
2985
+ */
2986
+ #copyResult(value, depth = 0) {
2987
+ if (depth > Limits.dataNesting) {
2988
+ this.#failNesting();
2989
+ }
2990
+
2991
+ this.#copiedIdentities = null;
2992
+
2993
+ if (value === null || typeof value !== 'object') {
2994
+ return value;
2995
+ }
2996
+
2997
+ // A result exports a dictionary as its entries, in their order, and a pair as its key and its value, each a frozen object of data, so it never turns back into a dictionary or a pair when it is bound again.
2998
+ if (value instanceof Dictionary) {
2999
+ const entries = Object.freeze(value.entries.map(entry => this.#copyResult(entry, depth + 1)));
3000
+
3001
+ this.#copiedIdentities = null;
3002
+
3003
+ return Object.freeze({ entries });
3004
+ }
3005
+
3006
+ if (value instanceof Pair) {
3007
+ const key = this.#copyResult(value.key, depth + 1);
3008
+ const copied = Object.freeze({ key, value: this.#copyResult(value.value, depth + 1) });
3009
+
3010
+ this.#copiedIdentities = null;
3011
+
3012
+ return copied;
3013
+ }
3014
+
3015
+ if (Array.isArray(value)) {
3016
+ const copy = [];
3017
+ const itemIdentities = [];
3018
+ let hasIdentities = false;
3019
+
3020
+ for (const item of value) {
3021
+ copy.push(this.#copyResult(item, depth + 1));
3022
+
3023
+ const identities = this.#copiedIdentities;
3024
+
3025
+ itemIdentities.push(identities);
3026
+ hasIdentities ||= identities !== null;
3027
+ }
3028
+
3029
+ const sources = this.#sourcesByList.get(value) ?? null;
3030
+ const provenance = this.#provenanceByList.get(value) ?? null;
3031
+
3032
+ this.#copiedIdentities = sources === null && provenance === null && !hasIdentities ? null : { sources, provenance, items: itemIdentities };
3033
+
3034
+ return Object.freeze(copy);
3035
+ }
3036
+
3037
+ if (ObjectUtils.isNonArrayObject(value)) {
3038
+ const copy = {};
3039
+ const fieldSelections = this.#objectFieldSelections.get(value);
3040
+ let fieldIdentities = null;
3041
+
3042
+ for (const name of Object.keys(value)) {
3043
+ const field = this.#copyResult(value[name], depth + 1);
3044
+ const copied = this.#copiedIdentities;
3045
+
3046
+ // A read copies every field of every row, so an ordinary field is assigned here, where the assignment sees only the copies' shapes; sharing the one in `defineDataProperty` with live state's writes made reads measurably slower.
3047
+ if (name === '__proto__') {
3048
+ ObjectUtils.defineDataProperty(copy, name, field);
3049
+ } else {
3050
+ copy[name] = field;
3051
+ }
3052
+
3053
+ const selections = Evaluator.#selectionOf(fieldSelections ?? null, name) ?? null;
3054
+ const identities = selections === null ? copied : Evaluator.#withSelections(copied, selections);
3055
+
3056
+ if (identities !== null) {
3057
+ fieldIdentities ??= {};
3058
+ ObjectUtils.defineDataProperty(fieldIdentities, name, identities);
3059
+ }
3060
+ }
3061
+
3062
+ this.#copiedIdentities = fieldIdentities === null ? null : { fields: fieldIdentities };
3063
+
3064
+ return Object.freeze(copy);
3065
+ }
3066
+
3067
+ return value;
3068
+ }
3069
+
3070
+ /** Describes a result's metadata from the identities its copy left, the records objects share kept for this description alone. */
3071
+ /**
3072
+ * What describes a result's metadata once it is read: the compilation's own evaluator, which holds the resolved definition and the registry describing reads, and neither the bindings nor the environment, so a result
3073
+ * its host keeps unread keeps nothing of the evaluation that made it.
3074
+ */
3075
+ static #describerOf(describer, result, pendingReads) {
3076
+ return () => describer.#describeMetadata(result, pendingReads);
3077
+ }
3078
+
3079
+ #describeMetadata(result, pendingReads) {
3080
+ this.#recentFieldRecords = new WeakMap();
3081
+
3082
+ try {
3083
+ const record = this.#resolveRecord(result.node, result.value, result.identities, null);
3084
+
3085
+ // A result none of whose reads was pending has no pending reads at all, and its record stays the one every such result shares.
3086
+ return pendingReads === null ? record : Object.freeze({ ...record, pending: this.#describePendingReads(pendingReads) });
3087
+ } finally {
3088
+ this.#recentFieldRecords = null;
3089
+ }
3090
+ }
3091
+
3092
+ /**
3093
+ * The pending reads a result's record holds, made from the requests its evaluation kept as its metadata is first read: one for each distinct read, the request its host answered pending, a module member's without a
3094
+ * receiver, and the origin of the value the read gave, the one the record of that value carries. Reads of one member, of one receiver, with the same arguments, at one place are one.
3095
+ */
3096
+ #describePendingReads(pendingReads) {
3097
+ const reads = [];
3098
+
3099
+ for (let index = 0; index < pendingReads.length; index += 2) {
3100
+ const request = pendingReads[index];
3101
+
3102
+ if (!reads.some(read => Evaluator.#isSameRead(read, request))) {
3103
+ const node = pendingReads[index + 1];
3104
+
3105
+ reads.push(Object.freeze({ ...request, origin: this.#getOrigin(node, this.#candidateOfRead(node, request.declaration)) }));
3106
+ }
3107
+ }
3108
+
3109
+ return Object.freeze(reads);
3110
+ }
3111
+
3112
+ /** The candidate the record of a read's value names: the declaration a deferred access selected, the name a computed property access selected, or none where resolution selected the declaration. */
3113
+ #candidateOfRead(node, declaration) {
3114
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
3115
+
3116
+ if (resolution?.kind === 'deferred') {
3117
+ return declaration;
3118
+ }
3119
+
3120
+ if (resolution?.kind === 'computedPropertyAccess') {
3121
+ for (const [name, selected] of resolution.resolutionsByName) {
3122
+ if (selected.declaration === declaration) {
3123
+ return name;
3124
+ }
3125
+ }
3126
+ }
3127
+
3128
+ return null;
3129
+ }
3130
+
3131
+ /** Whether a read is the one a request asks for: the same member at the same place, of the same receiver, with the same arguments, each the same value. */
3132
+ static #isSameRead(read, request) {
3133
+ if (read.declaration !== request.declaration || read.pointer !== request.pointer || !Object.is(read.receiver, request.receiver)) {
3134
+ return false;
3135
+ }
3136
+
3137
+ const names = Object.keys(request.args);
3138
+
3139
+ return names.length === Object.keys(read.args).length && names.every(name => Object.is(read.args[name], request.args[name]));
3140
+ }
3141
+
3142
+ /**
3143
+ * Resolves which record describes the value an expression gave at its place in the result, read from the identities its copy left there: the record of the expression that produced it, followed through locals, bodies, `do` blocks, function calls,
3144
+ * object-root fields, the branch or operand a selecting expression selected and the property a computed property access selected; a field read keeps the record its whole has for that field, or its whole's own.
3145
+ * An object expression's value records each field, and a projection's each item and the input it came from. The value is undefined where the expression is described for a part read from it, and nothing beyond its record is described then.
3146
+ * The context record is the record of the value `it` stands for where the expression stands, the value an object expression follows or the item it projects, or null where there is none.
3147
+ */
3148
+ #resolveRecord(node, value, identities, contextRecord) {
3149
+ const selections = identities?.selections ?? null;
3150
+
3151
+ for (;;) {
3152
+ switch (node.kind) {
3153
+ case 'local':
3154
+ node = this.#resolvedDefinition.resolutionOf(node).initializer;
3155
+ continue;
3156
+ case 'optionalChain':
3157
+ node = node.chain;
3158
+ continue;
3159
+ case 'body':
3160
+ node = node.result;
3161
+ continue;
3162
+ case 'do':
3163
+ node = node.body;
3164
+ continue;
3165
+ case 'call': {
3166
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
3167
+
3168
+ // A call of a member describes its value as any member's does.
3169
+ if (node.callee.kind !== 'local') {
3170
+ return this.#resolveMemberRecord(node, resolution, identities, contextRecord, selections, value);
3171
+ }
3172
+
3173
+ // A call of one a reference to a module's function alone produces its value itself, as the member it invokes does, the reference's declaration its one candidate where its receiver's type is unknown.
3174
+ if (resolution.functions === undefined) {
3175
+ return this.#getExpressionRecord(node, null, (resolution.declaration ?? resolution.candidates[0]).updates?.tolerance ?? null);
3176
+ }
3177
+
3178
+ const { functions } = resolution;
3179
+ const called = functions.length === 1 ? functions[0] : functions.find(entry => Evaluator.#getFunctionKey(entry) === Evaluator.#selectionOf(selections, node));
3180
+
3181
+ if (called === undefined) {
3182
+ return this.#getExpressionRecord(node, null, null);
3183
+ }
3184
+
3185
+ // A call of a reference to a module's function produces its value itself, as the member it invokes does.
3186
+ if (called.reference !== undefined) {
3187
+ return this.#getExpressionRecord(node, null, called.reference.updates?.tolerance ?? null);
3188
+ }
3189
+
3190
+ node = called.body;
3191
+ continue;
3192
+ }
3193
+ case 'memberAccess':
3194
+ case 'indexerAccess': {
3195
+ const resolution = this.#resolvedDefinition.resolutionOf(node);
3196
+ const candidate = resolution?.kind === 'computedPropertyAccess' ? (Evaluator.#selectionOf(selections, node) ?? null) : null;
3197
+ const selected = resolution?.kind === 'computedPropertyAccess' ? (candidate === null ? null : resolution.resolutionsByName.get(candidate)) : resolution;
3198
+
3199
+ // A field read through the object root follows its initializer.
3200
+ if (selected?.kind === 'objectRootField') {
3201
+ node = selected.initializer;
3202
+ continue;
3203
+ }
3204
+
3205
+ return this.#resolveMemberRecord(node, resolution, identities, contextRecord, selections, value);
3206
+ }
3207
+ case 'it':
3208
+ return contextRecord ?? this.#getExpressionRecord(node, null, null);
3209
+ case 'object':
3210
+ return this.#resolveObjectExpressionRecord(node, value, identities, contextRecord);
3211
+ default:
3212
+ if (SELECTING_KINDS.has(node.kind)) {
3213
+ const selected = Evaluator.#selectionOf(selections, node);
3214
+
3215
+ if (selected === undefined) {
3216
+ return this.#getExpressionRecord(node, null, null);
3217
+ }
3218
+
3219
+ node = selected;
3220
+ continue;
3221
+ }
3222
+
3223
+ return this.#getExpressionRecord(node, null, null);
3224
+ }
3225
+ }
3226
+ }
3227
+
3228
+ /**
3229
+ * Resolves the record of a member's value: a computed property access's by the name its evaluation selected, none where it selected none; a stored value's, an object field's or a data property's, the record of that
3230
+ * field of what it reads it from; and any other the member's own, with its declaration's tolerance. A field read through the object root is its caller's to follow.
3231
+ */
3232
+ #resolveMemberRecord(node, resolution, identities, contextRecord, selections, value) {
3233
+ // A deferred access's value was produced by the declaration it selected as it was evaluated, with that declaration's tolerance, and one read from data or never selected by none.
3234
+ if (resolution?.kind === 'deferred') {
3235
+ const declaration = Evaluator.#selectionOf(selections, node) ?? null;
3236
+
3237
+ return this.#getExpressionRecord(node, declaration, declaration?.updates?.tolerance ?? null);
3238
+ }
3239
+
3240
+ let selected = resolution;
3241
+ let candidate = null;
3242
+
3243
+ if (resolution?.kind === 'computedPropertyAccess') {
3244
+ candidate = Evaluator.#selectionOf(selections, node) ?? null;
3245
+
3246
+ if (candidate === null) {
3247
+ return this.#getExpressionRecord(node, null, null);
3248
+ }
3249
+
3250
+ selected = resolution.resolutionsByName.get(candidate);
3251
+ }
3252
+
3253
+ if (selected?.kind === 'objectField' || selected?.kind === 'dataProperty') {
3254
+ const whole = this.#resolveRecord(node.receiver, undefined, identities, contextRecord);
3255
+
3256
+ return whole.objectFields?.[candidate ?? selected.name] ?? Evaluator.#getInheritedRecord(whole);
3257
+ }
3258
+
3259
+ const record = this.#getExpressionRecord(node, candidate, selected?.declaration?.updates?.tolerance ?? null);
3260
+
3261
+ // A filter's answer describes each item it kept as its receiver describes it, its items' records, where they have any, and their sources and provenance realigned in its order, under the filter's own origin and
3262
+ // tolerance; a receiver no projection built gives it neither.
3263
+ if (Array.isArray(value) && selected?.declaration !== undefined && Vocabulary.answersReceiverItems(selected.declaration)) {
3264
+ const { items, sources, provenance } = this.#resolveRecord(Evaluator.#accessOf(node).receiver, value, identities, contextRecord);
3265
+
3266
+ return Object.freeze({ ...record, ...(items === undefined ? {} : { items }), ...(sources === undefined ? {} : { sources }), ...(provenance === undefined ? {} : { provenance }) });
3267
+ }
3268
+
3269
+ // A selection's answer records each item it read as its source and the provenance that item carried, as a projection's does, its items described by the selection's own record.
3270
+ if (Array.isArray(value) && selected?.declaration !== undefined && Vocabulary.projectsItems(selected.declaration) && identities?.sources != null) {
3271
+ return Object.freeze({ ...record, sources: identities.sources, provenance: identities.provenance });
3272
+ }
3273
+
3274
+ return record;
3275
+ }
3276
+
3277
+ /**
3278
+ * Resolves the record of an object expression's value, whichever result it gave: one object's record of each field, a projected list's record of each item with the reference each came from, or its own record alone where it
3279
+ * gave null or is described for a part read from it.
3280
+ */
3281
+ #resolveObjectExpressionRecord(node, value, identities, contextRecord) {
3282
+ // The fields read `it` as the value the expression follows, each item of it where it projects a list, and as the enclosing one where it follows none.
3283
+ const followed = node.context === undefined ? null : this.#resolveRecord(node.context, undefined, identities, contextRecord);
3284
+
3285
+ if (Array.isArray(value)) {
3286
+ const itemContextRecord = followed === null ? contextRecord : Evaluator.#getInheritedRecord(followed);
3287
+ const items = value.map((item, index) => this.#createObjectRecord(node, item, identities?.items?.[index] ?? null, itemContextRecord));
3288
+ const sources = identities?.sources ?? null;
3289
+ const provenance = identities?.provenance ?? null;
3290
+
3291
+ // A list no projection built records neither sources nor provenance at all, so a null source and absent sources or provenance stay apart.
3292
+ return Object.freeze({ origin: this.#getExpressionRecord(node, null, null).origin, tolerance: null, items: Object.freeze(items), ...(sources === null ? {} : { sources }), ...(provenance === null ? {} : { provenance }) });
3293
+ }
3294
+
3295
+ return value === null ? this.#getExpressionRecord(node, null, null) : this.#createObjectRecord(node, value, identities, followed ?? contextRecord);
3296
+ }
3297
+
3298
+ /**
3299
+ * Creates the record of one object an object expression built, with each field's, by its name in the order written, called once for a single object and once for each projected item; a field described for a part read from
3300
+ * it is described from the selections of the place it is read.
3301
+ */
3302
+ #createObjectRecord(node, object, identities, contextRecord) {
3303
+ const { objectFields: fields } = node;
3304
+ const records = new Array(fields.length);
3305
+
3306
+ for (let index = 0; index < fields.length; index++) {
3307
+ const field = fields[index];
3308
+
3309
+ records[index] = this.#resolveRecord(field.value, object === undefined ? undefined : object?.[field.name], object === undefined ? identities : identities?.fields?.[field.name] ?? null, contextRecord);
3310
+ }
3311
+
3312
+ // An object whose fields give the records a recent one did, as the items of a list mostly do, shares its record.
3313
+ let recent = this.#recentFieldRecords.get(node);
3314
+
3315
+ if (recent === undefined) {
3316
+ recent = [];
3317
+ this.#recentFieldRecords.set(node, recent);
3318
+ }
3319
+
3320
+ for (const kept of recent) {
3321
+ if (kept.contextRecord === contextRecord && records.every((record, index) => record === kept.fields[index])) {
3322
+ return kept.record;
3323
+ }
3324
+ }
3325
+
3326
+ const byName = Object.create(null);
3327
+
3328
+ for (let index = 0; index < fields.length; index++) {
3329
+ byName[fields[index].name] = records[index];
3330
+ }
3331
+
3332
+ const record = Object.freeze({ origin: this.#getExpressionRecord(node, null, null).origin, tolerance: null, objectFields: Object.freeze(byName) });
3333
+
3334
+ recent.unshift({ contextRecord, fields: records, record });
3335
+
3336
+ if (recent.length > RECENT_FIELD_RECORDS) {
3337
+ recent.pop();
3338
+ }
3339
+
3340
+ return record;
3341
+ }
3342
+
3343
+ /**
3344
+ * Gets the shared record of a value an expression produced itself, with the candidate it selected and the tolerance that expression applies, from `#expressionRecordCache`, creating it at the first lookup: one for this
3345
+ * resolution, its origin a token every evaluation of every resolution of its definition shares where it selects the same declaration.
3346
+ */
3347
+ #getExpressionRecord(node, candidate, tolerance) {
3348
+ let records = Evaluator.#expressionRecordCache.get(this.#resolvedDefinition);
3349
+
3350
+ if (records === undefined) {
3351
+ records = new Map();
3352
+ Evaluator.#expressionRecordCache.set(this.#resolvedDefinition, records);
3353
+ }
3354
+
3355
+ let byCandidate = records.get(node);
3356
+
3357
+ if (byCandidate === undefined) {
3358
+ byCandidate = new Map();
3359
+ records.set(node, byCandidate);
3360
+ }
3361
+
3362
+ let record = byCandidate.get(candidate);
3363
+
3364
+ if (record === undefined) {
3365
+ record = Object.freeze({ origin: this.#getOrigin(node, candidate), tolerance });
3366
+ byCandidate.set(candidate, record);
3367
+ }
3368
+
3369
+ return record;
3370
+ }
3371
+
3372
+ /** Gets the origin token of the value the expression produces through the declaration its resolution selects and the candidate, from `#originCache`, creating it at the first lookup of any resolution of the definition. */
3373
+ #getOrigin(node, candidate) {
3374
+ let byPointer = Evaluator.#originCache.get(this.#resolvedDefinition.definition);
3375
+
3376
+ if (byPointer === undefined) {
3377
+ byPointer = new Map();
3378
+ Evaluator.#originCache.set(this.#resolvedDefinition.definition, byPointer);
3379
+ }
3380
+
3381
+ const pointer = this.#resolvedDefinition.pointerOf(node);
3382
+ let tokens = byPointer.get(pointer);
3383
+
3384
+ if (tokens === undefined) {
3385
+ tokens = { byDeclaration: new WeakMap(), undeclared: new Map() };
3386
+ byPointer.set(pointer, tokens);
3387
+ }
3388
+
3389
+ const declaration = this.#resolvedDefinition.resolutionOf(node)?.declaration ?? null;
3390
+ let byCandidate = declaration === null ? tokens.undeclared : tokens.byDeclaration.get(declaration);
3391
+
3392
+ if (byCandidate === undefined) {
3393
+ byCandidate = new Map();
3394
+ tokens.byDeclaration.set(declaration, byCandidate);
3395
+ }
3396
+
3397
+ let origin = byCandidate.get(candidate);
3398
+
3399
+ if (origin === undefined) {
3400
+ origin = Object.freeze(Object.create(null));
3401
+ byCandidate.set(candidate, origin);
3402
+ }
3403
+
3404
+ return origin;
3405
+ }
3406
+
3407
+ /** Gets the shared record a part with none of its own inherits from its whole, its whole's origin and tolerance without the whole's object fields or items, from `#inheritedRecordCache`, creating it at the first lookup. */
3408
+ static #getInheritedRecord(whole) {
3409
+ if (whole.objectFields === undefined && whole.items === undefined) {
3410
+ return whole;
3411
+ }
3412
+
3413
+ let inherited = Evaluator.#inheritedRecordCache.get(whole);
3414
+
3415
+ if (inherited === undefined) {
3416
+ inherited = Object.freeze({ origin: whole.origin, tolerance: whole.tolerance });
3417
+ Evaluator.#inheritedRecordCache.set(whole, inherited);
3418
+ }
3419
+
3420
+ return inherited;
3421
+ }
3422
+
3423
+ /**
3424
+ * The selections with the node's recorded, the list itself where it is one already: a node recorded before takes the newer selection in its place, and null, where none was made, a new list. A map for each
3425
+ * place that made any measured a few hundredths of an evaluation's allocation, and a place makes few, so a list of each node beside its selection, searched in turn, holds them.
3426
+ * @param {unknown[] | null} selections
3427
+ */
3428
+ static #select(selections, node, selection) {
3429
+ if (selections === null) {
3430
+ return [node, selection];
3431
+ }
3432
+
3433
+ for (let index = 0; index < selections.length; index += 2) {
3434
+ if (selections[index] === node) {
3435
+ selections[index + 1] = selection;
3436
+
3437
+ return selections;
3438
+ }
3439
+ }
3440
+
3441
+ selections.push(node, selection);
3442
+
3443
+ return selections;
3444
+ }
3445
+
3446
+ /** The selections with every one the others hold recorded in turn, as `#select` records each, or a copy of the others where there were none, since a list is added to in place. */
3447
+ static #joinSelections(selections, others) {
3448
+ if (others === null) {
3449
+ return selections;
3450
+ }
3451
+
3452
+ if (selections === null) {
3453
+ return others.slice();
3454
+ }
3455
+
3456
+ for (let index = 0; index < others.length; index += 2) {
3457
+ Evaluator.#select(selections, others[index], others[index + 1]);
3458
+ }
3459
+
3460
+ return selections;
3461
+ }
3462
+
3463
+ /** The selection recorded for the node, or undefined where none is. */
3464
+ static #selectionOf(selections, node) {
3465
+ if (selections !== null) {
3466
+ for (let index = 0; index < selections.length; index += 2) {
3467
+ if (selections[index] === node) {
3468
+ return selections[index + 1];
3469
+ }
3470
+ }
3471
+ }
3472
+
3473
+ return undefined;
3474
+ }
3475
+
3476
+ /** The identities of a value with the selections made for it beside them, as their `selections`, or the identities alone where none were made. */
3477
+ static #withSelections(identities, selections) {
3478
+ return selections === null ? identities : { ...identities, selections };
3479
+ }
3480
+ }