@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,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
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { StringsMapper } from "@lionweb/ts-utils"
|
|
2
|
+
import { SingleRef } from "../references.js"
|
|
3
|
+
import {
|
|
4
|
+
Annotation,
|
|
5
|
+
Classifier,
|
|
6
|
+
Concept,
|
|
7
|
+
Containment,
|
|
8
|
+
Enumeration,
|
|
9
|
+
EnumerationLiteral,
|
|
10
|
+
Interface,
|
|
11
|
+
Language,
|
|
12
|
+
PrimitiveType,
|
|
13
|
+
Property,
|
|
14
|
+
Reference
|
|
15
|
+
} from "./types.js"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A factory that produces a {@link Language} instance,
|
|
20
|
+
* as well as {@link LanguageEntity entities} contained by that instance
|
|
21
|
+
* and {@link Feature features} of {@link Classifier classifiers}
|
|
22
|
+
* and {@link EnumerationLiteral enumeration literals} of {@link Enumeration enumerations}.
|
|
23
|
+
*
|
|
24
|
+
* The factory methods take care of proper containment.
|
|
25
|
+
* *Note:* also calling `havingEntities`, `havingFeatures`, and `havingLiterals` doesn't produce duplicates.
|
|
26
|
+
* (This is to stay backward compatible.)
|
|
27
|
+
*/
|
|
28
|
+
export class LanguageFactory {
|
|
29
|
+
|
|
30
|
+
readonly id: StringsMapper
|
|
31
|
+
readonly key: StringsMapper
|
|
32
|
+
readonly language: Language
|
|
33
|
+
|
|
34
|
+
constructor(name: string, version: string, id: StringsMapper, key: StringsMapper) {
|
|
35
|
+
this.id = id
|
|
36
|
+
this.key = key
|
|
37
|
+
this.language = new Language(name, version, this.id(name), this.key(name))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
annotation(name: string, extends_?: SingleRef<Annotation>): Annotation {
|
|
42
|
+
const annotation = new Annotation(this.language, name, this.key(this.language.name, name), this.id(this.language.name, name), extends_)
|
|
43
|
+
this.language.havingEntities(annotation)
|
|
44
|
+
return annotation
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
concept(name: string, abstract: boolean, extends_?: SingleRef<Concept>): Concept {
|
|
48
|
+
const concept = new Concept(this.language, name, this.key(this.language.name, name), this.id(this.language.name, name), abstract, extends_)
|
|
49
|
+
this.language.havingEntities(concept)
|
|
50
|
+
return concept
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface(name: string): Interface {
|
|
54
|
+
const intface = new Interface(this.language, name, this.key(this.language.name, name), this.id(this.language.name, name))
|
|
55
|
+
this.language.havingEntities(intface)
|
|
56
|
+
return intface
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
enumeration(name: string): Enumeration {
|
|
60
|
+
const enumeration = new Enumeration(this.language, name, this.key(this.language.name, name), this.id(this.language.name, name))
|
|
61
|
+
this.language.havingEntities(enumeration)
|
|
62
|
+
return enumeration
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
primitiveType(name: string): PrimitiveType {
|
|
66
|
+
const primitiveType = new PrimitiveType(this.language, name, this.key(this.language.name, name), this.id(this.language.name, name))
|
|
67
|
+
this.language.havingEntities(primitiveType)
|
|
68
|
+
return primitiveType
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
containment(classifier: Classifier, name: string): Containment {
|
|
73
|
+
const containment = new Containment(classifier, name, this.key(this.language.name, classifier.name, name), this.id(this.language.name, classifier.name, name))
|
|
74
|
+
classifier.havingFeatures(containment)
|
|
75
|
+
return containment
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
property(classifier: Classifier, name: string): Property {
|
|
79
|
+
const property = new Property(classifier, name, this.key(this.language.name, classifier.name, name), this.id(this.language.name, classifier.name, name))
|
|
80
|
+
classifier.havingFeatures(property)
|
|
81
|
+
return property
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
reference(classifier: Classifier, name: string): Reference {
|
|
85
|
+
const reference = new Reference(classifier, name, this.key(this.language.name, classifier.name, name), this.id(this.language.name, classifier.name, name))
|
|
86
|
+
classifier.havingFeatures(reference)
|
|
87
|
+
return reference
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
enumerationLiteral(enumeration: Enumeration, name: string): EnumerationLiteral {
|
|
92
|
+
const enumerationLiteral = new EnumerationLiteral(enumeration, name, this.key(this.language.name, enumeration.name, name), this.id(this.language.name, enumeration.name, name))
|
|
93
|
+
enumeration.havingLiterals(enumerationLiteral)
|
|
94
|
+
return enumerationLiteral
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LionWebJsonMetaPointer } from "@lionweb/json"
|
|
2
|
+
|
|
3
|
+
import { Classifier, Containment, Feature, Language, Property, Reference } from "./types.js"
|
|
4
|
+
import { MemoisingSymbolTable } from "./symbol-table.js"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type def. for functions that resolve a {@link Feature feature} of the indicated sub-type
|
|
9
|
+
* from a feature's and a classifier's meta-pointers,
|
|
10
|
+
* throwing an {@link Error error} when the feature couldn't be resolved,
|
|
11
|
+
* or it isn't of the expected sub-type.
|
|
12
|
+
*/
|
|
13
|
+
export type FeatureResolver<FT extends Feature> = (metaPointer: LionWebJsonMetaPointer, classifier: Classifier) => FT
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Type def. for an object containing {@link FeatureResolver resolvers} for
|
|
17
|
+
* {@link Property properties}, {@link Containmnent containments}, and {@link Reference references}.
|
|
18
|
+
*/
|
|
19
|
+
export type FeatureResolvers = {
|
|
20
|
+
resolvedPropertyFrom: FeatureResolver<Property>
|
|
21
|
+
resolvedContainmentFrom: FeatureResolver<Containment>
|
|
22
|
+
resolvedReferenceFrom: FeatureResolver<Reference>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @return an {@link FeatureResolvers object} for the given {@link Language languages}.
|
|
27
|
+
*/
|
|
28
|
+
export const featureResolversFor = (languages: Language[]): FeatureResolvers => {
|
|
29
|
+
const symbolTable = new MemoisingSymbolTable(languages)
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
const featureResolverFor = <FT extends Feature>(featureClassConstructor: new (...args: any[]) => FT): FeatureResolver<FT> =>
|
|
32
|
+
(featureMetaPointer, classifier: Classifier) => {
|
|
33
|
+
const classifierMetaPointer = classifier.metaPointer()
|
|
34
|
+
const featureLocationMessage = () => `feature with meta-pointer ${JSON.stringify(featureMetaPointer)} on classifier with meta-pointer ${JSON.stringify(classifierMetaPointer)}`
|
|
35
|
+
const feature = symbolTable.featureMatching(classifierMetaPointer, featureMetaPointer)
|
|
36
|
+
if (feature === undefined) {
|
|
37
|
+
throw new Error(`couldn't resolve ${featureLocationMessage()}`) // fail early <== unrecoverable
|
|
38
|
+
}
|
|
39
|
+
if (feature.constructor !== featureClassConstructor) { // feature's type must match desired type *exactly* — cheaper to evaluate than "feature instanceof featureClassConstructor"
|
|
40
|
+
throw new Error(`${featureLocationMessage()} is not a ${featureClassConstructor.name} but a ${feature.constructor.name}`) // fail early <== unrecoverable
|
|
41
|
+
}
|
|
42
|
+
/*
|
|
43
|
+
* We could make this function memoising as well, to avoid having to perform these checks every resolution.
|
|
44
|
+
* That memoisation would involve a 6-deep lookup, in the feature's meta-pointer + container's classifier meta-pointer.
|
|
45
|
+
* The checks seem cheap enough to not be problematic performance-wise, though.
|
|
46
|
+
*/
|
|
47
|
+
return feature as FT // valid <== feature.constructor === featureClassConstructor
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
resolvedPropertyFrom: featureResolverFor(Property),
|
|
51
|
+
resolvedContainmentFrom: featureResolverFor(Containment),
|
|
52
|
+
resolvedReferenceFrom: featureResolverFor(Reference)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|