Skip to content

Commit

Permalink
fix Ecore importer for enumerations
Browse files Browse the repository at this point in the history
  + fix serializer for enum literals
  • Loading branch information
dslmeinte committed Aug 31, 2023
1 parent 0080d7a commit a1835ce
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 57 deletions.
2 changes: 1 addition & 1 deletion models/instance/library.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"version": "1",
"key": "type"
},
"value": "1"
"value": "Special"
}
],
"children": [],
Expand Down
14 changes: 9 additions & 5 deletions src-test/library.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Node} from "../src/types.ts"
import {hashingIdGen} from "../src/id-generation.ts"
import {ModelAPI, updateSettings} from "../src/api.ts"
import {nameBasedConceptDeducerFor} from "../src/m3/functions.ts"
import {libraryLanguage} from "./m3/library-language.ts"
import {Enumeration, Node, Property} from "../src/mod.ts"


export enum BookType {
Expand Down Expand Up @@ -45,7 +45,14 @@ export type SpecialistBookWriter = Writer & BaseNode & {

export const libraryModelApi: ModelAPI<BaseNode> = {
conceptOf: (node) => nameBasedConceptDeducerFor(libraryLanguage)(node.concept),
getFeatureValue: (node, feature) => (node as any)[feature.name],
getFeatureValue: (node, feature) => {
const value = (node as any)[feature.name]
// (hateful necessity because of the nature of enumerations in TypeScript:)
if (feature instanceof Property && feature.type instanceof Enumeration && feature.type.name === "BookType") {
return BookType[value]
}
return value
},
nodeFor: (_parent, concept, id, _settings) => ({
id,
concept: concept.name
Expand Down Expand Up @@ -86,6 +93,3 @@ export const libraryModel: BaseNode[] = [
jackLondon
]

// TODO instantiate exact same library as Federico?


7 changes: 2 additions & 5 deletions src-test/m3/ecore/importer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import {serializeLanguage} from "../../../src/m3/serializer.ts"
import {EcoreXml} from "../../../src-utils/m3/ecore/types.ts"
import {issuesLanguage} from "../../../src/m3/constraints.ts"
import {checkReferences} from "../../../src/m3/reference-checker.ts"
import {generatePlantUmlForLanguage} from "../../../src-utils/m3/diagrams/PlantUML-generator.ts"
import {generateMermaidForLanguage} from "../../../src-utils/m3/diagrams/Mermaid-generator.ts"
import {logIssues, logUnresolvedReferences, undefinedValuesDeletedFrom} from "../../utils/test-helpers.ts"
import {libraryLanguage} from "../library-language.ts"
import {sortSerialization} from "../../../src-utils/serialization-utils.ts"


/**
Expand All @@ -31,9 +30,7 @@ Deno.test("Ecore importer", async (tctx) => {
logIssues(issues)
assertEquals(issues, [])
const serialization = serializeLanguage(language)
assertEquals(undefinedValuesDeletedFrom(serialization), undefinedValuesDeletedFrom(serializeLanguage(libraryLanguage)))
assertEquals(generatePlantUmlForLanguage(language), generatePlantUmlForLanguage(libraryLanguage))
assertEquals(generateMermaidForLanguage(language), generateMermaidForLanguage(libraryLanguage))
assertEquals(sortSerialization(undefinedValuesDeletedFrom(serialization)), sortSerialization(undefinedValuesDeletedFrom(serializeLanguage(libraryLanguage))))
})

})
Expand Down
80 changes: 53 additions & 27 deletions src-utils/m3/ecore/importer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import {Classifier, Concept, Feature, Language, LanguageEntity, PrimitiveType} from "../../../src/m3/types.ts"
import {
Classifier,
Concept,
Enumeration,
Feature,
Language,
LanguageEntity,
PrimitiveType
} from "../../../src/m3/types.ts"
import {LanguageFactory} from "../../../src/m3/factory.ts"
import {
checkDefinedData,
Expand All @@ -9,13 +17,16 @@ import {
wrapIdGen
} from "../../../src/id-generation.ts"
import {EClassifier, EcoreXml, EStructuralFeature} from "./types.ts"
import {ConceptType, keyOf, namedsOf, qualifiedNameOf} from "../../../src/m3/functions.ts"
import {keyOf, namedsOf, qualifiedNameOf} from "../../../src/m3/functions.ts"
import {builtinPrimitives} from "../../../src/m3/builtins.ts"
import {duplicatesAmong} from "../../../src/utils/grouping.ts"
import {asArray} from "../../../src/utils/array-helpers.ts"


const localRefPrefix = "#//"
/**
* "Dereferences" a type descriptor string by removing the optional <pre>"#//"</pre> prefix.
*/
const deref = (typeDescriptor: string): string =>
typeDescriptor.startsWith(localRefPrefix)
? typeDescriptor.substring(localRefPrefix.length)
Expand All @@ -24,6 +35,14 @@ const deref = (typeDescriptor: string): string =>

const {booleanDatatype, integerDatatype, stringDatatype} = builtinPrimitives

const typeDesc2primitiveType: Record<string, PrimitiveType> = {
"ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString": stringDatatype,
"ecore:EDataType http://www.eclipse.org/emf/2003/XMLType#//String": stringDatatype,
"ecore:EDataType http://www.eclipse.org/emf/2003/XMLType#//Boolean": booleanDatatype,
"ecore:EDataType http://www.eclipse.org/emf/2003/XMLType#//Int": integerDatatype
}


/**
* Converts a parsed Ecore XML metamodel (file) to a {@link Language LIonCore/M3 instance}.
*/
Expand All @@ -46,9 +65,17 @@ export const asLIonCoreLanguage = (ecoreXml: EcoreXml, version: string): Languag

// phase 1: convert EClassifiers but without their EStructuralFeatures (in the case of EClasses)

const convertEClassifier = (eClassifier: EClassifier): ConceptType =>
factory.concept(eClassifier["@name"], false)
// TODO (#10) ConceptInterface, Enumeration
const convertEClassifier = (eClassifier: EClassifier): LanguageEntity => {
const xsiType = eClassifier["@xsi:type"]
if (xsiType === "ecore:EClass") {
return factory.concept(eClassifier["@name"], false)
}
if (xsiType === "ecore:EEnum") {
return factory.enumeration(eClassifier["@name"])
}
throw new Error(`don't know how to convert an EClassifier with descriptor "${xsiType}"`)
}
// TODO (#10) ConceptInterface?

const convertedEClassifiers: [eClassifier: EClassifier, element: LanguageEntity][] =
ePackage["eClassifiers"]
Expand All @@ -63,27 +90,18 @@ export const asLIonCoreLanguage = (ecoreXml: EcoreXml, version: string): Languag

// phase 2: also convert features of EClasses

const convertEDataType = (eDataType: string): PrimitiveType => {
switch (eDataType) {
case "ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString":
case "ecore:EDataType http://www.eclipse.org/emf/2003/XMLType#//String":
return stringDatatype
case "ecore:EDataType http://www.eclipse.org/emf/2003/XMLType#//Boolean":
return booleanDatatype
case "ecore:EDataType http://www.eclipse.org/emf/2003/XMLType#//Int":
return integerDatatype
default:
throw new Error(`don't know what to convert this EDataType ref. descriptor to: ${eDataType}`)
}
}

const convertEStructuralFeature = (container: Classifier, feature: EStructuralFeature): Feature => {
const metaType = feature["@xsi:type"]
const name = feature["@name"]
switch (metaType) {
case "ecore:EAttribute": {
const typeDesc = feature["@eType"]
const property = factory.property(container, name)
.ofType(convertEDataType(feature["@eType"]))
.ofType(
typeDesc in typeDesc2primitiveType
? typeDesc2primitiveType[typeDesc]
: eClassifierConversionFor(deref(typeDesc))
)
if (feature["@lowerBound"] === "0") {
property.isOptional()
}
Expand All @@ -110,20 +128,28 @@ export const asLIonCoreLanguage = (ecoreXml: EcoreXml, version: string): Languag
}

convertedEClassifiers.forEach(([source, target]) => {
if (source["@xsi:type"] === "ecore:EClass") {
const eClass = source
const container = target as Classifier
container
const xsiType = source["@xsi:type"]
if (xsiType === "ecore:EClass") {
const classifier = target as Classifier
classifier
.havingFeatures(
...asArray(source.eStructuralFeatures)
.map((feature) =>
convertEStructuralFeature(container, feature)
convertEStructuralFeature(classifier, feature)
)
)
if (eClass["@eSuperTypes"] !== undefined) {
(target as Concept).extends = eClassifierConversionFor(deref(eClass["@eSuperTypes"])) as Concept
if (source["@eSuperTypes"] !== undefined) {
(target as Concept).extends = eClassifierConversionFor(deref(source["@eSuperTypes"])) as Concept
}
}
if (xsiType === "ecore:EEnum") {
const classifier = target as Enumeration
classifier
.havingLiterals(
...asArray(source.eLiterals)
.map((literal) => factory.enumerationLiteral(classifier, literal["@name"]))
)
}
})


Expand Down
9 changes: 8 additions & 1 deletion src-utils/m3/ecore/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export type EcorePackage = ENamed & {
"eClassifiers": EClassifier[]
}

export type EClassifier = EClass
export type EClassifier = EClass | EEnum

export type EClass = ENamed & {
"@xsi:type": "ecore:EClass"
Expand All @@ -52,3 +52,10 @@ export type EReference = ENamed & {
"@containment": boolean
}

export type EEnum = ENamed & {
"@xsi:type": "ecore:EEnum"
"eLiterals"?: AnyNumberOf<EEnumLiteral>
}

export type EEnumLiteral = ENamed

4 changes: 3 additions & 1 deletion src/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ export const deserializeModel = <NT extends Node>(
.find((element) =>
element instanceof Concept && element.key === conceptMetaPointer.key
) as (Concept | undefined)
// TODO replace with idBasedConceptDeducer as soon as that can return undefined (without throwing an Error)

if (concept === undefined) {
throw new Error(`can't deserialize a node having concept with ID "${conceptMetaPointer.key}"`)
Expand All @@ -97,12 +96,15 @@ export const deserializeModel = <NT extends Node>(
const value = serializedPropertiesPerKey[property.key][0].value
if (property.type instanceof PrimitiveType) {
settings[property.key] = deserializeBuiltin(value, property as Property)
return
}
if (property.type instanceof Enumeration) {
const literal = property.type.literals.find((literal) => literal.key = value)
if (literal !== undefined) {
settings[property.key] = literal
// FIXME literal is now of type EnumerationLiteral...from M3, so typically shouldn't end up in an M1 (-- only works for M2s)
}
return
}
// (property is not handled, because neither a primitive type nor of enumeration type)
}
Expand Down
26 changes: 9 additions & 17 deletions src/serializer.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import {ConceptDeducer as _ConceptDeducer, ModelAPI} from "./api.ts"
import {ModelAPI} from "./api.ts"
import {MetaPointer, SerializationChunk, SerializedNode} from "./serialization.ts"
import {asIds} from "./functions.ts"
import {Node} from "./types.ts"
import {
Containment,
Enumeration,
EnumerationLiteral,
isINamed,
Language,
PrimitiveType,
Property,
Reference
} from "./m3/types.ts"
import {Containment, Enumeration, isINamed, Language, PrimitiveType, Property, Reference} from "./m3/types.ts"
import {allFeaturesOf} from "./m3/functions.ts"
import {asArray} from "./utils/array-helpers.ts"
import {BuiltinPrimitive, lioncoreBuiltins, serializeBuiltin} from "./m3/builtins.ts"
Expand Down Expand Up @@ -71,15 +62,16 @@ export const serializeNodes = <NT extends Node>(nodes: NT[], api: ModelAPI<NT>):
return serializeBuiltin(value as BuiltinPrimitive)
}
if (feature.type instanceof Enumeration) {
return (value as EnumerationLiteral).key
return value as string // value is the key of an EnumerationLiteral
}
return null
})()
if (encodedValue !== null)
serializedNode.properties.push({
property: featureMetaPointer,
value: encodedValue
})
if (encodedValue !== null) {
serializedNode.properties.push({
property: featureMetaPointer,
value: encodedValue
})
}
return
}
if (feature instanceof Containment) {
Expand Down

0 comments on commit a1835ce

Please sign in to comment.