MPS bundles sources with generated models by default. Users of the models are thus able to navigate to definitions of the concepts that they are using in their code. It is a matter of a singleControl + click for them to see the implementation of a method that they are calling or a class they are instantiating, for example. This is very convenient for the users, since they can grasp many of the ideas of the language/library authors simply by peeking at the implementation side.
There may be situations in life when hiding the implementation could be desired, though. Especially closed-source projects need to protect carefully their intellectual property contained in the implementation. Their users should still be able to call the code, but after pressing Control + B they only get to see the class and method signatures:
Combine that with obfuscated class files and chances for someone reverse-engineering the fruits of your hard work are pretty low. Please read on to find out how to do this.
The BuildLanguage offers a strip implementation flag to indicate that a particular artifact should have the sources removed.
Setting this flag to true on one of the build layout commands (module, sources of, plugin) will instruct the build process to remove the sources of implementation from the generated artifacts. This flag will ensure that BaseLanguage methods have their bodies replaced with an empty StatementList and classes have static and instance initializers removed. May you wish to hide implementation of a Language, the flag will ensure the behavior methods have their bodies also replaced with an empty StatementList.
When building a language and the strip implementation flag is set, the generator module must have the Generate Templates flag set on. Otherwise the stripped generator will not work for the users of your language since the templates would be missing.
To summarize, MPS supports out of the box:
You only need to set the strip implementation flag in your build scripts for the desired solution or a language.
If you strip implementation from a language definition, the users of your language will be able to use your language fully. They, however, will not see the implementation and they will not be able to extend many aspects your language.
Just as BaseLanguage code allows for hiding implementation, your languages can allow for implementation hiding, as well.
Please, do not confuse "Customizing implementation stripping" with "Hiding implementation of your language". While the latter is concerned with hiding the way you implemented your language and works automatically, as described above, this paragraph is concerned about creating a language that allows its usages to hide their implementation.
MPS gives you three marker interfaces to demarcate the intended behavior of the concepts of your language with respect to implementation stripping:
If, for example, we wanted to the Robot Kaja sample language (bundled with MPS) to allow its usages to hide their implementation, we can achieve that in a few steps. Let's assume the following scenario:
The Robot Kaja language allows Libraries of routines to be created. Library is a root concept and holds a collection or RoutineDefinitions. The RobotRoutines library that uses the Robot Kaja language may create several Library root nodes and implement a few handy routines in them.
When used in the SampleRobotScripts code, the developer can always navigate to definition and see the full implementation of the RobotRoutines library.
We want to offer the authors of libraries such as RobotRoutines the ability to hide the implementation. Once implemented properly, the developers of SampleRobotScripts would only see the routines signatures and their empty bodies:
The Robot Kaja language needs to have its Script, Library and RoutineDefinition concepts marked with the InterfacePart interface, because user code can refer to them.
To hide the implementation of routines we need to hide their bodies. The routine body is a CommandList concept. To hide it we have to mark it with either the ImplementationPart interface or the ImplementationWithStubPart interface. They both result in hiding the implementation, however, the latter lets you provide a replacement "stub" concept, that will be inserted instead of the removed implementation. The stub gives you the chance to provide a nicer look of the code with removed implementation and should always be considered for hidden implementation that gets occasionally seen by the end user.
The overhead of ImplementationWithStubPart over ImplementationPart is in creating the stub concept. For our CommandList we'll need a StubCommandList concept and mark it with IDontSubstituteByDefault and IStubForAnotherConcept. The IStubForAnotherConcept is needed since StubCommandList inherits the ImplementationWithStubPart interface from CommandList, and thus MPS needs to be told explicitly that StubCommandList is a stub itself and does not need to be stubbed by yet another stub.
If StubCommandList was extending AbstractCommand instead of CommandList, it would not have to be marked as IStubForAnotherConcept as AbstractCommand is not an ImplementationWithStubPart.
The stub editor should make it politely obvious to the reader that the implementation has been removed and is not to be seen here.
Upon rebuild and packaging the language is ready to help the author of RobotRoutines to hide the implementation of her library.
Once the Robot Kaja language supports implementation stripping, the author of the RobotRoutines library can set the strip implementation flag in her build script and thus have the sources of the implementation of her library removed from the generated plugin.
This is needed for concepts, such as the one below - the modelAccessor child has cardinality 1 and so is mandatory in the stubbed concept.
The stub concept has to follow the naming convention of prepending Stub to the stubbed concept's name. It may or may not need to extend the stubbed concept - this depends on how the original concept is being referred to from the rest of the language. If the stub concept has to extend the stubbed concept, it also needs to somehow treat the mandatory child so that the child reference does not stay empty.
The suggested way is to specialize the child or reference relationship and change the target concept to BaseConcept.
In the behavior aspect in the constructor we then set the reference to point to some dummy node, such as an IntegerConstant. This will ensure the link does not stay empty, yet we avoid the need of creating nodes fully satisfying the original modelAccessor link in the stubbed concept.