Skip to content

Latest commit

 

History

History
480 lines (354 loc) · 23.8 KB

lionweb-mps-design.adoc

File metadata and controls

480 lines (354 loc) · 23.8 KB

Technical Design of LionWeb-MPS

1. High-level Facades

MPS Language ↔ LionCore M3
MPS Language → JSON
MPS Instance Model ↔ JSON

2. Design Principles

No static access

We should not have any static method calls inside the core logic. Every required interface should be a constructor parameter.

Rationale: Simplifies testing, and potentially the compatibility to different LionWeb versions.

This includes core MPS classes like MetaAdapterFactory — we wrap them in an interface (IMetaAdapterFactoryHelper). Rationale: Uniform code structure.

We accept the trade-off that classes have lots of constructor parameters. As mitigation, we provide facades.

One task per class, for all parts of a language

Each class should perform exactly one task. For example, we split up mapping keys (MpsCompleteKeyMapper) from encoding keys (EncodeToLionWebKeyConverter). Each class performs their single task on all parts of a language, e.g. Concepts, Properties, Enumerations, etc. A lot of these classes implement IKeyMapper

Rationale: Simplifies testing, and allows to combine the tasks in different ways.

We accept the trade-off of deep call stacks (i.e. lots of indirection). As mitigation, we provide wrapper classes that combine several tasks with daisy-chaining (e.g. CompositeGuaranteedMapper) or delegation (e.g. ADelegateKeyMapper) patterns.

Two-phase conversions

We convert representations in two phases: First, we create all required target representations (e.g. Nodes, Concepts, Containments). Afterwards, we link the target representations according to the source links.

The alternative would be to create a target representation, and immediately create+link all linked target representations.

Rationale: We chose the former approach, as the latter is harder to debug, especially in combination with the "single task" principle mentioned above.

We accept the trade-off that we have to hold all target representations in memory.

Index-based default values

We have to deal with default or special values (e.g. built-in primitive types) in every converter. We list them in central types like ILionCoreConstants and IJsonConstants in the same order. In each converter we can get the appropriate two lists and correlate them by index.

Rationale: Index-based mapping is error-prone, but we need every possible combination between representations typically once. Maintaining all of these maps would be more effort, and error-prone than carefully ordered lists.

Example: We list primitive types as

  • List<PrimitiveType>[getBoolean(), getInteger(), getString(), getJSON()] in IJsonConstants.listPrimitiveTypes()

  • List<node<PrimitiveType>>[lcBooleanType(), lcIntegerType(), lcStringType(), lcJsonType()] in ILionCoreConstants.listLcPrimitiveTypes(),

  • List<string>[idBooleanType(), idIntegerType(), idStringType(), idJsonType()] in ILionCoreConstants.listSPrimitiveTypeIds()

Annotate nullability

We use annotations @NotNull and @Nullable on all single-valued return types and parameters of public methods. "single-valued" are all types that are not collections, sequences, iterables, etc.

Rationale: MPS typechecker and generated run-time code helps a lot in dealing with potential null values. Our converters should be robust, so they have to deal with null values at all possible places anyway.

JavaDoc for all classes and public methods

We write technical documentation as JavaDoc on all classes and public methods. We can omit the JavaDoc if and only if the class'/method’s purpose, usage, assumptions, and limitations are clear and obvious from its name, without looking at the implementation. Include comparisons to similar, but slightly different classes/methods.

Rationale: Especially with above’s "single task" principle, lots of classes look very similar. Even after glancing through the code their differences might not be obvious — we need to explain it.

Specific exceptions

Except NullPointerException, we should only use custom specializations of RuntimeException.

Rationale: We want to distinguish between properly handled exceptional situations (e.g. user tries to import instances of non-existent concepts) from potential coding issues (e.g. IndexOutOfBoundException because we mismatched default values).

Test everything

Our converters are riddled with special cases. There only way we can handle them consistently without regressions is very good test coverage.

3. Representations of Languages

M3 language variants
LionCore MPS M3

Module io.lionweb.mps.m3 contains LionCore meta-meta-model as MPS language. Mainly used to test conversion between MPS and LionCore concepts.

LionCore Java M3

