@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
package/src/m3/types.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript type definitions for the `LionCore` M3 (=meta-meta) model.
|
|
3
|
+
* A LionWeb language (at the M2 meta level) can be represented as an instance of the {@link Language} type.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { LionWebId, LionWebJsonMetaPointer, LionWebKey } from "@lionweb/json"
|
|
7
|
+
import { ResolveInfoDeducer } from "../reading.js"
|
|
8
|
+
import { MultiRef, SingleRef, unresolved } from "../references.js"
|
|
9
|
+
import { Node } from "../types.js"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The key of the LionCore language containing the built-ins.
|
|
14
|
+
* (It's defined here because of instantiation order.)
|
|
15
|
+
*/
|
|
16
|
+
const lioncoreBuiltinsKey = "LionCore-builtins"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// Types appear roughly in the order of top-to-down+left-to-right in the diagram at:
|
|
20
|
+
// https://lionweb-io.github.io/specification/metametamodel/metametamodel.html#_overview
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
interface INamed {
|
|
24
|
+
name: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isINamed = (node: object): node is INamed =>
|
|
28
|
+
"name" in node && typeof node.name === "string"
|
|
29
|
+
|
|
30
|
+
const simpleNameDeducer: ResolveInfoDeducer<Node> =
|
|
31
|
+
(node: Node) => isINamed(node) ? node.name : undefined
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
interface IKeyed extends INamed {
|
|
35
|
+
key: LionWebId
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* An interface with one method to return a meta type,
|
|
41
|
+
* independent of the class's name obtained through `<node>.constructor.name`,
|
|
42
|
+
* which may be brittle when using bundlers.
|
|
43
|
+
*/
|
|
44
|
+
interface IMetaTyped {
|
|
45
|
+
metaType(): string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Abstract base class for nodes in an LionCore instance,
|
|
50
|
+
* providing an ID, a key, and the containment hierarchy.
|
|
51
|
+
*/
|
|
52
|
+
abstract class M3Node implements IKeyed, IMetaTyped {
|
|
53
|
+
metaType(): string {
|
|
54
|
+
throw new Error("#metaType() not implemented")
|
|
55
|
+
}
|
|
56
|
+
parent?: M3Node
|
|
57
|
+
/*
|
|
58
|
+
* Note: every parent in an M2 (i.e., a Language, Concept, Interface, Enumeration) implements IKeyed.
|
|
59
|
+
* Because that's just an interface and is implemented by {@link M3Node},
|
|
60
|
+
* we can type parent as M3Node?.
|
|
61
|
+
*/
|
|
62
|
+
readonly id: LionWebId
|
|
63
|
+
name: string
|
|
64
|
+
key: LionWebId
|
|
65
|
+
protected constructor(id: LionWebId, name: string, key: LionWebId, parent?: M3Node) {
|
|
66
|
+
this.id = id
|
|
67
|
+
this.name = name
|
|
68
|
+
this.key = key
|
|
69
|
+
this.parent = parent
|
|
70
|
+
}
|
|
71
|
+
havingKey(key: LionWebId) {
|
|
72
|
+
this.key = key
|
|
73
|
+
return this
|
|
74
|
+
}
|
|
75
|
+
annotations: Node[] = [] // (containment)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
abstract class Feature extends M3Node {
|
|
79
|
+
optional /*: boolean */ = false
|
|
80
|
+
// TODO look at order of constructors' arguments!
|
|
81
|
+
constructor(classifier: Classifier, name: string, key: LionWebKey, id: LionWebId) {
|
|
82
|
+
super(id, name, key, classifier)
|
|
83
|
+
}
|
|
84
|
+
isOptional() {
|
|
85
|
+
this.optional = true
|
|
86
|
+
return this
|
|
87
|
+
}
|
|
88
|
+
get classifier(): Classifier {
|
|
89
|
+
return this.parent! as Classifier
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class Property extends Feature {
|
|
94
|
+
metaType(): string {
|
|
95
|
+
return "Property"
|
|
96
|
+
}
|
|
97
|
+
type: SingleRef<DataType> = unresolved // (reference)
|
|
98
|
+
ofType(type: DataType): Property {
|
|
99
|
+
this.type = type
|
|
100
|
+
return this
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
abstract class Link extends Feature {
|
|
105
|
+
multiple /*: boolean */ = false
|
|
106
|
+
type: SingleRef<Classifier> = unresolved // (reference)
|
|
107
|
+
isMultiple() {
|
|
108
|
+
this.multiple = true
|
|
109
|
+
return this
|
|
110
|
+
}
|
|
111
|
+
ofType(type: Classifier) {
|
|
112
|
+
this.type = type
|
|
113
|
+
return this
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class Containment extends Link {
|
|
118
|
+
metaType(): string {
|
|
119
|
+
return "Containment"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class Reference extends Link {
|
|
124
|
+
metaType(): string {
|
|
125
|
+
return "Reference"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
abstract class LanguageEntity extends M3Node {
|
|
130
|
+
constructor(language: Language, name: string, key: LionWebId, id: LionWebId) {
|
|
131
|
+
super(id, name, key, language)
|
|
132
|
+
}
|
|
133
|
+
get language(): Language {
|
|
134
|
+
return this.parent! as Language
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
abstract class Classifier extends LanguageEntity {
|
|
139
|
+
features: Feature[] = [] // (containment)
|
|
140
|
+
havingFeatures(...features: Feature[]) {
|
|
141
|
+
this.features.push(...features.filter((feature) => this.features.indexOf(feature) < 0))
|
|
142
|
+
return this
|
|
143
|
+
}
|
|
144
|
+
metaPointer(): LionWebJsonMetaPointer {
|
|
145
|
+
const {language} = this
|
|
146
|
+
return {
|
|
147
|
+
language: language.key,
|
|
148
|
+
version: language.version,
|
|
149
|
+
key: this.key
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class Concept extends Classifier {
|
|
155
|
+
metaType(): string {
|
|
156
|
+
return "Concept"
|
|
157
|
+
}
|
|
158
|
+
abstract: boolean
|
|
159
|
+
partition: boolean
|
|
160
|
+
extends?: SingleRef<Concept> // (reference)
|
|
161
|
+
implements: MultiRef<Interface> = [] // (reference)
|
|
162
|
+
constructor(language: Language, name: string, key: LionWebKey, id: LionWebId, abstract: boolean, extends_?: SingleRef<Concept>) {
|
|
163
|
+
super(language, name, key, id)
|
|
164
|
+
this.abstract = abstract
|
|
165
|
+
this.extends = extends_
|
|
166
|
+
this.partition = false
|
|
167
|
+
}
|
|
168
|
+
implementing(...interfaces: Interface[]): Concept {
|
|
169
|
+
// TODO check actual types of interfaces, or use type shapes/interfaces
|
|
170
|
+
this.implements.push(...interfaces)
|
|
171
|
+
return this
|
|
172
|
+
}
|
|
173
|
+
isPartition(): Concept {
|
|
174
|
+
this.partition = true
|
|
175
|
+
return this
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
class Annotation extends Classifier {
|
|
180
|
+
metaType(): string {
|
|
181
|
+
return "Annotation"
|
|
182
|
+
}
|
|
183
|
+
extends?: SingleRef<Annotation> // (reference)
|
|
184
|
+
implements: MultiRef<Interface> = [] // (reference)
|
|
185
|
+
annotates: SingleRef<Classifier> = unresolved // (reference)
|
|
186
|
+
constructor(language: Language, name: string, key: LionWebKey, id: LionWebId, extends_?: SingleRef<Annotation>) {
|
|
187
|
+
super(language, name, key, id)
|
|
188
|
+
this.extends = extends_
|
|
189
|
+
}
|
|
190
|
+
implementing(...interfaces: Interface[]): Annotation {
|
|
191
|
+
// TODO check actual types of interfaces, or use type shapes/interfaces
|
|
192
|
+
this.implements.push(...interfaces)
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
annotating(classifier: Classifier): Annotation {
|
|
196
|
+
this.annotates = classifier
|
|
197
|
+
return this
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
class Interface extends Classifier {
|
|
202
|
+
metaType(): string {
|
|
203
|
+
return "Interface"
|
|
204
|
+
}
|
|
205
|
+
extends: MultiRef<Interface> = [] // (reference)
|
|
206
|
+
extending(...interfaces: Interface[]): Interface {
|
|
207
|
+
// TODO check actual types of interfaces, or use type shapes/interfaces
|
|
208
|
+
this.extends.push(...interfaces)
|
|
209
|
+
return this
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
abstract class DataType extends LanguageEntity {}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Misspelled alias of {@link DataType}, kept for backward compatibility, and to be deprecated and removed later.
|
|
217
|
+
*/
|
|
218
|
+
abstract class Datatype extends DataType {}
|
|
219
|
+
|
|
220
|
+
class PrimitiveType extends DataType {
|
|
221
|
+
metaType(): string {
|
|
222
|
+
return "PrimitiveType"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
class Enumeration extends DataType {
|
|
227
|
+
metaType(): string {
|
|
228
|
+
return "Enumeration"
|
|
229
|
+
}
|
|
230
|
+
literals: EnumerationLiteral[] = [] // (containment)
|
|
231
|
+
havingLiterals(...literals: EnumerationLiteral[]) {
|
|
232
|
+
this.literals.push(...literals.filter((literal) => this.literals.indexOf(literal) < 0))
|
|
233
|
+
return this
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
class EnumerationLiteral extends M3Node {
|
|
238
|
+
metaType(): string {
|
|
239
|
+
return "EnumerationLiteral"
|
|
240
|
+
}
|
|
241
|
+
constructor(enumeration: Enumeration, name: string, key: LionWebKey, id: LionWebId) {
|
|
242
|
+
super(id, name, key, enumeration)
|
|
243
|
+
}
|
|
244
|
+
get enumeration(): Enumeration {
|
|
245
|
+
return this.parent! as Enumeration
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
class Language extends M3Node {
|
|
250
|
+
metaType(): string {
|
|
251
|
+
return "Language"
|
|
252
|
+
}
|
|
253
|
+
version: string
|
|
254
|
+
entities: LanguageEntity[] = [] // (containment)
|
|
255
|
+
dependsOn: MultiRef<Language> = [] // special (!) reference
|
|
256
|
+
// (!) special because deserializer needs to be aware of where to get the instance from
|
|
257
|
+
constructor(name: string, version: string, id: LionWebId, key: LionWebKey) {
|
|
258
|
+
super(id, name, key)
|
|
259
|
+
this.version = version
|
|
260
|
+
}
|
|
261
|
+
havingEntities(...entities: LanguageEntity[]): Language {
|
|
262
|
+
this.entities.push(...entities.filter((entity) => this.entities.indexOf(entity) < 0))
|
|
263
|
+
return this
|
|
264
|
+
}
|
|
265
|
+
dependingOn(...languages: Language[]): Language {
|
|
266
|
+
this.dependsOn.push(
|
|
267
|
+
...languages
|
|
268
|
+
.filter((language) => language.key !== lioncoreBuiltinsKey)
|
|
269
|
+
)
|
|
270
|
+
return this
|
|
271
|
+
}
|
|
272
|
+
equals(that: Language): boolean {
|
|
273
|
+
return this.key === that.key && this.version === that.version
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Sum type of all LionCore type definitions whose meta-type is a concrete (thus: instantiable) Concept.
|
|
280
|
+
* All the classes in this sum type extend (from) {@link M3Node},
|
|
281
|
+
* so they also implement {@link INamed} and {@link IKeyed}.
|
|
282
|
+
*/
|
|
283
|
+
type M3Concept =
|
|
284
|
+
| Annotation
|
|
285
|
+
| Concept
|
|
286
|
+
| Containment
|
|
287
|
+
| Enumeration
|
|
288
|
+
| EnumerationLiteral
|
|
289
|
+
| Interface
|
|
290
|
+
| Language
|
|
291
|
+
| PrimitiveType
|
|
292
|
+
| Property
|
|
293
|
+
| Reference
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
export {
|
|
297
|
+
Annotation,
|
|
298
|
+
Classifier,
|
|
299
|
+
Concept,
|
|
300
|
+
Containment,
|
|
301
|
+
DataType,
|
|
302
|
+
Datatype,
|
|
303
|
+
Enumeration,
|
|
304
|
+
EnumerationLiteral,
|
|
305
|
+
Feature,
|
|
306
|
+
Interface,
|
|
307
|
+
Language,
|
|
308
|
+
LanguageEntity,
|
|
309
|
+
Link,
|
|
310
|
+
PrimitiveType,
|
|
311
|
+
Property,
|
|
312
|
+
Reference,
|
|
313
|
+
isINamed,
|
|
314
|
+
lioncoreBuiltinsKey,
|
|
315
|
+
simpleNameDeducer
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export type {
|
|
319
|
+
IKeyed,
|
|
320
|
+
IMetaTyped,
|
|
321
|
+
INamed,
|
|
322
|
+
M3Concept,
|
|
323
|
+
M3Node
|
|
324
|
+
}
|
|
325
|
+
|
package/src/reading.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Classifier, Enumeration, EnumerationLiteral, Feature } from "./m3/index.js"
|
|
2
|
+
import { Node } from "./types.js"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type def. for functions that deduce the {@link Classifier classifier} of a given {@link Node node}.
|
|
7
|
+
*/
|
|
8
|
+
export type ClassifierDeducer<NT extends Node> = (node: NT) => Classifier
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type def. for functions that deduce the string value of the `resolveInfo` field of a
|
|
12
|
+
* {@link LionWebJsonReferenceTarget serialized reference target}, or {@code undefined}
|
|
13
|
+
* to indicate that no `resolveInfo` could be derived.
|
|
14
|
+
*/
|
|
15
|
+
export type ResolveInfoDeducer<NT extends Node> = (node: NT) => string | undefined
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* An interface that's used to parametrize generic serialization of
|
|
19
|
+
* (in-memory) nodes of the given type (parameter).
|
|
20
|
+
* Implementations of these interfaces {w|c}ould be:
|
|
21
|
+
* - specific to LionCore (so to match m3/types.ts)
|
|
22
|
+
* - generic to serialize {@link DynamicNode dynamic nodes}
|
|
23
|
+
*/
|
|
24
|
+
export interface Reader<NT extends Node> {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @return The {@link Concept concept} of the given node
|
|
28
|
+
*/
|
|
29
|
+
classifierOf: ClassifierDeducer<NT>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @return The value of the given {@link Feature feature} on the given node.
|
|
33
|
+
*/
|
|
34
|
+
getFeatureValue: (node: NT, feature: Feature) => unknown
|
|
35
|
+
// TODO split to getPropertyValue, &c.?
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @return The {@link EnumerationLiteral} corresponding to
|
|
39
|
+
* the given {@link Enumeration} and the runtime encoding of a literal of it,
|
|
40
|
+
*/
|
|
41
|
+
enumerationLiteralFrom: (encoding: unknown, enumeration: Enumeration) => EnumerationLiteral | null
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @return The string value of the `resolveInfo` field of a {@link LionWebJsonReferenceTarget serialized reference target},
|
|
45
|
+
* or {@code undefined} to indicate that no `resolveInfo` could be derived.
|
|
46
|
+
*/
|
|
47
|
+
resolveInfoFor?: ResolveInfoDeducer<NT>
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Alias for {@link Reader}, kept for backward compatibility, and to be deprecated and removed later.
|
|
53
|
+
*/
|
|
54
|
+
export interface ExtractionFacade<NT extends Node> extends Reader<NT> {}
|
|
55
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LionWebId } from "@lionweb/json"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `unresolved` symbol indicates a reference value which hasn't been resolved yet.
|
|
6
|
+
* It differs from an unset (`undefined`) value.
|
|
7
|
+
*/
|
|
8
|
+
export const unresolved = null
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A type definition for a reference value that can be unresolved.
|
|
12
|
+
*/
|
|
13
|
+
export type SingleRef<T> = typeof unresolved | T
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks whether a given (at most) single-valued reference actually refers to something.
|
|
17
|
+
*/
|
|
18
|
+
export const isRef = <T>(ref?: SingleRef<T>): ref is T =>
|
|
19
|
+
ref !== undefined && ref !== unresolved
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A type alias for a multi-valued reference, to make it look consistent with {@link SingleRef}.
|
|
23
|
+
*/
|
|
24
|
+
export type MultiRef<T> = T[]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A type that expresses a value is either an {@link LionWebId} or a value to indicate that resolution to a node previously failed.
|
|
29
|
+
*/
|
|
30
|
+
export type IdOrUnresolved = LionWebId | typeof unresolved;
|
|
31
|
+
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
currentSerializationFormatVersion,
|
|
3
|
+
LionWebId,
|
|
4
|
+
LionWebJsonChunk,
|
|
5
|
+
LionWebJsonMetaPointer,
|
|
6
|
+
LionWebJsonNode
|
|
7
|
+
} from "@lionweb/json"
|
|
8
|
+
import { asArray, keepDefineds, lazyMapGet, Nested3Map, uniquesAmong } from "@lionweb/ts-utils"
|
|
9
|
+
import { asIds } from "./functions.js"
|
|
10
|
+
import { Reader } from "./reading.js"
|
|
11
|
+
import { Node } from "./types.js"
|
|
12
|
+
import { BuiltinPropertyValueSerializer } from "./m3/builtins.js"
|
|
13
|
+
import { inheritsDirectlyFrom } from "./m3/functions.js"
|
|
14
|
+
import {
|
|
15
|
+
Classifier,
|
|
16
|
+
Containment,
|
|
17
|
+
Enumeration,
|
|
18
|
+
Feature,
|
|
19
|
+
Language,
|
|
20
|
+
PrimitiveType,
|
|
21
|
+
Property,
|
|
22
|
+
Reference,
|
|
23
|
+
simpleNameDeducer
|
|
24
|
+
} from "./m3/types.js"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Interface for objects that expose a method to serialize a property's value.
|
|
29
|
+
*/
|
|
30
|
+
export interface PropertyValueSerializer {
|
|
31
|
+
serializeValue(value: unknown, property: Property): string | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Misspelled alias of {@link PropertyValueSerializer}, kept for backward compatibility, and to be deprecated and removed later.
|
|
36
|
+
*/
|
|
37
|
+
export interface PrimitiveTypeSerializer extends PropertyValueSerializer {}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const isPropertyValueSerializer = (value: unknown): value is PropertyValueSerializer =>
|
|
41
|
+
typeof value === "object" && value !== null && "serializeValue" in value && typeof value.serializeValue === "function"
|
|
42
|
+
// (we can't check the rest of the signature – i.e. arguments and their types – at runtime, because that's JavaScript)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Type to provide (non-required) options to the serializer.
|
|
47
|
+
*/
|
|
48
|
+
export type SerializationOptions = Partial<{
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Determines whether empty feature values are explicitly serialized or skipped during serialization.
|
|
52
|
+
* (The specification states that empty feature values SHOULD be serialized, but not that they MUST be.)
|
|
53
|
+
* Default = true, meaning that empty feature values are *not* skipped.
|
|
54
|
+
*/
|
|
55
|
+
serializeEmptyFeatures: boolean
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A {@link PropertyValueSerializer} implementation.
|
|
59
|
+
* Default = DefaultPropertyValueSerializer.
|
|
60
|
+
*/
|
|
61
|
+
propertyValueSerializer: PropertyValueSerializer
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Misspelled alias of {@link #propertyValueSerializer}, kept for backward compatibility, and to be deprecated and removed later.
|
|
65
|
+
*/
|
|
66
|
+
primitiveTypeSerializer: PropertyValueSerializer
|
|
67
|
+
|
|
68
|
+
}>
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @return the {@link LionWebJsonMetaPointer} for the given {@link Feature}.
|
|
72
|
+
*/
|
|
73
|
+
export const metaPointerFor = (feature: Feature): LionWebJsonMetaPointer => {
|
|
74
|
+
const { language } = feature.classifier
|
|
75
|
+
return {
|
|
76
|
+
language: language.key,
|
|
77
|
+
version: language.version,
|
|
78
|
+
key: feature.key
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @return a function that serializes the {@link Node nodes} passed to it.
|
|
85
|
+
*/
|
|
86
|
+
export const nodeSerializer = <NT extends Node>(reader: Reader<NT>, serializationOptions?: SerializationOptions) => {
|
|
87
|
+
const propertyValueSerializer =
|
|
88
|
+
serializationOptions?.propertyValueSerializer ?? serializationOptions?.primitiveTypeSerializer ?? new BuiltinPropertyValueSerializer()
|
|
89
|
+
const serializeEmptyFeatures = serializationOptions?.serializeEmptyFeatures ?? true
|
|
90
|
+
|
|
91
|
+
const languageKey2version2classifierKey2allFeatures: Nested3Map<Feature[]> = {}
|
|
92
|
+
const memoisedAllFeaturesOf = (classifier: Classifier): Feature[] =>
|
|
93
|
+
lazyMapGet(
|
|
94
|
+
lazyMapGet(
|
|
95
|
+
lazyMapGet(
|
|
96
|
+
languageKey2version2classifierKey2allFeatures,
|
|
97
|
+
classifier.language.key,
|
|
98
|
+
() => ({})
|
|
99
|
+
),
|
|
100
|
+
classifier.language.version,
|
|
101
|
+
() => ({})
|
|
102
|
+
),
|
|
103
|
+
classifier.key,
|
|
104
|
+
() => uniquesAmong( // make unique in case a feature was inherited from multiple super-classifiers
|
|
105
|
+
[ ...classifier.features, ...(inheritsDirectlyFrom(classifier).flatMap(memoisedAllFeaturesOf)) ]
|
|
106
|
+
/*
|
|
107
|
+
* [NOTE]
|
|
108
|
+
* The allFeaturesOf function uses flatMapNonCyclingFollowing which avoids that features of a super-classifier are added multiple times.
|
|
109
|
+
* Unfortunately, to make use of the memoising, we can't use flatMapNonCyclingFollowing in the same way here.
|
|
110
|
+
* So, we have to remove duplicates ourselves.
|
|
111
|
+
*/
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return (nodes: NT[]): LionWebJsonChunk => {
|
|
116
|
+
const serializedNodes: LionWebJsonNode[] = [] // keep nodes as much as possible "in order"
|
|
117
|
+
const ids: { [id: LionWebId]: boolean } = {} // maintain a map to keep track of IDs of nodes that have been serialized
|
|
118
|
+
const languagesUsed: Language[] = []
|
|
119
|
+
const usedLanguageKey2Version2Boolean: { [key: string]: { [version: string]: boolean } } = {}
|
|
120
|
+
const registerLanguageUsed = (language: Language) => {
|
|
121
|
+
const version2Boolean = lazyMapGet<{ [version: string]: boolean }>(usedLanguageKey2Version2Boolean, language.key, () => ({}))
|
|
122
|
+
if (!version2Boolean[language.version]) {
|
|
123
|
+
version2Boolean[language.version] = true
|
|
124
|
+
languagesUsed.push(language)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const visit = (node: NT, parent?: NT) => {
|
|
129
|
+
if (node.id in ids) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const classifier = reader.classifierOf(node)
|
|
134
|
+
const language = classifier.language
|
|
135
|
+
registerLanguageUsed(language)
|
|
136
|
+
const serializedNode: LionWebJsonNode = {
|
|
137
|
+
id: node.id,
|
|
138
|
+
classifier: classifier.metaPointer(),
|
|
139
|
+
properties: [],
|
|
140
|
+
containments: [],
|
|
141
|
+
references: [],
|
|
142
|
+
annotations: [],
|
|
143
|
+
parent: null
|
|
144
|
+
}
|
|
145
|
+
serializedNodes.push(serializedNode)
|
|
146
|
+
ids[node.id] = true
|
|
147
|
+
memoisedAllFeaturesOf(classifier).forEach((feature) => {
|
|
148
|
+
const value = reader.getFeatureValue(node, feature)
|
|
149
|
+
const featureLanguage = feature.classifier.language
|
|
150
|
+
registerLanguageUsed(featureLanguage)
|
|
151
|
+
const featureMetaPointer = metaPointerFor(feature)
|
|
152
|
+
if (feature instanceof Property) {
|
|
153
|
+
if (value === undefined && !serializeEmptyFeatures) {
|
|
154
|
+
// for immediate backward compatibility: skip empty property values regardless of options?.skipEmptyValues
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
const encodedValue = (() => {
|
|
158
|
+
// (could also just inspect type of value:)
|
|
159
|
+
if (feature.type instanceof PrimitiveType) {
|
|
160
|
+
return propertyValueSerializer.serializeValue(value, feature)
|
|
161
|
+
}
|
|
162
|
+
if (feature.type instanceof Enumeration) {
|
|
163
|
+
return reader.enumerationLiteralFrom(value, feature.type)?.key
|
|
164
|
+
}
|
|
165
|
+
return undefined
|
|
166
|
+
})()
|
|
167
|
+
serializedNode.properties.push({
|
|
168
|
+
property: featureMetaPointer,
|
|
169
|
+
value: (encodedValue as string) ?? null // (undefined -> null)
|
|
170
|
+
})
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
if (feature instanceof Containment) {
|
|
174
|
+
const children = asArray(value) as (NT | null)[]
|
|
175
|
+
if (children.length === 0 && !serializeEmptyFeatures) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
serializedNode.containments.push({
|
|
179
|
+
containment: featureMetaPointer,
|
|
180
|
+
children: keepDefineds(asIds(children))
|
|
181
|
+
.map(childId => childId as string)
|
|
182
|
+
})
|
|
183
|
+
children.forEach(childOrNull => {
|
|
184
|
+
if (childOrNull !== null) {
|
|
185
|
+
visit(childOrNull, node)
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (feature instanceof Reference) {
|
|
191
|
+
// Note: value can be null === typeof unresolved, e.g. on an unset (or previously unresolved) single-valued reference
|
|
192
|
+
const targets = asArray(value) as (NT | null)[]
|
|
193
|
+
if (targets.length === 0 && !serializeEmptyFeatures) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
serializedNode.references.push({
|
|
197
|
+
reference: featureMetaPointer,
|
|
198
|
+
targets: keepDefineds(targets) // (skip "non-connected" targets)
|
|
199
|
+
.map(t => t as NT)
|
|
200
|
+
.map(t => ({
|
|
201
|
+
resolveInfo:
|
|
202
|
+
(reader.resolveInfoFor ? reader.resolveInfoFor(t) : simpleNameDeducer(t)) ?? null,
|
|
203
|
+
reference: t.id
|
|
204
|
+
}))
|
|
205
|
+
})
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const annotations = asArray(node.annotations) as NT[] // assumes that annotations also all are of type NT (which is not unreasonable)
|
|
211
|
+
serializedNode.annotations = annotations.map(annotation => annotation.id)
|
|
212
|
+
annotations.forEach(annotation => visit(annotation, node))
|
|
213
|
+
|
|
214
|
+
serializedNode.parent = parent?.id ?? null // (undefined -> null)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
nodes.forEach(node => visit(node, undefined))
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
serializationFormatVersion: currentSerializationFormatVersion,
|
|
221
|
+
languages: languagesUsed.map(({ key, version }) => ({ key, version })),
|
|
222
|
+
nodes: serializedNodes
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @return a {@link LionWebJsonChunk} of the given model (i.e., an array of {@link Node nodes} - the first argument) to the LionWeb serialization JSON format.
|
|
229
|
+
* <em>Note:</em> this function will be deprecated and removed later — use {@link nodeSerializer} instead.
|
|
230
|
+
*/
|
|
231
|
+
export const serializeNodes = <NT extends Node>(
|
|
232
|
+
nodes: NT[],
|
|
233
|
+
reader: Reader<NT>,
|
|
234
|
+
propertyValueSerializerOrOptions?: PropertyValueSerializer | SerializationOptions
|
|
235
|
+
): LionWebJsonChunk =>
|
|
236
|
+
nodeSerializer<NT>(
|
|
237
|
+
reader,
|
|
238
|
+
isPropertyValueSerializer(propertyValueSerializerOrOptions)
|
|
239
|
+
? {
|
|
240
|
+
propertyValueSerializer: propertyValueSerializerOrOptions
|
|
241
|
+
}
|
|
242
|
+
: propertyValueSerializerOrOptions
|
|
243
|
+
)(nodes)
|
|
244
|
+
|
package/src/types.ts
ADDED