In the last post, we stopped at the fact that we are able to add an array of input weather data, or rather, Time + Temperature data, tried to use Behavior and dealt with the concepts.
It's time to do something useful, because while everything we implemented could be implemented in any other language, with the exception of cool syntax.
First of all, we introduce time limits. Now we will limit it so that the clock is in the limit of 0-24, and minutes are 0-60, otherwise a compilation error will be issued.
Constraints is an aspect of the language that is responsible for the validity of the concept implementation. In our case, we need to restrict the property hours and minutes, so we create the Constraints aspect of the Time concept.
Here we see 3 points that are responsible for the structure of the AST.
Then we can determine some characteristics for properties. This is what we need, and as long as we know this is enough, we don’t need to mess around with the scope.
Everything, in principle, is simple and clear: we can override the getter and setter, but we are interested in is valid . In it, we get the input propertyValue and the current node . We have a simple condition, so we write as we would write in Java.
')
Now, if we put together a language and poke it in a sandbox, then we should show if the hour value is> = 24 or <0.
Writing the implementation for minutes is easy for you.
So, great, it works. Now it’s worth trying to translate it into Java, because it’s not for nothing that we did it all!
First, add the Generator aspect and call it main .
It's empty, so we will create a new root mapping rule . Select in the concept field our root concept PredictionList , and to the right of the arrow, press Alt + Enter → New Root Template → Java class . Logically, you should have something like this:
If we open map_PredictionList , then we will have something like a Java class with meta information above it.
This is our template, template and generally the most important starting point. But there is one thing: we don’t want to convert our WeatherTimedData into some kind of primitive, right? We want to have an object of the class WeatherTimedData, but here bams! We do not have such a class! So we can write it by hand. To do this, create a new Solution , call it what we want, and most importantly, add the Jetbrains.mps.baseLanguage to Used Languages so that we can write our classes.
I got them like this:
Now we need to learn how to use it in our generations in Java code. Go to the model properties of the generator and add to WeatherClasses dependencies and click export = true .
Now we can use these classes in the generation, which we will now do.
It looks like a simple class, but we need to make sure that name is set to the name PredictionList . Weather forecast for St. Petersburg - there will be St. Petersburg. For Moscow - Moscow will be. Click on the line "Here should be city" and use the hot key Alt + Enter and select property macro in the drop-down list.
The point is very simple - we are given a node of type PredictionList and we need to return a string. Click on the dollar sign, which appeared next to the line and edit the code in the inspector.
We are building a project, opening our Sandbox solution, where we only have input weather data for St. Petersburg, click PCM → Preview Generated Text, and we will have real Java! Now we can not screen it, but calmly throw off copy-paste.
package WeatherPrediction.sandbox; /*Generated by MPS */ import WeatherClasses.structure.PredictionList; public class PredictionListImpl extends PredictionList { /*package*/ String name = "Saint Petersburg"; }
It is worth noting the import statement, MPS generated an import of PredictionList , which we wrote to a separate Solution . Such a solution can be called "help", "support solutions". Well, I personally really like to call them that.
Well, let's add another implementation for the input array.
We create an empty linkedlist (why not) in which we will store the input data.
In the constructor, we use 2 steep macros at once.
The $ LOOP $ macro does the following: it passes through this collection, and does something for each item. The result is an array of generated data that simply follows each other. In this case, we are iterating over node.weatherData.items
The macro $ COPY_SRC $ is used to get the result of the conversion to another model of some concept. At the moment we have only 1 template: it is “main” and it is a template for PredictionList , and how does MPS understand what to do and how ..?
At such times, the MPS looks into the configuration of the main generator, or rather, in the reduction rules , where we are now empty.
Doing exactly the same thing you did with the PredictionList .
Go to reduce_WeatherTimedData , press Shift + Space, select Expression. Now just write
new WeatherTimedData(0, 0, null)
Now we need to wrap this Expression in a TemplateFragment, whose contents will be used in our main PredictionListImpl.
Select the entire expression using the hotkey ctrl + w, press Alt + Enter → Create Template Fragment.
Now we need to replace the zeros with the property macro , which we already know how to do (click on 0, press Alt Enter → Add property macro. In the inspector, we write
(templateValue, genContext, node, operationContext)->int { node.time.hours; }
which corresponds to replacing 0 with the current value of the clock. We do the same thing with the minutes, and we will not replace null - I'm lazy now, but the idea is the same - we add the reduction rule for the Temperature concept, and instead of property macro we use the macro $ COPY_SRC $
After that, we replace 0 and 0 with the property macro, which we already know how to call (select 0, Alt + Enter → Add Property Macro → node.time.minutes ).
We collect language and go to Sandbox solution.
So, for the source code in the Weather
language
Weather prediction rules for Saint Petersburg [ 0 : 23 ] { temperature = 23.3 °C } [ 12 : 24 ] { temperature = 100.0 °F } [ 23 : 33 ] { temperature = 4.4 °C }
It turns out this Java code:
package WeatherPrediction.sandbox; /*Generated by MPS */ import WeatherClasses.structure.PredictionList; import java.util.Deque; import WeatherClasses.structure.WeatherTimedData; import jetbrains.mps.internal.collections.runtime.LinkedListSequence; import java.util.LinkedList; public class PredictionListImpl extends PredictionList { /*package*/ String name = "Saint Petersburg"; /*package*/ Deque<WeatherTimedData> input = LinkedListSequence.fromLinkedListNew(new LinkedList<WeatherTimedData>()); public PredictionListImpl() { LinkedListSequence.fromLinkedListNew(this.input).addElement(new WeatherTimedData(0, 23, null)); LinkedListSequence.fromLinkedListNew(this.input).addElement(new WeatherTimedData(12, 24, null)); LinkedListSequence.fromLinkedListNew(this.input).addElement(new WeatherTimedData(23, 33, null)); } }
It would be possible to replace the linkedlist with a regular array, but this would be more difficult, since it would be necessary to push the indices. It could be replaced with ArrayList, but MPS was the first to advise linked list. It would be possible to write a separate support-class for WeatherData , and not to add data in the constructor, there are many ways to make the generated code better. Or worse. In general, now we can generate (!) Java code from our language. In general, now we can generate Java code for any other language, because we have mastered the knowledge of Generator !
Thanks for attention.
Source: https://habr.com/ru/post/334376/
All Articles