Module org.lionweb.lionweb.java contains LionCore meta-meta-model as Java classes. Used to serialize / unserialize LionWeb M2 and M1 models to / from JSON.

Language structure

Module jetbrains.mps.lang.structure contains MPS meta-meta-model as MPS language. Used to define languages inside MPS.

SLanguage interfaces

Model org.jetbrains.mps.openapi.language contains MPS meta-meta-model as Java interfaces. Used by MPS to represent deployed language interfaces.

IImportedLanguages

MPS is very picky how to change already existing languages. This interface separates converting to an MPS language from importing the MPS language.

ExistingImportedLanguage

Applies only changes from the imported language to an already existing MPS language.

ReadonlyImportedLanguage

Represents existing MPS languages that cannot be changed (e.g. because they are part of plugins, or core MPS). Any detected change from the imported language throws an exception.

NewImportedLanguage

Simply creates an imported, not yet existing language.

3.1. Internal representations

MPS represents languages in lots of differnt ways, depending on the context. There’s always more than one way how to get from one representation to the other, but these ways are not equal — especially in case of half-broken languages.

Use MpsLanguageConverter to convert between all representations. It choses the most successful way, and clearly reports success (either by potential @Nullable result or throws IllegalStateException).

Note
The following representations are only used internally in MPS. The diagram shows how to get from one representation to another.
link:mps-language-classes.puml[role=include]
Language class

Model jetbrains.mps.smodel contains a language as module.

SLanguageAdapter class

Model jetbrains.mps.smodel.adapter.structure (and contained models) contain MPS meta-meta-model as Java classes.

LanguageRuntime class

Model jetbrains.mps.smodel.language contains the runtime representation of a language with all its aspects.

SLanguageId class

Model jetbrains.mps.smodel.adapter.ids contains a unique identifier for a language.

4. Available Converters

4.1. M2

LionCore2JsonConverter

Exports LionWeb M2 Languages expressed in MPS language io.lionweb.mps.m3 to LionWeb JSON Languages.

Json2LionCoreConverter

Imports LionWeb JSON Languages to LionWeb M2 Languages expressed in MPS language io.lionweb.mps.m3.

Mps2LionCoreConverter

Converts MPS language structure models to to LionWeb M2 Languages expressed in MPS language io.lionweb.mps.m3. The converted language does not need to be deployed.

LionCore2MpsConverter

Converts LionWeb M2 Languages expressed in MPS language io.lionweb.mps.m3 to AImportedLanguages.

Language2LionCoreConverter

Converts deployed MPS SLanguages to to LionWeb M2 Languages expressed in MPS language io.lionweb.mps.m3.

IndirectLanguage2LionCoreConverter

Converts the transitive closure of deployed MPS SLanguages to LionWeb M2 Languages expressed in MPS language io.lionweb.mps.m3.

Language2JsonConverter

Converts MPS SLanguages to LionWeb JSON Languages. The source of this converter are compiled languages inside MPS.

IndirectLanguage2JsonConverter

Converts the transitive closure of MPS SLanguages to LionWeb JSON Languages.

FineGrainedClosureLanguage2JsonConverter

Converts the minimally required concepts of MPS SLanguages to LionWeb JSON Languages.

Json2LanguageConverter

Converts LionWeb JSON Languages to compiled SLanguages present in MPS. Fails if any part of the source language is not present in MPS.

4.2. M1

LionWeb2MpsConverter

Converts a sequence of SerializedNodes (originating from parsed LionWeb JSON) to MPS SNodes. Handles every SerializedNode as a new MPS SNode. Assumes all used languages are present as built languages in MPS.

MergingLionWeb2MpsConverter

Converts a sequence of SerializedNodes (originating from parsed LionWeb JSON) to MPS SNodes. Merges existing SerializedNode with MPS SNodes if they have the same node id. Assumes all used languages are present as built languages in MPS.

AMps2LionWebConverter

Converts MPS SNodes to LionWeb JSON SerializedNodes. Subclasses decide which nodes besides the input nodes should be processed:

ClosureMps2LionWebConverter

Converts the transitive closure of all MPS SNodes listed in the constructor, all descendants, and all references to LionWeb JSON SerializedNodes.

DescendantMps2LionWebConverter

