@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,401 @@
1
+ /**
2
+ * Type — a Nexo type: a named type, a generic one with its type arguments among them, an object type, the object root, a function type, whose values are function references, a type parameter or `Any`, each nullable or not, the type of null, and the type no value has;
3
+ * a value alone, whose relations to another type `TypeChecker` answers
4
+ */
5
+
6
+ import { Names } from './Names.mjs';
7
+
8
+ const NAME = /^@?[A-Za-z][A-Za-z0-9]*$/;
9
+ const TYPE_PARAMETER = /^(?:[A-Z]|@[A-Za-z][A-Za-z0-9]*)$/;
10
+
11
+ /** The name of the type of a JavaScript primitive, by what `typeof` answers for it. */
12
+ /** The scalar types' names by the JavaScript type of their values: `NexoModule.scalarTypeNames`, which a test keeps this map to, by `typeof`, since `Type` imports nothing of the vocabulary. */
13
+ const PRIMITIVE_TYPE_NAMES = Object.freeze({ string: 'String', number: 'Number', boolean: 'Boolean' });
14
+
15
+ /** How many type arguments each of Nexo's own generic types takes, by its name: the generic value types `Vocabulary` declares, which a test keeps this table to, since `Type` imports nothing of the vocabulary. */
16
+ const GENERIC_TYPE_ARITIES = Object.freeze({ List: 1, Dictionary: 2, Pair: 2 });
17
+
18
+ export class Type {
19
+ /** The names of Nexo's own generic types, each with how many type arguments it takes. */
20
+ static genericTypeArities = GENERIC_TYPE_ARITIES;
21
+
22
+ /** @type {Map<string, Type | null>} */
23
+ static #parseCache = new Map();
24
+
25
+ /** The type's kind: named, object, root, function, typeParameter, any, null or never. */
26
+ kind;
27
+
28
+ /** The type's name, for a named type or a type parameter: a named type's local name, unique within its module. */
29
+ name;
30
+
31
+ /** The name of the module that declares a named type, null for one of Nexo's own types and for a name no module qualifies yet. */
32
+ moduleName;
33
+
34
+ /** A generic named type's type arguments, in the order its declaration declares its parameters; empty for any other type. @type {readonly Type[]} */
35
+ typeArguments;
36
+
37
+ /** An object type's field types by name, or the fields of the outermost object the object root reads. */
38
+ fieldTypes;
39
+
40
+ /** The object expression that builds the outermost object the object root refers to, which its fields belong to; null for any other type. */
41
+ objectExpression;
42
+
43
+ /** The functions a function reference of the type may be, each with the scope it was declared in, which a call through the value is checked against; null for any other type. @type {readonly object[] | null} */
44
+ functions;
45
+
46
+ /** Whether the type admits null. */
47
+ isNullable;
48
+
49
+ constructor(kind, { name = null, moduleName = null, typeArguments = [], fieldTypes = null, objectExpression = null, functions = null, isNullable = false } = {}) {
50
+ this.kind = kind;
51
+ this.name = name;
52
+ this.moduleName = moduleName;
53
+ this.typeArguments = Object.freeze([...typeArguments]);
54
+ this.fieldTypes = fieldTypes;
55
+ this.objectExpression = objectExpression;
56
+ this.functions = functions;
57
+ this.isNullable = isNullable;
58
+ Object.freeze(this);
59
+ }
60
+
61
+ /** The type the notation writes, or null where it is not a type: a named type qualified where its module's name and a dot precede it. */
62
+ static parse(text) {
63
+ if (!Type.#parseCache.has(text)) {
64
+ Type.#parseCache.set(text, new TypeReader(text).readAll());
65
+ }
66
+
67
+ return Type.#parseCache.get(text);
68
+ }
69
+
70
+ /** A named type, one of Nexo's own types where no module declares it. */
71
+ static createNamedType(name, isNullable = false, moduleName = null) {
72
+ return new Type('named', { name, moduleName, isNullable });
73
+ }
74
+
75
+ /**
76
+ * The identity, written once it is first read: valid for good, since a type never changes, and released with the type. Registries look types up by it wherever a value is captured, copied or checked, and a string
77
+ * joined anew for each lookup is hashed anew.
78
+ * @type {string | null | undefined}
79
+ */
80
+ #identity;
81
+
82
+ /** A named type's identity, its module's name and its local name, or its local name alone for one of Nexo's own types; null for any other type. */
83
+ get identity() {
84
+ if (this.#identity === undefined) {
85
+ this.#identity = this.kind !== 'named' ? null : this.moduleName === null ? this.name : `${this.moduleName}.${this.name}`;
86
+ }
87
+
88
+ return this.#identity;
89
+ }
90
+
91
+ /** The type of a string, a number or a Boolean, the language's `String`, `Number` or `Boolean`. */
92
+ static createPrimitiveType(value) {
93
+ return Type.createNamedType(PRIMITIVE_TYPE_NAMES[typeof value]);
94
+ }
95
+
96
+ /** A list of the item type: Nexo's own generic `List`, its one type argument the item type. */
97
+ static createListType(itemType, isNullable = false) {
98
+ return new Type('named', { name: 'List', typeArguments: [itemType], isNullable });
99
+ }
100
+
101
+ /** A dictionary of the key type and the value type: Nexo's own generic `Dictionary`. */
102
+ static createDictionaryType(keyType, valueType, isNullable = false) {
103
+ return new Type('named', { name: 'Dictionary', typeArguments: [keyType, valueType], isNullable });
104
+ }
105
+
106
+ /** A pair of the key type and the value type: Nexo's own generic `Pair`. */
107
+ static createPairType(keyType, valueType, isNullable = false) {
108
+ return new Type('named', { name: 'Pair', typeArguments: [keyType, valueType], isNullable });
109
+ }
110
+
111
+ /** Whether the type is a dictionary, Nexo's own `Dictionary`. */
112
+ get isDictionary() {
113
+ return this.kind === 'named' && this.moduleName === null && this.name === 'Dictionary';
114
+ }
115
+
116
+ /** Whether the type is a pair, Nexo's own `Pair`. */
117
+ get isPair() {
118
+ return this.kind === 'named' && this.moduleName === null && this.name === 'Pair';
119
+ }
120
+
121
+ /** Whether the type is a list, Nexo's own `List`. */
122
+ get isList() {
123
+ return this.kind === 'named' && this.moduleName === null && this.name === 'List';
124
+ }
125
+
126
+ /** The type an expression of each item of the collection reads as `it`: a list's item type, and a dictionary's value type, since a dictionary's value operations evaluate against its values; null for any other type. */
127
+ get itemContextType() {
128
+ return this.isList ? this.typeArguments[0] : this.isDictionary ? this.typeArguments[1] : null;
129
+ }
130
+
131
+ /** The type of a list's items, its one type argument; null for any other type. */
132
+ get itemType() {
133
+ return this.isList ? this.typeArguments[0] : null;
134
+ }
135
+
136
+ /**
137
+ * The type of the object field an object type holds under the name, which may be the type null; null where it holds none, and for a type of any other kind, the object root among them, whose completed fields its own
138
+ * reads check.
139
+ */
140
+ getObjectFieldType(name) {
141
+ return this.kind === 'object' ? (this.fieldTypes.get(name) ?? null) : null;
142
+ }
143
+
144
+ /** The object type of the field types, by name. */
145
+ static createObjectType(fieldTypes, isNullable = false) {
146
+ return new Type('object', { fieldTypes, isNullable });
147
+ }
148
+
149
+ /**
150
+ * The object root, a reference to the outermost object the object expression builds, through which the fields completed where the reference is written are read: its type identifies the object expression and those fields,
151
+ * and holds no data, so no result holds the reference.
152
+ */
153
+ static createObjectRootType(objectExpression, fieldTypes, isNullable = false) {
154
+ return new Type('objectRoot', { fieldTypes, objectExpression, isNullable });
155
+ }
156
+
157
+ /** The function type of a function reference, which may be any of the functions, each a function the expression declares with the scope it captures: no data, so no result holds one. */
158
+ static createFunctionType(functions, isNullable = false) {
159
+ return new Type('function', { functions: Object.freeze([...new Set(functions)]), isNullable });
160
+ }
161
+
162
+ /** A type parameter a declaration's types write, whose argument matching infers. */
163
+ static createTypeParameter(name, isNullable = false) {
164
+ return new Type('typeParameter', { name, isNullable });
165
+ }
166
+
167
+ /** The type of null alone, which fits every nullable type. */
168
+ static get null() {
169
+ return Type.#null;
170
+ }
171
+
172
+ static #null = new Type('null', { isNullable: true });
173
+
174
+ /** The type no value has, which fits every type: the item type of the empty list. */
175
+ static get never() {
176
+ return Type.#never;
177
+ }
178
+
179
+ static #never = new Type('never');
180
+
181
+ /** The type of a value of any type that isn't null, `Any`, whose concrete type is known when it is evaluated. */
182
+ static get any() {
183
+ return Type.#any;
184
+ }
185
+
186
+ static #any = new Type('any');
187
+
188
+ /** The same type, admitting null; never admitting null is null's type. */
189
+ toNullable() {
190
+ if (this.kind === 'never') {
191
+ return Type.null;
192
+ }
193
+
194
+ return this.isNullable ? this : new Type(this.kind, { name: this.name, moduleName: this.moduleName, typeArguments: this.typeArguments, fieldTypes: this.fieldTypes, objectExpression: this.objectExpression, functions: this.functions, isNullable: true });
195
+ }
196
+
197
+ /** The same type, admitting no null. */
198
+ toNonNullable() {
199
+ return !this.isNullable || this.kind === 'null' ? this : new Type(this.kind, { name: this.name, moduleName: this.moduleName, typeArguments: this.typeArguments, fieldTypes: this.fieldTypes, objectExpression: this.objectExpression, functions: this.functions, isNullable: false });
200
+ }
201
+
202
+ /** Whether the two types are the same, nullability included. */
203
+ equals(otherType) {
204
+ if (this.kind !== otherType.kind || this.name !== otherType.name || this.moduleName !== otherType.moduleName || this.objectExpression !== otherType.objectExpression || this.isNullable !== otherType.isNullable) {
205
+ return false;
206
+ }
207
+
208
+ if (this.typeArguments.length !== otherType.typeArguments.length) {
209
+ return false;
210
+ }
211
+
212
+ // A loop rather than a callback, so a comparison allocates nothing, as a structure's record is compared for each value copied.
213
+ for (let index = 0; index < this.typeArguments.length; index++) {
214
+ if (!this.typeArguments[index].equals(otherType.typeArguments[index])) {
215
+ return false;
216
+ }
217
+ }
218
+
219
+ if (this.fieldTypes !== null) {
220
+ return this.fieldTypes.size === otherType.fieldTypes.size && [...this.fieldTypes].every(([name, fieldType]) => otherType.fieldTypes.get(name)?.equals(fieldType) === true);
221
+ }
222
+
223
+ if (this.functions !== null) {
224
+ return this.functions.length === otherType.functions.length && this.functions.every(candidate => otherType.functions.includes(candidate));
225
+ }
226
+
227
+ return true;
228
+ }
229
+
230
+ /** A field's name as the notation writes it: a name as it is, and any other key of data quoted, as JSON writes a string, so no key reads as the notation around it. */
231
+ static #fieldNameOf(name) {
232
+ return Names.isIdentifier(name) ? name : JSON.stringify(name);
233
+ }
234
+
235
+ /**
236
+ * The notation of the type, each named type in it by its local name, or, where it is written qualified, by its module and its local name, as a declaration names it: one of Nexo's own written alone, and `Any` and
237
+ * `Never`, qualified by Nexo's module, as `Nexo.Number`, and a generic one, which a qualified name never writes, by its local name with its type arguments.
238
+ */
239
+ toString(isQualified = false) {
240
+ let text;
241
+
242
+ switch (this.kind) {
243
+ case 'object':
244
+ text = `{ ${[...this.fieldTypes].map(([name, fieldType]) => `${Type.#fieldNameOf(name)}: ${fieldType.toString(isQualified)}`).join(', ')} }`;
245
+ break;
246
+ case 'objectRoot':
247
+ text = this.fieldTypes.size === 0 ? 'root { }' : `root { ${[...this.fieldTypes].map(([name, fieldType]) => `${Type.#fieldNameOf(name)}: ${fieldType.toString(isQualified)}`).join(', ')} }`;
248
+ break;
249
+ case 'function':
250
+ text = 'function';
251
+ break;
252
+ case 'null':
253
+ return 'null';
254
+ case 'never':
255
+ return isQualified ? `${Names.nexoModuleName}.Never` : 'Never';
256
+ case 'any':
257
+ text = isQualified ? `${Names.nexoModuleName}.Any` : 'Any';
258
+ break;
259
+ case 'named':
260
+ text = isQualified && (this.moduleName !== null || this.typeArguments.length === 0) ? `${this.moduleName ?? Names.nexoModuleName}.${this.name}` : this.name;
261
+
262
+ if (this.typeArguments.length > 0) {
263
+ text = `${text}<${this.typeArguments.map(typeArgument => typeArgument.toString(isQualified)).join(', ')}>`;
264
+ }
265
+
266
+ break;
267
+ default:
268
+ text = this.name;
269
+ }
270
+
271
+ return this.isNullable ? `${text}?` : text;
272
+ }
273
+
274
+ /** How a message writes two types it names together: each by its local names, and both qualified where they read alike and are different types, as `home.Sensor` and `garden.Sensor`. */
275
+ static namesOf(firstType, secondType) {
276
+ const isAlike = String(firstType) === String(secondType) && !firstType.equals(secondType);
277
+
278
+ return [firstType.toString(isAlike), secondType.toString(isAlike)];
279
+ }
280
+
281
+ /**
282
+ * The type with each type parameter that has an argument replaced by it, nullable where the parameter is; a concrete type, and a parameter with no argument, stay as they are.
283
+ * @param {Map<string, Type>} typeArguments The arguments, by parameter name.
284
+ */
285
+ resolveTypeParameters(typeArguments) {
286
+ if (this.kind === 'typeParameter') {
287
+ const typeArgument = typeArguments.get(this.name);
288
+
289
+ if (typeArgument === undefined) {
290
+ return this;
291
+ }
292
+
293
+ return this.isNullable ? typeArgument.toNullable() : typeArgument;
294
+ }
295
+
296
+ if (this.typeArguments.length > 0) {
297
+ return new Type(this.kind, { name: this.name, moduleName: this.moduleName, typeArguments: this.typeArguments.map(typeArgument => typeArgument.resolveTypeParameters(typeArguments)), isNullable: this.isNullable });
298
+ }
299
+
300
+ return this;
301
+ }
302
+ }
303
+
304
+ class TypeReader {
305
+ #text;
306
+ #offset = 0;
307
+
308
+ constructor(text) {
309
+ this.#text = typeof text === 'string' ? text : '';
310
+ }
311
+
312
+ readAll() {
313
+ const type = this.#readType();
314
+
315
+ return type !== null && this.#offset === this.#text.length ? type : null;
316
+ }
317
+
318
+ #readType() {
319
+ const word = /@?[A-Za-z][A-Za-z0-9]*/y;
320
+ word.lastIndex = this.#offset;
321
+ const match = word.exec(this.#text);
322
+
323
+ if (match === null || !NAME.test(match[0])) {
324
+ return null;
325
+ }
326
+
327
+ this.#offset = word.lastIndex;
328
+
329
+ // A name its module's name and a dot qualify is a type that module declares.
330
+ if (this.#text[this.#offset] === '.' && !match[0].startsWith('@')) {
331
+ word.lastIndex = this.#offset + 1;
332
+ const local = word.exec(this.#text);
333
+
334
+ if (local === null || local[0].startsWith('@')) {
335
+ return null;
336
+ }
337
+
338
+ this.#offset = word.lastIndex;
339
+
340
+ return this.#readNullable(Type.createNamedType(local[0], false, match[0]));
341
+ }
342
+
343
+ let type;
344
+
345
+ // One of Nexo's own generic types takes its type arguments in angle brackets, separated by a comma and a space, and no other name takes any.
346
+ if (this.#text[this.#offset] === '<') {
347
+ const arity = Object.hasOwn(GENERIC_TYPE_ARITIES, match[0]) ? GENERIC_TYPE_ARITIES[match[0]] : 0;
348
+ const typeArguments = [];
349
+
350
+ this.#offset++;
351
+
352
+ for (let index = 0; index < arity; index++) {
353
+ if (index > 0) {
354
+ if (!this.#text.startsWith(', ', this.#offset)) {
355
+ return null;
356
+ }
357
+
358
+ this.#offset += 2;
359
+ }
360
+
361
+ const typeArgument = this.#readType();
362
+
363
+ if (typeArgument === null) {
364
+ return null;
365
+ }
366
+
367
+ typeArguments.push(typeArgument);
368
+ }
369
+
370
+ if (arity === 0 || this.#text[this.#offset] !== '>') {
371
+ return null;
372
+ }
373
+
374
+ this.#offset++;
375
+ type = new Type('named', { name: match[0], typeArguments });
376
+ } else if (match[0] === 'null') {
377
+ type = Type.null;
378
+ } else if (match[0] === 'Never') {
379
+ type = Type.never;
380
+ } else if (match[0] === 'Any') {
381
+ type = Type.any;
382
+ } else if (TYPE_PARAMETER.test(match[0])) {
383
+ type = Type.createTypeParameter(match[0]);
384
+ } else {
385
+ type = Type.createNamedType(match[0]);
386
+ }
387
+
388
+ return this.#readNullable(type);
389
+ }
390
+
391
+ /** The type, made nullable where `?` follows it. */
392
+ #readNullable(type) {
393
+ if (this.#text[this.#offset] === '?') {
394
+ this.#offset++;
395
+
396
+ return type.toNullable();
397
+ }
398
+
399
+ return type;
400
+ }
401
+ }