@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.
- package/CHANGELOG.md +1 -0
- package/package.json +3 -3
- package/src/deserializer.ts +224 -0
- package/src/dynamic-facade.ts +63 -0
- package/src/extraction.ts +31 -0
- package/src/functions.ts +28 -0
- package/src/handler.ts +57 -0
- package/src/index.ts +13 -0
- package/src/m1/reference-utils.ts +106 -0
- package/src/m3/README.md +16 -0
- package/src/m3/builtins.ts +170 -0
- package/src/m3/constraints.ts +109 -0
- package/src/m3/deserializer.ts +38 -0
- package/src/m3/facade.ts +130 -0
- package/src/m3/factory.ts +98 -0
- package/src/m3/feature-resolvers.ts +55 -0
- package/src/m3/functions.ts +379 -0
- package/src/m3/index.ts +12 -0
- package/src/m3/lioncore.ts +139 -0
- package/src/m3/reference-checker.ts +38 -0
- package/src/m3/serializer.ts +13 -0
- package/src/m3/symbol-table.ts +119 -0
- package/src/m3/types.ts +325 -0
- package/src/reading.ts +55 -0
- package/src/references.ts +31 -0
- package/src/serializer.ts +244 -0
- package/src/types.ts +11 -0
- package/src/version.ts +5 -0
- package/src/writing.ts +79 -0
|
@@ -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
|
+
|
package/src/m3/index.ts
ADDED
|
@@ -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
|
+
|