@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.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +148 -6
- package/package.json +23 -3
- package/src/common/ArrayUtils.mjs +16 -0
- package/src/common/ObjectUtils.mjs +33 -0
- package/src/language/DefinitionLocations.mjs +41 -0
- package/src/language/DefinitionValidator.mjs +1265 -0
- package/src/language/DefinitionWriter.mjs +475 -0
- package/src/language/Dictionary.mjs +52 -0
- package/src/language/Evaluator.mjs +3480 -0
- package/src/language/ImportedVocabulary.mjs +137 -0
- package/src/language/Imports.mjs +141 -0
- package/src/language/Index.mjs +29 -0
- package/src/language/LanguageResolver.mjs +4068 -0
- package/src/language/Limits.mjs +78 -0
- package/src/language/LineStructure.mjs +385 -0
- package/src/language/Lowering.mjs +453 -0
- package/src/language/MemberSelector.mjs +89 -0
- package/src/language/Names.mjs +65 -0
- package/src/language/NexoError.mjs +145 -0
- package/src/language/Pair.mjs +21 -0
- package/src/language/ParameterBindings.mjs +361 -0
- package/src/language/ParsedTexts.mjs +62 -0
- package/src/language/Parser.mjs +1817 -0
- package/src/language/Range.mjs +51 -0
- package/src/language/RecentlyUsed.mjs +50 -0
- package/src/language/ResolvedDefinition.mjs +219 -0
- package/src/language/ResultPath.mjs +101 -0
- package/src/language/ScopeLocals.mjs +102 -0
- package/src/language/StructureTypes.mjs +48 -0
- package/src/language/SyntaxTree.mjs +203 -0
- package/src/language/TextLocations.mjs +185 -0
- package/src/language/TextPositions.mjs +23 -0
- package/src/language/Tokenizer.mjs +917 -0
- package/src/language/Type.mjs +401 -0
- package/src/language/TypeChecker.mjs +449 -0
- package/src/language/ValueCapture.mjs +315 -0
- package/src/language/Versions.mjs +14 -0
- package/src/language/vocabulary/Declarations.mjs +64 -0
- package/src/language/vocabulary/ItemPositions.mjs +48 -0
- package/src/language/vocabulary/ListOrdering.mjs +145 -0
- package/src/language/vocabulary/MemberFailure.mjs +21 -0
- package/src/language/vocabulary/NexoModule.mjs +1119 -0
- package/src/language/vocabulary/NexoRegistry.mjs +1540 -0
- package/src/language/vocabulary/TypeNameScope.mjs +63 -0
- package/src/language/vocabulary/Vocabulary.mjs +640 -0
- package/src/nexo.d.ts +743 -0
- package/src/nexo.js +338 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ImportedVocabulary — what a script's imports make available: the namespaces their local names begin, the types a script names, and the declarations it may select, Nexo's own always among them
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Imports } from './Imports.mjs';
|
|
6
|
+
import { Type } from './Type.mjs';
|
|
7
|
+
import { TypeNameScope } from './vocabulary/TypeNameScope.mjs';
|
|
8
|
+
import { Vocabulary } from './vocabulary/Vocabulary.mjs';
|
|
9
|
+
|
|
10
|
+
export class ImportedVocabulary {
|
|
11
|
+
/** The registry the imports name modules, types and extensions of. */
|
|
12
|
+
#registry;
|
|
13
|
+
|
|
14
|
+
/** The module each local name a module import gives names, by that local name. @type {Map<string, string>} */
|
|
15
|
+
#namespaceModules = new Map();
|
|
16
|
+
|
|
17
|
+
/** The names of the modules the script imports whole, under whichever local names. @type {Set<string>} */
|
|
18
|
+
#importedModules = new Set();
|
|
19
|
+
|
|
20
|
+
/** The scope the script's type names are read in, which its imports give. @type {TypeNameScope} */
|
|
21
|
+
#typeNameScope;
|
|
22
|
+
|
|
23
|
+
/** The types and the extensions the script imports by name, each a declaration whose members the import makes available. @type {Set<object>} */
|
|
24
|
+
#importedDeclarations = new Set();
|
|
25
|
+
|
|
26
|
+
/** The ordinary types the script imports by name and every supertype above each, whose members an imported type has as its own or inherits. @type {Set<object>} */
|
|
27
|
+
#typeLineages = new Set();
|
|
28
|
+
|
|
29
|
+
/** The extension each local name a type import of an extension gives names, by that local name, which an extension access names it by first. @type {Map<string, object>} */
|
|
30
|
+
#importedExtensions = new Map();
|
|
31
|
+
|
|
32
|
+
/** The first local name a type import gives each type or extension it imports, by the declaration. @type {Map<object, string>} */
|
|
33
|
+
#declarationLocalNames = new Map();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {import('./vocabulary/NexoRegistry.mjs').NexoRegistry} registry The registry the imports name declarations of.
|
|
37
|
+
* @param {readonly object[]} imports The script's imports, in a definition's shape, each naming a module the registry holds and, for a type import, a public type or extension it declares.
|
|
38
|
+
*/
|
|
39
|
+
constructor(registry, imports) {
|
|
40
|
+
this.#registry = registry;
|
|
41
|
+
this.#typeNameScope = TypeNameScope.createScriptScope(imports);
|
|
42
|
+
|
|
43
|
+
for (const entry of imports) {
|
|
44
|
+
const localName = Imports.localNameOf(entry);
|
|
45
|
+
|
|
46
|
+
if (Imports.isModuleImport(entry)) {
|
|
47
|
+
this.#namespaceModules.set(localName, entry.module);
|
|
48
|
+
this.#importedModules.add(entry.module);
|
|
49
|
+
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const declaration = registry.getModuleType(entry.module, entry.type);
|
|
54
|
+
|
|
55
|
+
this.#importedDeclarations.add(declaration);
|
|
56
|
+
|
|
57
|
+
if (!this.#declarationLocalNames.has(declaration)) {
|
|
58
|
+
this.#declarationLocalNames.set(declaration, localName);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (declaration.kind === 'extension') {
|
|
62
|
+
this.#importedExtensions.set(localName, declaration);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (declaration.kind !== 'extension') {
|
|
66
|
+
for (let reached = Type.createNamedType(entry.type, false, entry.module); reached !== null; reached = registry.getSupertype(reached)) {
|
|
67
|
+
this.#typeLineages.add(registry.getTypeDeclaration(reached));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The local names the module imports give, each beginning its module's namespace where it stands in place of a value. */
|
|
74
|
+
get namespaceNames() {
|
|
75
|
+
return new Set(this.#namespaceModules.keys());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The name of the module the local name a module import gives names, or undefined where no module import gives it. */
|
|
79
|
+
getNamespaceModule(localName) {
|
|
80
|
+
return this.#namespaceModules.get(localName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The local name the module imports give the module first, which a name the script writes qualified by it begins with, or undefined where the script imports it under none. */
|
|
84
|
+
getNamespaceName(moduleName) {
|
|
85
|
+
return [...this.#namespaceModules].find(([, module]) => module === moduleName)?.[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The names of the modules the script imports whole, whose public extensions an extension access names unqualified beside Nexo's own. */
|
|
89
|
+
get importedModuleNames() {
|
|
90
|
+
return [...this.#importedModules];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The extension the local name a type import gives names, which an extension access names it by before any module's of its name; undefined where no import of an extension gives it. */
|
|
94
|
+
getImportedExtension(localName) {
|
|
95
|
+
return this.#importedExtensions.get(localName);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The local name a type import gives the type or the extension, the first where several do, or undefined where none imports it by name. */
|
|
99
|
+
getLocalNameOf(declaration) {
|
|
100
|
+
return this.#declarationLocalNames.get(declaration);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Whether the script imports the module whole, which makes its public vocabulary available. */
|
|
104
|
+
importsModule(moduleName) {
|
|
105
|
+
return this.#importedModules.has(moduleName);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The scope the script's type names are read in: its type imports' names first, then Nexo's own types, then its imported modules', qualified by the local names its module imports give. */
|
|
109
|
+
get typeNameScope() {
|
|
110
|
+
return this.#typeNameScope;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether the script's imports make the declaration available to select: one of Nexo's own, which every script has; one its declaring module's import makes available; one an imported extension declares; or one an
|
|
115
|
+
* imported type declares within it, or a supertype above it does, which the imported type inherits. A module member is reached through its module's import alone.
|
|
116
|
+
*/
|
|
117
|
+
makesAvailable(declaration) {
|
|
118
|
+
const moduleName = this.#registry.getDeclaringModuleName(declaration);
|
|
119
|
+
|
|
120
|
+
if (moduleName === Vocabulary.moduleName || this.#importedModules.has(moduleName)) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const containing = this.#registry.getContainingType(declaration);
|
|
125
|
+
|
|
126
|
+
if (containing === undefined) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return containing.kind === 'extension' ? this.#importedDeclarations.has(containing) : this.#typeLineages.has(containing);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The declarations the script's imports make available, in the order given. */
|
|
134
|
+
getAvailable(declarations) {
|
|
135
|
+
return declarations.filter(declaration => this.makesAvailable(declaration));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports — a script's imports in a definition's shape: the local name each gives, the imports a host supplies, checked as a definition's are, and the imports a definition records, each once, in order of the local name it gives
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NexoErrors } from './NexoError.mjs';
|
|
6
|
+
import { Names } from './Names.mjs';
|
|
7
|
+
|
|
8
|
+
/** The keys an import holds: its module, its type where it imports one, and its alias where it gives a local name other than its own. */
|
|
9
|
+
const IMPORT_KEYS = Object.freeze(['module', 'type', 'alias']);
|
|
10
|
+
|
|
11
|
+
export class Imports {
|
|
12
|
+
/** The local name the import gives in its script: its alias, or else its type's name, or else its module's. */
|
|
13
|
+
static localNameOf(entry) {
|
|
14
|
+
return entry.alias ?? entry.type ?? entry.module;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Whether the import imports a module, whose local name begins its namespace, rather than one of its types. */
|
|
18
|
+
static isModuleImport(entry) {
|
|
19
|
+
return entry.type === undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The local names the imports give modules, each beginning its module's namespace where it stands in place of a value. */
|
|
23
|
+
static namespaceNamesOf(imports) {
|
|
24
|
+
return new Set(imports.filter(entry => Imports.isModuleImport(entry)).map(entry => Imports.localNameOf(entry)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Whether two imports import one declaration under one local name, which counts once. */
|
|
28
|
+
static isSameImport(one, other) {
|
|
29
|
+
return one.module === other.module && one.type === other.type && Imports.localNameOf(one) === Imports.localNameOf(other);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The import as text writes it: `import`, its alias and `=` where it has one, its module and, where it imports a type, a dot and the type's name, as `import Measures = Measurements`. */
|
|
33
|
+
static quote(entry) {
|
|
34
|
+
const source = entry.type === undefined ? entry.module : `${entry.module}.${entry.type}`;
|
|
35
|
+
|
|
36
|
+
return entry.alias === undefined ? `import ${source}` : `import ${entry.alias} = ${source}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The imports, each once, in order of the local name it gives, the order a definition records them in, so the order they were written or supplied in decides nothing. */
|
|
40
|
+
static mergeImports(...lists) {
|
|
41
|
+
const merged = [];
|
|
42
|
+
|
|
43
|
+
for (const entry of lists.flat()) {
|
|
44
|
+
if (!merged.some(other => Imports.isSameImport(entry, other))) {
|
|
45
|
+
merged.push(entry);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return merged.sort((one, other) => Imports.#compareLocalNames(Imports.localNameOf(one), Imports.localNameOf(other)));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The two imports that give one local name to different declarations, the first such pair in order, or null where none do. */
|
|
53
|
+
static findConflict(imports) {
|
|
54
|
+
for (let index = 0; index < imports.length; index++) {
|
|
55
|
+
const other = imports.slice(0, index).find(earlier => Imports.localNameOf(earlier) === Imports.localNameOf(imports[index]) && !Imports.isSameImport(earlier, imports[index]));
|
|
56
|
+
|
|
57
|
+
if (other !== undefined) {
|
|
58
|
+
return [other, imports[index]];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The imports a host supplies, each a module's name or an import in a definition's shape, as definition-shaped imports, each once, in order of the local name it gives. Each is checked as a definition's import is,
|
|
67
|
+
* and two giving one local name to different declarations are refused, each with NEXO2008.
|
|
68
|
+
* @param {unknown} supplied
|
|
69
|
+
*/
|
|
70
|
+
static createSuppliedImports(supplied) {
|
|
71
|
+
if (supplied === undefined) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!Array.isArray(supplied)) {
|
|
76
|
+
throw NexoErrors.structure('NEXO2008', "A host's imports are a list, each a module's name or an import, as { module: 'Home' }", {});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const imports = supplied.map(item => {
|
|
80
|
+
const entry = typeof item === 'string' ? { module: item } : item;
|
|
81
|
+
|
|
82
|
+
Imports.validateImport(entry, message => {
|
|
83
|
+
throw NexoErrors.structure('NEXO2008', `${message}, and the host supplies ${Imports.#describe(item)}`, {});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return Object.freeze(Object.fromEntries(IMPORT_KEYS.filter(key => entry[key] !== undefined).map(key => [key, entry[key]])));
|
|
87
|
+
});
|
|
88
|
+
const conflict = Imports.findConflict(imports);
|
|
89
|
+
|
|
90
|
+
if (conflict !== null) {
|
|
91
|
+
throw NexoErrors.structure('NEXO2008', Imports.describeConflict(conflict), {});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Imports.mergeImports(imports);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks one import's shape, calling `fail` with why it breaks it: an object holding a module, an identifier other than Nexo's, a type where it imports one and an alias only where it differs from the local name the
|
|
99
|
+
* import gives without one, each an identifier.
|
|
100
|
+
*/
|
|
101
|
+
static validateImport(entry, fail) {
|
|
102
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry) || Object.keys(entry).some(key => !IMPORT_KEYS.includes(key))) {
|
|
103
|
+
fail("An import holds its module, its type where it imports one, and its alias where it gives a local name other than its own");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!Names.isIdentifier(entry.module)) {
|
|
107
|
+
fail("An import's module is a registered module's name, a letter followed by letters and digits");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (entry.module === Names.nexoModuleName) {
|
|
111
|
+
fail("Nexo's own vocabulary is always available, so no script imports it or one of its types");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (entry.type !== undefined && !Names.isIdentifier(entry.type)) {
|
|
115
|
+
fail("An import's type is the local name of a type its module declares, a letter followed by letters and digits");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (entry.alias !== undefined && !Names.isIdentifier(entry.alias)) {
|
|
119
|
+
fail("An import's alias is a local name, a letter followed by letters and digits");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (entry.alias !== undefined && entry.alias === (entry.type ?? entry.module)) {
|
|
123
|
+
fail(`'${entry.alias}' is the local name the import gives without an alias, so it holds none`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Why two imports conflict: the local name they share, and each as text writes it. */
|
|
128
|
+
static describeConflict([one, other]) {
|
|
129
|
+
return `'${Imports.localNameOf(one)}' is the local name both '${Imports.quote(one)}' and '${Imports.quote(other)}' give, to different declarations; give one of them an alias of its own`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** An item a host supplied, as a message quotes it. */
|
|
133
|
+
static #describe(item) {
|
|
134
|
+
return typeof item === 'string' ? `'${item}'` : JSON.stringify(item) ?? String(item);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The order of two local names, by their characters' code units, so every reader orders a definition's imports alike. */
|
|
138
|
+
static #compareLocalNames(one, other) {
|
|
139
|
+
return one < other ? -1 : one > other ? 1 : 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index — a position counted from a list's end, `^n`: its offset, a nonnegative safe integer, `^1` the last item and `^0` the end past it
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class Index {
|
|
6
|
+
/** How far from the end the position stands. */
|
|
7
|
+
#offset;
|
|
8
|
+
|
|
9
|
+
/** @param {number} offset A nonnegative safe integer, which its expression's evaluation checked. */
|
|
10
|
+
constructor(offset) {
|
|
11
|
+
this.#offset = offset;
|
|
12
|
+
Object.freeze(this);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** How far from the end the position stands. */
|
|
16
|
+
get offset() {
|
|
17
|
+
return this.#offset;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The offset from the start the index stands at within a list of the length: the length less its offset, which is negative for an index before the start. */
|
|
21
|
+
resolveIn(length) {
|
|
22
|
+
return length - this.#offset;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The index as a script writes it. */
|
|
26
|
+
toString() {
|
|
27
|
+
return `^${this.#offset}`;
|
|
28
|
+
}
|
|
29
|
+
}
|