Last week we talked about cross-model generation, the issue of generating a model whose generated code depends on the generated code of another model.

To support cross-model generation in the general case you need to learn, and experiment with, generation plans and checkpoints. However, if you are generating Java, there is an easier solution: use the baseLanguageInternal language.

This language, jetbrains.mps.baseLanguageInternal, contains concepts for references that can be used in place of the usual base language concepts, but in place of references they let you enter text. This means that you no longer have to bother with setting up reference macros in the generator and worry about checkpoints. You only need to know the exact name of the class, method, or variable that you want to refer to.

This probably sounds complicated, so let’s look at an example. Here is what a “Hello, world!” program might look like, using base language internal:

The internal parts are marked with green. For example, this line:

generates this Java code:

PrintStream systemOut = System.out;

but the InternalTypedStaticFieldReference concept used in the former does not reference the class java.lang.System, nor the static field out. Instead they are entered directly as text. It does reference PrintStream in the brackets to give a type to the expression.

Same for the println method called using InternalPartialInstanceMethodCall. Its name is set as a string rather than by referencing the corresponding InstanceMethodDeclaration from the model java.io@java_stub. It is even possible to insert a call to a non-existent method this way. The code will be generated without errors but will of course fail to compile.

Here is the full body of the class generated from the example above:

The baseLanguageInternal language provides about a dozen concepts built by the same principle, that let you refer to variables or methods by their name even if you don’t have a direct reference available. Here is a list of all such concepts in MPS 2021.3:

  • InternalAnonymousClass for defining anonymous inner classes, the extended class name is given as a string.
  • InternalClassCreator to create an instance of a class whose name is given as a string.
  • InternalClassExpression to use .class on a class whose name is given as a string.
  • InternalClassifierType to use a class as a type, the class name being given as a string.
  • InternalNewExpression combines a generic new expression with InternalClassCreator.
  • InternalPartialFieldReference, InternalStaticFieldReference, InternalTypedStaticFieldReference, InternalVariableReference to refer to a field, a variable, or a parameter, whose name is given as a string.
  • InternalPartialInstanceMethodCall, InternalStaticMethodCall to call a method whose name is given as a string.
  • InternalSuperMethodCallOperation to call a method on the superclass, the method name being given as a string.
  • InternalThisExpression to be used where the normal ThisExpression is not allowed.

Besides these concepts, the language contains other concepts that can be useful when generating Java code, such as concepts that extract a particular expression into a field or a method. However, those concepts are not related to cross-model generation and so are out of scope of today’s post.

With the concepts listed above, it is possible to avoid the complexities of cross-model generation. They provide a nice escape hatch that lets us plug text directly into our projectional base language code.