@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/deserializer.d.ts +2 -2
  3. package/dist/deserializer.d.ts.map +1 -1
  4. package/dist/deserializer.js +7 -10
  5. package/dist/deserializer.js.map +1 -1
  6. package/dist/extraction.d.ts.map +1 -1
  7. package/dist/functions.d.ts.map +1 -1
  8. package/dist/handler.d.ts +1 -1
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +0 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/m1/reference-utils.d.ts +1 -1
  14. package/dist/m1/reference-utils.d.ts.map +1 -1
  15. package/dist/m3/builtins.d.ts.map +1 -1
  16. package/dist/m3/builtins.js +2 -1
  17. package/dist/m3/builtins.js.map +1 -1
  18. package/dist/m3/constraints.d.ts.map +1 -1
  19. package/dist/m3/constraints.js +2 -2
  20. package/dist/m3/constraints.js.map +1 -1
  21. package/dist/m3/deserializer.d.ts.map +1 -1
  22. package/dist/m3/feature-resolvers.d.ts +23 -0
  23. package/dist/m3/feature-resolvers.d.ts.map +1 -0
  24. package/dist/m3/feature-resolvers.js +32 -0
  25. package/dist/m3/feature-resolvers.js.map +1 -0
  26. package/dist/m3/functions.d.ts +17 -1
  27. package/dist/m3/functions.d.ts.map +1 -1
  28. package/dist/m3/functions.js +21 -5
  29. package/dist/m3/functions.js.map +1 -1
  30. package/dist/m3/index.d.ts +2 -0
  31. package/dist/m3/index.d.ts.map +1 -1
  32. package/dist/m3/index.js +2 -0
  33. package/dist/m3/index.js.map +1 -1
  34. package/dist/m3/reference-checker.d.ts.map +1 -1
  35. package/dist/m3/serializer.d.ts.map +1 -1
  36. package/dist/{symbol-table.d.ts → m3/symbol-table.d.ts} +9 -14
  37. package/dist/m3/symbol-table.d.ts.map +1 -0
  38. package/dist/m3/symbol-table.js +38 -0
  39. package/dist/m3/symbol-table.js.map +1 -0
  40. package/dist/m3/types.d.ts.map +1 -1
  41. package/dist/references.d.ts +1 -1
  42. package/dist/references.d.ts.map +1 -1
  43. package/dist/serializer.d.ts.map +1 -1
  44. package/dist/serializer.js +19 -9
  45. package/dist/serializer.js.map +1 -1
  46. package/package.json +31 -31
  47. package/src/deserializer.ts +224 -0
  48. package/src/dynamic-facade.ts +63 -0
  49. package/src/extraction.ts +31 -0
  50. package/src/functions.ts +28 -0
  51. package/src/handler.ts +57 -0
  52. package/src/index.ts +13 -0
  53. package/src/m1/reference-utils.ts +106 -0
  54. package/src/m3/README.md +16 -0
  55. package/src/m3/builtins.ts +170 -0
  56. package/src/m3/constraints.ts +109 -0
  57. package/src/m3/deserializer.ts +38 -0
  58. package/src/m3/facade.ts +130 -0
  59. package/src/m3/factory.ts +98 -0
  60. package/src/m3/feature-resolvers.ts +55 -0
  61. package/src/m3/functions.ts +379 -0
  62. package/src/m3/index.ts +12 -0
  63. package/src/m3/lioncore.ts +139 -0
  64. package/src/m3/reference-checker.ts +38 -0
  65. package/src/m3/serializer.ts +13 -0
  66. package/src/m3/symbol-table.ts +119 -0
  67. package/src/m3/types.ts +325 -0
  68. package/src/reading.ts +55 -0
  69. package/src/references.ts +31 -0
  70. package/src/serializer.ts +244 -0
  71. package/src/types.ts +11 -0
  72. package/src/version.ts +5 -0
  73. package/src/writing.ts +79 -0
  74. package/dist/symbol-table.d.ts.map +0 -1
  75. package/dist/symbol-table.js +0 -66
  76. 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
+
@@ -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
+
@@ -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
+