Skip to content

Commit

Permalink
fix issue #143 by introducing an explicit meta type on M3 types
Browse files Browse the repository at this point in the history
  • Loading branch information
dslmeinte committed Mar 27, 2024
1 parent ce1a034 commit fe16c60
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 9 deletions.
2 changes: 2 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ The following is a list of links to potential starting points:
* The deserializer now deserializes all nodes in the serialization chunk, not just the root nodes (identified as having `parent` set to `null`).
(This fixes [issue #145](https://github.com/LionWeb-io/lionweb-typescript/issues/145).)
* Also: `deserializeChunk` is renamed to `deserializeSerializationChunk` for naming consistency, although `deserializeChunk` is retained as an alias.
* Add a method `metaType` to M3 types, to deduce classifiers' names from.
(That fixes [issue #143](https://github.com/LionWeb-io/lionweb-typescript/issues/143).)

### 0.6.2

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/m3/facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ import {
} from "./types.js"
import {builtinFeatures} from "./builtins.js"
import {lioncore, metaConcepts, metaFeatures} from "./lioncore.js"
import {classBasedClassifierDeducerFor, qualifiedNameOf} from "./functions.js"
import {metaTypedBasedClassifierDeducerFor, qualifiedNameOf} from "./functions.js"


const {inamed_name} = builtinFeatures
const {ikeyed_key} = metaFeatures


export const lioncoreExtractionFacade: ExtractionFacade<M3Concept> = ({
classifierOf: classBasedClassifierDeducerFor(lioncore),
classifierOf: metaTypedBasedClassifierDeducerFor(lioncore),
getFeatureValue: (node, feature) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(node as any)[feature.name], // (mirrors name-based update of settings)
Expand Down
18 changes: 14 additions & 4 deletions packages/core/src/m3/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Enumeration,
Feature,
IKeyed,
IMetaTyped,
INamed,
Interface,
isINamed,
Expand Down Expand Up @@ -251,11 +252,19 @@ const nameBasedClassifierDeducerFor = (language: Language) =>
/**
* @return a {@link ClassifierDeducer classifier deducer} that deduces the classifier of nodes by looking up
* the classifier in the given {@link Language language} by matching the node object's class name to classifiers' names.
* **Note** that this is not reliable when using bundlers who might minimize class names, and such.
*/
const classBasedClassifierDeducerFor = <NT extends Node>(language: Language): ClassifierDeducer<NT> => {
const deducer = nameBasedClassifierDeducerFor(language)
return (node: NT) => deducer(node.constructor.name)
}
const classBasedClassifierDeducerFor = <NT extends Node>(language: Language): ClassifierDeducer<NT> =>
(node: NT) => nameBasedClassifierDeducerFor(language)(node.constructor.name)


/**
* @return a {@link ClassifierDeducer classifier deducer} that deduces the classifier of nodes that implement {@link IMetaTyped}
* by looking up the classifier in the given {@link Language language} by matching the result of {@link IMetaTyped#metaType}
* to classifiers' names.
*/
const metaTypedBasedClassifierDeducerFor = <NT extends Node & IMetaTyped>(language: Language): ClassifierDeducer<NT> =>
(node: NT) => nameBasedClassifierDeducerFor(language)(node.metaType())


/**
Expand Down Expand Up @@ -287,6 +296,7 @@ export {
isProperty,
isReference,
keyOf,
metaTypedBasedClassifierDeducerFor,
nameBasedClassifierDeducerFor,
nameOf,
namedsOf,
Expand Down
51 changes: 48 additions & 3 deletions packages/core/src/m3/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,28 @@ const isIKeyed = (node: Node): node is IKeyed =>
isINamed(node) && "key" in node && typeof node.key === "string"


/**
* An interface with one method to return a meta type,
* independent of the class's name obtained through `<node>.constructor.name`,
* which may be brittle when using bundlers.
*/
interface IMetaTyped {
metaType(): string
}

/**
* Abstract base class for nodes in an LionCore instance,
* providing an ID, a key, and the containment hierarchy.
*/
abstract class M3Node implements IKeyed {
abstract class M3Node implements IKeyed, IMetaTyped {
metaType(): string {
throw new Error("#metaType() not implemented")
}
parent?: M3Node
/*
* Note: every parent in an M2 (i.e., a Language, Concept, Interface, Enumeration) implements IKeyed.
* Because that's just an interface and is implemented by {@link M3Node}.
* Because that's just an interface and is implemented by {@link M3Node},
* we can type parent as M3Node?.
*/
readonly id: Id
name: string
Expand Down Expand Up @@ -81,6 +94,9 @@ abstract class Feature extends M3Node {
}

class Property extends Feature {
metaType(): string {
return "Property"
}
type: SingleRef<Datatype> = unresolved // (reference)
ofType(type: Datatype): Property {
this.type = type
Expand All @@ -102,9 +118,15 @@ abstract class Link extends Feature {
}

class Containment extends Link {
metaType(): string {
return "Containment"
}
}

class Reference extends Link {
metaType(): string {
return "Reference"
}
}

abstract class LanguageEntity extends M3Node {
Expand Down Expand Up @@ -134,6 +156,9 @@ abstract class Classifier extends LanguageEntity {
}

class Concept extends Classifier {
metaType(): string {
return "Concept"
}
abstract: boolean
partition: boolean
extends?: SingleRef<Concept> // (reference)
Expand All @@ -156,6 +181,9 @@ class Concept extends Classifier {
}

class Annotation extends Classifier {
metaType(): string {
return "Annotation"
}
extends?: SingleRef<Annotation> // (reference)
implements: MultiRef<Interface> = [] // (reference)
annotates: SingleRef<Classifier> = unresolved // (reference)
Expand All @@ -175,6 +203,9 @@ class Annotation extends Classifier {
}

class Interface extends Classifier {
metaType(): string {
return "Interface"
}
extends: MultiRef<Interface> = [] // (reference)
extending(...interfaces: Interface[]): Interface {
// TODO check actual types of interfaces, or use type shapes/interfaces
Expand All @@ -185,9 +216,16 @@ class Interface extends Classifier {

abstract class Datatype extends LanguageEntity {}

class PrimitiveType extends Datatype {}
class PrimitiveType extends Datatype {
metaType(): string {
return "PrimitiveType"
}
}

class Enumeration extends Datatype {
metaType(): string {
return "Enumeration"
}
literals: EnumerationLiteral[] = [] // (containment)
havingLiterals(...literals: EnumerationLiteral[]) {
this.literals.push(...literals)
Expand All @@ -196,6 +234,9 @@ class Enumeration extends Datatype {
}

class EnumerationLiteral extends M3Node {
metaType(): string {
return "EnumerationLiteral"
}
constructor(enumeration: Enumeration, name: string, key: string, id: Id) {
super(id, name, key, enumeration)
}
Expand All @@ -205,6 +246,9 @@ class EnumerationLiteral extends M3Node {
}

class Language extends M3Node {
metaType(): string {
return "Language"
}
version: string
entities: LanguageEntity[] = [] // (containment)
dependsOn: MultiRef<Language> = [] // special (!) reference
Expand Down Expand Up @@ -277,6 +321,7 @@ export {

export type {
IKeyed,
IMetaTyped,
INamed,
M3Concept,
M3Node
Expand Down
31 changes: 31 additions & 0 deletions packages/test/src/languages/meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { builtinPrimitives, concatenator, LanguageFactory, lastOf } from "@lionweb/core"

const factory = new LanguageFactory(
"meta",
"1",
concatenator("-"),
lastOf
)

/**
* A "meta" language in which every instance of a meta concept has name "{A|a}[n]<name of meta concept>".
* This is useful to test {@link ClassifierDeducer classifier deducers} with.
*/
export const metaLanguage = factory.language

const anAnnotation = factory.annotation("AnAnnotation")

const aConcept = factory.concept("AConcept", false)
const aProperty = factory.property(aConcept, "aProperty").ofType(builtinPrimitives.jsonDatatype)
const aContainment = factory.containment(aConcept, "aContainment").ofType(aConcept)
const aReference = factory.reference(aConcept, "aReference").ofType(aConcept)
aConcept.havingFeatures(aProperty, aContainment, aReference)

const anEnumeration = factory.enumeration("AnEnumeration")
const anEnumerationLiteral = factory.enumerationLiteral(anEnumeration, "anEnumerationLiteral")
anEnumeration.havingLiterals(anEnumerationLiteral)

const anInterface = factory.interface("AnInterface")

metaLanguage.havingEntities(anAnnotation, aConcept, anEnumeration, anInterface)

15 changes: 15 additions & 0 deletions packages/test/src/m3/functions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {assert} from "chai"
const {equal} = assert

import {allContaineds} from "@lionweb/core"
import {metaLanguage} from "../languages/meta.js"


describe("meta-typed classifier deducer" , () => {
it("works for M3", () => {
for (const thing of allContaineds(metaLanguage)) {
equal(thing.metaType(), thing.constructor.name)
}
})
})

0 comments on commit fe16c60

Please sign in to comment.