@lionweb/core 0.7.0-beta.18 → 0.7.0-beta.19

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.
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Various functions on M3 models.
3
+ */
4
+
5
+
6
+ import { LionWebId, LionWebKey } from "@lionweb/json"
7
+ import { cycleWith, flatMapNonCyclingFollowing, sortByStringKey } from "@lionweb/ts-utils"
8
+ import { containmentChain } from "../functions.js"
9
+ import { ClassifierDeducer } from "../reading.js"
10
+ import { isRef, unresolved } from "../references.js"
11
+ import { Node } from "../types.js"
12
+ import {
13
+ Annotation,
14
+ Classifier,
15
+ Concept,
16
+ Containment,
17
+ DataType,
18
+ Enumeration,
19
+ Feature,
20
+ IKeyed,
21
+ IMetaTyped,
22
+ INamed,
23
+ Interface,
24
+ isINamed,
25
+ Language,
26
+ LanguageEntity,
27
+ Link,
28
+ M3Concept,
29
+ Property,
30
+ Reference
31
+ } from "./types.js"
32
+
33
+
34
+ /**
35
+ * @return The type of the given {@link Feature}
36
+ */
37
+ const type = (feature: Feature): Classifier | DataType | typeof unresolved =>
38
+ (feature as (Link | Property)).type
39
+
40
+
41
+ const isProperty = (feature: Feature): feature is Property =>
42
+ feature instanceof Property
43
+
44
+ const isContainment = (feature: Feature): feature is Containment =>
45
+ feature instanceof Containment
46
+
47
+ const isReference = (feature: Feature): feature is Reference =>
48
+ feature instanceof Reference
49
+
50
+ const isMultiple = (feature: Feature): feature is Link =>
51
+ feature instanceof Link && feature.multiple
52
+
53
+
54
+ /**
55
+ * The (names of the) metatypes of a feature.
56
+ */
57
+ type FeatureMetaType =
58
+ | "Containment"
59
+ | "Property"
60
+ | "Reference"
61
+
62
+ /**
63
+ * @return the (name of the) metatype of the given {@link Feature feature}.
64
+ */
65
+ const featureMetaType = (feature: Feature): FeatureMetaType => {
66
+ if (feature instanceof Containment) {
67
+ return "Containment"
68
+ }
69
+ if (feature instanceof Property) {
70
+ return "Property"
71
+ }
72
+ if (feature instanceof Reference) {
73
+ return "Reference"
74
+ }
75
+ throw new Error(`unhandled Feature sub type ${feature.constructor.name}`)
76
+ }
77
+
78
+
79
+ /**
80
+ * Determines whether a {@link Feature feature} is "relational",
81
+ * i.e. it's a {@link Link containment or reference}.
82
+ */
83
+ const isRelational = (feature: Feature): feature is Link =>
84
+ feature instanceof Link
85
+
86
+ /**
87
+ * @return the relations among the given {@link Feature features}.
88
+ */
89
+ const relations = (features: Feature[]): Link[] =>
90
+ features.filter(isRelational)
91
+
92
+ /**
93
+ * @return the non-relations among the given {@link Feature features}.
94
+ */
95
+ const nonRelationalFeatures = (features: Feature[]): Feature[] =>
96
+ features.filter((feature) => !isRelational(feature))
97
+
98
+
99
+ /**
100
+ * @return the relations of the given {@link LanguageEntity language element}.
101
+ */
102
+ const relationsOf = (element: LanguageEntity): Link[] =>
103
+ element instanceof Classifier
104
+ ? relations(element.features)
105
+ : []
106
+
107
+
108
+ /**
109
+ * @return The "things", i.e. {@link M3Concept}s, directly contained by the given "thing".
110
+ * These can be:
111
+ * {@link LanguageEntity language entities}, {@link Feature features}, and {@link EnumerationLiteral enumeration literals}
112
+ * (and all their sub types).
113
+ */
114
+ const directlyContaineds = (thing: M3Concept): M3Concept[] => {
115
+ if (thing instanceof Language) {
116
+ return thing.entities
117
+ }
118
+ if (thing instanceof Classifier) {
119
+ return thing.features as M3Concept[] // (cast is necessary because of presence of Feature#classifier getter...?)
120
+ }
121
+ if (thing instanceof Enumeration) {
122
+ return thing.literals
123
+ }
124
+ return []
125
+ }
126
+
127
+
128
+ /**
129
+ * @return All {@link M3Concept nodes} contained in this {@link Language language},
130
+ * including the language itself.
131
+ */
132
+ const allContaineds = (language: Language): M3Concept[] =>
133
+ [
134
+ language,
135
+ ...directlyContaineds(language),
136
+ ...directlyContaineds(language).flatMap(directlyContaineds)
137
+ ]
138
+
139
+
140
+ /**
141
+ * Performs a depth-first tree traversal of a language, "flatMapping" the `map` function on every node.
142
+ * It avoids visiting nodes twice (to avoid potential infinite loops), but doesn't report cycles.
143
+ */
144
+ const flatMap = <T>(language: Language, map: (t: M3Concept) => T[]): T[] =>
145
+ flatMapNonCyclingFollowing(map, directlyContaineds)(language)
146
+
147
+
148
+ /**
149
+ * @return string the name of the given {@link INamed named thing}.
150
+ */
151
+ const nameOf = <T extends INamed>({name}: T): string =>
152
+ name
153
+
154
+
155
+ /**
156
+ * @return the given named things sorted by name
157
+ */
158
+ export const nameSorted = <T extends INamed>(ts: T[]): T[] =>
159
+ sortByStringKey(ts, nameOf)
160
+
161
+
162
+ /**
163
+ * @return the concatenation of the names of the given nodes using the given separator.
164
+ */
165
+ const concatenateNamesOf = (separator: string, nodes: M3Concept[]): string =>
166
+ nodes
167
+ .filter(isINamed)
168
+ .map(nameOf)
169
+ .join(separator)
170
+ // !! slight overkill: every node in an M2 is an M3Concept, so IKeyed and INamed
171
+
172
+ /**
173
+ * @return the qualified name of the given {@link INamed named thing}.
174
+ */
175
+ const qualifiedNameOf = <T extends INamed & Node>(node: T, separator = "."): string =>
176
+ concatenateNamesOf(separator, containmentChain(node).reverse() as M3Concept[])
177
+
178
+
179
+ /**
180
+ * @return the {@link INamed named things} in this {@link Language language}
181
+ * (excluding the language itself)
182
+ */
183
+ const namedsOf = (language: Language): M3Concept[] =>
184
+ allContaineds(language).filter(isINamed)
185
+
186
+
187
+ /**
188
+ * @return the key of the given {@link INamed named thing}.
189
+ */
190
+ const keyOf = <T extends IKeyed>({key}: T): LionWebKey =>
191
+ key
192
+
193
+
194
+ type ConcreteClassifier = Concept | Annotation
195
+
196
+
197
+ /**
198
+ * Determines whether the given {@link LanguageEntity metamodel element} is
199
+ * *concrete*, i.e. is instantiable.
200
+ */
201
+ const isConcrete = (thing: LanguageEntity): thing is ConcreteClassifier =>
202
+ (thing instanceof Concept && !thing.abstract) || (thing instanceof Annotation)
203
+
204
+ /**
205
+ * Determines whether the given {@link LanguageEntity metamodel element} is a {@link Concept concept}
206
+ * which is a partition.
207
+ */
208
+ const isPartition = (thing: LanguageEntity): thing is Concept =>
209
+ thing instanceof Concept && thing.partition
210
+
211
+ /**
212
+ * @return an array of {@link Classifier classifiers} that it **directly** inherits from.
213
+ */
214
+ const inheritsDirectlyFrom = (classifier: Classifier): Classifier[] => {
215
+ if (classifier instanceof Concept || classifier instanceof Annotation) {
216
+ return [
217
+ ...(
218
+ isRef(classifier.extends)
219
+ ? [classifier.extends as Classifier]
220
+ : []
221
+ ),
222
+ ...classifier.implements
223
+ ]
224
+ }
225
+ if (classifier instanceof Interface) {
226
+ return classifier.extends
227
+ }
228
+ throw new Error(`classifier type ${typeof classifier} not handled`)
229
+ }
230
+
231
+ /**
232
+ * Alias for {@link inheritsDirectlyFrom}, kept for backward compatibility, and to be deprecated and removed later.
233
+ */
234
+ const inheritsFrom = inheritsDirectlyFrom;
235
+
236
+ /**
237
+ * @return an array that's either an inheritance cycle, or empty (meaning: no inheritance cycle).
238
+ */
239
+ const inheritanceCycleWith = (classifier: Classifier) =>
240
+ cycleWith(classifier, inheritsDirectlyFrom)
241
+
242
+ /**
243
+ * Alias for {@link inheritanceCycleWith}, kept for backward compatibility, and to be deprecated and removed later.
244
+ */
245
+ const inheritedCycleWith = inheritanceCycleWith;
246
+
247
+ /**
248
+ * @return *all* super types (through `extends` or `implements`) of the given {@link Classifier classifier}.
249
+ */
250
+ const allSuperTypesOf = (classifier: Classifier): Classifier[] =>
251
+ flatMapNonCyclingFollowing(inheritsDirectlyFrom, inheritsDirectlyFrom)(classifier)
252
+
253
+
254
+ /**
255
+ * @return *all* {@link Feature features} of the given {@link Classifier classifier},
256
+ * including the inherited ones.
257
+ */
258
+ const allFeaturesOf = (classifier: Classifier): Feature[] =>
259
+ flatMapNonCyclingFollowing((ci) => ci.features, inheritsDirectlyFrom)(classifier)
260
+
261
+
262
+ /**
263
+ * Determines whether the given {@link LanguageEntity language element} is an {@link Enumeration enumeration}.
264
+ */
265
+ const isEnumeration = (element: LanguageEntity): element is Enumeration =>
266
+ element instanceof Enumeration
267
+
268
+
269
+ /**
270
+ * @return a function that looks up a classifier from the given {@link Language language} by its ID.
271
+ */
272
+ const idBasedClassifierDeducerFor = (language: Language) =>
273
+ (id: LionWebId) =>
274
+ language.entities.find((element) => element instanceof Classifier && element.id === id) as Classifier
275
+
276
+ /**
277
+ * @return a function that looks up a classifier from the given {@link Language language} by its name.
278
+ */
279
+ const nameBasedClassifierDeducerFor = (language: Language) =>
280
+ (name: string) =>
281
+ language.entities.find((element) => element instanceof Classifier && element.name === name) as Classifier
282
+
283
+
284
+ /**
285
+ * @return a {@link ClassifierDeducer classifier deducer} that deduces the classifier of nodes by looking up
286
+ * the classifier in the given {@link Language language} by matching the node object's class name to classifiers' names.
287
+ * **Note** that this is not reliable when using bundlers who might minimize class names, and such.
288
+ */
289
+ const classBasedClassifierDeducerFor = <NT extends Node>(language: Language): ClassifierDeducer<NT> =>
290
+ (node: NT) => nameBasedClassifierDeducerFor(language)(node.constructor.name)
291
+
292
+
293
+ /**
294
+ * @return a {@link ClassifierDeducer classifier deducer} that deduces the classifier of nodes that implement {@link IMetaTyped}
295
+ * by looking up the classifier in the given {@link Language language} by matching the result of {@link IMetaTyped#metaType}
296
+ * to classifiers' names.
297
+ */
298
+ const metaTypedBasedClassifierDeducerFor = <NT extends Node & IMetaTyped>(language: Language): ClassifierDeducer<NT> =>
299
+ (node: NT) => nameBasedClassifierDeducerFor(language)(node.metaType())
300
+
301
+
302
+ /**
303
+ * @return all {@link Concept concepts} defined in the given {@link Language language}.
304
+ */
305
+ const conceptsOf = (language: Language): Concept[] =>
306
+ language.entities.filter((entity) => entity instanceof Concept) as Concept[]
307
+
308
+
309
+ const isInstantiableClassifier = (entity: LanguageEntity): boolean =>
310
+ entity instanceof Annotation
311
+ || (entity instanceof Concept && !entity.abstract)
312
+ // leaves out Interface and Concept { abstract: true }
313
+
314
+ /**
315
+ * @return an array of all instantiable {@link Classifier classifiers} of the given {@link Language language}.
316
+ */
317
+ const instantiableClassifiersOf = (language: Language): Classifier[] =>
318
+ language.entities.filter(isInstantiableClassifier) as Classifier[]
319
+
320
+
321
+ /**
322
+ * @return whether the two given {@link Classifiers classifiers} are the same (/identical by meta-pointer).
323
+ */
324
+ const areSameClassifiers = (left: Classifier, right: Classifier) =>
325
+ (left === right) || (
326
+ areSameLanguages(left.language, right.language) && left.key === right.key
327
+ )
328
+
329
+ /**
330
+ * @return whether the two given {@link Language languages} are the same (/identical by meta-pointer).
331
+ */
332
+ const areSameLanguages = (left: Language, right: Language) =>
333
+ (left === right) || (
334
+ left.key === right.key && left.version === right.version
335
+ )
336
+
337
+
338
+ export {
339
+ allContaineds,
340
+ allFeaturesOf,
341
+ allSuperTypesOf,
342
+ areSameClassifiers,
343
+ areSameLanguages,
344
+ classBasedClassifierDeducerFor,
345
+ concatenateNamesOf,
346
+ conceptsOf,
347
+ containmentChain,
348
+ directlyContaineds,
349
+ featureMetaType,
350
+ flatMap,
351
+ idBasedClassifierDeducerFor,
352
+ inheritanceCycleWith,
353
+ inheritedCycleWith,
354
+ inheritsFrom,
355
+ inheritsDirectlyFrom,
356
+ instantiableClassifiersOf,
357
+ isConcrete,
358
+ isContainment,
359
+ isEnumeration,
360
+ isMultiple,
361
+ isPartition,
362
+ isProperty,
363
+ isReference,
364
+ keyOf,
365
+ metaTypedBasedClassifierDeducerFor,
366
+ nameBasedClassifierDeducerFor,
367
+ nameOf,
368
+ namedsOf,
369
+ nonRelationalFeatures,
370
+ relations,
371
+ relationsOf,
372
+ type,
373
+ qualifiedNameOf
374
+ }
375
+
376
+ export type {
377
+ FeatureMetaType
378
+ }
379
+
@@ -0,0 +1,12 @@
1
+ export * from "./builtins.js"
2
+ export * from "./constraints.js"
3
+ export * from "./deserializer.js"
4
+ export * from "./facade.js"
5
+ export * from "./factory.js"
6
+ export * from "./feature-resolvers.js"
7
+ export * from "./functions.js"
8
+ export * from "./lioncore.js"
9
+ export * from "./reference-checker.js"
10
+ export * from "./serializer.js"
11
+ export * from "./symbol-table.js"
12
+ export * from "./types.js"
@@ -0,0 +1,139 @@
1
+ import { currentReleaseVersion } from "../version.js"
2
+ import { builtinClassifiers, builtinPrimitives } from "./builtins.js"
3
+ import { LanguageFactory } from "./factory.js"
4
+
5
+ const lioncoreKey = "LionCore-M3"
6
+
7
+ const factory = new LanguageFactory(
8
+ "LionCore_M3",
9
+ currentReleaseVersion,
10
+ (...names) => "-id-" + (names.length === 1 ? lioncoreKey : names.slice(1).join("-")),
11
+ (...names) => names.slice(1).join("-")
12
+ )
13
+ /*
14
+ * ID: `-id-${key}`
15
+ * key: qualified name _without_ "LionCore_M3", dash-separated
16
+ */
17
+
18
+ /**
19
+ * Definition of LionCore in terms of itself.
20
+ */
21
+ export const lioncore = factory.language.havingKey("LionCore-M3")
22
+
23
+ const { inamed } = builtinClassifiers
24
+ const { booleanDataType, stringDataType } = builtinPrimitives
25
+
26
+ const ikeyed = factory.interface("IKeyed").extending(inamed)
27
+
28
+ const ikeyed_key = factory.property(ikeyed, "key").ofType(stringDataType)
29
+
30
+ const feature = factory.concept("Feature", true).implementing(ikeyed)
31
+
32
+ const feature_optional = factory.property(feature, "optional").ofType(booleanDataType)
33
+
34
+ const property = factory.concept("Property", false, feature)
35
+
36
+ const property_type = factory.reference(property, "type")
37
+
38
+ const link = factory.concept("Link", true, feature)
39
+
40
+ const link_multiple = factory.property(link, "multiple").ofType(booleanDataType)
41
+
42
+ const link_type = factory.reference(link, "type")
43
+
44
+ const containment = factory.concept("Containment", false, link)
45
+
46
+ const reference = factory.concept("Reference", false, link)
47
+
48
+ const languageEntity = factory.concept("LanguageEntity", true).implementing(ikeyed)
49
+
50
+ const classifier = factory.concept("Classifier", true, languageEntity)
51
+
52
+ const classifier_features = factory.containment(classifier, "features").isOptional().isMultiple().ofType(feature)
53
+
54
+ link_type.ofType(classifier)
55
+
56
+ const annotation = factory.concept("Annotation", false, classifier)
57
+
58
+ const annotation_annotates = factory.reference(annotation, "annotates").isOptional().ofType(classifier)
59
+
60
+ const annotation_extends = factory.reference(annotation, "extends").isOptional().ofType(annotation)
61
+
62
+ const annotation_implements = factory.reference(annotation, "implements").isMultiple().isOptional()
63
+
64
+ const concept = factory.concept("Concept", false, classifier)
65
+
66
+ const concept_abstract = factory.property(concept, "abstract").ofType(booleanDataType)
67
+
68
+ const concept_partition = factory.property(concept, "partition").ofType(booleanDataType)
69
+
70
+ const concept_extends = factory.reference(concept, "extends").isOptional().ofType(concept)
71
+
72
+ const concept_implements = factory.reference(concept, "implements").isOptional().isMultiple()
73
+
74
+ const interface_ = factory.concept("Interface", false, classifier)
75
+
76
+ const interface_extends = factory.reference(interface_, "extends").isOptional().isMultiple().ofType(interface_)
77
+
78
+ annotation_implements.ofType(interface_)
79
+ concept_implements.ofType(interface_)
80
+
81
+ const dataType = factory.concept("DataType", true, languageEntity)
82
+
83
+ property_type.ofType(dataType)
84
+
85
+ const primitiveType = factory.concept("PrimitiveType", false, dataType)
86
+
87
+ const enumeration = factory.concept("Enumeration", false, dataType)
88
+
89
+ const enumeration_literals = factory.containment(enumeration, "literals").isMultiple().isOptional()
90
+
91
+ const enumerationLiteral = factory.concept("EnumerationLiteral", false).implementing(ikeyed)
92
+
93
+ enumeration_literals.ofType(enumerationLiteral)
94
+
95
+ const language = factory.concept("Language", false).implementing(ikeyed).isPartition()
96
+
97
+ const language_version = factory.property(language, "version").ofType(stringDataType)
98
+
99
+ const language_entities = factory.containment(language, "entities").isOptional().isMultiple().ofType(languageEntity)
100
+
101
+ const language_dependsOn = factory.reference(language, "dependsOn").isOptional().isMultiple().ofType(language)
102
+
103
+ export const metaConcepts = {
104
+ annotation,
105
+ classifier,
106
+ concept,
107
+ interface: interface_,
108
+ containment,
109
+ enumeration,
110
+ enumerationLiteral,
111
+ ikeyed,
112
+ language,
113
+ primitiveType,
114
+ property,
115
+ reference
116
+ }
117
+
118
+ export const metaFeatures = {
119
+ annotation_annotates,
120
+ annotation_extends,
121
+ annotation_implements,
122
+ classifier_features,
123
+ concept_abstract,
124
+ concept_partition,
125
+ concept_extends,
126
+ concept_implements,
127
+ interface_extends,
128
+ enumeration_literals,
129
+ feature_optional,
130
+ ikeyed_key,
131
+ language_dependsOn,
132
+ language_entities,
133
+ language_version,
134
+ link_multiple,
135
+ link_type,
136
+ property_type
137
+ }
138
+
139
+ export { lioncoreKey }
@@ -0,0 +1,38 @@
1
+ import { SingleRef, unresolved } from "../references.js"
2
+ import { flatMap, qualifiedNameOf } from "./functions.js"
3
+ import { Concept, Containment, Language, Property, Reference } from "./types.js"
4
+
5
+
6
+ /**
7
+ * Checks whether the metamodel of the given language contains unresolved references.
8
+ */
9
+ export const checkReferences = (language: Language): string[] =>
10
+ flatMap(
11
+ language,
12
+ (thing) => {
13
+
14
+ const locations: string[] = []
15
+ const check = (ref: SingleRef<unknown>, location: string) => {
16
+ if (ref === unresolved) {
17
+ locations.push(location)
18
+ }
19
+ }
20
+
21
+ if (thing instanceof Concept) {
22
+ check(thing.extends, `<Concept>${qualifiedNameOf(thing)}#extends`)
23
+ }
24
+ if (thing instanceof Containment) {
25
+ check(thing.type, `<Containment>${qualifiedNameOf(thing)}#type`)
26
+ }
27
+ if (thing instanceof Property) {
28
+ check(thing.type, `<Property>${qualifiedNameOf(thing)}#type`)
29
+ }
30
+ if (thing instanceof Reference) {
31
+ check(thing.type, `<Reference>${qualifiedNameOf(thing)}#type`)
32
+ }
33
+
34
+ return locations
35
+ }
36
+ )
37
+ // TODO (#8) make this generic, parametrized by a {@link Metamodel}
38
+
@@ -0,0 +1,13 @@
1
+ import { LionWebJsonChunk } from "@lionweb/json"
2
+ import { nodeSerializer } from "../serializer.js"
3
+ import { lioncoreReader } from "./facade.js"
4
+ import { Language } from "./types.js"
5
+
6
+
7
+ /**
8
+ * Serializes languages (i.e., instances of the LionCore metametamodel, using {@link M3Concept these type definitions})
9
+ * into the LionWeb serialization JSON format.
10
+ */
11
+ export const serializeLanguages = (...languages: Language[]): LionWebJsonChunk =>
12
+ nodeSerializer(lioncoreReader)(languages)
13
+
@@ -0,0 +1,119 @@
1
+ import { LionWebJsonMetaPointer, LionWebKey } from "@lionweb/json"
2
+ import { lazyMapGet } from "@lionweb/ts-utils"
3
+ import { allFeaturesOf } from "./functions.js"
4
+ import { Classifier, Feature, Language, LanguageEntity } from "./types.js"
5
+
6
+
7
+ /**
8
+ * Interface for objects that can look up within languages, based on given {@link LionWebJsonMetaPointer meta pointers}.
9
+ * This is meant to be able to properly encapsulate performance optimizations, also outside of the context
10
+ * of deserialization.
11
+ */
12
+ interface SymbolTable {
13
+
14
+ /**
15
+ * Looks up the {@link Language}, as pointed to by the given language key and version.
16
+ */
17
+ languageMatching(key: LionWebKey, version: string): Language | undefined
18
+
19
+ /**
20
+ * Looks up the {@link LanguageEntity}, as pointed to by the given {@link LionWebJsonMetaPointer},
21
+ * or {@code undefined} if it couldn't be found.
22
+ */
23
+ entityMatching(entityMetaPointer: LionWebJsonMetaPointer): LanguageEntity | undefined
24
+
25
+ /**
26
+ * Looks up the {@link Feature}, as pointed to by the {@link LionWebJsonMetaPointer} given second,
27
+ * as a feature of the {@link Classifier}, as pointed to by the {@link LionWebJsonMetaPointer} given first,
28
+ * or {@code undefined} it it couldn't be found.
29
+ * <em>Note</em> that the {@code language} and {@code version} values of both {@link LionWebJsonMetaPointer}-typed arguments should coincide,
30
+ * although this is typically not checked!
31
+ */
32
+ featureMatching(entityMetaPointer: LionWebJsonMetaPointer, featureMetaPointer: LionWebJsonMetaPointer): Feature | undefined
33
+
34
+ }
35
+
36
+
37
+ type EntityInfo = {
38
+ entity: LanguageEntity
39
+ allFeatures: Feature[] // === [] if entity is not a Classifier
40
+ featureKey2feature: { [featureKey: LionWebKey]: Feature } // populated through memoisation
41
+ }
42
+
43
+ class MemoisingSymbolTable implements SymbolTable {
44
+
45
+ private readonly languages: Language[]
46
+
47
+ constructor(languages: Language[]) {
48
+ this.languages = languages
49
+ }
50
+
51
+ private readonly languageKey2version2language: { [languageKey: LionWebKey]: { [version: string]: Language } } = {}
52
+
53
+ languageMatching = (languageKey: LionWebKey, version: string): Language | undefined =>
54
+ lazyMapGet(
55
+ lazyMapGet(this.languageKey2version2language, languageKey, () => ({})),
56
+ version,
57
+ () => this.languages.find((language) =>
58
+ language.key === languageKey
59
+ && language.version === version
60
+ )
61
+ )
62
+
63
+
64
+ private readonly languageKey2version2entityKey2entityInfo: { [languageKey: LionWebKey]: { [version: string]: { [entityKey: LionWebKey]: (EntityInfo | undefined) } } } = {}
65
+
66
+ private entityInfoMatching = (entityMetaPointer: LionWebJsonMetaPointer): undefined | EntityInfo =>
67
+ lazyMapGet(
68
+ lazyMapGet(
69
+ lazyMapGet(this.languageKey2version2entityKey2entityInfo, entityMetaPointer.language, () => ({})),
70
+ entityMetaPointer.version,
71
+ () => ({})
72
+ ),
73
+ entityMetaPointer.key,
74
+ () => {
75
+ const entity = this.languageMatching(entityMetaPointer.language, entityMetaPointer.version)
76
+ ?.entities
77
+ .find((entity) => entity.key === entityMetaPointer.key)
78
+ return entity === undefined
79
+ ? undefined
80
+ : {
81
+ entity,
82
+ allFeatures: entity instanceof Classifier ? allFeaturesOf(entity) : [], featureKey2feature: {}
83
+ }
84
+ }
85
+ )
86
+
87
+ entityMatching = (entityMetaPointer: LionWebJsonMetaPointer): LanguageEntity | undefined =>
88
+ this.entityInfoMatching(entityMetaPointer)?.entity
89
+
90
+ /**
91
+ * Looks up the {@link LanguageEntity}, as pointed to by the given {@link LionWebJsonMetaPointer},
92
+ * and @returns all its {@link Feature features} or an empty array if it couldn't be found.
93
+ */
94
+ allFeaturesOfEntityMatching = (entityMetaPointer: LionWebJsonMetaPointer): Feature[] =>
95
+ this.entityInfoMatching(entityMetaPointer)?.allFeatures ?? []
96
+
97
+ featureMatching(classifierMetaPointer: LionWebJsonMetaPointer, featureMetaPointer: LionWebJsonMetaPointer): Feature | undefined {
98
+ const entityInfo = this.entityInfoMatching(classifierMetaPointer)
99
+ if (entityInfo === undefined || !(entityInfo.entity instanceof Classifier)) {
100
+ return undefined
101
+ }
102
+ return lazyMapGet(
103
+ entityInfo.featureKey2feature,
104
+ featureMetaPointer.key,
105
+ () => entityInfo.allFeatures.find((feature) => feature.key === featureMetaPointer.key)
106
+ )
107
+ }
108
+
109
+ }
110
+
111
+
112
+ export type {
113
+ SymbolTable
114
+ }
115
+
116
+ export {
117
+ MemoisingSymbolTable
118
+ }
119
+