Substitute node immediately upon condition

Hi!

I made a substitute menu which replaces the current node with another node if the child of the current node is of a specific type. To illustrate:

I instantiated two types of nodes, Child1 and Child2 somewhere. My node (let's name it ChildWrapper) contains exactly 1 child, an AbstractChildReference (so in practice, it can either reference a Child1 or a Child2 node). What I want from this substitute menu is that when my ChildWrapper contains Child1, replace it with Child1Wrapper, and if contains Child2, replace it with Child2Wrapper.

I already made this using a substitute menu included in a transformation menu, but I can't figure out how to automatically execute it. Right now I have to press ctrl+space and choose the correct action from the transformation menu, but I want it to happen automagically. Once I choose a Child2 node from the completion menu and press enter, I want to automatically replace my ChildWrapper with a Child2Wrapper.

Can I do this somehow?

 

(To elaborate a bit on my use case: The children I've declared are either Constants or Functions. However, a statement can't be just a constant, it has to be Constant = Value. So if I start my line with the name of a constant (I store it in a reference), then I want to replace my TermHolderStatement with a ConstantEqualsValue statement. However, if I start the line with a function, then I want to replace it with a FunctionStatement. Obviously, I could just ctrl+space at the beginning of the line and choose which one I want, a ConstantEqualsValue or a FunctionStatement, but that's not exactly comfortable from an editing standpoint. And since both start with a reference name, I can't use aliases to solve this.)

10 comments
Comment actions Permalink

Can you add some screenshots of what you're trying to do and your current solution? It would help us help you.

1
Comment actions Permalink

Sure! Coming back to it after a week, I may have a solution that's built on checking rules (allow all kinds of statements, with a right transform on the "=" sign, and show a typesystem error if it's meaningless semantically), but I'd rather not let the user type in invalid content.

 

 

Here, you can see that two of my options are a predicate and a "term = term" statement. Which are used like this respectively:

 

This works perfectly. However, I want to be able to just type Test on an empty line, and have the "Test() = ____" TermEqualsStatement concept inserted automatically.

I have an editable EmptyStatement concept for this:

 

 

Both predicates and terms are IDeclarableReferences, so I can enter both in an empty line:

 

And because I know that if I enter a function name (say, Parent), the only valid statement is this:

 

I want to automatically replace my EmptyStatement with a TermEqualsStatement as soon as the reference inside the empty statement is an instance of a Function.

As such, my main question is: How can I achieve this? Which aspect do I have to change and for which concept? I'm guessing I have to modify the behavior of the EmptyStatement, but I just don't see how. I think I know the substitution code itself, as I can create a transformation menu entry which does exactly this, but it only executes when I choose it from the menu, and not automagically.

0
Comment actions Permalink

Ok I'm still missing some details about your language definition (structure and editor declarations), but I think I can give you some advice already.

The easiest way would be to use grammar cells from MPS-extensions, specifically the wrapper cell (grammar.wrap). In your editors for TermEqualsStatement you would wrap the left-hand side of the equation with grammar.wrap. This will modify the substitute menus such that when you start typing the left-hand side an entire TermEqualsStatement will be created.

This can also of course be accomplished with MPS built-in substitute menus but it's more complicated. You need to modify the substitute menu for TermEqualsStatement to include a "wrap substitute menu" part wrapping the menu for whatever is the concept on the left-hand side of TermEqualsStatement, and provide code to convert the left-hand side into a full TermEqualsStatement.

You'll also need to be careful because creating a substitute menu will automatically override the default one and if you want those entries back you'll need to add them manually.

0
Comment actions Permalink

Also, to clarify, you don’t actually need the reference in the empty statement, it doesn’t conceptually belong there and it’s not necessary to achieve what you want.

0
Comment actions Permalink

Thank you for your help so far! I had to do other things for a while and I got back to this yesterday. I managed to do it after one of the MPS tutorials had a similar thing: Check if there are any aliases that begin with the pattern I type, and if not, execute an action (in my case, a substitution).

 

 

Also yeah, in the end I didn't need the reference in EmptyStatement, you're right that it doesn't belong there.

I have one last question then that I couldn't figure out: How can I keep the letter "J" in the above example? How can I "insert" the pattern that I matched against in term1 of the new node? As a string is not a reference, I can't just write "term1.reference = pattern". :/

0
Comment actions Permalink

I'll start with the obligatory reminder: with grammar cells it's literally a matter of adding a couple of grammar.wrap cells in appropriate places and you're done.

