@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,453 @@
1
+ /**
2
+ * Lowering — a syntax tree turned into the definition it writes, with the span of text each node, field, local, binding and import of it came from
3
+ */
4
+
5
+ import { Imports } from './Imports.mjs';
6
+ import { Names } from './Names.mjs';
7
+ import { Versions } from './Versions.mjs';
8
+ import { SyntaxTree } from './SyntaxTree.mjs';
9
+
10
+ /** @typedef {import('./Parser.mjs').Span} Span */
11
+ /** @typedef {import('./SyntaxTree.mjs').SyntaxNode} SyntaxNode */
12
+
13
+ export class Lowering {
14
+ /** @type {WeakMap<object, Span>} */
15
+ #spans = new WeakMap();
16
+
17
+ /** The text the tree was read from, which spells each word a name is. */
18
+ #text;
19
+
20
+ /** The local names the script's imports give modules, which a lexical reference reads where no local or field of its name is in scope. @type {ReadonlySet<string>} */
21
+ #namespaceNames;
22
+
23
+ /** The names of the locals and the fields in scope where the node being lowered stands, which a name reads; any other name reads the script parameter of its name. @type {Set<string>} */
24
+ #localNames = new Set();
25
+
26
+ /** The parameters a bare name reads, where no local or field in scope has its name, rather than written with `~`, which validation marks as written alone. @type {WeakSet<object>} */
27
+ #scriptParametersWrittenAlone = new WeakSet();
28
+
29
+ /**
30
+ * The definition the tree writes, its imports those the text writes and those a host supplies, each once, in order of the local name it gives; the span of text each of its nodes, fields, locals, conditional bindings
31
+ * and written imports came from, which the tree is no longer needed for; and the parameters written alone.
32
+ * @param {import('./SyntaxTree.mjs').ScriptNode} tree
33
+ * @param {readonly object[]} [suppliedImports] The imports a host supplies, in a definition's shape, checked already.
34
+ */
35
+ static lower(tree, suppliedImports = []) {
36
+ const written = tree.children.filter(child => child.kind === 'import-declaration').map(declaration => {
37
+ const entry = SyntaxTree.importOf(declaration, tree.text);
38
+
39
+ return { entry, span: { start: SyntaxTree.startOf(declaration), end: SyntaxTree.endOf(declaration) } };
40
+ });
41
+ const imports = Imports.mergeImports(written.map(({ entry }) => entry), suppliedImports);
42
+ const lowering = new Lowering(tree.text, Imports.namespaceNamesOf(imports));
43
+
44
+ // A written import keeps the span its declaration has, where a host's of the same declaration and local name merged into it.
45
+ written.forEach(({ entry, span }) => lowering.#spans.set(entry, span));
46
+
47
+ const expression = lowering.#lower(SyntaxTree.bodyOf(tree));
48
+
49
+ return { definition: { version: Versions.definition, imports, expression }, spans: lowering.#spans, scriptParametersWrittenAlone: lowering.#scriptParametersWrittenAlone };
50
+ }
51
+
52
+ /**
53
+ * @param {string} text The text the tree was read from.
54
+ * @param {ReadonlySet<string>} namespaceNames The local names the script's imports give modules.
55
+ */
56
+ constructor(text, namespaceNames) {
57
+ this.#text = text;
58
+ this.#namespaceNames = namespaceNames;
59
+ }
60
+
61
+ #lower(child) {
62
+ if (SyntaxTree.isToken(child)) {
63
+ return this.#lowerToken(child);
64
+ }
65
+
66
+ const { children } = child;
67
+
68
+ switch (child.kind) {
69
+ case 'conditional-expression':
70
+ return this.#lowerConditional(children, 0, SyntaxTree.endOf(child));
71
+ case 'body':
72
+ return this.#lowerBody(child);
73
+ case 'function-expression': {
74
+ // Its parameters are in scope in its body alone, beside the locals in scope where it is declared.
75
+ const close = children.findIndex(part => SyntaxTree.isToken(part) && part.type === ')');
76
+ const parameters = children.slice(2, close).filter(part => part.type !== ',').map(part => SyntaxTree.wordOf(part, this.#text));
77
+ const enclosingLocalNames = this.#localNames;
78
+
79
+ this.#localNames = new Set([...this.#localNames, ...parameters]);
80
+
81
+ try {
82
+ return this.#span({ kind: 'function', parameters, body: this.#lower(children.at(-2)) }, child);
83
+ } finally {
84
+ this.#localNames = enclosingLocalNames;
85
+ }
86
+ }
87
+ case 'arrow-function': {
88
+ // Its parameters, one name alone or names in parentheses, are in scope in its body alone, an expression whose value is its result.
89
+ const arrow = children.findIndex(part => SyntaxTree.isToken(part) && part.type === '=>');
90
+ const parameters = children.slice(0, arrow).filter(part => part.type !== ',' && part.type !== '(' && part.type !== ')').map(part => SyntaxTree.wordOf(part, this.#text));
91
+ const enclosingLocalNames = this.#localNames;
92
+
93
+ this.#localNames = new Set([...this.#localNames, ...parameters]);
94
+
95
+ try {
96
+ return this.#span({ kind: 'function', parameters, body: this.#lower(children.at(-1)), arrow: true }, child);
97
+ } finally {
98
+ this.#localNames = enclosingLocalNames;
99
+ }
100
+ }
101
+ case 'function-call':
102
+ // A call of the function a name holds calls the local its name reads, a lexical reference's or a bare name's alike.
103
+ return this.#span({ kind: 'call', callee: this.#span({ kind: 'local', name: children[0].value }, children[0]), arguments: this.#argumentsOf(children.slice(2, -1)) }, child);
104
+ case 'do-expression':
105
+ // A do block holds its body, an expression alone or locals and the `return` that gives its result.
106
+ return this.#span({ kind: 'do', body: this.#lower(children[1]) }, child);
107
+ case 'disjunction':
108
+ case 'conjunction':
109
+ return this.#span({ kind: child.kind === 'disjunction' ? 'or' : 'and', operands: children.filter((_, index) => index % 2 === 0).map(operand => this.#lower(operand)) }, child);
110
+ case 'negation':
111
+ return this.#span({ kind: 'not', operand: this.#lower(children[1]) }, child);
112
+ case 'equality': {
113
+ // `!=` is the negation of `==`, which spans what the equality does.
114
+ const equality = this.#span({ kind: 'equality', left: this.#lower(children[0]), right: this.#lower(children[2]) }, child);
115
+
116
+ return children[1].type === '!=' ? this.#span({ kind: 'not', operand: equality }, child) : equality;
117
+ }
118
+ case 'comparison':
119
+ // A chain is one comparison, its operands in order and an operator between each two.
120
+ return this.#span({ kind: 'comparison', operands: children.filter((_, index) => index % 2 === 0).map(operand => this.#lower(operand)), operators: children.filter((_, index) => index % 2 === 1).map(operator => operator.type) }, child);
121
+ case 'additive':
122
+ case 'multiplicative': {
123
+ // Operators of one level associate to the left, each operation spanning from the first operand to its own right operand.
124
+ let value = this.#lower(children[0]);
125
+
126
+ for (let index = 1; index < children.length; index += 2) {
127
+ value = { kind: 'arithmetic', operator: children[index].type, left: value, right: this.#lower(children[index + 1]) };
128
+ this.#spans.set(value, { start: SyntaxTree.startOf(child), end: SyntaxTree.endOf(children[index + 1]) });
129
+ }
130
+
131
+ return value;
132
+ }
133
+ case 'from-end-index':
134
+ return this.#span({ kind: 'fromEndIndex', offset: this.#lower(children[1]) }, child);
135
+ case 'range-expression': {
136
+ // A boundary stands before or after the range's `..`, each left out where the text leaves it out.
137
+ const dots = children.findIndex(part => SyntaxTree.isToken(part) && part.type === '..');
138
+ const range = { kind: 'range' };
139
+
140
+ if (dots > 0) {
141
+ range.start = this.#lower(children[0]);
142
+ }
143
+
144
+ if (dots < children.length - 1) {
145
+ range.end = this.#lower(children[dots + 1]);
146
+ }
147
+
148
+ return this.#span(range, child);
149
+ }
150
+ case 'arithmetic-negation': {
151
+ // An arithmetic negation of a numeric literal is the negative literal, negative zero included, a grouped one and a negative one among them, as `-(5)` and `- -5` are.
152
+ const operand = this.#lower(children[1]);
153
+
154
+ if (operand.kind === 'literal' && typeof operand.value === 'number') {
155
+ return this.#span({ kind: 'literal', value: -operand.value }, child);
156
+ }
157
+
158
+ return this.#span({ kind: 'arithmeticNegation', operand }, child);
159
+ }
160
+ case 'coalescing':
161
+ return this.#span({ kind: 'coalescing', value: this.#lower(children[0]), fallback: this.#lower(children[2]) }, child);
162
+ case 'attempt':
163
+ return this.#span({ kind: 'try', operand: this.#lower(children[1]) }, child);
164
+ case 'conversion':
165
+ return this.#span({ kind: 'conversion', type: SyntaxTree.typeNameOf(children[2], this.#text), operand: this.#lower(children[0]) }, child);
166
+ case 'cast': {
167
+ // A cast's operand written as a member chain within an optional enclosed access is the optional chain of its accesses.
168
+ const [operand] = children;
169
+
170
+ return this.#span({ kind: 'cast', type: SyntaxTree.typeNameOf(children[2], this.#text), operand: operand.kind === 'member-chain' ? this.#lowerMemberChain(operand, operand) : this.#lower(operand) }, child);
171
+ }
172
+ case 'namespace-access':
173
+ return this.#lowerNamespaceAccess(child);
174
+ case 'parenthesized-expression':
175
+ case 'receiver':
176
+ return this.#lower(children[1]);
177
+ case 'optional-enclosed-access':
178
+ return this.#lowerMemberChain(children[1], child);
179
+ case 'chain':
180
+ return this.#lowerChain(child);
181
+ case 'object':
182
+ return this.#lowerObject(child, undefined, SyntaxTree.startOf(child));
183
+ case 'list-expression':
184
+ // A list's items in the order written, the commas between them left out.
185
+ return this.#span({ kind: 'list', items: children.slice(1, -1).filter(part => !(SyntaxTree.isToken(part) && part.type === ',')).map(item => this.#lower(item)) }, child);
186
+ case 'dictionary-expression':
187
+ // A dictionary's entries in the order written, each its key's string and its value, which spans from its key; `[=]` holds none.
188
+ return this.#span({
189
+ kind: 'dictionary',
190
+ entries: children.filter(part => part.kind === 'dictionary-entry').map(entry => this.#span({ key: entry.children[0].value, value: this.#lower(entry.children[2]) }, entry)),
191
+ }, child);
192
+ case 'interpolated-string':
193
+ // Its text and its interpolations in the order written, each run of text the characters it stands for; a run that stands for none, a block's margin before an interpolation, is no part.
194
+ return this.#span({
195
+ kind: 'interpolatedString',
196
+ parts: children.slice(1, -1)
197
+ .filter(part => !SyntaxTree.isToken(part) || part.value !== '')
198
+ .map(part => (SyntaxTree.isToken(part) ? { kind: 'text', value: part.value } : { kind: 'expression', expression: this.#lower(part.children[1]) })),
199
+ }, child);
200
+ default:
201
+ throw new TypeError(`No definition is lowered from a ${child.kind}`);
202
+ }
203
+ }
204
+
205
+ #lowerToken(token) {
206
+ switch (token.type) {
207
+ case 'it':
208
+ return this.#span({ kind: 'it' }, token);
209
+ case 'literal':
210
+ return this.#span({ kind: 'literal', value: token.value }, token);
211
+ case 'identifier': {
212
+ // `root` is the object root wherever it stands; any other name is the local or the field its lexical scope declares, and otherwise the script parameter of its name.
213
+ if (Names.isRoot(token.value)) {
214
+ return this.#span({ kind: 'root' }, token);
215
+ }
216
+
217
+ const read = this.#span({ kind: this.#localNames.has(token.value) ? 'local' : 'scriptParameter', name: token.value }, token);
218
+
219
+ if (read.kind === 'scriptParameter') {
220
+ this.#scriptParametersWrittenAlone.add(read);
221
+ }
222
+
223
+ return read;
224
+ }
225
+ case 'lexical-reference':
226
+ // An identifier after `@` is the local or the field of its name in scope, and otherwise the import binding of its name, the namespace of the module an import gives it; validation refuses any other.
227
+ return this.#span({ kind: !this.#localNames.has(token.value) && this.#namespaceNames.has(token.value) ? 'namespace' : 'local', name: token.value }, token);
228
+ case 'script-parameter':
229
+ return this.#span({ kind: 'scriptParameter', name: token.value }, token);
230
+ default:
231
+ throw new TypeError(`No definition is lowered from the token '${token.type}'`);
232
+ }
233
+ }
234
+
235
+ /**
236
+ * A namespace access: a member read through the module's namespace, or a call of one through it, its arguments as written, the member spanning the namespace's name to its own and the call the access; or the
237
+ * namespace alone, which a computed property access its chain reads follows.
238
+ */
239
+ #lowerNamespaceAccess(access) {
240
+ const [namespaceName, , nameToken, ...rest] = access.children;
241
+ const receiver = this.#span({ kind: 'namespace', name: namespaceName.value }, namespaceName);
242
+
243
+ if (nameToken === undefined) {
244
+ return receiver;
245
+ }
246
+ const member = { kind: 'memberAccess', receiver, name: SyntaxTree.wordOf(nameToken, this.#text) };
247
+
248
+ this.#spans.set(member, { start: SyntaxTree.startOf(namespaceName), end: SyntaxTree.endOf(nameToken) });
249
+
250
+ if (rest.length === 0) {
251
+ return member;
252
+ }
253
+
254
+ return this.#span({ kind: 'call', callee: member, arguments: this.#argumentsOf(rest.slice(1, -1)) }, access);
255
+ }
256
+
257
+ /** A body: its locals in order, each in scope after its value, through the locals after it and the result, which its `return` gives. */
258
+ #lowerBody(body) {
259
+ const { children } = body;
260
+ const enclosingLocalNames = this.#localNames;
261
+ const locals = [];
262
+
263
+ try {
264
+ for (const part of children.slice(0, -2)) {
265
+ const local = this.#span({ name: SyntaxTree.wordOf(part.children[1], this.#text), value: this.#lower(part.children[3]) }, part);
266
+
267
+ locals.push(local);
268
+ this.#localNames = new Set(this.#localNames).add(local.name);
269
+ }
270
+
271
+ return this.#span({ kind: 'body', locals, result: this.#lower(children.at(-1)) }, body);
272
+ } finally {
273
+ this.#localNames = enclosingLocalNames;
274
+ }
275
+ }
276
+
277
+ /** The conditional whose clause begins at the index, its `if` or an `elseif`, with every clause after it its alternative, each spanning from its keyword to the end of the last alternative. */
278
+ #lowerConditional(children, index, end) {
279
+ const start = children[index].start;
280
+ const test = children[index + 1];
281
+ const isConditionalBinding = test.kind === 'conditional-binding';
282
+ const tested = isConditionalBinding ? this.#span({ name: SyntaxTree.wordOf(test.children[1], this.#text), value: this.#lower(test.children[3]) }, test) : this.#lower(test);
283
+ // A conditional binding's name is in scope in its consequent alone.
284
+ const consequent = isConditionalBinding ? this.#lowerWithin([tested.name], children[index + 3]) : this.#lower(children[index + 3]);
285
+ const next = children[index + 4];
286
+ const alternative = next.value === 'elseif' ? this.#lowerConditional(children, index + 4, end) : this.#lower(children[index + 5]);
287
+ const node = isConditionalBinding ? { kind: 'conditional', binding: tested, consequent, alternative } : { kind: 'conditional', condition: tested, consequent, alternative };
288
+
289
+ this.#spans.set(node, { start, end });
290
+
291
+ return node;
292
+ }
293
+
294
+ /** A chain's input and what continues it, each part spanning from the chain's start to its own end, held by an optional chain where an access of it is optional. */
295
+ #lowerChain(chain) {
296
+ const { children } = chain;
297
+ const start = SyntaxTree.startOf(chain);
298
+ let value = this.#lower(children[0]);
299
+ let isOptional = false;
300
+
301
+ for (let index = 1; index < children.length; index++) {
302
+ const part = children[index];
303
+
304
+ if (part.kind === 'object') {
305
+ value = this.#lowerObject(part, value, start);
306
+ } else {
307
+ isOptional ||= SyntaxTree.isOptionalAccess(part.children[0].type);
308
+ value = this.#lowerMemberExpression(part, value, start, false);
309
+ }
310
+ }
311
+
312
+ return isOptional ? this.#span({ kind: 'optionalChain', chain: value }, chain) : value;
313
+ }
314
+
315
+ /**
316
+ * A member after its receiver: a dot property access, a call of the member it names, a computed property access, its name the name a string literal is where it is one, or an indexer, spanning from the start given to
317
+ * its own end, a call's member to its name's, and optional where its token says so or where every access is, as an optional enclosed access makes them.
318
+ */
319
+ #lowerMemberExpression(part, receiver, start, isEveryOptional) {
320
+ const [first, second] = part.children;
321
+ const optional = isEveryOptional || SyntaxTree.isOptionalAccess(first.type);
322
+ const optionalProperty = optional ? { optional } : {};
323
+ let node;
324
+
325
+ switch (part.kind) {
326
+ case 'extension-access':
327
+ return this.#lowerExtensionAccess(part, receiver, start, optional);
328
+ case 'dot-property-access':
329
+ node = { kind: 'memberAccess', receiver, name: SyntaxTree.wordOf(second, this.#text), ...optionalProperty };
330
+ break;
331
+ case 'member-call': {
332
+ const callee = { kind: 'memberAccess', receiver, name: SyntaxTree.wordOf(second, this.#text), ...optionalProperty };
333
+
334
+ this.#spans.set(callee, { start, end: SyntaxTree.endOf(second) });
335
+ node = { kind: 'call', callee, arguments: this.#argumentsOf(part.children.slice(3, -1)) };
336
+ break;
337
+ }
338
+ case 'computed-property-access': {
339
+ const expression = this.#lower(part.children[2]);
340
+ const name = expression.kind === 'literal' && Names.isIdentifier(expression.value) ? expression.value : expression;
341
+
342
+ node = { kind: 'memberAccess', receiver, name, ...optionalProperty };
343
+ break;
344
+ }
345
+ default:
346
+ // An indexer's receiver is what is before its brackets, the member access of a named indexer's name among them, which resolution reads as one.
347
+ node = { kind: 'indexerAccess', receiver, argument: this.#lower(second), ...optionalProperty };
348
+ }
349
+
350
+ this.#spans.set(node, { start, end: SyntaxTree.endOf(part) });
351
+
352
+ return node;
353
+ }
354
+
355
+ /**
356
+ * An extension access: a member access for each of its names, as written, the first on the receiver, holding the colon, optional where its colon or every access is, and each later one on the one before it,
357
+ * continuing it, which resolution reads as the access's member, its extension, that extension's module or an ordinary access of the member's answer; the last is called where its arguments follow.
358
+ */
359
+ #lowerExtensionAccess(part, receiver, start, optional) {
360
+ const opening = part.children.findIndex(child => SyntaxTree.isToken(child) && child.type === '(');
361
+ const names = (opening === -1 ? part.children.slice(1) : part.children.slice(1, opening)).filter(child => child.type !== '.');
362
+ let access = null;
363
+
364
+ for (const name of names) {
365
+ access = access === null
366
+ ? { kind: 'memberAccess', receiver, name: SyntaxTree.wordOf(name, this.#text), ...(optional ? { optional } : {}), extension: true }
367
+ : { kind: 'memberAccess', receiver: access, name: SyntaxTree.wordOf(name, this.#text), continues: true };
368
+ this.#spans.set(access, { start, end: SyntaxTree.endOf(name) });
369
+ }
370
+
371
+ if (opening === -1) {
372
+ return access;
373
+ }
374
+
375
+ const node = { kind: 'call', callee: access, arguments: this.#argumentsOf(part.children.slice(opening + 1, -1)) };
376
+
377
+ this.#spans.set(node, { start, end: SyntaxTree.endOf(part) });
378
+
379
+ return node;
380
+ }
381
+
382
+ /** The arguments a call's parentheses hold, the commas between them left out. */
383
+ #argumentsOf(parts) {
384
+ return parts.filter(part => !(SyntaxTree.isToken(part) && part.type === ',')).map(argument => this.#lower(argument));
385
+ }
386
+
387
+ /** Lowers a member chain into the optional chain of its accesses, every one of them optional, spanning what the span node does. */
388
+ #lowerMemberChain(memberChain, spanned) {
389
+ const { children } = memberChain;
390
+ const start = SyntaxTree.startOf(memberChain);
391
+ let value = this.#lower(children[0]);
392
+
393
+ for (let index = 1; index < children.length; index++) {
394
+ value = this.#lowerMemberExpression(children[index], value, start, true);
395
+ }
396
+
397
+ return this.#span({ kind: 'optionalChain', chain: value }, spanned);
398
+ }
399
+
400
+ /** An object expression, of the context it follows where it follows one, spanning from the start given, its locals left out where it has none. */
401
+ #lowerObject(object, context, start) {
402
+ const node = context === undefined ? { kind: 'object' } : { kind: 'object', context };
403
+ const locals = [];
404
+ const objectFields = [];
405
+ const enclosingLocalNames = this.#localNames;
406
+
407
+ // Each local is in scope after its value, through the locals and the fields after it, and each field after its value, through the fields after it.
408
+ for (const child of object.children) {
409
+ if (child.kind === 'local') {
410
+ const local = this.#span({ name: SyntaxTree.wordOf(child.children[1], this.#text), value: this.#lower(child.children[3]) }, child);
411
+
412
+ locals.push(local);
413
+ this.#localNames = new Set(this.#localNames).add(local.name);
414
+ } else if (child.kind === 'field') {
415
+ const field = this.#span({ name: SyntaxTree.wordOf(child.children[0], this.#text), value: this.#lower(child.children[2]) }, child);
416
+
417
+ objectFields.push(field);
418
+ this.#localNames = new Set(this.#localNames).add(field.name);
419
+ }
420
+ }
421
+
422
+ this.#localNames = enclosingLocalNames;
423
+
424
+ if (locals.length > 0) {
425
+ node.locals = locals;
426
+ }
427
+
428
+ node.objectFields = objectFields;
429
+ this.#spans.set(node, { start, end: SyntaxTree.endOf(object) });
430
+
431
+ return node;
432
+ }
433
+
434
+ /** The child lowered with the names in scope besides those already. */
435
+ #lowerWithin(names, child) {
436
+ const enclosingLocalNames = this.#localNames;
437
+
438
+ this.#localNames = new Set([...this.#localNames, ...names]);
439
+
440
+ try {
441
+ return this.#lower(child);
442
+ } finally {
443
+ this.#localNames = enclosingLocalNames;
444
+ }
445
+ }
446
+
447
+ /** Records the part as spanning the syntax node or token. */
448
+ #span(part, spanned) {
449
+ this.#spans.set(part, { start: SyntaxTree.startOf(spanned), end: SyntaxTree.endOf(spanned) });
450
+
451
+ return part;
452
+ }
453
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * MemberSelector — the one selection of a declaration among those of a name an access reads for a receiver: a member, a named indexer or an unnamed one, the owner of the receiver's type first and the most specific
3
+ * otherwise; and which members an extension access reads
4
+ */
5
+
6
+ import { Vocabulary } from './vocabulary/Vocabulary.mjs';
7
+
8
+ /**
9
+ * What a selection answers: the declaration it selects; the declarations it can't choose among; the internal declarations alone applying, which no script reaches; or none, where none applies.
10
+ * @typedef {{ kind: 'selected', declaration: object } | { kind: 'ambiguous', declarations: object[] } | { kind: 'internal', declarations: object[] } | { kind: 'none' }} Selection
11
+ */
12
+
13
+ export class MemberSelector {
14
+ /** The registry whose declarations it selects among, which owns their types, their extensions and their accessibility. */
15
+ #registry;
16
+
17
+ /** The checker of the relation a receiver applies to a declaration by, and of which receiver type is the more specific. */
18
+ #typeChecker;
19
+
20
+ /**
21
+ * @param {object} registry The registry whose declarations it selects among.
22
+ * @param {object} typeChecker The checker of the registry's types.
23
+ */
24
+ constructor(registry, typeChecker) {
25
+ this.#registry = registry;
26
+ this.#typeChecker = typeChecker;
27
+ }
28
+
29
+ /**
30
+ * Selects, among the candidates an access reads, the declaration a receiver of the type reads, as a member is selected wherever it is written: of those applicable to the receiver, an internal one is no candidate; an
31
+ * applicable member the owner of the receiver's type declares on the types it owns wins over every other, the most specific of them hiding the others; and otherwise the applicable declaration whose receiver type is more
32
+ * specific than every other's is selected, a member another module declares within a supertype, or an extension member, among them. It answers the competing declarations where none is more specific than every other.
33
+ * Registration order never decides.
34
+ * @param {object[]} candidates The declarations of the name the access reads, `.` the members of types and `:` the extension members, or the unnamed indexers.
35
+ * @returns {Selection}
36
+ */
37
+ select(candidates, receiverType) {
38
+ const applicable = candidates.filter(declaration => this.#typeChecker.resolveApplyingReceiverType(declaration, receiverType) !== undefined);
39
+ const accessible = applicable.filter(declaration => !this.#registry.isInternal(declaration));
40
+
41
+ if (accessible.length === 0) {
42
+ return applicable.length === 0 ? { kind: 'none' } : { kind: 'internal', declarations: applicable };
43
+ }
44
+
45
+ const owner = MemberSelector.#ownerOf(receiverType);
46
+ const owned = accessible.filter(declaration => this.#isOwnedBy(declaration, owner));
47
+ const competing = this.#typeChecker.mostSpecificOf(owned.length > 0 ? owned : accessible, receiverType);
48
+
49
+ // An indexer's signatures compete as one candidate, its first standing for them, which its key's type then chooses among.
50
+ if (competing.length === 1 || this.#areSignaturesOfOne(competing)) {
51
+ return { kind: 'selected', declaration: competing[0] };
52
+ }
53
+
54
+ return { kind: 'ambiguous', declarations: competing };
55
+ }
56
+
57
+ /** Whether the declarations are all signatures of one indexer, as the registry gives them. */
58
+ #areSignaturesOfOne(declarations) {
59
+ const signatures = this.#registry.getIndexerSignatures(declarations[0]);
60
+
61
+ return signatures.length > 1 && declarations.every(declaration => signatures.includes(declaration));
62
+ }
63
+
64
+ /** Whether the member is an extension member, which `:` reads and `.` never does: one declared within an extension, whichever module declares it and whichever type it extends. */
65
+ isExtensionMember(declaration) {
66
+ return this.#registry.getContainingType(declaration)?.kind === 'extension';
67
+ }
68
+
69
+ /** The candidates a receiver of unknown type may read, which choose no declaration: the accessible ones, an internal one no candidate. */
70
+ getAccessible(candidates) {
71
+ return candidates.filter(declaration => !this.#registry.isInternal(declaration));
72
+ }
73
+
74
+ /** The name of the module that owns the type: the module that declares it, Nexo's own for its value types; none for a type no declaration names, an object type among them. */
75
+ static #ownerOf(type) {
76
+ if (type.kind !== 'named') {
77
+ return null;
78
+ }
79
+
80
+ return type.moduleName ?? Vocabulary.moduleName;
81
+ }
82
+
83
+ /** Whether the member is one the owner declares within a type of its own, a type or Nexo's own value type, never an extension member. */
84
+ #isOwnedBy(declaration, owner) {
85
+ const containing = this.#registry.getContainingType(declaration);
86
+
87
+ return owner !== null && containing !== undefined && !this.isExtensionMember(declaration) && this.#registry.getDeclaringModuleName(declaration) === owner;
88
+ }
89
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Names — Nexo's identifiers: how one is spelled, the words the language reserves, and which identifiers a lexical reference writes with a prefix
3
+ */
4
+
5
+ /** A letter followed by letters and digits, which is also a JavaScript identifier. */
6
+ const PATTERN = /^[A-Za-z][A-Za-z0-9]*$/;
7
+ const LITERALS = new Map([['true', true], ['false', false], ['null', null]]);
8
+ const KEYWORDS = new Set(['it', 'not', 'try', 'as', 'to', 'and', 'or', 'if', 'then', 'elseif', 'else', 'let', 'do', 'end', 'return', 'function', 'import']);
9
+
10
+ /** The object root's word, which stands for a reference to the outermost object being built where an expression stands. */
11
+ const ROOT = 'root';
12
+
13
+ /** The namespace names of text whose imports give none. */
14
+ const NO_NAMESPACE_NAMES = Object.freeze(new Set());
15
+
16
+ export class Names {
17
+ /** The words the language reserves, which keep their meaning where an expression stands and which a reference to a local or a field of their name writes after `@`: those that write a literal, the keywords and `root`. */
18
+ static reservedWords = Object.freeze([...LITERALS.keys(), ...KEYWORDS, ROOT]);
19
+
20
+ /** The namespace names of text read without imports: none. */
21
+ static noNamespaceNames = NO_NAMESPACE_NAMES;
22
+
23
+ /** The name of Nexo's own module, which every script has and none imports. */
24
+ static nexoModuleName = 'Nexo';
25
+
26
+ /** Whether the text is spelled as an identifier: a letter followed by letters and digits, a reserved word among them, since a position that expects a name tells it from the word. */
27
+ static isIdentifier(text) {
28
+ return typeof text === 'string' && PATTERN.test(text);
29
+ }
30
+
31
+ /** Whether the word is one the language reserves, which keeps its meaning where an expression stands: a literal's word, a keyword or `root`. */
32
+ static isReservedWord(word) {
33
+ return LITERALS.has(word) || KEYWORDS.has(word) || word === ROOT;
34
+ }
35
+
36
+ /**
37
+ * Whether an identifier written alone where a value stands means something other than a binding of its name, so a reference to the local, the field or the script parameter writes it with `@` or `~`: a word the language
38
+ * reserves, or, in text read through a registry, the name of one of its modules, which begins its namespace there.
39
+ * @param {string} word
40
+ * @param {ReadonlySet<string>} [namespaceNames] The namespace names the text is read with.
41
+ */
42
+ static needsPrefix(word, namespaceNames = NO_NAMESPACE_NAMES) {
43
+ return Names.isReservedWord(word) || namespaceNames.has(word);
44
+ }
45
+
46
+ /** Whether the word is a keyword. */
47
+ static isKeyword(word) {
48
+ return KEYWORDS.has(word);
49
+ }
50
+
51
+ /** Whether the word is the object root's. */
52
+ static isRoot(word) {
53
+ return word === ROOT;
54
+ }
55
+
56
+ /** Whether the word writes a literal. */
57
+ static isLiteral(word) {
58
+ return LITERALS.has(word);
59
+ }
60
+
61
+ /** The value of the literal the word writes. */
62
+ static literalValueOf(word) {
63
+ return LITERALS.get(word);
64
+ }
65
+ }