Converts all MPS SNodes listed in the constructor, and all descendants, to LionWeb JSON SerializedNodes.

ListedMps2LionWebConverter

Converts only the MPS SNodes listed in the constructor to LionWeb JSON SerializedNodes.

5. Important supporting classes

ILionCoreConstants

Access to constants like built-in elements in different language representations.

IJsonConstants

Access to constants like built-in elements in Java JSON.

IdEncoder

Encodes and decodes with Base64_url.

ILanguageDependsOnFinder

Finds all languages extended and/or needed by a language.

LionWebAttributeFinder

We store additional information (like concept’s keys) in SNodeAttributes. This class helps accessing them.

MpsLanguageConverter

Easy access to all the conversions between language representations.

AnnotationFinder

Identifies LionWeb Annotation (aka MPS Attribute) Concepts.

BuiltinsUsage

Identifies factual MPS language dependencies that are substituted by LionWeb builtins dependencies.

M1Serializer

Serializes instance level (M1) nodes.

M2Serializer

Serializes language level (M2) nodes.

Deserializer

Deserializes JSON nodes of any level (M1/M2).

6. MPS as LionWeb repository

6.1. HTTP interface

Solution io.lionweb.mps.server.plugin adds some endpoints to the MPS http server:

/lionweb/bulk

LionWeb bulk protocol implementation that serves the MPS repository. Supports GET and POST requests.

Possible URLs:

/lionweb/language

LionWeb bulk protocol (read-only) that serves MPS languages in LionCore. Supports GET requests. Addresses languages by their MPS module id.

/lionweb/language/key

LionWeb bulk protocol (read-only) that serves MPS languages in LionCore. Supports GET requests. Addresses languages by their key.

7. Imported Languages

Caution
This part of lionweb-mps is currently not maintained. We might reactivate it in the future.
Imported Language Hierarchy
interface IImportedLanguage {
    getLanguage(): SLanguage
    getRootNodes(): node[]

    apply(ILanguageCreator): string[]
}

abstract class AImportedLanguage implements IImportedLanguage {
    new(Metamodel, SLanguage, LionCoreConstants)

    getLanguage(): SLanguage
    getRootNodes(): node[]
    register(): ILionCore2MpsMap
    convert(ILionCore2MpsMap)
    link(ILionCore2MpsMap)
    getMap(): ILionCore2MpsMap
}

abstract class ADeltaImportedLanguage extends AImportedLanguage {
    new(Metamodel, SLanguage, Language, model, LionCoreConstants)

    getDeltas(): IDelta
    getModel(): model
}

class ReadonlyImportedLanguage extends ADeltaImportedLanguage

class ExistingImportedLanguage extends ADeltaImportedLanguage

class NewImportedLanguage extends ADeltaImportedLanguage

7.1. Deltas

Caution
This part of lionweb-mps is currently not maintained. We might reactivate it in the future.
Deltas
interface IDelta

abstract class ALanguageDelta implements IDelta {
    changedLanguage: SLanguage
}

class RenameLanguageDelta extends ALanguageDelta {
    oldName: string
    newName: string
}

abstract class ANodeDelta implements IDelta {
    changedNode: SNode
}

abstract class AParentedDelta extends ANodeDelta {
    parent: SNode
    changedLink: SContainmentLink
}

class AddDelta extends AParentedDelta {
    getNew(): node
}

class RemoveDelta extends AParentedDelta {
    getRemmoved(): node
}
Node Deltas
interface IDelta

abstract class ANodeDelta implements IDelta {
    changedNode: SNode
}

class ChangeConceptDelta extends ANodeDelta {
    oldConcept: SAbstractConcept
    newConcept: SAbstractConcept
}

class ChangeLinkDelta extends ANodeDelta {
    oldValue: SNode
    newValue: SNode
    changedLink: SAbstractLink
}

class ChangePropertyDelta extends ANodeDelta {
    oldValue: string
    newValue: string
    changedLink: SProperty
}

class MoveModelDelta extends ANodeDelta {
    oldModel: SModel
    newModel: SModel
    oldParent: SNode
}

class MoveParentDelta extends ANodeDelta {
    oldParent: SNode
    newParent: SNode
}