Now, if you can't use grammar cells or want to learn how the menus work at a lower level, and are prepared to make mistakes and spend time fixing them, then I have a few comments.

First, to implement the reference lookup use a parameterized menu. Parameterize by scope elements (ModelConstraints.getReferenceDescriptor(...).getScope().getAvailableElements(null)).

Second, I'm not quite sure why you're looking for existing concepts and filtering them out. (Which MPS tutorial is doing that?) Is it to prevent users from referring to terms/functions that they accidentally named like concepts? If that's what you're looking to prevent, I suggest checking the name at the declaration site instead.

Third, I have previously written more details on how transformation/substitute menu system works here: https://mps-support.jetbrains.com/hc/en-us/community/posts/360007750439/comments/360002071780. You may or may not find it useful.

1
Comment actions Permalink

Also, I just tried recreating your example and MPS offers the terms for me automatically due to the way how smart references work. I don't even need grammar cells. The demo project is at https://github.com/specificlanguages/expressions-demo. Screenshot below:

0
Comment actions Permalink

All right, I'll try to explain my project because I think not doing that was my first mistake:

Long story short I'm in university and I had to choose a semester-long project for DSLs, and I chose MPS as the "framework", and first order / predicate logic as the language.

Predicate logic has predicates in it, like "dead(x)", which is either true, or false. It also has functions, which, on the other hand, return an object - for example "parent(x)" is a function, because it returns the parent of x, let's say y. Constants (John) and variables (x) are also technically objects, they behave like functions.

As such, "dead(x)" is a valid boolean statement, but "parent(x)" is not. Conversely, "dead(x) = John" makes no sense, but "parent(x) = John" does (= is not an assignment, it's an equality check: "parent(x) == y").

Also, to make all this work, I have to define the names of predicates and functions before usage (like you did in your demo project).

So knowing this, I want to tune the editor so that when I enter "dead", the editor will change it into a predicate statement: dead( ___ ). The default behaviour for MPS is perfect for this, and it works without any magic, yes.

However, if I enter "parent", I know that "parent( ___ )" is not a valid statement, only "parent( ___ ) = ___" is. Now: parent( ___ ) is a function reference, and "___ = ___" is a TermEqualsAtomicStatement, which can contain either function, constant or variable references, in short, terms. As such, MPS' default behaviour doesn't work, because I want to type in a term, and have a TermEqualsAtomicStatement inserted.
To reiterate, when the user enters text which can only be interpreted as a reference to a term (so it can't be any keywords, or predicates), I want to "wrap" the text in a TermEqualsAtomicStatement.

You can see the implementation here:

 

 

TL;DR: This works. I have solved every issue regarding this question / problem except this one:

 

 

As you can see in the gif here, my current substitute action doesn't "remember" the text I've already typed in ("J" in the gif). So by typing "John" (which is a constant), the substitute action matches against "J", and replaces the EmptyStatement with a TermEqualsAtomicStatement, because none of the keywords or predicates begin with J. This part works. However, the J disappears. And I want to keep that J pattern text in the new TermEqualsAtomicStatement I create, or, to be specific in the left child of the statement (where I continued typing "ohn").

This is my very last problem, and thank you for everything so far!

0
Comment actions Permalink

I think your problem will have to be solved differently, you'll have to back out a bit.

What's happening is that a substitution is triggered by you typing 'J' into a cell for the current node, but after the substitution is executed the current node is replaced with a fresh new node and the 'J' is lost in the process. There's no easy way to remember the 'J' and put it into the newly created cell. And even if you could do it, it's not good engineering. Also, what if there's both a function and a variable starting with 'J'? Which one would your code pick?

What you should do instead is build a menu out of three parameterized menus. With parameterized menus you can define an action for each value of the parameter object. So in your case, for each visible instance of Variable you'll define an action that would create an instance of TermEqualsAtomicStatement with teas.term1 set to reference the variable. For each instance of Function you'd define an action that would create another appropriate expression (FunctionCallStatement or whatever). This way if there's both a function and a variable named Foo, they will each have an entry in the completion menu and the user can choose what they wanted.

1
Comment actions Permalink

I'm gonna be honest, I didn't believe you first because it seemed like I was this close to solving it my way, but it turns out that nope, you were right. Following your advice, I made the parameterized menus and they work perfectly, thank you!

 

 

Here's the solution for predicates, for posterity's sake.

0

Please sign in to leave a comment.