Concept inheritance and overlapping reduction rules Follow
Hi folks,
I'm currently struggling with an issue that is related to concept inheritance and the generator aspect. A simplified version of my scenario is:
- I have two concepts "A" and "B" while "B" extends "A"
- "A" has two property declarations: id: string and def: boolean
- "B" has also a property declaration: label: string
- Let's assume that I want to add 200 more concepts that inherit from "A"
Let's say that I want to generate XML code for my models. What is the recommended way of writing the corresponding reduction rules? Is it somehow possible to define a reduction rule for "A" which defines how the properties "id" and "def" are transformed so that the reduction rules for the inheriting concepts only require to define the transformation rules for those properties they add (e.g. "label" in the given example)? The reason for my question is that I don't want to repeat myself by writing almost the same reduction rules for each concept that is a sub-concept of A.
I've already searched and read several posts but unfortunately, I was not able to find a solution.
Thank you very much in advance!
LaAck
Please sign in to leave a comment.
Not sure I completely get the question. You'll need to write a reduction rule for each concept you'd like to handle in a specific way. Reduction rule is merely an indicator 'For an instance of given concept do this and that'. You can have generic reduction rule for A, and few reduction rules for specific B concept, those with extra properties. MPS Generator would pick the most specific rule, and if there's no rule for e.g. B173, then it would use rules for superconcepts (here, A).
If your question is rather about re-using the same template for concepts that extend A, you can have a template declaration for A (that would handle id and def), and $CALL$ it from reduction rule for any B to process shared values. Reduction rule would handle B-specific properties before/after the call.
You may also have a single rule for A, that would do some A-specific handling, and then do $SWITCH$ for B to inject B-specific code into middle of A code.
Thank you very much for the fast and informative reply! Sorry, my description was too vague. Actually, your second and third explanations are (partially) the answers I was looking for. My questions targeted on reusable code on the one hand and the strategy for using possible solutions for that in the context of hierarchical output document formats like JSON or XML.
The latter, i.e. applying the template-and-$CALL$ solution to generate JSON, still leaves a part of the question open. Let's say B should be transformed into an object in JSON notation (means: { ...}) and the properties of A and B should be members of this object. How can I implement the corresponding template? The issue is that I have to instantiate a JSON object in order to create the template content for members. Hence, if I follow your suggestion to $CALL$ template A from template B, I have the issue that it inserts a complete JSON object (the transformation result for A) instead of the desired members only. The reason is that I can have only one template fragment which is why I cannot declare each member of A to be part of the template separately. Instead the only way to include all members is to declare the surrounding JSON object to be the template fragment. But this way the result is a structure like
which is no valid JSON. Instead I want to have:
I hope my explanations are comprehensible. Please let me know if you need further information.
Thank you very much!
There's no limitation on what templateForA produces. It could be whole 'json object' or just a bunch of declarations. Only stuff annotated with TF annotations constitutes template output, anything else is just a context:
First template, membersOfA, uses JSON object just as context, with Template Fragments denoting true output of the template when invoked. Second template, wholeB, denotes whole JSON object as its output (TF), and injects output of membersOfA template into appropriate role.
Thank you for this concise example. I've already tried this solution before but without the "$LOOP$" in membersOfA. In my source language the concepts A and B have only properties but no children:
Thus, I don't know how the expression for $LOOP$ would look like? How can I iterate over properties using a node macro? Maybe, I miss a detail of your explanation...
Well, if key-value pairs are represented as concept properties, not children, just use them in respective property macros:
where property macro over 'value' takes node.id value, and second property macro does something like String.valueOf(node.def)
Second template would use node.label as an expression for a property macro around 'value' as well:
Thank you again for the fast reply. However, the template with the two template fragments perfectly describes my first try. If I do this, I get an error message (cf. screenshot below). After your explanations, I think that the issue is caused by an odd design of the JSON language, right? I reused the JSON language from the MPS plugin repository (https://plugins.jetbrains.com/plugin/10096-json-language) and migrated it myself.
I think the root cause of the issue is that in the JSON language definition looks simplified as follows:
This way your suggested template fragments are both containing instances of "Pair" which have different instances of "Member" as parent, which fits the error message.
To summarize it: Multiple template fragments can be used only iff they share the same parent node? Is there any workaround if this is not possible or do I have to override the language definition?
Template fragments within a single template indeed have to share same parent node. However, I don't see what's wrong in this case - it's same parent node (Object), and same aggregation role (members). You just need to be careful to put TF around proper element (which could be tricky, and might need you to use reflective editor to select proper element). Here, you'd have 2 Member instances, each holding Pair instance, within single Object context node, and need to put TF around Member instance, not around Pair. TF restriction of same parent would be satisfied then.
Just to explain the logic behind 'same root and role' restriction: think about call site of a template. The place you write $CALL$ has context node that serves as a parent for template nodes produced by the template, and its child that holds CALL macro lives in a certain role, that gives the hint where nodes produced by the template would get placed. So, in your wholeB template, you place CALL macro around placeholder Member, and two Member instances produced by two TF of membersOfA template would fit there nicely.
Ah, got your point. Without this restriction it would be just "guessing" where to place the nodes generated by calling the template.
Regarding the language the issue was indeed that the editor implementation of Member just renders the instance of Pair without anything else. This way I was never able to select the instance of Member but instead always worked on pairs. I just put a horizontal collection around the Pair instance and added a small
Thank you very much, this solved the issue (cf. screenshot below).