We are going to learn how to allow the elements of your language to be easily commented out in a similar way that languages such as C or Java allow you to disable statements simply by prepending them with //. This capability is not baked into languages automatically. It is the language designer, who needs to think about which elements may become commented out, what key combination should toggle the commented out status of an element on and off as well as how a commented out node should look on the screen.
Note: Unlike in many popular text-based languages, in MPS we distinguish between commenting out code versus adding documentation comments into code. This document focuses on the former. If you are more interested in adding documentation comments to your languages, you may like to checkout the Documentation comments cookbook instead.
In this tutorial we will follow the conventions from Java or BaseLanguage to prepend commented out elements with //, to display the commented out code in gray and to use the Control + / key combination to toggle the commented out status. On the implementation side of things, the commented out nodes will be annotated with a marker CommentOut annotation, which will ensure the expected qualities:
- The commented out nodes will be displayed with a different visual style
- The commented out nodes will be removed from scope so that other parts of the code-base may not refer to them
- Errors in commented out nodes will be ignored and not reported to the users
We chose the Calculator tutorial language as a testbed for our experiments. You can find the calculator-tutorial project included in the set of sample projects that comes with the MPS distribution. We will allow both the input fields and the output fields to be commented out. I recommend you skimmed through the tutorial to familiarize yourself with the language before we continue.
Now, with scopes out of the way, we can start building the machinery for commenting nodes out. Our approach will be based on annotations that when added to nodes will have impact on their behavior as well as on their visual appearance. There are several vital components that will enable commenting out code:
- A marker interface that indicates, which nodes can be commented out
- The annotation itself to indicate whether a node has been commented out or not
- An action providing a keyboard shortcut to quickly comment or uncomment code
- Removing the commented out nodes from scope by updating the Calculator's getScope() method
- Supressing error reporting inside commented out nodes through the use of the ISuppressErrors interface
- Updating the code generation process in order to ignore commented out nodes
We start with a new interface to mark all commentable nodes:
To suppress errors in commented out code, we mark the new interface with the ICanSuppressErrors interface. Thus all nodes implementing ICanBeCommentedOut will be able to hold errors when commented out without the IDE complaining.
Now both InputField and OutputField concepts have to implement the new ICanBeCommentedOut interface.
We are still missing the core pice of our puzzle - the actual annotation that will be attributed to input and output fields to indicate, whether they are commented out or not. Let's create it now:
Notice we specified the role as commentedOut. This is the name to query a node whether it has been annotated with CommentOutAnnotation or not. We also indicate that the annotation can only be attributed to nodes implementing ICanBeCommentedOut, which in our case is InputField and OutputField.
The editor of CommentOutAnnotation is the key element in defining the visual behavior of commented out elements. We prepend the annotated node with // constant and for both the constant and the annotated node we specify the Comment style in the inspector (Alt + 2):
To toggle to commented out status of a node on and off, we will create new KeyMap.
To edit the code we will use concepts from BaseLanguage and smodel, which should be available by default. If not, import them through Control + L. We will also need the actions language in order to create initialized nodes easily. This one may need to be imported explicitly (Control + L).
Now we can complete the action:
Whenever Control + / is pressed on a node implementing ICanBeCommentedOut, the action will add or remove the CommentOutAnnotation to/from the node.
The KeyMap needs to be added to all fields that should react to Control + /. This is to InputField and OutputField in our case.
After compilation you are able to use Control + / both on input fields and output fields to toggle the commented out status:
However, when you comment out an input field, you can still refer to it from the output field. This is obviously wrong and we will fix it just now.
All we have to do is to update the Calculator scoping behavior so that it excludes all commented out fields. We can use the FilteringScope class with great effect:
Commented out nodes are now excluded from the completion menu and references to them appear broken.
People also expect errors in commented out code to be ignored.
This is something that we can support easily. We already made all fields implement ICanSuppressErrors to indicate that they can under certain conditions hide the errors in them. Now we just need to say when to suppress the errors - through the ISuppressErrors annotation:
If the CommentOutAnnotation implements ISuppressErrors, MPS will ignore errors whenever the annotation is attributed to a field. If you recompile you will see that errors in commented out nodes are not underline in red.
Typically we also want to exclude commented out nodes from code generation. In our case, we would like to avoid showing an input field component on the screen for a commented out input or output field.
When generating code we currently simply take all input or output fields in the calculator and generate code for them.
This means we take all fields, including the ones that have been commented out and generate visual components for them. Instead, we now have to remove all nodes that are marked with CommentOutAnnotation.
I would like to present two alternatives here:
- Using behavior methods on the calculator to do the filtering and call those from the generator template
- Use a pre-generation script that will remove all commented out nodes from the model before the generator starts.
I decided to use the Behavior aspect of Calculator to define two new methods that will do the filtering for us. Obviously we need collections here (Control + L):
These methods return all respective fields that are not commented out:
We certainly have to update the generator to use the new methods instead of direct input or output field retrieval:
After a rebuild (Control + Shift + A)
we get a form without the commented out fields.
Another approach is to run a mapping script before the generator starts, which removes all the commented out nodes. So we create a script that scans all Calculators for descendants of type ICanBeCommentedOut and removes those marked with the CommentedOut annotation:
We than hook the script up with the generator:
Now the generator can only see valid nodes and will never generate code for commented out ones.
We can finish our tour here. Both input fields and output fields can be commented out and in easily with immediate effects on the visual appearance as well as on reference resolution and error reporting.
You may also like to know how to add support for documentational comments into your languages. The Documentation comments cookbook, which builds on the knowlenge we acquired here, will teach you exactly that.