@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,1119 @@
1
+ /**
2
+ * NexoModule — a vocabulary's declarations as data, each that runs code carrying its implementation
3
+ */
4
+
5
+ import { ObjectUtils } from '../../common/ObjectUtils.mjs';
6
+ import { NexoErrors } from '../NexoError.mjs';
7
+ import { Names } from '../Names.mjs';
8
+ import { Type } from '../Type.mjs';
9
+
10
+ /** The kinds of member a module declares: a property, read by its name; a function, called; and an indexer, given its key in brackets, after its name or, an unnamed one, after its receiver. */
11
+ const MEMBER_KINDS = ['property', 'function', 'indexer', 'operator'];
12
+
13
+ /** The operators a type's members may hold, each named by the operation the language's own syntax invokes it for: `cast`, which `as` invokes, and `convert`, which `to` invokes. */
14
+ const OPERATOR_NAMES = ['cast', 'convert'];
15
+
16
+ /** Who invokes a function: the evaluator, as it evaluates the script, or the host, which the script's host call is prepared for. */
17
+ const INVOKERS = ['evaluator', 'host'];
18
+
19
+ /** The form a member's arguments take: one for each parameter in order, or one object whose fields supply them. */
20
+ const ARGUMENT_FORMS = ['positional', 'options'];
21
+
22
+ /** How a member changes, as the specification's categories name it, `derived` for a contract that comes from the items and expression a member selects or evaluates. */
23
+ const CHANGE_CATEGORIES = ['constant', 'observable', 'partly-observable', 'unobserved', 'derived'];
24
+
25
+ /** How a member reads, as the specification's modes name it, `derived` for a contract that comes from the items and expression a member selects or evaluates. */
26
+ const READING_MODES = ['fresh', 'maintained', 'captured', 'derived'];
27
+ const NULL_POLICIES = ['propagate', 'accept'];
28
+ const SELECTION_KINDS = ['type'];
29
+ const PARAMETER_KINDS = ['value', 'expression', 'predicate', 'function'];
30
+
31
+ /** What an observation type provides: a signal that an answer may have changed, or a sampled value a member reads. */
32
+ const OBSERVATION_CONTRACTS = ['invalidation', 'maintained'];
33
+
34
+ /** The categories whose changes observations cover. */
35
+ const OBSERVED_CHANGE_CATEGORIES = ['observable', 'partly-observable'];
36
+
37
+ /** The scalar types Nexo defines, each a value of data alone: what conversion operators convert between, what data and equality read as scalars, and what a default of one is. */
38
+ const SCALAR_TYPE_NAMES = Object.freeze(['Number', 'String', 'Boolean']);
39
+
40
+ /** The kinds of type a module declares: a reference type, whose values have identity; a structured type, of data; and an extension, the members it adds to a type another module owns, which is never a value's type. */
41
+ const TYPE_KINDS = ['reference', 'structured', 'extension'];
42
+
43
+ /** A declaration's name as it declares its type parameters: a local name, and, in angle brackets after it, the parameters, separated by commas. */
44
+ const DECLARED_NAME = /^([^<>]*)(?:<(.*)>)?$/;
45
+
46
+ /** What a module can declare. */
47
+ const MODULE_KEYS = ['typeOf', 'types', 'members', 'typeSelectors', 'mappings', 'observationTypes', 'observationTargets'];
48
+
49
+ /** The fields each member kind takes, beside those every member takes, so a field one does not take is refused rather than ignored. */
50
+ const MEMBER_KEYS = ['name', 'kind', 'result', 'impl', 'internal'];
51
+ const MEMBER_KEYS_BY_KIND = {
52
+ property: ['updates'],
53
+ function: ['invokedBy', 'arguments', 'parameters', 'updates'],
54
+ // An indexer's arguments are its one key, positional, so it takes no argument form; who invokes it is read to refuse the host.
55
+ indexer: ['invokedBy', 'parameters', 'updates'],
56
+ // An operator is invoked by the language's own syntax, given its value alone, so it takes no parameters and declares no updates.
57
+ operator: [],
58
+ };
59
+
60
+ /** How a location names an unnamed indexer, which declares no name: as a script writes its access after the receiver. */
61
+ const UNNAMED_INDEXER_NAME = '[]';
62
+ const UPDATES_KEYS = ['changes', 'reads', 'misses', 'tolerance', 'observations'];
63
+ const PARAMETER_KEYS = ['name', 'kind', 'type', 'required', 'default', 'fixed', 'selects', 'selector', 'nulls', 'context', 'omitted', 'parameterTypes'];
64
+ /** The keys each kind of type takes, so a key its kind does not take, a structured type's `fields` among them, is refused rather than ignored. */
65
+ const TYPE_KEYS_BY_KIND = {
66
+ reference: ['name', 'kind', 'isInstance', 'supertype', 'abstract', 'import', 'members', 'internal'],
67
+ structured: ['name', 'kind', 'import', 'members', 'internal'],
68
+ // An extension's types are its module's and Nexo's, and foreign ones written qualified, so it imports none.
69
+ extension: ['name', 'kind', 'target', 'members', 'internal'],
70
+ };
71
+
72
+ /** The keys a property of a structured type's data shape takes: its name, its kind and the type of its value, which Nexo reads from the data, so it takes no `impl`. */
73
+ const DATA_PROPERTY_KEYS = ['name', 'kind', 'result', 'internal'];
74
+ const TYPE_SELECTOR_KEYS = ['name'];
75
+ const MAPPING_KEYS = ['selector', 'key', 'type'];
76
+ const OBSERVATION_TYPE_KEYS = ['name', 'contract', 'identity', 'shared', 'impl'];
77
+ const OBSERVATION_TARGET_KEYS = ['name', 'from', 'count'];
78
+
79
+ export class NexoModule {
80
+ #name;
81
+ #typeOf;
82
+ #types;
83
+ #members;
84
+ #typeSelectors;
85
+ #mappings;
86
+ #observationTypes;
87
+ #observationTargets;
88
+
89
+ /**
90
+ * @param {string} name The module's name.
91
+ * @param {object} contents What the module declares, each declaration that runs code carrying its implementation.
92
+ * @param {object[]} [contents.types] The types it declares, reference and structured, each with its own members, which take it as their receiver, a structured type's data shape the properties it declares
93
+ * directly without updates; and its extensions, each with the members it adds to its target, which take a value of the target as their receiver.
94
+ * @param {object[]} [contents.members] Its module members, properties and functions without a receiver, which its namespace reaches, a function its host invokes among them.
95
+ * @param {object[]} [contents.typeSelectors] The type selectors it declares, each by its name alone, through which a function's fixed argument selects the type of its result.
96
+ * @param {object[]} [contents.mappings] The keys it maps for type selectors, its own or another module's, each with the selector it belongs to, the key, any string, and the type it maps to.
97
+ * @param {object[]} [contents.observationTypes] The types of observation it declares.
98
+ * @param {object[]} [contents.observationTargets] The targets its observation types observe besides a receiver and an argument, each found from the receiver or independent of it, one target or several.
99
+ */
100
+ constructor(name, contents = {}) {
101
+ if (!Names.isIdentifier(name)) {
102
+ throw NexoErrors.module('NEXO4500', 'A module is named as a name is', typeof name === 'string' ? { module: name } : {});
103
+ }
104
+
105
+ // A declaration nested in a type is located at the type it stands in, as well as at its module, each by its name where it is a string, since a name of another value names nothing.
106
+ const failWithin = typeName => (code, message, declarationName, declarationKey) => {
107
+ throw NexoErrors.module(code, message, {
108
+ module: name,
109
+ ...(typeof typeName === 'string' ? { type: typeName } : {}),
110
+ ...(typeof declarationName === 'string' ? { declaration: declarationName } : {}),
111
+ ...(declarationKey === undefined ? {} : { declarationKey }),
112
+ });
113
+ };
114
+ const fail = failWithin(undefined);
115
+
116
+ // A module's contents take their keys as a declaration does, refused by codes of their own.
117
+ NexoModule.#validateKeys(contents, MODULE_KEYS, "A module's contents", fail, null, {
118
+ notObjectCode: 'NEXO4501',
119
+ unknownKeyCode: 'NEXO4502',
120
+ declaringSubject: 'A module',
121
+ });
122
+
123
+ const { typeOf, types = [], members = [], typeSelectors = [], mappings = [], observationTypes = [], observationTargets = [] } = contents;
124
+
125
+ if (typeOf !== undefined && typeof typeOf !== 'function') {
126
+ fail('NEXO4051', "A module's typeOf is a function, which names the type of a value it recognizes", undefined, 'typeOf');
127
+ }
128
+
129
+ for (const [key, list] of Object.entries({ types, members, typeSelectors, mappings, observationTypes, observationTargets })) {
130
+ if (!Array.isArray(list)) {
131
+ fail('NEXO4503', `A module's ${key} are a list`, undefined, key);
132
+ }
133
+ }
134
+
135
+ // Data that reaches itself is refused before anything reads into it, since validating, copying and freezing a declaration each walk its data.
136
+ for (const [key, list] of Object.entries({ types, members, typeSelectors, mappings, observationTypes, observationTargets })) {
137
+ list.forEach(declared => NexoModule.#refuseCycles(declared, NexoModule.#declarationNameOf(declared, key), fail));
138
+ }
139
+
140
+ this.#name = name;
141
+ this.#typeOf = typeOf;
142
+
143
+ // Each declaration but a type is read without its keys that hold undefined, which it leaves out as it leaves out a key it does not write; a type is kept as it is given.
144
+ const normalized = declarations => declarations.map(declared => NexoModule.#withoutUndefinedValues(declared));
145
+ const memberDeclarations = normalized(members);
146
+ const typeSelectorDeclarations = normalized(typeSelectors);
147
+ const mappingDeclarations = normalized(mappings);
148
+ const observationTypeDeclarations = normalized(observationTypes);
149
+ const observationTargetDeclarations = normalized(observationTargets);
150
+
151
+ NexoModule.#validateTypeDeclarations(types, fail, failWithin);
152
+ NexoModule.#validateMembers(memberDeclarations, fail);
153
+ NexoModule.#validateTypeSelectors(typeSelectorDeclarations, fail);
154
+ NexoModule.#validateMappings(mappingDeclarations, name, fail);
155
+ NexoModule.#validateObservationTypes(observationTypeDeclarations, fail);
156
+ NexoModule.#validateObservationTargets(observationTargetDeclarations, fail);
157
+
158
+ this.#types = NexoModule.#freezeItems(types.map(type => NexoModule.#createTypeCopy(type)));
159
+ this.#members = NexoModule.#freezeItems(memberDeclarations.map(member => NexoModule.#createMemberCopy(member)));
160
+ this.#typeSelectors = NexoModule.#freezeItems(typeSelectorDeclarations.map(selector => NexoModule.#copyOf(selector)));
161
+ this.#mappings = NexoModule.#freezeItems(mappingDeclarations.map(mapping => NexoModule.#copyOf(mapping)));
162
+ this.#observationTypes = NexoModule.#freezeItems(observationTypeDeclarations.map(type => NexoModule.#createObservationTypeCopy(type)));
163
+ this.#observationTargets = NexoModule.#freezeItems(observationTargetDeclarations.map(target => NexoModule.#copyOf(target)));
164
+ }
165
+
166
+ /** The module's name. */
167
+ get name() {
168
+ return this.#name;
169
+ }
170
+
171
+ /** The module's recognition of a host's value, naming the most specific of its reference types the value is of, or null; undefined where it recognizes none. */
172
+ get typeOf() {
173
+ return this.#typeOf;
174
+ }
175
+
176
+ /** The types the module declares, its reference types and its structured types. */
177
+ get types() {
178
+ return this.#types;
179
+ }
180
+
181
+ /** The members the module adds. */
182
+ get members() {
183
+ return this.#members;
184
+ }
185
+
186
+ /** The type selectors the module declares. */
187
+ get typeSelectors() {
188
+ return this.#typeSelectors;
189
+ }
190
+
191
+ /** The keys the module maps for type selectors, each to a type. */
192
+ get mappings() {
193
+ return this.#mappings;
194
+ }
195
+
196
+ /** The types of observation the module declares. */
197
+ get observationTypes() {
198
+ return this.#observationTypes;
199
+ }
200
+
201
+ /** The targets the module's observation types observe besides a receiver and an argument. */
202
+ get observationTargets() {
203
+ return this.#observationTargets;
204
+ }
205
+
206
+ /** The names of the scalar types Nexo defines, each a value of data alone, which conversion operators, data, equality and defaults read. */
207
+ static get scalarTypeNames() {
208
+ return SCALAR_TYPE_NAMES;
209
+ }
210
+
211
+ /** Whether the value is a module `createModule` made. */
212
+ static isModule(value) {
213
+ return value instanceof NexoModule;
214
+ }
215
+
216
+ /** Whether the declaration is a host call's: a function the host invokes, which the evaluator never does. */
217
+ static isInvokedByHost(declaration) {
218
+ return declaration.invokedBy === 'host';
219
+ }
220
+
221
+ /** The name a message gives a member, as an access writes it: its name, or `[…]` for an unnamed indexer, which has none. */
222
+ static writtenNameOf(declaration) {
223
+ return declaration.name ?? '[…]';
224
+ }
225
+
226
+ /** The object without its fields that hold undefined, which a declaration leaves out as it leaves out a field it does not write. */
227
+ static #withoutUndefinedValues(object) {
228
+ return ObjectUtils.isNonArrayObject(object) ? Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined)) : object;
229
+ }
230
+
231
+ /**
232
+ * Refuses a declaration that is no object, or that declares a key its kind does not take, naming that key; a key holding undefined is one it leaves out. A declaration is named by the name given, by its own where none
233
+ * is, and by none where the name is null, as a module's contents are, which keep codes of their own, the subject that declares their keys, and a hint for a key.
234
+ */
235
+ static #validateKeys(declaration, keys, what, fail, name, { notObjectCode = 'NEXO4504', unknownKeyCode = 'NEXO4030', declaringSubject = what } = {}) {
236
+ if (!ObjectUtils.isNonArrayObject(declaration)) {
237
+ fail(notObjectCode, `${what} ${name === null ? 'are' : 'is'} an object declaring ${keys.join(', ')}`, name);
238
+ }
239
+
240
+ const declarationName = name === null ? undefined : (name ?? declaration.name);
241
+
242
+ for (const key of Object.keys(NexoModule.#withoutUndefinedValues(declaration))) {
243
+ if (!keys.includes(key)) {
244
+ fail(unknownKeyCode, `${declaringSubject} declares ${keys.join(', ')}, and '${key}' is none of them`, declarationName, key);
245
+ }
246
+ }
247
+ }
248
+
249
+ /** The failure of a declaration's part under the key given, `updates` or `parameters`: its own key is located beneath that key, and the part itself at it. */
250
+ static #failUnder(fail, partKey) {
251
+ return (code, message, declarationName, declarationKey) => fail(code, message, declarationName, declarationKey === undefined ? partKey : `${partKey}.${declarationKey}`);
252
+ }
253
+
254
+ static #freezeItems(items) {
255
+ return Object.freeze(items.map(item => NexoModule.#deepFreeze(item)));
256
+ }
257
+
258
+ /** Freezes the data a declaration holds, leaving its implementation, a function its module owns, as it is. */
259
+ static #deepFreeze(value) {
260
+ if (value !== null && typeof value === 'object') {
261
+ Object.values(value).forEach(item => NexoModule.#deepFreeze(item));
262
+ Object.freeze(value);
263
+ }
264
+
265
+ return value;
266
+ }
267
+
268
+ /**
269
+ * Refuses a declaration whose data reaches itself through the objects it holds, lists and class instances among them, which data never does, naming the declaration key it stands under: every object freezing walks into.
270
+ * A value two parts of it share, which reaches itself through neither, is no cycle, and a function, an implementation among them, is never looked into, as copying and freezing never look into one.
271
+ */
272
+ static #refuseCycles(declaration, name, fail) {
273
+ const visit = (value, ancestors, declarationKey) => {
274
+ if (value === null || typeof value !== 'object') {
275
+ return;
276
+ }
277
+
278
+ if (ancestors.has(value)) {
279
+ fail('NEXO4562', 'A declaration holds a value that reaches itself, which data never does', name, declarationKey);
280
+ }
281
+
282
+ ancestors.add(value);
283
+
284
+ for (const [key, item] of Object.entries(value)) {
285
+ visit(item, ancestors, declarationKey ?? key);
286
+ }
287
+
288
+ ancestors.delete(value);
289
+ };
290
+
291
+ visit(declaration, new Set(), undefined);
292
+ }
293
+
294
+ /** The name a declaration's diagnostics give it before it is validated: a mapping's key, and any other declaration's own name, each where it is a string. */
295
+ static #declarationNameOf(declared, key) {
296
+ if (!ObjectUtils.isNonArrayObject(declared)) {
297
+ return undefined;
298
+ }
299
+
300
+ const name = key === 'mappings' ? declared.key : declared.name;
301
+
302
+ return typeof name === 'string' ? name : undefined;
303
+ }
304
+
305
+ /** A copy of a declaration's data, through its lists and objects, each function in it kept as it is. */
306
+ static #copyOf(value) {
307
+ if (Array.isArray(value)) {
308
+ return value.map(item => NexoModule.#copyOf(item));
309
+ }
310
+
311
+ if (ObjectUtils.isPlainObject(value)) {
312
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, NexoModule.#copyOf(item)]));
313
+ }
314
+
315
+ return value;
316
+ }
317
+
318
+ static #isTypeNotation(notation) {
319
+ return Type.parse(notation) !== null;
320
+ }
321
+
322
+ /** What is wrong with a notation that writes no type, for a message about it: none where it is left out, which a message of its own refuses, and otherwise that it is no type's notation. */
323
+ static #notationFailureOf(notation) {
324
+ return notation === undefined ? '' : `, and ${typeof notation === 'string' ? `'${notation}'` : 'a value no string'} is no type's notation`;
325
+ }
326
+
327
+ /** Whether the type notation writes `@selected`, where the type a fixed argument selects stands. */
328
+ static #writesSelected(notation) {
329
+ const writes = type => {
330
+ switch (type.kind) {
331
+ case 'typeParameter':
332
+ return type.name === '@selected';
333
+ case 'named':
334
+ return type.typeArguments.some(writes);
335
+ case 'object':
336
+ return [...type.fieldTypes.values()].some(writes);
337
+ default:
338
+ return false;
339
+ }
340
+ };
341
+
342
+ return notation !== undefined && writes(Type.parse(notation));
343
+ }
344
+
345
+ /** Refuses an `impl`, the implementation a declaration carries, that is given and is no function. */
346
+ static #validateImplementation(declaration, name, fail) {
347
+ if (declaration.impl !== undefined && typeof declaration.impl !== 'function') {
348
+ fail('NEXO4505', 'Its `impl` is a function', name, 'impl');
349
+ }
350
+ }
351
+
352
+ /** Validates the types a module declares, each named once in the module, since a type is identified by its module and its name, and each one's own members, located at it. */
353
+ static #validateTypeDeclarations(types, fail, failWithin) {
354
+ const names = new Set();
355
+
356
+ for (const type of types) {
357
+ NexoModule.#validateTypeDeclaration(type, fail, failWithin);
358
+
359
+ // A type and an extension are named once among a module's types, by their local names, the parameters a name declares taking no part.
360
+ const { name } = NexoModule.#parseDeclaredName(type.name);
361
+
362
+ if (names.has(name)) {
363
+ fail('NEXO4033', `The type '${name}' is declared already`, name);
364
+ }
365
+
366
+ names.add(name);
367
+ }
368
+ }
369
+
370
+ /**
371
+ * A declaration's name as it is written: its local name and the type parameters it declares in angle brackets after it, `ListExtensions<T>` naming `ListExtensions` and declaring `T`, and none where it writes no
372
+ * brackets. A name that writes brackets with nothing in them, or a parameter without a name, answers the failure.
373
+ * @returns {{ name: string, typeParameters: string[], failure?: string }}
374
+ */
375
+ static #parseDeclaredName(written) {
376
+ const match = typeof written === 'string' ? DECLARED_NAME.exec(written) : null;
377
+
378
+ if (match === null) {
379
+ return { name: written, typeParameters: [] };
380
+ }
381
+
382
+ const [, name, list] = match;
383
+
384
+ if (list === undefined) {
385
+ return { name, typeParameters: [] };
386
+ }
387
+
388
+ const typeParameters = list.split(',').map(parameter => parameter.trim());
389
+
390
+ if (typeParameters.some(parameter => !Names.isIdentifier(parameter))) {
391
+ return { name, typeParameters, failure: list.trim() === '' ? `'${written}' writes empty angle brackets, where a declaration declares its type parameters` : `'${written}' declares a type parameter without a name` };
392
+ }
393
+
394
+ return { name, typeParameters };
395
+ }
396
+
397
+ /** Refuses a declared name's failed parameters (NEXO4010) and a parameter it declares twice (NEXO4041), answering its local name and its parameters. */
398
+ static #validateDeclaredName(written, fail, declarationKey = 'name') {
399
+ const parsed = NexoModule.#parseDeclaredName(written);
400
+
401
+ if (parsed.failure !== undefined) {
402
+ fail('NEXO4010', parsed.failure, parsed.name, declarationKey);
403
+ }
404
+
405
+ const repeated = parsed.typeParameters.find((parameter, index) => parsed.typeParameters.indexOf(parameter) < index);
406
+
407
+ if (repeated !== undefined) {
408
+ fail('NEXO4041', `'${written}' declares the type parameter '${repeated}' twice`, parsed.name, declarationKey);
409
+ }
410
+
411
+ return parsed;
412
+ }
413
+
414
+ static #validateTypeDeclaration(type, fail, failWithin) {
415
+ if (ObjectUtils.isNonArrayObject(type) && !TYPE_KINDS.includes(type.kind)) {
416
+ fail('NEXO4554', type.kind === undefined ? `A type declares its kind, ${TYPE_KINDS.join(' or ')}` : `A type is of the kind ${TYPE_KINDS.join(' or ')}, and '${String(type.kind)}' is none of them`, type.name, 'kind');
417
+ }
418
+
419
+ NexoModule.#validateKeys(type, TYPE_KEYS_BY_KIND[type?.kind] ?? TYPE_KEYS_BY_KIND.structured, type?.kind === 'extension' ? 'An extension' : `A ${type?.kind ?? 'structured'} type`, fail);
420
+
421
+ const { name, typeParameters } = NexoModule.#validateDeclaredName(type.name, fail);
422
+
423
+ if (!Names.isIdentifier(name)) {
424
+ fail('NEXO4552', 'A type is named by an identifier, a letter followed by letters and digits', name, 'name');
425
+ }
426
+
427
+ // A type's data is never generic, so only an extension's name declares type parameters.
428
+ if (type.kind !== 'extension' && typeParameters.length > 0) {
429
+ fail('NEXO4558', `'${type.name}' is a ${type.kind} type, which declares no type parameters`, name, 'name');
430
+ }
431
+
432
+ if (type.kind === 'extension') {
433
+ NexoModule.#validateExtensionTarget(type, name, fail);
434
+ }
435
+
436
+ const imports = type.import ?? [];
437
+
438
+ if (!Array.isArray(imports) || !imports.every(moduleName => Names.isIdentifier(moduleName))) {
439
+ fail('NEXO4507', 'A type imports modules by their names, as a list', type.name, 'import');
440
+ }
441
+
442
+ const repeated = imports.find((moduleName, index) => imports.indexOf(moduleName) < index);
443
+
444
+ if (repeated !== undefined) {
445
+ fail('NEXO4508', `The type imports the module '${repeated}' twice`, type.name, 'import');
446
+ }
447
+
448
+ NexoModule.#validateInternal(type, name, fail);
449
+
450
+ if (type.kind === 'reference') {
451
+ NexoModule.#validateReferenceType(type, fail);
452
+ }
453
+
454
+ NexoModule.#validateOwnMembers(type, fail, failWithin(name), typeParameters);
455
+ }
456
+
457
+ /** Refuses an extension that names no target (NEXO4012), one that is no type (NEXO4512), and one that is `Any` or nullable (NEXO4559): its one target, a type or a parameter it declares, which a member is read on a value of. */
458
+ static #validateExtensionTarget(extension, name, fail) {
459
+ if (extension.target === undefined) {
460
+ fail('NEXO4012', 'An extension names its target, the type its members extend', name, 'target');
461
+ }
462
+
463
+ if (!NexoModule.#isTypeNotation(extension.target)) {
464
+ fail('NEXO4512', `An extension names its target as a type${NexoModule.#notationFailureOf(extension.target)}`, name, 'target');
465
+ }
466
+
467
+ const target = Type.parse(extension.target);
468
+
469
+ if (target.isNullable || target.kind === 'any') {
470
+ fail('NEXO4559', `An extension's target is never Any, since a parameter it declares extends every type, and never nullable, since a member is read on a value, and ${extension.target} is ${target.isNullable ? 'nullable' : 'Any'}`, name, 'target');
471
+ }
472
+ }
473
+
474
+ /** Validates a reference type's membership, the supertype it names and whether it is abstract, keeping its `isInstance` as it is. */
475
+ static #validateReferenceType(type, fail) {
476
+ if (type.isInstance === undefined) {
477
+ fail('NEXO4017', 'A reference type declares its isInstance, which answers whether a value belongs to it', type.name, 'isInstance');
478
+ }
479
+
480
+ if (typeof type.isInstance !== 'function') {
481
+ fail('NEXO4051', "A reference type's isInstance is a function, which answers whether a value belongs to it", type.name, 'isInstance');
482
+ }
483
+
484
+ if (type.supertype !== undefined && !NexoModule.#isTypeNotation(type.supertype)) {
485
+ fail('NEXO4512', `A reference type names its supertype as a type${NexoModule.#notationFailureOf(type.supertype)}`, type.name, 'supertype');
486
+ }
487
+
488
+ if (type.abstract !== undefined && typeof type.abstract !== 'boolean') {
489
+ fail('NEXO4555', 'Whether a reference type is abstract is a Boolean', type.name, 'abstract');
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Validates the members a type declares within it, a list of members each taking the type as its receiver, so none names a target, a structured type's properties without updates its data shape; each is located at
495
+ * the type.
496
+ */
497
+ static #validateOwnMembers(type, fail, failWithinType, typeParameters) {
498
+ if (type.members === undefined) {
499
+ return;
500
+ }
501
+
502
+ if (!Array.isArray(type.members)) {
503
+ fail('NEXO4503', "A type's members are a list", type.name, 'members');
504
+ }
505
+
506
+ type.members.forEach(member => NexoModule.#validateMember(NexoModule.#withoutUndefinedValues(member), failWithinType, type.kind, typeParameters));
507
+ }
508
+
509
+ /**
510
+ * Creates the stored copy of a valid type: its data without its keys that hold undefined, named by its local name with the type parameters it declares beside it, and each of its own members as a member's stored
511
+ * copy is created.
512
+ */
513
+ static #createTypeCopy(type) {
514
+ const copy = { ...NexoModule.#copyOf(NexoModule.#withoutUndefinedValues(type)), ...NexoModule.#namedCopyOf(type.name) };
515
+
516
+ return type.members === undefined ? copy : { ...copy, members: type.members.map(member => NexoModule.#createMemberCopy(NexoModule.#withoutUndefinedValues(member))) };
517
+ }
518
+
519
+ /** The keys a declared name gives its stored copy: its local name, and the type parameters it declares, `typeParameters`, where it declares any. */
520
+ static #namedCopyOf(written) {
521
+ const { name, typeParameters } = NexoModule.#parseDeclaredName(written);
522
+
523
+ return typeParameters.length === 0 ? { name } : { name, typeParameters: Object.freeze(typeParameters) };
524
+ }
525
+
526
+ /** Validates the type selectors a module declares, each named as a name is, once in the module. */
527
+ static #validateTypeSelectors(selectors, fail) {
528
+ const names = new Set();
529
+
530
+ for (const selector of selectors) {
531
+ NexoModule.#validateKeys(selector, TYPE_SELECTOR_KEYS, 'A type selector', fail);
532
+
533
+ if (!Names.isIdentifier(selector.name)) {
534
+ fail('NEXO4006', 'A type selector is named as a name is', selector.name, 'name');
535
+ }
536
+
537
+ if (names.has(selector.name)) {
538
+ fail('NEXO4007', `The type selector '${selector.name}' is declared twice`, selector.name, 'name');
539
+ }
540
+
541
+ names.add(selector.name);
542
+ }
543
+ }
544
+
545
+ /** Validates the mappings a module declares, each key once in its selector within the module, since a key is never replaced. */
546
+ static #validateMappings(mappings, moduleName, fail) {
547
+ const keys = new Set();
548
+
549
+ for (const mapping of mappings) {
550
+ NexoModule.#validateMapping(mapping, fail);
551
+
552
+ const key = JSON.stringify([mapping.selector, mapping.key]);
553
+
554
+ if (keys.has(key)) {
555
+ fail('NEXO4038', `The type selector '${mapping.selector}' maps the key '${mapping.key}' already, as the module '${moduleName}' declared it, and a key is never replaced`, mapping.key, 'key');
556
+ }
557
+
558
+ keys.add(key);
559
+ }
560
+ }
561
+
562
+ /** Validates a mapping: the selector it belongs to, its key, any string, as a fixed argument may give any, and the type it maps the key to. A mapping is located by its key, as its selector's keys are told apart. */
563
+ static #validateMapping(mapping, fail) {
564
+ NexoModule.#validateKeys(mapping, MAPPING_KEYS, 'A mapping', fail, ObjectUtils.isNonArrayObject(mapping) && typeof mapping.key === 'string' ? mapping.key : null);
565
+
566
+ if (typeof mapping.key !== 'string') {
567
+ fail('NEXO4008', 'A mapping declares its key, a string, as a fixed argument gives it', undefined, 'key');
568
+ }
569
+
570
+ // A selector that isn't a name can't be one a module declares.
571
+ if (!Names.isIdentifier(mapping.selector)) {
572
+ fail(mapping.selector === undefined ? 'NEXO4008' : 'NEXO4039', 'A mapping names the type selector it belongs to', mapping.key, 'selector');
573
+ }
574
+
575
+ if (!NexoModule.#isTypeNotation(mapping.type)) {
576
+ fail(mapping.type === undefined ? 'NEXO4008' : 'NEXO4512', `A mapping declares the type its key maps to${NexoModule.#notationFailureOf(mapping.type)}`, mapping.key, 'type');
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Validates how a member changes and reads, the changes it misses, the tolerance it compares with and the observations that cover its changes, as one `updates` object.
582
+ * @param {object} member The declaration whose updates they are.
583
+ * @param {Map<string, object>} parameters Its parameters by name, which an observation can give an argument of.
584
+ * @param {boolean} takesReceiver Whether it takes a receiver, which a module member takes none of.
585
+ */
586
+ static #validateUpdates(member, parameters, takesReceiver, name, fail) {
587
+ const updates = NexoModule.#withoutUndefinedValues(member.updates);
588
+
589
+ NexoModule.#validateKeys(updates, UPDATES_KEYS, 'Its updates', NexoModule.#failUnder(fail, 'updates'), name);
590
+
591
+ if (!CHANGE_CATEGORIES.includes(updates.changes) || !READING_MODES.includes(updates.reads)) {
592
+ const updatesKey = CHANGE_CATEGORIES.includes(updates.changes) ? 'reads' : 'changes';
593
+
594
+ fail(updates[updatesKey] === undefined ? 'NEXO4023' : 'NEXO4514', `It declares how it changes, one of ${CHANGE_CATEGORIES.join(', ')}, and how it reads, one of ${READING_MODES.join(', ')}`, name, `updates.${updatesKey}`);
595
+ }
596
+
597
+ // A member reading maintained values reads as null while it is pending, before its first sample, so its result can be null.
598
+ if (updates.reads === 'maintained' && typeof member.result === 'string' && !member.result.trim().endsWith('?')) {
599
+ fail('NEXO4071', `It reads maintained values, so it reads as null until its first sample arrives, and its result is ${member.result}, which can't be null: declare ${member.result.trim()}?`, name, 'result');
600
+ }
601
+
602
+ if (updates.changes === 'partly-observable' && (typeof updates.misses !== 'string' || updates.misses === '')) {
603
+ fail('NEXO4058', 'A partly observable member states the changes it misses', name, 'updates.misses');
604
+ }
605
+
606
+ if (updates.changes !== 'partly-observable' && updates.misses !== undefined) {
607
+ fail('NEXO4058', `It changes as ${updates.changes}, so it misses no changes`, name, 'updates.misses');
608
+ }
609
+
610
+ // Whether its result holds a number is checked where the member is registered, which knows every type.
611
+ if (updates.tolerance !== undefined && !(Number.isFinite(updates.tolerance) && updates.tolerance >= 0)) {
612
+ fail('NEXO4515', 'Its tolerance is a number that is not negative', name, 'updates.tolerance');
613
+ }
614
+
615
+ const observations = updates.observations ?? [];
616
+
617
+ if (!Array.isArray(observations)) {
618
+ fail('NEXO4516', 'Its observations are a list', name, 'updates.observations');
619
+ }
620
+
621
+ if (OBSERVED_CHANGE_CATEGORIES.includes(updates.changes) && observations.length === 0) {
622
+ fail('NEXO4059', `It changes as ${updates.changes}, so it names the observations that cover its changes`, name, 'updates.observations');
623
+ }
624
+
625
+ if (!OBSERVED_CHANGE_CATEGORIES.includes(updates.changes) && observations.length > 0) {
626
+ fail('NEXO4059', `It changes as ${updates.changes}, so it names no observations`, name, 'updates.observations');
627
+ }
628
+
629
+ for (const observationDeclaration of observations) {
630
+ NexoModule.#validateObservation(observationDeclaration, parameters, takesReceiver, name, fail);
631
+ }
632
+ }
633
+
634
+ /** Validates one observation a declaration names: its type, what it observes, which a module member's never a receiver is, and the arguments its type takes. */
635
+ static #validateObservation(observationDeclaration, parameters, takesReceiver, name, fail) {
636
+ const declarationKey = 'updates.observations';
637
+
638
+ if (observationDeclaration === null || typeof observationDeclaration !== 'object' || !Names.isIdentifier(observationDeclaration.type)) {
639
+ fail(observationDeclaration === null || typeof observationDeclaration !== 'object' ? 'NEXO4504' : 'NEXO4060', 'An observation names its type as a name is', name, declarationKey);
640
+ }
641
+
642
+ // A function a member is given, an expression and a predicate among them, lasts as long as its invocation, so no observation, which outlives it, is given one, directly or within a list.
643
+ const references = Object.values(observationDeclaration).flatMap(value => (Array.isArray(value) ? value : [value]));
644
+ const functionArgumentReference = references.find(value => typeof value?.argument === 'string' && ['function', 'expression', 'predicate'].includes(parameters.get(value.argument)?.kind));
645
+
646
+ if (functionArgumentReference !== undefined) {
647
+ fail('NEXO4055', `The observation '${observationDeclaration.type}' is given the parameter '${functionArgumentReference.argument}', a function that lasts only while the member's invocation does`, name, declarationKey);
648
+ }
649
+
650
+ // A target is named as a name is, and whether its observation type's module declares it is the registry's to check, which knows every module.
651
+ // Whether `of` takes a form an observation may name, an identifier or an argument reference; that the target it names exists, registration checks.
652
+ const namesObserved = Names.isIdentifier(observationDeclaration.of) || NexoModule.#isArgumentReference(observationDeclaration.of, parameters);
653
+
654
+ if (!namesObserved) {
655
+ fail('NEXO4060', `The observation '${observationDeclaration.type}' observes the receiver, an argument of the member or an observation target by its name, and not '${JSON.stringify(observationDeclaration.of)}'`, name, declarationKey);
656
+ }
657
+
658
+ if (!takesReceiver && observationDeclaration.of === 'receiver') {
659
+ fail('NEXO4057', `The observation '${observationDeclaration.type}' observes the receiver, and a module member takes no receiver`, name, declarationKey);
660
+ }
661
+
662
+ for (const [key, value] of Object.entries(observationDeclaration)) {
663
+ if (key !== 'type' && key !== 'of' && !NexoModule.#isObservationArgument(value, parameters)) {
664
+ fail(NexoModule.#isMisplacedPrefix(value, parameters) ? 'NEXO4061' : 'NEXO4060', `The observation '${observationDeclaration.type}' gives '${key}' a value that is no literal, list of literals or argument of the member, which a prefix, a string, is joined to only where its parameter is a value parameter of type string`, name, declarationKey);
665
+ }
666
+ }
667
+ }
668
+
669
+ /** Whether the value joins a prefix to an argument of the member whose parameter is not a value parameter of type string, the one parameter a prefix is joined to. */
670
+ static #isMisplacedPrefix(value, parameters) {
671
+ if (!ObjectUtils.isNonArrayObject(value) || Object.keys(value).length !== 2 || typeof value.prefix !== 'string' || value.prefix === '' || !parameters.has(value.argument)) {
672
+ return false;
673
+ }
674
+
675
+ const parameter = parameters.get(value.argument);
676
+
677
+ return parameter.kind !== 'value' || parameter.type !== 'String';
678
+ }
679
+
680
+ static #isArgumentReference(value, parameters) {
681
+ return value !== null && typeof value === 'object' && Object.keys(value).length === 1 && typeof value.argument === 'string' && parameters.has(value.argument);
682
+ }
683
+
684
+ /** Whether the value is an argument with a prefix joined to it, `{ argument, prefix }`, as `{ argument: 'name', prefix: 'aria-' }` names the attribute `aria-` and the name: the prefix a string no empty one, and the parameter a value parameter of type string, so nothing but a string is joined to it. */
685
+ static #isPrefixedArgumentReference(value, parameters) {
686
+ if (value === null || typeof value !== 'object' || Object.keys(value).length !== 2 || typeof value.prefix !== 'string' || value.prefix === '' || typeof value.argument !== 'string') {
687
+ return false;
688
+ }
689
+
690
+ const parameter = parameters.get(value.argument);
691
+
692
+ return parameter?.kind === 'value' && parameter.type === 'String';
693
+ }
694
+
695
+ /** Whether the value is an argument an observation may give its observation type: a literal, a reference to an argument of the member, which can carry a prefix, or a list of them. */
696
+ static #isObservationArgument(value, parameters) {
697
+ if (value === null || ['string', 'number', 'boolean'].includes(typeof value)) {
698
+ return true;
699
+ }
700
+
701
+ if (Array.isArray(value)) {
702
+ return value.every(item => NexoModule.#isObservationArgument(item, parameters));
703
+ }
704
+
705
+ return NexoModule.#isArgumentReference(value, parameters) || NexoModule.#isPrefixedArgumentReference(value, parameters);
706
+ }
707
+
708
+ /** Validates the targets a module declares: each named once, never `receiver`, found from the receiver where it says `from: 'receiver'`, and several where it says `count: 'many'`. */
709
+ static #validateObservationTargets(targets, fail) {
710
+ const names = new Set();
711
+
712
+ for (const target of targets) {
713
+ NexoModule.#validateKeys(target, OBSERVATION_TARGET_KEYS, 'An observation target', fail);
714
+
715
+ if (!Names.isIdentifier(target.name)) {
716
+ fail('NEXO4006', 'An observation target is named as a name is', target.name, 'name');
717
+ }
718
+
719
+ if (target.name === 'receiver') {
720
+ fail('NEXO4037', "'receiver' names an observation's receiver, so no target is named by it", target.name, 'name');
721
+ }
722
+
723
+ if (names.has(target.name)) {
724
+ fail('NEXO4517', `The observation target '${target.name}' is declared twice`, target.name, 'name');
725
+ }
726
+
727
+ names.add(target.name);
728
+
729
+ if (target.from !== undefined && target.from !== 'receiver') {
730
+ fail('NEXO4518', "A target found from the receiver says from: 'receiver', and one independent of it leaves from out", target.name, 'from');
731
+ }
732
+
733
+ if (target.count !== undefined && target.count !== 'many') {
734
+ fail('NEXO4519', "A target of several says count: 'many', and one target leaves count out", target.name, 'count');
735
+ }
736
+ }
737
+ }
738
+
739
+ /** Validates the observation types a module declares, each once in the module. */
740
+ static #validateObservationTypes(types, fail) {
741
+ const names = new Set();
742
+
743
+ for (const type of types) {
744
+ NexoModule.#validateObservationType(type, fail);
745
+
746
+ if (names.has(type.name)) {
747
+ fail('NEXO4007', `The observation type '${type.name}' is declared twice`, type.name, 'name');
748
+ }
749
+
750
+ names.add(type.name);
751
+ }
752
+ }
753
+
754
+ static #validateObservationType(type, fail) {
755
+ NexoModule.#validateKeys(type, OBSERVATION_TYPE_KEYS, 'An observation type', fail);
756
+
757
+ const identity = type?.identity ?? [];
758
+
759
+ if (!Names.isIdentifier(type?.name) || !OBSERVATION_CONTRACTS.includes(type.contract)) {
760
+ fail(Names.isIdentifier(type?.name) ? 'NEXO4520' : 'NEXO4006', `An observation type is named as a name is and is one of ${OBSERVATION_CONTRACTS.join(', ')}`, type?.name, Names.isIdentifier(type?.name) ? 'contract' : 'name');
761
+ }
762
+
763
+ if (!Array.isArray(identity) || !identity.every(argument => Names.isIdentifier(argument))) {
764
+ fail('NEXO4521', 'It names the arguments that belong to the identity of an observation, as identifiers', type.name, 'identity');
765
+ }
766
+
767
+ if (type.shared !== undefined && typeof type.shared !== 'boolean') {
768
+ fail('NEXO4522', 'It declares whether observations are shared, as a Boolean', type.name, 'shared');
769
+ }
770
+
771
+ NexoModule.#validateImplementation(type, type.name, fail);
772
+ }
773
+
774
+ /** Creates the stored copy of a valid observation type, its identity none and its observations shared where it says neither. */
775
+ static #createObservationTypeCopy(type) {
776
+ return NexoModule.#copyOf({ identity: [], shared: true, ...type });
777
+ }
778
+
779
+ /** Validates the module members a module declares, each named once in the module, since its namespace reaches it by its name alone. */
780
+ static #validateMembers(members, fail) {
781
+ const moduleMemberNames = new Set();
782
+
783
+ for (const member of members) {
784
+ NexoModule.#validateMember(member, fail);
785
+
786
+ if (moduleMemberNames.has(member.name)) {
787
+ fail('NEXO4549', `The module member '${member.name}' is declared already`, member.name, 'name');
788
+ }
789
+
790
+ moduleMemberNames.add(member.name);
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Validates a member: a module member, declared flat, which takes no receiver, or one declared within a type or an extension of the kind given, whose receiver the type or the extension's target is; a property a
796
+ * structured type declares directly without updates is a property of its data shape, which takes its name, its kind and its result alone.
797
+ */
798
+ static #validateMember(member, fail, containingKind = undefined, containingParameters = []) {
799
+ const takesReceiver = containingKind !== undefined;
800
+ const isDataProperty = containingKind === 'structured' && member?.kind === 'property' && member.updates === undefined;
801
+ // A member's place gives its receiver, so no member names a target.
802
+ const memberKeys = isDataProperty ? DATA_PROPERTY_KEYS : MEMBER_KEYS;
803
+ const kindKeys = isDataProperty ? [] : (MEMBER_KEYS_BY_KIND[member?.kind] ?? []);
804
+
805
+ if (!ObjectUtils.isNonArrayObject(member)) {
806
+ fail('NEXO4504', `A member is an object declaring ${memberKeys.join(', ')} and what its kind takes`);
807
+ }
808
+
809
+ // An unnamed indexer declares no name, and is located as its access is written; any other member declares its own type parameters in its name.
810
+ const isUnnamedIndexer = member.kind === 'indexer' && member.name === undefined;
811
+ const declared = isUnnamedIndexer ? { name: member.name, typeParameters: [] } : NexoModule.#validateDeclaredName(member.name, fail);
812
+ const name = isUnnamedIndexer ? UNNAMED_INDEXER_NAME : declared.name;
813
+ const ownParameter = declared.typeParameters.find(parameter => containingParameters.includes(parameter));
814
+
815
+ if (ownParameter !== undefined) {
816
+ fail('NEXO4043', `'${member.name}' declares the type parameter '${ownParameter}', which its extension declares, so its extension's stands within it`, name, 'name');
817
+ }
818
+
819
+ // A member is read after a dot or, an indexer, after a colon, where only a member's name stands, so a keyword names one.
820
+ if (!isUnnamedIndexer && !Names.isIdentifier(name)) {
821
+ fail(name === undefined ? 'NEXO4002' : 'NEXO4523', 'A member is named as a name is, a keyword among them', name, 'name');
822
+ }
823
+
824
+ if (!MEMBER_KINDS.includes(member.kind)) {
825
+ fail('NEXO4524', `A member is one of ${MEMBER_KINDS.join(', ')}`, name, 'kind');
826
+ }
827
+
828
+ if (member.kind === 'operator') {
829
+ NexoModule.#validateOperator(member, declared, takesReceiver, name, fail);
830
+ }
831
+
832
+ NexoModule.#validateKeys(member, [...memberKeys, ...kindKeys], isDataProperty ? "A property of a structured type's data" : `A member of kind '${member.kind}'`, fail, isUnnamedIndexer ? UNNAMED_INDEXER_NAME : undefined);
833
+
834
+ if (member.invokedBy !== undefined && !INVOKERS.includes(member.invokedBy)) {
835
+ fail('NEXO4525', `A function is invoked by one of ${INVOKERS.join(', ')}, the evaluator where it says none`, name, 'invokedBy');
836
+ }
837
+
838
+ const isInvokedByHost = NexoModule.isInvokedByHost(member);
839
+
840
+ // A host call's result is the type of each value its host supplies, and one that supplies none declares none.
841
+ if (!(isInvokedByHost && member.result === undefined) && !NexoModule.#isTypeNotation(member.result)) {
842
+ fail(member.result === undefined ? 'NEXO4003' : 'NEXO4512', `${isInvokedByHost ? 'It declares the type of each value its host supplies, or none, where it supplies none' : 'It declares the type of its result'}${NexoModule.#notationFailureOf(member.result)}`, name, 'result');
843
+ }
844
+
845
+ const parameters = Array.isArray(member.parameters) ? member.parameters.map(parameter => NexoModule.#withParameterDefaults(NexoModule.#withoutUndefinedValues(parameter))) : (member.parameters ?? []);
846
+
847
+ if (!Array.isArray(parameters)) {
848
+ fail('NEXO4526', 'Its parameters are a list', name, 'parameters');
849
+ }
850
+
851
+ const parameterNames = new Set();
852
+
853
+ for (const parameter of parameters) {
854
+ NexoModule.#validateParameter(parameter, name, fail);
855
+
856
+ if (parameterNames.has(parameter.name)) {
857
+ fail('NEXO4527', `The parameter '${parameter.name}' is declared twice`, name, 'parameters');
858
+ }
859
+
860
+ parameterNames.add(parameter.name);
861
+ }
862
+
863
+ if (isInvokedByHost) {
864
+ NexoModule.#validateHostInvokedFunction(member, parameters, name, fail);
865
+ }
866
+
867
+ NexoModule.#validateSelections(member, parameters, name, fail);
868
+ NexoModule.#validateForm(member, parameters, takesReceiver, name, fail);
869
+
870
+ if (kindKeys.includes('updates') && !isInvokedByHost) {
871
+ // A property declared directly in a structured type without updates is its data, which Nexo reads; any other property declares how it changes and how it reads, as a function does.
872
+ if (member.updates === undefined && member.kind === 'property') {
873
+ fail('NEXO4032', "It declares its updates, how it changes and how it reads, since only a property declared directly in a structured type declares none, and is that type's data", name, 'updates');
874
+ }
875
+
876
+ if (member.updates === undefined) {
877
+ fail('NEXO4003', 'It declares its updates, how it changes and how it reads', name, 'updates');
878
+ }
879
+
880
+ NexoModule.#validateUpdates(member, new Map(parameters.map(parameter => [parameter.name, parameter])), takesReceiver, name, fail);
881
+ }
882
+
883
+ NexoModule.#validateInternal(member, name, fail);
884
+ NexoModule.#validateImplementation(member, name, fail);
885
+ }
886
+
887
+ /**
888
+ * Refuses an operator named other than `cast` or `convert` (NEXO4560), one declaring type parameters (NEXO4561), since its destination selects it and no rule binds a parameter from a destination, and a module
889
+ * member that is one (NEXO4022), since the language's own syntax invokes it on a value of its type, which a module member has none of.
890
+ */
891
+ static #validateOperator(member, declared, takesReceiver, name, fail) {
892
+ if (!OPERATOR_NAMES.includes(declared.name)) {
893
+ fail('NEXO4560', `An operator is named ${OPERATOR_NAMES.join(' or ')}, the operation the language's syntax invokes it for, and '${declared.name}' is neither`, name, 'name');
894
+ }
895
+
896
+ if (declared.typeParameters.length > 0) {
897
+ fail('NEXO4561', `The operator '${member.name}' declares type parameters, which its destination never binds, so it uses its extension's`, name, 'name');
898
+ }
899
+
900
+ if (!takesReceiver) {
901
+ fail('NEXO4022', 'An operator is invoked on a value of its type, and a module member takes none', name, 'kind');
902
+ }
903
+ }
904
+
905
+ /** Refuses an `internal` that is given and is no Boolean: `false` where it is left out, and any other value, a string among them, refused. */
906
+ static #validateInternal(declaration, name, fail) {
907
+ if (declaration.internal !== undefined && typeof declaration.internal !== 'boolean') {
908
+ fail('NEXO4031', 'Whether it is internal, available within its declaring module alone, is a Boolean', name, 'internal');
909
+ }
910
+ }
911
+
912
+ /**
913
+ * Creates the stored copy of a valid member: its updates' observations none where it names none, and its parameters each without its keys that hold undefined, and with whether it is required and its null policy
914
+ * where it leaves them out.
915
+ */
916
+ static #createMemberCopy(member) {
917
+ const parameters = (member.parameters ?? []).map(parameter => NexoModule.#withParameterDefaults(NexoModule.#withoutUndefinedValues(parameter)));
918
+ const updates = member.updates === undefined ? {} : { updates: { observations: [], ...NexoModule.#withoutUndefinedValues(member.updates) } };
919
+ // A member that declares its own type parameters is named by its local name, and keeps them beside it.
920
+ const named = member.name === undefined ? {} : NexoModule.#namedCopyOf(member.name);
921
+
922
+ // A property is read with no arguments, so it keeps an empty list of parameters, which every member then has.
923
+ return NexoModule.#copyOf({ ...member, ...named, parameters, ...updates });
924
+ }
925
+
926
+ /**
927
+ * Refuses what a host call cannot take: an indexer's kind, since it is a function, which no other key it declares makes one, so it is refused first; updates, since it is never evaluated; an implementation, since its
928
+ * host invokes it; and an expression or a predicate parameter, a body of each item of a list the call follows, whose argument its host could keep neither as a value nor as a function of its own contract.
929
+ */
930
+ static #validateHostInvokedFunction(member, parameters, name, fail) {
931
+ if (member.kind === 'indexer') {
932
+ fail('NEXO4053', 'A function the host invokes is written as a function call, never as an indexer', name, 'invokedBy');
933
+ }
934
+
935
+ if (member.updates !== undefined) {
936
+ fail('NEXO4030', 'Its host invokes it, and the evaluator never evaluates it, so it declares no updates', name, 'updates');
937
+ }
938
+
939
+ if (member.impl !== undefined) {
940
+ fail('NEXO4030', 'Its host invokes it, so its module gives Nexo no implementation of it', name, 'impl');
941
+ }
942
+
943
+ const unprepared = parameters.find(parameter => parameter.kind !== 'value' && parameter.kind !== 'function');
944
+
945
+ if (unprepared !== undefined) {
946
+ fail('NEXO4056', `A function the host invokes takes value and function parameters, each argument prepared as a value or a function its host keeps, and '${unprepared.name}' is ${unprepared.kind === 'expression' ? 'an' : 'a'} ${unprepared.kind} parameter`, name, 'parameters');
947
+ }
948
+ }
949
+
950
+ /**
951
+ * Checks the fixed parameters a member takes: a function's one at most, which selects a type, whose result writes `@selected` where that type stands, as a member selecting none writes none.
952
+ */
953
+ static #validateSelections(member, parameters, name, fail) {
954
+ const typeSelectingParameters = parameters.filter(parameter => parameter.fixed === true && parameter.selects === 'type');
955
+
956
+ if (typeSelectingParameters.length > 1) {
957
+ fail('NEXO4009', `A function has one parameter at most that selects a type, since its result writes one @selected, and ${typeSelectingParameters.map(parameter => `'${parameter.name}'`).join(' and ')} each select one`, name, 'parameters');
958
+ }
959
+
960
+ if (typeSelectingParameters.length === 1 && !NexoModule.#writesSelected(member.result)) {
961
+ fail('NEXO4529', `Its parameter '${typeSelectingParameters[0].name}' selects a type, so its result writes @selected where that type stands`, name, 'result');
962
+ }
963
+
964
+ if (typeSelectingParameters.length === 0 && NexoModule.#writesSelected(member.result)) {
965
+ fail('NEXO4040', 'Its result writes @selected, and none of its parameters selects a type', name, 'result');
966
+ }
967
+ }
968
+
969
+ /**
970
+ * Checks how the member is written, by its kind, then the form its arguments take: a property is accessed and takes none; a function is called, its arguments positional, one for each parameter in order, so none that
971
+ * is required follows one that is optional, or an options object; and an indexer is given its one key in brackets.
972
+ */
973
+ static #validateForm(member, parameters, takesReceiver, name, fail) {
974
+ if (member.kind === 'indexer') {
975
+ NexoModule.#validateIndexer(parameters, takesReceiver, name, fail);
976
+ }
977
+
978
+ if (member.kind === 'function') {
979
+ NexoModule.#validateArgumentForm(member, parameters, name, fail);
980
+ }
981
+ }
982
+
983
+ /** Checks an indexer, named or unnamed: its key is its one parameter, a required value parameter, given positionally in brackets after its receiver, so a module member is none. */
984
+ static #validateIndexer(parameters, takesReceiver, name, fail) {
985
+ const [parameter] = parameters;
986
+
987
+ if (parameters.length !== 1 || parameter.kind !== 'value' || !parameter.required) {
988
+ fail('NEXO4020', 'An indexer takes its key as its one parameter, a required value parameter', name, 'parameters');
989
+ }
990
+
991
+ if (!takesReceiver) {
992
+ fail('NEXO4022', 'An indexer is written after its receiver, and a module member takes none', name, 'kind');
993
+ }
994
+ }
995
+
996
+ /** Checks the form a function's arguments take: none where it has no parameters, and otherwise one it declares, its positional arguments supplying its parameters in order, so none that is required follows one that is optional. */
997
+ static #validateArgumentForm(member, parameters, name, fail) {
998
+ if (parameters.length === 0) {
999
+ if (member.arguments !== undefined) {
1000
+ fail('NEXO4534', 'It has no parameters, so it declares no form of its arguments', name, 'arguments');
1001
+ }
1002
+
1003
+ return;
1004
+ }
1005
+
1006
+ if (!ARGUMENT_FORMS.includes(member.arguments)) {
1007
+ fail(member.arguments === undefined ? 'NEXO4003' : 'NEXO4535', `It has parameters, so it declares the form of its arguments, ${ARGUMENT_FORMS.join(' or ')}`, name, 'arguments');
1008
+ }
1009
+
1010
+ // Arguments supply positional parameters in order, so one left out is one after the last given, which only an optional parameter can be.
1011
+ const firstOptional = parameters.findIndex(parameter => !parameter.required);
1012
+ const lateRequired = firstOptional === -1 ? undefined : parameters.slice(firstOptional).find(parameter => parameter.required);
1013
+
1014
+ if (member.arguments === 'positional' && lateRequired !== undefined) {
1015
+ fail('NEXO4019', `The required parameter '${lateRequired.name}' follows an optional one, and positional arguments supply parameters in order`, name, 'parameters');
1016
+ }
1017
+ }
1018
+
1019
+ /**
1020
+ * The parameter with what its declaration leaves implicit: required unless it declares a default, and, but for a function parameter, which is never null, propagating null, as a parameter does unless its
1021
+ * declaration says otherwise. What it gives is kept, and validated as given.
1022
+ */
1023
+ static #withParameterDefaults(parameter) {
1024
+ if (!ObjectUtils.isNonArrayObject(parameter)) {
1025
+ return parameter;
1026
+ }
1027
+
1028
+ return { required: !Object.hasOwn(parameter, 'default'), ...(parameter.kind === 'function' ? {} : { nulls: 'propagate' }), ...parameter };
1029
+ }
1030
+
1031
+ static #validateParameter(parameter, memberName, fail) {
1032
+ NexoModule.#validateKeys(parameter, PARAMETER_KEYS, 'A parameter', NexoModule.#failUnder(fail, 'parameters'), memberName);
1033
+
1034
+ const declarationKey = 'parameters';
1035
+
1036
+ // A function is never null, so a function parameter declares no null policy.
1037
+ const hasValidNullPolicy = parameter?.kind === 'function' ? parameter.nulls === undefined : NULL_POLICIES.includes(parameter?.nulls);
1038
+
1039
+ if (!Names.isIdentifier(parameter.name) || !PARAMETER_KINDS.includes(parameter.kind) || !hasValidNullPolicy || typeof parameter.required !== 'boolean') {
1040
+ fail(parameter.kind === 'function' && !hasValidNullPolicy ? 'NEXO4054' : 'NEXO4536', 'A parameter declares its name and its kind, and, where it says them, whether it is required, a Boolean, and how it handles null, propagate or accept, which a function parameter leaves out', memberName, declarationKey);
1041
+ }
1042
+
1043
+ if (!NexoModule.#isTypeNotation(parameter.type)) {
1044
+ fail(parameter.type === undefined ? 'NEXO4536' : 'NEXO4512', `The parameter '${parameter.name}' declares a type${NexoModule.#notationFailureOf(parameter.type)}`, memberName, declarationKey);
1045
+ }
1046
+
1047
+ // A function parameter's contract declares the type of each argument the member calls the function with, in order, and, as its type, the type of the result it takes from it.
1048
+ if (parameter.kind === 'function') {
1049
+ if (!Array.isArray(parameter.parameterTypes) || !parameter.parameterTypes.every(type => NexoModule.#isTypeNotation(type))) {
1050
+ fail(Array.isArray(parameter.parameterTypes) ? 'NEXO4512' : 'NEXO4537', `The parameter '${parameter.name}' is a function that declares its parameterTypes, a list of the types it is called with`, memberName, declarationKey);
1051
+ }
1052
+
1053
+ if (!parameter.required) {
1054
+ fail('NEXO4054', `The parameter '${parameter.name}' is a function, so it is required`, memberName, declarationKey);
1055
+ }
1056
+
1057
+ if (parameter.fixed !== undefined) {
1058
+ fail('NEXO4030', `The parameter '${parameter.name}' is a function, so it cannot be fixed`, memberName, declarationKey);
1059
+ }
1060
+
1061
+ if (parameter.context !== undefined) {
1062
+ fail('NEXO4030', `The parameter '${parameter.name}' is a function, so it declares no 'context'`, memberName, declarationKey);
1063
+ }
1064
+ } else if (parameter.parameterTypes !== undefined) {
1065
+ fail('NEXO4030', `The parameter '${parameter.name}' is no function, so it declares no parameterTypes`, memberName, declarationKey);
1066
+ }
1067
+
1068
+ // A value is evaluated once, where the call is written, so it has no implicit parameter of its own.
1069
+ if (parameter.kind === 'value' && parameter.context !== undefined) {
1070
+ fail('NEXO4030', `The parameter '${parameter.name}' is a value, evaluated once where the call is written, so it declares no 'context'`, memberName, declarationKey);
1071
+ }
1072
+
1073
+ if (parameter.required && 'default' in parameter) {
1074
+ fail('NEXO4538', `The required parameter '${parameter.name}' declares no default`, memberName, declarationKey);
1075
+ }
1076
+
1077
+ if ((parameter.kind === 'expression' || parameter.kind === 'predicate') && parameter.context !== 'item') {
1078
+ fail('NEXO4539', `The ${parameter.kind} parameter '${parameter.name}' declares its implicit parameter as context: 'item'`, memberName, declarationKey);
1079
+ }
1080
+
1081
+ // A predicate's expression gives any value, and its implementation receives that value's truthiness.
1082
+ if (parameter.kind === 'predicate' && parameter.type !== 'Boolean') {
1083
+ fail('NEXO4540', `The predicate parameter '${parameter.name}' is of type boolean, the truthiness its implementation receives`, memberName, declarationKey);
1084
+ }
1085
+
1086
+ if (parameter.fixed !== undefined && typeof parameter.fixed !== 'boolean') {
1087
+ fail('NEXO4541', `The parameter '${parameter.name}' declares whether it is fixed as a Boolean`, memberName, declarationKey);
1088
+ }
1089
+
1090
+ if (parameter.fixed !== true) {
1091
+ if (parameter.selects !== undefined || parameter.selector !== undefined) {
1092
+ fail('NEXO4542', `The parameter '${parameter.name}' selects nothing, since it is not fixed`, memberName, declarationKey);
1093
+ }
1094
+
1095
+ return;
1096
+ }
1097
+
1098
+ if (!SELECTION_KINDS.includes(parameter.selects)) {
1099
+ const predicateHint = parameter.selects === 'predicate' ? '; a test of the receiver is passed as a function, to a function parameter' : '';
1100
+
1101
+ fail('NEXO4543', `The fixed parameter '${parameter.name}' declares what it selects, one of ${SELECTION_KINDS.join(', ')}${predicateHint}`, memberName, declarationKey);
1102
+ }
1103
+
1104
+ // A type is selected through the type selector the parameter names.
1105
+ if (!Names.isIdentifier(parameter.selector)) {
1106
+ fail('NEXO4544', `The fixed parameter '${parameter.name}' selects a type, so it names the type selector it selects through`, memberName, declarationKey);
1107
+ }
1108
+
1109
+ // A fixed argument names a declaration, which only a string can.
1110
+ if (parameter.kind !== 'value' || parameter.type !== 'String') {
1111
+ fail('NEXO4545', `The fixed parameter '${parameter.name}' is a value parameter of type string, since it names what it selects`, memberName, declarationKey);
1112
+ }
1113
+
1114
+ // What a fixed argument selects is what the script says, so a script always writes it, and none is left to a default.
1115
+ if (parameter.required === false || parameter.default !== undefined) {
1116
+ fail('NEXO4072', `The fixed parameter '${parameter.name}' is required, since the argument a script writes for it selects what it does`, memberName, declarationKey);
1117
+ }
1118
+ }
1119
+ }