diff --git a/models/instance/library.json b/models/instance/library.json index a69fc5cb..1e6edbe3 100644 --- a/models/instance/library.json +++ b/models/instance/library.json @@ -69,7 +69,7 @@ "version": "1", "key": "type" }, - "value": "1" + "value": "Special" } ], "children": [], diff --git a/src-test/library.ts b/src-test/library.ts index 00b9fd75..fbdb4ba3 100644 --- a/src-test/library.ts +++ b/src-test/library.ts @@ -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 { @@ -45,7 +45,14 @@ export type SpecialistBookWriter = Writer & BaseNode & { export const libraryModelApi: ModelAPI = { 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 @@ -86,6 +93,3 @@ export const libraryModel: BaseNode[] = [ jackLondon ] -// TODO instantiate exact same library as Federico? - - diff --git a/src-test/m3/ecore/importer.test.ts b/src-test/m3/ecore/importer.test.ts index dfb589d0..0ca76787 100644 --- a/src-test/m3/ecore/importer.test.ts +++ b/src-test/m3/ecore/importer.test.ts @@ -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" /** @@ -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)))) }) }) diff --git a/src-utils/m3/ecore/importer.ts b/src-utils/m3/ecore/importer.ts index 9e43cb15..70bb1a21 100644 --- a/src-utils/m3/ecore/importer.ts +++ b/src-utils/m3/ecore/importer.ts @@ -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, @@ -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
"#//"
prefix. + */ const deref = (typeDescriptor: string): string => typeDescriptor.startsWith(localRefPrefix) ? typeDescriptor.substring(localRefPrefix.length) @@ -24,6 +35,14 @@ const deref = (typeDescriptor: string): string => const {booleanDatatype, integerDatatype, stringDatatype} = builtinPrimitives +const typeDesc2primitiveType: Record = { + "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}. */ @@ -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"] @@ -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() } @@ -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"])) + ) + } }) diff --git a/src-utils/m3/ecore/types.ts b/src-utils/m3/ecore/types.ts index 99f7e3aa..caec7c1c 100644 --- a/src-utils/m3/ecore/types.ts +++ b/src-utils/m3/ecore/types.ts @@ -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" @@ -52,3 +52,10 @@ export type EReference = ENamed & { "@containment": boolean } +export type EEnum = ENamed & { + "@xsi:type": "ecore:EEnum" + "eLiterals"?: AnyNumberOf +} + +export type EEnumLiteral = ENamed + diff --git a/src/deserializer.ts b/src/deserializer.ts index 4d5cd63f..98f11197 100644 --- a/src/deserializer.ts +++ b/src/deserializer.ts @@ -76,7 +76,6 @@ export const deserializeModel = ( .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}"`) @@ -97,12 +96,15 @@ export const deserializeModel = ( 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) } diff --git a/src/serializer.ts b/src/serializer.ts index 74f76dc6..e9d1b6ef 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -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" @@ -71,15 +62,16 @@ export const serializeNodes = (nodes: NT[], api: ModelAPI): 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) {