@lionweb/core 0.7.0-beta.2 → 0.7.0-beta.20
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 +9 -0
- package/dist/deserializer.d.ts +2 -2
- package/dist/deserializer.d.ts.map +1 -1
- package/dist/deserializer.js +7 -10
- package/dist/deserializer.js.map +1 -1
- package/dist/extraction.d.ts.map +1 -1
- package/dist/functions.d.ts.map +1 -1
- package/dist/handler.d.ts +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/m1/reference-utils.d.ts +1 -1
- package/dist/m1/reference-utils.d.ts.map +1 -1
- package/dist/m3/builtins.d.ts.map +1 -1
- package/dist/m3/builtins.js +2 -1
- package/dist/m3/builtins.js.map +1 -1
- package/dist/m3/constraints.d.ts.map +1 -1
- package/dist/m3/constraints.js +2 -2
- package/dist/m3/constraints.js.map +1 -1
- package/dist/m3/deserializer.d.ts.map +1 -1
- package/dist/m3/feature-resolvers.d.ts +23 -0
- package/dist/m3/feature-resolvers.d.ts.map +1 -0
- package/dist/m3/feature-resolvers.js +32 -0
- package/dist/m3/feature-resolvers.js.map +1 -0
- package/dist/m3/functions.d.ts +17 -1
- package/dist/m3/functions.d.ts.map +1 -1
- package/dist/m3/functions.js +21 -5
- package/dist/m3/functions.js.map +1 -1
- package/dist/m3/index.d.ts +2 -0
- package/dist/m3/index.d.ts.map +1 -1
- package/dist/m3/index.js +2 -0
- package/dist/m3/index.js.map +1 -1
- package/dist/m3/reference-checker.d.ts.map +1 -1
- package/dist/m3/serializer.d.ts.map +1 -1
- package/dist/{symbol-table.d.ts → m3/symbol-table.d.ts} +9 -14
- package/dist/m3/symbol-table.d.ts.map +1 -0
- package/dist/m3/symbol-table.js +38 -0
- package/dist/m3/symbol-table.js.map +1 -0
- package/dist/m3/types.d.ts.map +1 -1
- package/dist/references.d.ts +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +19 -9
- package/dist/serializer.js.map +1 -1
- package/package.json +31 -31
- 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/dist/symbol-table.d.ts.map +0 -1
- package/dist/symbol-table.js +0 -66
- package/dist/symbol-table.js.map +0 -1
package/src/handler.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A simplistic handler to which problems that arise during deserialization,
|
|
3
|
+
* are reported as plain text by calling the `reportProblem` function.
|
|
4
|
+
*/
|
|
5
|
+
export interface SimplisticHandler {
|
|
6
|
+
reportProblem: (message: string) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A default simplistic handler that just outputs everything of the console.
|
|
11
|
+
*/
|
|
12
|
+
export const defaultSimplisticHandler: SimplisticHandler = {
|
|
13
|
+
reportProblem: (message) => {
|
|
14
|
+
console.log(message)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A simplistic handler that just accumulates problems (in terms of their messages).
|
|
21
|
+
*/
|
|
22
|
+
export class AccumulatingSimplisticHandler implements SimplisticHandler {
|
|
23
|
+
private _allProblems: string[] = []
|
|
24
|
+
reportProblem(message: string) {
|
|
25
|
+
this._allProblems.push(message)
|
|
26
|
+
}
|
|
27
|
+
get allProblems() {
|
|
28
|
+
return this._allProblems
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A simplistic handler that aggregates problems by their message.
|
|
35
|
+
* This is convenient for problems that arise many times during deserialization
|
|
36
|
+
* but produce the exact same message every time.
|
|
37
|
+
*/
|
|
38
|
+
export class AggregatingSimplisticHandler implements SimplisticHandler {
|
|
39
|
+
private messageByCount: { [message: string]: number } = {}
|
|
40
|
+
reportProblem(message: string) {
|
|
41
|
+
this.messageByCount[message] = (this.messageByCount[message] ?? 0) + 1
|
|
42
|
+
}
|
|
43
|
+
reportAllProblemsOnConsole(asTable = false) {
|
|
44
|
+
if (asTable) {
|
|
45
|
+
console.table(this.messageByCount)
|
|
46
|
+
} else {
|
|
47
|
+
Object.entries(this.messageByCount)
|
|
48
|
+
.forEach(([message, count]) => {
|
|
49
|
+
console.log(`${message} (${count})`)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
allProblems() {
|
|
54
|
+
return { ...this.messageByCount }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./deserializer.js"
|
|
2
|
+
export * from "./dynamic-facade.js"
|
|
3
|
+
export * from "./extraction.js"
|
|
4
|
+
export * from "./handler.js"
|
|
5
|
+
export * from "./functions.js"
|
|
6
|
+
export * from "./reading.js"
|
|
7
|
+
export * from "./references.js"
|
|
8
|
+
export * from "./serializer.js"
|
|
9
|
+
export * from "./types.js"
|
|
10
|
+
export * from "./version.js"
|
|
11
|
+
export * from "./writing.js"
|
|
12
|
+
export * from "./m1/reference-utils.js"
|
|
13
|
+
export * from "./m3/index.js"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Reader } from "../reading.js"
|
|
2
|
+
import { allFeaturesOf, Reference } from "../m3/index.js"
|
|
3
|
+
import { Node } from "../types.js"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents information about a source and target node related through a {@link Reference}.
|
|
8
|
+
* An index of `null` means that the reference is (at most) single-valued.
|
|
9
|
+
*/
|
|
10
|
+
export class ReferenceValue<NT extends Node> {
|
|
11
|
+
constructor(
|
|
12
|
+
public readonly sourceNode: NT,
|
|
13
|
+
public readonly targetNode: NT,
|
|
14
|
+
public readonly reference: Reference,
|
|
15
|
+
public readonly index: number | null
|
|
16
|
+
) {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Finds all references within the given scope, as {@link ReferenceValue reference values}.
|
|
22
|
+
* To search within all nodes under a collection of root nodes,
|
|
23
|
+
* use _child extraction_ to compute all nodes in the forest hanging off of those root nodes as scope.
|
|
24
|
+
* Note that any reference is found uniquely,
|
|
25
|
+
* i.e. the returned {@link ReferenceValue reference values} are pairwise distinct,
|
|
26
|
+
* even if the scope passed contains duplicate nodes.
|
|
27
|
+
*
|
|
28
|
+
* @param scope - the {@link Node nodes} that are searched for references
|
|
29
|
+
* @param reader - a {@link Reader} to reflect on nodes.
|
|
30
|
+
* _Note_ that it's assumed that its {@link getFeatureValue} function doesn't throw.
|
|
31
|
+
*/
|
|
32
|
+
export const referenceValues = <NT extends Node>(
|
|
33
|
+
scope: NT[],
|
|
34
|
+
reader: Reader<NT>
|
|
35
|
+
): ReferenceValue<NT>[] => {
|
|
36
|
+
const visit = (sourceNode: NT, reference: Reference): ReferenceValue<NT>[] => {
|
|
37
|
+
if (reference.multiple) {
|
|
38
|
+
const targetNodes = (reader.getFeatureValue(sourceNode, reference) ?? []) as NT[]
|
|
39
|
+
return targetNodes
|
|
40
|
+
.map((targetNode, index) =>
|
|
41
|
+
new ReferenceValue<NT>(sourceNode, targetNode, reference, index)
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const targetNode = reader.getFeatureValue(sourceNode, reference) as (NT | undefined)
|
|
46
|
+
if (targetNode !== undefined) {
|
|
47
|
+
return [new ReferenceValue<NT>(sourceNode, targetNode, reference, null)]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [...new Set(scope)] // ~ .distinct()
|
|
54
|
+
.flatMap((sourceNode) =>
|
|
55
|
+
allFeaturesOf(reader.classifierOf(sourceNode))
|
|
56
|
+
.filter((feature) => feature instanceof Reference)
|
|
57
|
+
.map((feature) => feature as Reference)
|
|
58
|
+
.flatMap((reference) => visit(sourceNode, reference))
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Finds all references coming into the given target node or any of the given target nodes,
|
|
65
|
+
* within the given scope, as {@link ReferenceValue reference values}.
|
|
66
|
+
* To search within all nodes under a collection of root nodes,
|
|
67
|
+
* use _child extraction_ to compute all nodes in the forest hanging off of those root nodes as scope.
|
|
68
|
+
* Note that any reference is found uniquely,
|
|
69
|
+
* i.e. the returned {@link ReferenceValue reference values} are pairwise distinct,
|
|
70
|
+
* even if the given target nodes or scope contain duplicate nodes.
|
|
71
|
+
*
|
|
72
|
+
* @param targetNodeOrNodes - one or more target {@link Node nodes} for which the incoming references are searched
|
|
73
|
+
* @param scope - the {@link Node nodes} that are searched for references
|
|
74
|
+
* @param reader - a {@link Reader} to reflect on nodes.
|
|
75
|
+
* _Note_ that it's assumed that its {@link getFeatureValue} function doesn't throw.
|
|
76
|
+
*/
|
|
77
|
+
export const incomingReferences = <NT extends Node>(
|
|
78
|
+
targetNodeOrNodes: NT[] | NT,
|
|
79
|
+
scope: NT[],
|
|
80
|
+
reader: Reader<NT>
|
|
81
|
+
): ReferenceValue<NT>[] => {
|
|
82
|
+
const targetNodes = Array.isArray(targetNodeOrNodes) ? targetNodeOrNodes : [targetNodeOrNodes]
|
|
83
|
+
return referenceValues(scope, reader)
|
|
84
|
+
.filter((referenceValue) => targetNodes.indexOf(referenceValue.targetNode) > -1)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Finds all references to nodes that are not in the given scope, as {@link ReferenceValue reference values}.
|
|
90
|
+
* To search within all nodes under a collection of root nodes,
|
|
91
|
+
* use _child extraction_ to compute all nodes in the forest hanging off of those root nodes as scope.
|
|
92
|
+
* Note that any reference is found uniquely,
|
|
93
|
+
* i.e. the returned {@link ReferenceValue reference values} are pairwise distinct,
|
|
94
|
+
* even if the given scope contains duplicate nodes.
|
|
95
|
+
*
|
|
96
|
+
* @param scope - the {@link Node nodes} that form the scope of “reachable” nodes
|
|
97
|
+
* @param reader - a {@link Reader} to reflect on nodes.
|
|
98
|
+
* _Note_ that it's assumed that its {@link getFeatureValue} function doesn't throw.
|
|
99
|
+
*/
|
|
100
|
+
export const referencesToOutOfScopeNodes = <NT extends Node>(
|
|
101
|
+
scope: NT[],
|
|
102
|
+
reader: Reader<NT>
|
|
103
|
+
): ReferenceValue<NT>[] =>
|
|
104
|
+
referenceValues(scope, reader)
|
|
105
|
+
.filter((referenceValue) => scope.indexOf(referenceValue.targetNode) === -1)
|
|
106
|
+
|
package/src/m3/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# LionCore M3 metametamodel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## Aspects
|
|
5
|
+
|
|
6
|
+
* [TypeScript type definitions](./types.ts)
|
|
7
|
+
* [Factory](./factory.ts) for conveniently creating M3 instances
|
|
8
|
+
* [Classifiers built in (`LionCore-builtins`) to LionCore](./builtins.ts)
|
|
9
|
+
* [Facades specific for M3 instances](./facade.ts)
|
|
10
|
+
* Persistence: [serializer](./serializer.ts) and [deserializer](./deserializer.ts)
|
|
11
|
+
* [Constraints checker](./constraints.ts)
|
|
12
|
+
* Convenience/helper [functions](./functions.ts) defined on M3 concepts
|
|
13
|
+
* ([Reference checker](./reference-checker.ts))
|
|
14
|
+
|
|
15
|
+
An interesting place to start might be the [self-definition](./lioncore.ts) of LionCore (`LionCore-M3`) using its own [TypeScript type definitions](./types.ts).
|
|
16
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { asMinimalJsonString, StringsMapper } from "@lionweb/ts-utils"
|
|
2
|
+
import { PropertyValueDeserializer } from "../deserializer.js"
|
|
3
|
+
import { PropertyValueSerializer } from "../serializer.js"
|
|
4
|
+
import { currentReleaseVersion } from "../version.js"
|
|
5
|
+
import { LanguageFactory } from "./factory.js"
|
|
6
|
+
import { Classifier, Concept, DataType, lioncoreBuiltinsKey, Property } from "./types.js"
|
|
7
|
+
|
|
8
|
+
const lioncoreBuiltinsIdAndKeyGenerator: StringsMapper = (...names) => [lioncoreBuiltinsKey, ...names.slice(1)].join("-")
|
|
9
|
+
|
|
10
|
+
const factory = new LanguageFactory(
|
|
11
|
+
"LionCore_builtins",
|
|
12
|
+
currentReleaseVersion,
|
|
13
|
+
lioncoreBuiltinsIdAndKeyGenerator,
|
|
14
|
+
lioncoreBuiltinsIdAndKeyGenerator
|
|
15
|
+
)
|
|
16
|
+
/*
|
|
17
|
+
* ID == key: `LionCore-builtins-${qualified name _without_ "LionCore-builtins", dash-separated}`
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Definition of a LionCore language that serves as a standard library of built-in primitive types.
|
|
22
|
+
*/
|
|
23
|
+
const lioncoreBuiltins = factory.language
|
|
24
|
+
|
|
25
|
+
const stringDataType = factory.primitiveType("String")
|
|
26
|
+
const booleanDataType = factory.primitiveType("Boolean")
|
|
27
|
+
const integerDataType = factory.primitiveType("Integer")
|
|
28
|
+
const jsonDataType = factory.primitiveType("JSON")
|
|
29
|
+
|
|
30
|
+
const node = factory.concept("Node", true)
|
|
31
|
+
|
|
32
|
+
const isBuiltinNodeConcept = (classifier: Classifier) =>
|
|
33
|
+
classifier instanceof Concept &&
|
|
34
|
+
classifier.language.key === lioncoreBuiltinsKey &&
|
|
35
|
+
classifier.language.version === currentReleaseVersion &&
|
|
36
|
+
classifier.key === builtinClassifiers.node.key &&
|
|
37
|
+
(classifier as Concept).abstract
|
|
38
|
+
|
|
39
|
+
const inamed = factory.interface("INamed")
|
|
40
|
+
|
|
41
|
+
const inamed_name = factory.property(inamed, "name").ofType(stringDataType)
|
|
42
|
+
|
|
43
|
+
const builtinPrimitives = {
|
|
44
|
+
stringDataType,
|
|
45
|
+
booleanDataType,
|
|
46
|
+
integerDataType,
|
|
47
|
+
jsonDataType,
|
|
48
|
+
/**
|
|
49
|
+
* Misspelled alias of {@link stringDataType}, kept for backward compatibility, and to be deprecated and removed later.
|
|
50
|
+
*/
|
|
51
|
+
stringDatatype: stringDataType,
|
|
52
|
+
/**
|
|
53
|
+
* Misspelled alias of {@link booleanDataType}, kept for backward compatibility, and to be deprecated and removed later.
|
|
54
|
+
*/
|
|
55
|
+
booleanDatatype: booleanDataType,
|
|
56
|
+
/**
|
|
57
|
+
* Misspelled alias of {@link integerDataType}, kept for backward compatibility, and to be deprecated and removed later.
|
|
58
|
+
*/
|
|
59
|
+
integerDatatype: integerDataType,
|
|
60
|
+
/**
|
|
61
|
+
* Misspelled alias of {@link jsonDataType}, kept for backward compatibility, and to be deprecated and removed later.
|
|
62
|
+
*/
|
|
63
|
+
jsonDatatype: jsonDataType
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const builtinClassifiers = {
|
|
67
|
+
node,
|
|
68
|
+
inamed
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const builtinFeatures = {
|
|
72
|
+
inamed_name
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Determines whether two data types should be structurally equal based on equality of: meta type, key, and language's key.
|
|
77
|
+
*/
|
|
78
|
+
const shouldBeIdentical = (left: DataType, right: DataType): boolean =>
|
|
79
|
+
left.key === right.key && left.language.key === right.language.key && left.metaType() === right.metaType()
|
|
80
|
+
|
|
81
|
+
abstract class DataTypeRegister<T> {
|
|
82
|
+
private map = new Map<DataType, T>()
|
|
83
|
+
|
|
84
|
+
public register(dataType: DataType, t: T) {
|
|
85
|
+
this.map.set(dataType, t)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected byType(targetDataType: DataType): T | undefined {
|
|
89
|
+
for (const [dataType, t] of this.map.entries()) {
|
|
90
|
+
if (shouldBeIdentical(targetDataType, dataType)) {
|
|
91
|
+
return t
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return undefined
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class BuiltinPropertyValueDeserializer
|
|
99
|
+
extends DataTypeRegister<(value: string) => unknown>
|
|
100
|
+
implements PropertyValueDeserializer
|
|
101
|
+
{
|
|
102
|
+
constructor() {
|
|
103
|
+
super()
|
|
104
|
+
this.register(stringDataType, value => value)
|
|
105
|
+
this.register(booleanDataType, value => JSON.parse(value))
|
|
106
|
+
this.register(integerDataType, value => Number(value))
|
|
107
|
+
this.register(jsonDataType, value => JSON.parse(value as string))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
deserializeValue(value: string | undefined, property: Property): unknown | undefined {
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
if (property.optional) {
|
|
113
|
+
return undefined
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`can't deserialize undefined as the value of required property "${property.name}" (on classifier "${property.classifier.name}" in language "${property.classifier.language.name}")`)
|
|
116
|
+
}
|
|
117
|
+
const { type } = property
|
|
118
|
+
if (type == null) {
|
|
119
|
+
throw new Error(`can't deserialize property "${property.name}" (on classifier "${property.classifier.name}" in language "${property.classifier.language.name}") with unspecified type`)
|
|
120
|
+
}
|
|
121
|
+
const specificDeserializer = this.byType(type)
|
|
122
|
+
if (specificDeserializer != undefined) {
|
|
123
|
+
return specificDeserializer(value)
|
|
124
|
+
} else {
|
|
125
|
+
throw new Error(`can't deserialize value of property "${property.name}" (on classifier "${property.classifier.name}" in language "${property.classifier.language.name}") of type "${type!.name}": ${value}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Misspelled alias of {@link BuiltinPropertyValueDeserializer}, kept for backward compatibility, and to be deprecated and removed later.
|
|
132
|
+
*/
|
|
133
|
+
export class DefaultPrimitiveTypeDeserializer extends BuiltinPropertyValueDeserializer {}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
export class BuiltinPropertyValueSerializer extends DataTypeRegister<(value: unknown) => string> implements PropertyValueSerializer {
|
|
137
|
+
constructor() {
|
|
138
|
+
super()
|
|
139
|
+
this.register(stringDataType, value => value as string)
|
|
140
|
+
this.register(booleanDataType, value => `${value as boolean}`)
|
|
141
|
+
this.register(integerDataType, value => `${value as number}`)
|
|
142
|
+
this.register(jsonDataType, value => asMinimalJsonString(value))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
serializeValue(value: unknown | undefined, property: Property): string | null {
|
|
146
|
+
if (value === undefined) {
|
|
147
|
+
if (property.optional) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
throw new Error(`can't serialize undefined as the value of required property "${property.name}" (on classifier "${property.classifier.name}" in language "${property.classifier.language.name}")`)
|
|
151
|
+
}
|
|
152
|
+
const { type } = property
|
|
153
|
+
if (type == null) {
|
|
154
|
+
throw new Error(`can't serialize property "${property.name}" (on classifier "${property.classifier.name}" in language "${property.classifier.language.name}") with unspecified type`)
|
|
155
|
+
}
|
|
156
|
+
const specificSerializer = this.byType(type)
|
|
157
|
+
if (specificSerializer != undefined) {
|
|
158
|
+
return specificSerializer(value)
|
|
159
|
+
} else {
|
|
160
|
+
throw new Error(`can't serialize value of property "${property.name}" (on classifier "${property.classifier.name}" in language "${property.classifier.language.name}") of type "${type!.name}": ${value}`)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Misspelled alias of {@link BuiltinPropertyValueSerializer}, kept for backward compatibility, and to be deprecated and removed later.
|
|
167
|
+
*/
|
|
168
|
+
export class DefaultPrimitiveTypeSerializer extends BuiltinPropertyValueSerializer {}
|
|
169
|
+
|
|
170
|
+
export { builtinPrimitives, builtinClassifiers, builtinFeatures, isBuiltinNodeConcept, lioncoreBuiltins, shouldBeIdentical }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { duplicatesAmong } from "@lionweb/ts-utils"
|
|
2
|
+
import { idOf } from "../functions.js"
|
|
3
|
+
import { allContaineds, flatMap, inheritanceCycleWith, keyOf, namedsOf, qualifiedNameOf } from "./functions.js"
|
|
4
|
+
import { Classifier, isINamed, Language, M3Concept } from "./types.js"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type definition for an issue corresponding
|
|
9
|
+
* to a violation of a constraint on a {@link M3Concept language object}.
|
|
10
|
+
*/
|
|
11
|
+
export type Issue = {
|
|
12
|
+
location: M3Concept
|
|
13
|
+
message: string
|
|
14
|
+
secondaries: M3Concept[]
|
|
15
|
+
}
|
|
16
|
+
// TODO back this type with an M2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const base64urlRegex = /^[A-Za-z0-9_-]+$/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @return whether the given string is a valid identifier according to the LionWeb specification – see [here](https://github.com/LionWeb-io/specification/blob/main/2023.1/metametamodel/metametamodel.adoc#identifiers) for the relevant part.
|
|
23
|
+
* This is essentially whether the given string is a valid, non-empty [Base64url](https://en.wikipedia.org/wiki/Base64#Variants_summary_table) string.
|
|
24
|
+
*/
|
|
25
|
+
const isValidIdentifier = (str: string): boolean =>
|
|
26
|
+
base64urlRegex.test(str)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Computes the {@link Issue issues} (i.e., constraint violations) for the given language.
|
|
31
|
+
* (This computation is resilient against e.g. inheritance cycles.)
|
|
32
|
+
*/
|
|
33
|
+
export const issuesLanguage = (language: Language): Issue[] =>
|
|
34
|
+
[
|
|
35
|
+
...flatMap(
|
|
36
|
+
language,
|
|
37
|
+
(t) => {
|
|
38
|
+
|
|
39
|
+
const issues: Issue[] = []
|
|
40
|
+
const issue = (message: string, secondaries?: M3Concept[]): void => {
|
|
41
|
+
issues.push({
|
|
42
|
+
location: t,
|
|
43
|
+
message,
|
|
44
|
+
secondaries: secondaries ?? []
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The id consists only of latin characters (upper/lowercase), numbers, underscores, and hyphens
|
|
49
|
+
const id = t.id.trim()
|
|
50
|
+
!isValidIdentifier(id) && issue(`An ID must consist only of latin characters (upper/lowercase), numbers, underscores, and hyphens`)
|
|
51
|
+
|
|
52
|
+
// The key consists only of latin characters (upper/lowercase), numbers, underscores, and hyphens
|
|
53
|
+
const key = t.key.trim()
|
|
54
|
+
!isValidIdentifier(key) && issue(`A KEY must consist only of latin characters (upper/lowercase), numbers, underscores, and hyphens`)
|
|
55
|
+
|
|
56
|
+
if(isINamed(t)) {
|
|
57
|
+
const trimmedName = t.name.trim()
|
|
58
|
+
// The name should be a non-empty string
|
|
59
|
+
trimmedName.length === 0 && issue(`A ${t.constructor.name}'s name must not be empty`)
|
|
60
|
+
|
|
61
|
+
// The name should not contain whitespace characters
|
|
62
|
+
trimmedName.includes(" ") && issue(`A ${t.constructor.name}'s name cannot contain whitespace characters`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (t instanceof Language) {
|
|
66
|
+
// The name should not start with a number
|
|
67
|
+
const name = t.name.trim()
|
|
68
|
+
!isNaN(parseInt(name[0])) && issue(`A Language's name cannot start with a number`)
|
|
69
|
+
|
|
70
|
+
// The version is a non-empty string
|
|
71
|
+
const version = t.version.trim();
|
|
72
|
+
version.length === 0 && issue(`A Language's version must be a non-empty string`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The classifier should not inherit from itself (directly or indirectly)
|
|
76
|
+
if (t instanceof Classifier) {
|
|
77
|
+
const cycle = inheritanceCycleWith(t);
|
|
78
|
+
(cycle.length > 0) && issue(`A ${t.constructor.name} can't inherit (directly or indirectly) from itself, but ${qualifiedNameOf(t)} does so through the following cycle: ${cycle.map((t) => qualifiedNameOf(t)).join(" -> ")}`)
|
|
79
|
+
// TODO check whether it needs to be "a" or "an", or just say "An instance of ..."
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return issues
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
...Object.entries(duplicatesAmong(allContaineds(language), idOf))
|
|
86
|
+
.flatMap(
|
|
87
|
+
([id, ts]) => ts.map(
|
|
88
|
+
(t) => ({ location: t, message: `Multiple (nested) language elements with the same ID "${id}" exist in this language`, secondaries: ts.filter((otherT) => t !== otherT) })
|
|
89
|
+
)
|
|
90
|
+
),
|
|
91
|
+
...Object.entries(duplicatesAmong(namedsOf(language), keyOf)) // all M3Concept-s that are INamed are also IKeyed
|
|
92
|
+
.flatMap(
|
|
93
|
+
([key, ts]) => ts.map(
|
|
94
|
+
(t) => ({ location: t, message: `Multiple (nested) language elements with the same key "${key}" exist in this language`, secondaries: ts.filter((otherT) => t !== otherT) })
|
|
95
|
+
)
|
|
96
|
+
),
|
|
97
|
+
...Object.entries(duplicatesAmong(namedsOf(language), qualifiedNameOf))
|
|
98
|
+
.flatMap(
|
|
99
|
+
([key, ts]) => ts.map(
|
|
100
|
+
(t) => ({ location: t, message: `Multiple (nested) language elements with the same key "${key}" exist in this language`, secondaries: ts.filter((otherT) => t !== otherT) })
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
// not here: unresolved references are a problem on a lower level
|
|
107
|
+
// TODO (#8) check whether references are resolved
|
|
108
|
+
// Same goes for duplicate IDs, but for completeness' and symmetry's sake, we're also checking it here.
|
|
109
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { LionWebJsonChunk } from "@lionweb/json"
|
|
2
|
+
import { deserializeSerializationChunk } from "../deserializer.js"
|
|
3
|
+
import { nodesExtractorUsing } from "../extraction.js"
|
|
4
|
+
import { defaultSimplisticHandler, SimplisticHandler } from "../handler.js"
|
|
5
|
+
import { BuiltinPropertyValueDeserializer, lioncoreBuiltins } from "./builtins.js"
|
|
6
|
+
import { lioncoreReader, lioncoreWriter } from "./facade.js"
|
|
7
|
+
import { lioncore } from "./lioncore.js"
|
|
8
|
+
import { Language } from "./types.js"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Deserializes languages that have been serialized into the LionWeb serialization JSON format
|
|
13
|
+
* as an instance of the LionCore metametamodel, using {@link _M3Concept these type definitions}.
|
|
14
|
+
*/
|
|
15
|
+
export const deserializeLanguages = (serializationChunk: LionWebJsonChunk, ...dependentLanguages: Language[]): Language[] =>
|
|
16
|
+
deserializeLanguagesWithHandler(serializationChunk, defaultSimplisticHandler, ...dependentLanguages)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Deserializes languages that have been serialized into the LionWeb serialization JSON format
|
|
20
|
+
* as an instance of the LionCore metametamodel, using {@link _M3Concept these type definitions}.
|
|
21
|
+
* This function takes a handler to be able to see what problems occurred.
|
|
22
|
+
*/
|
|
23
|
+
export const deserializeLanguagesWithHandler = (
|
|
24
|
+
serializationChunk: LionWebJsonChunk,
|
|
25
|
+
handler: SimplisticHandler,
|
|
26
|
+
...dependentLanguages: Language[]
|
|
27
|
+
): Language[] =>
|
|
28
|
+
deserializeSerializationChunk(
|
|
29
|
+
serializationChunk,
|
|
30
|
+
lioncoreWriter,
|
|
31
|
+
[lioncore, ...dependentLanguages],
|
|
32
|
+
[lioncoreBuiltins, ...dependentLanguages].flatMap(nodesExtractorUsing(lioncoreReader)),
|
|
33
|
+
new BuiltinPropertyValueDeserializer(),
|
|
34
|
+
handler
|
|
35
|
+
)
|
|
36
|
+
.filter((rootNode) => rootNode instanceof Language)
|
|
37
|
+
.map((language) => (language as Language).dependingOn(...dependentLanguages))
|
|
38
|
+
|
package/src/m3/facade.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { builtinFeatures } from "./builtins.js"
|
|
2
|
+
import { metaTypedBasedClassifierDeducerFor, qualifiedNameOf } from "./functions.js"
|
|
3
|
+
import { lioncore, metaConcepts, metaFeatures } from "./lioncore.js"
|
|
4
|
+
import { Reader } from "../reading.js"
|
|
5
|
+
import {
|
|
6
|
+
Annotation,
|
|
7
|
+
Classifier,
|
|
8
|
+
Concept,
|
|
9
|
+
Containment,
|
|
10
|
+
Enumeration,
|
|
11
|
+
EnumerationLiteral,
|
|
12
|
+
Interface,
|
|
13
|
+
Language,
|
|
14
|
+
M3Concept,
|
|
15
|
+
PrimitiveType,
|
|
16
|
+
Property,
|
|
17
|
+
Reference
|
|
18
|
+
} from "./types.js"
|
|
19
|
+
import { updateSettingsNameBased, Writer } from "../writing.js"
|
|
20
|
+
|
|
21
|
+
const { inamed_name } = builtinFeatures
|
|
22
|
+
const { ikeyed_key } = metaFeatures
|
|
23
|
+
|
|
24
|
+
export const lioncoreReader: Reader<M3Concept> = {
|
|
25
|
+
classifierOf: metaTypedBasedClassifierDeducerFor(lioncore),
|
|
26
|
+
getFeatureValue: (node, feature) =>
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
(node as any)[feature.name], // (mirrors name-based update of settings)
|
|
29
|
+
enumerationLiteralFrom: (value, _) => value as EnumerationLiteral | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Alias for {@link lioncoreReader}, kept for backward compatibility, and to be deprecated and removed later.
|
|
34
|
+
*/
|
|
35
|
+
export const lioncoreExtractionFacade = lioncoreReader
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @return An implementation of {@link Writer} for instances of the LionCore M3 (so M2s).
|
|
39
|
+
*/
|
|
40
|
+
export const lioncoreWriter: Writer<M3Concept> = {
|
|
41
|
+
nodeFor: (parent, classifier, id, propertySettings) => {
|
|
42
|
+
switch (classifier.key) {
|
|
43
|
+
case metaConcepts.annotation.key:
|
|
44
|
+
return new Annotation(
|
|
45
|
+
parent as Language,
|
|
46
|
+
propertySettings[inamed_name.key] as string,
|
|
47
|
+
propertySettings[ikeyed_key.key] as string,
|
|
48
|
+
id
|
|
49
|
+
)
|
|
50
|
+
case metaConcepts.concept.key:
|
|
51
|
+
return new Concept(
|
|
52
|
+
parent as Language,
|
|
53
|
+
propertySettings[inamed_name.key] as string,
|
|
54
|
+
propertySettings[ikeyed_key.key] as string,
|
|
55
|
+
id,
|
|
56
|
+
propertySettings[metaFeatures.concept_abstract.key] as boolean
|
|
57
|
+
)
|
|
58
|
+
case metaConcepts.interface.key:
|
|
59
|
+
return new Interface(
|
|
60
|
+
parent as Language,
|
|
61
|
+
propertySettings[inamed_name.key] as string,
|
|
62
|
+
propertySettings[ikeyed_key.key] as string,
|
|
63
|
+
id
|
|
64
|
+
)
|
|
65
|
+
case metaConcepts.containment.key:
|
|
66
|
+
return new Containment(
|
|
67
|
+
parent as Classifier,
|
|
68
|
+
propertySettings[inamed_name.key] as string,
|
|
69
|
+
propertySettings[ikeyed_key.key] as string,
|
|
70
|
+
id
|
|
71
|
+
)
|
|
72
|
+
case metaConcepts.enumeration.key:
|
|
73
|
+
return new Enumeration(
|
|
74
|
+
parent as Language,
|
|
75
|
+
propertySettings[inamed_name.key] as string,
|
|
76
|
+
propertySettings[ikeyed_key.key] as string,
|
|
77
|
+
id
|
|
78
|
+
)
|
|
79
|
+
case metaConcepts.enumerationLiteral.key:
|
|
80
|
+
return new EnumerationLiteral(
|
|
81
|
+
parent as Enumeration,
|
|
82
|
+
propertySettings[inamed_name.key] as string,
|
|
83
|
+
propertySettings[ikeyed_key.key] as string,
|
|
84
|
+
id
|
|
85
|
+
)
|
|
86
|
+
case metaConcepts.language.key:
|
|
87
|
+
return new Language(
|
|
88
|
+
propertySettings[inamed_name.key] as string,
|
|
89
|
+
propertySettings[metaFeatures.language_version.key] as string,
|
|
90
|
+
id,
|
|
91
|
+
propertySettings[metaFeatures.ikeyed_key.key] as string
|
|
92
|
+
)
|
|
93
|
+
case metaConcepts.primitiveType.key:
|
|
94
|
+
return new PrimitiveType(
|
|
95
|
+
parent as Language,
|
|
96
|
+
propertySettings[inamed_name.key] as string,
|
|
97
|
+
propertySettings[ikeyed_key.key] as string,
|
|
98
|
+
id
|
|
99
|
+
)
|
|
100
|
+
case metaConcepts.property.key:
|
|
101
|
+
return new Property(
|
|
102
|
+
parent as Classifier,
|
|
103
|
+
propertySettings[inamed_name.key] as string,
|
|
104
|
+
propertySettings[ikeyed_key.key] as string,
|
|
105
|
+
id
|
|
106
|
+
)
|
|
107
|
+
case metaConcepts.reference.key:
|
|
108
|
+
return new Reference(
|
|
109
|
+
parent as Classifier,
|
|
110
|
+
propertySettings[inamed_name.key] as string,
|
|
111
|
+
propertySettings[ikeyed_key.key] as string,
|
|
112
|
+
id
|
|
113
|
+
)
|
|
114
|
+
default:
|
|
115
|
+
throw new Error(
|
|
116
|
+
`don't know a node of concept ${qualifiedNameOf(classifier)} with key ${classifier.key} that's not in LionCore M3`
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
setFeatureValue: (node, feature, value) => {
|
|
121
|
+
updateSettingsNameBased(node as unknown as Record<string, unknown>, feature, value)
|
|
122
|
+
},
|
|
123
|
+
encodingOf: literal => literal
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Alias for {@link lioncoreWriter}, kept for backward compatibility, and to be deprecated and removed later.
|
|
128
|
+
*/
|
|
129
|
+
export const lioncoreInstantationFacade = lioncoreWriter
|
|
130
|
+
|