Simple "Constrains" problem...

Hallo everybody,

I am very new to JetBrains MPS and I'm having trouble with the constraints definition.

So what I have:
  • Concept "Component" with children "Port[0..n]"
  • Root Concept "ComponentList" where several Components and its ports can be defined
  • A smart-reference "PortReference" which links to the Ports defined above

What I would like to have:
  • A concept "Instance" which references to a single "Component" and which has a children collection of "PortReferences".
  • These "PortReferences" shall be allowed to point only to those "Ports" that are children of the referenced "Component"

Now, my problem:
It is possible to reference to ports, which are not children of the respective Component.

So, I suppose I have to define a constraint to get my problem solved... I tried the user guide, the calculator and the constants tutorial. Likely it is a very simple thing, but I really don't get it at the moment... :-\

I would appreciate for any help. Thank you in advance.

Best regards,
Sebastian
0
17 comments
Hi Sebastian,

welcome to MPS!

I suggest you made Instance implement ScopeProvider and override (Control/Cmd + O) its getScope(kind, child) behavior method:

public Scope getScope(concept<> kind, node<> child)
  overrides ScopeProvider.getScope {
  if (kind.isExactly(Port)) {
    return new SimpleRoleScope(this.component, link/Component : ports/) {
      public string getName(node<> child) {
        child : INamedConcept.name;
      }
    };
  } else {
 return super<ScopeProvider>.getScope(kind, child)
;
  }
}

This way Instances will take care of providing Ports to whoever asks for them. Additionally you'll have to define a constraint for PortReference so that it asks ancestor ScopeProviders for available Ports:

link {port}
  referent set handler:<none>
  scope:
    inherited for Port
  presentation :
    <no presentation>
;

That should do the trick.
0
Avatar
Permanently deleted user
It works! :-) Thank you for your fast and precise response, Vaclav. You are the man! ;-)

But honestly, I'm not sure if I have understood the mechanism completely...

But let me give it a try: The inherited scope delegates scope resolution to the next ancestor, which implements ScopeProvider. This means for the present case: (PortReference ->) Port -> Component -> ComponentReference -> Instance (implementing ScopeProvider). Is that right, so far? Then, the getScope() method will be invoked. The SimpleRoleScope() method helps building own custom scopes. The method looks at the referenced Component (this.component_ref) und provides the ports connected to this Component (link/Component : ports/), right? But what does the getName method here? What is meant with child?
0
I'm glad it works :)
You are right with your reasoning about scope resolution - the inherited scope investigates all ancestors implementing ScopeProvider in the AST up to the root node. In Java, for example, searching for a target of a variable reference in a method would go first to the method's body searching for local variables, then to method declaration searching among parameters, then the surrounding class searching for fields.
In our case, the search is much shorter, PortReferences are children of Instances, so the wrapping Instance is asked first. Since it returns a non-null scope, the search does not continue further up the AST.

Scopes in MPS need to be able to filter out candidates (Ports in our case) by string values, thus they need to have an implementation of the getName() method, that returns a string representing each node in scope.
We use the Port's name as a natural representation of Ports. The "child" parameter of the method in our case is a Port that is in the scope.
0
Avatar
Permanently deleted user
Thanks for giving clearness! :-)
0
Avatar
Permanently deleted user
Oh man, frustration is back again...

I have extended my project with a concept "Connector". It has references:

instance_from_ref : Instance[1]
port_from_ref : PortReference[1]
instance_to_ref : Instance[1]
port_to_ref : PortReference[1]

instance_from_ref points to a instance defined prior (works fine, so far...)
port_from_ref shall be able to point only to a port of the instance "instance_from_ref".


Problem is that it can point to ports of other instances, too. To get above this, I made concept "Connector" implement ScopeProvider and overrided the getScope behavior as following:

public Scope getScope(concept<> kind, node<> child)
  overrides ScopeProvider.getScope {
  if (kind.isExactly(PortReference)) {
    return new SimpleRoleScope(this.instance_from_ref, link/Instance : port_refs/) {
      public string getName(node<> child) {
        child : INamedConcept.name;
      }
    };
  } else {
 return super<ScopeProvider>.getScope(kind, child)
;
  }
}

Nevertheless, the method returns all Ports of the instances... Any ideas what went wrong?
0
You are on the right track. I would just change the getScope() method a bit:

public Scope getScope(concept<> kind, node<> child)
  overrides ScopeProvider.getScope {
  if (kind.isExactly(Port)) {
    return new SimpleRoleScope(this.instance_from_ref.component, link/Component : ports/) {
      public string getName(node<> child) {
        child : Port.name;
      }
    };
  } else {
 return super<ScopeProvider>.getScope(kind, child)
;
  }
}

The PortReferences will still be looking for Ports, thus kind must be "Port". So we can take all ports from the component of the instance.
If, instead, you only want ports that are being referred to by the instance, try the following code:

public Scope getScope(concept<> kind, node<> child)
  overrides ScopeProvider.getScope {
  if (kind.isExactly(Port)) {
    sequence<node<Port>> ports = this.instance_from_ref.ports.select({~it => it.port; });
    return new ListScope(ports) {
      public string getName(node<> child) {
        child : INamedConcept.name;
      }
    };
  } else {
 return super<ScopeProvider>.getScope(kind, child)
;
  }
}


0
Avatar
Permanently deleted user
Hi.
Great that Vaclav replied so quickly and thoroughly.
If you need some extra help, I can help via e.g. a Skype call with screen sharing, etc. (I like to spread the MPS goodness). Just let me know if you get stuck.

Best,
Karol Depka Pradzinski
0
Avatar
Permanently deleted user
First, thank you very much again, Vaclav. But I'm sorry, still the referred ports from ALL existing instances are returned. It's the second thing you mentioned: I need to return only the ports referred to the ONE instance (instance_from_ref). So its something like a reference to one of the references linked to the ports.

I can't find a mistake. Everything seems to be correct... :-\

Any additional ideas?
0
Just to be sure I'm on the right track, can you please check that the attached images show the intended behavior? If the instance refers to two ports of a component, only these two ports should be available in the connector.
img1.png
and
img2.png


Other than that, I think it would help if I could have a look at the project. Is there a chance you could share it?
0
Avatar
Permanently deleted user
Yeah, this is almost the thing I'm looking for.

  • I have a component list with ports, too
  • Then, in a second node, I define which components shall be "instanciated" and which ports I want to use
  • Then I define (several) connectors, which "connect" these instances: Here I want to select an instance (instance_from_ref) and for "port_from_ref" only the chosen ports shall appear.

So in difference to your example, I first define several instances BEFORE I define the connectors.

Is that understandable? But I can share my project, of course.
0
Avatar
Permanently deleted user
Hi Karol, thanks very much for your offer. Great community!! :-)
0
What puzzles me is that you use Instance and not InstanceReference in Connector definition

instance_from_ref : Instance[1]
0
Avatar
Permanently deleted user
Hmm... why not?

It's defined at references, so instance_from_ref links to an Instance... or am I wrong?

Just uploaded my project:
https://dl.dropboxusercontent.com/u/6379971/ArchictectureDescription.zip
0
All right, that explains a lot. Sorry for not noticing this earlier. Your "port_from_ref:PortReference[1]" definition must be a child of Connector, not a reference.
0
Avatar
Permanently deleted user
Thanks a lot! Sometimes it's so easy ... "facepalm"

But is there a way to distinguish, if the ports of the instance_from_ref or the instance_of_ref shall be returned? (in dependency if I want to select a port_from or port_to reference)
0
To distinguish the role of the child that you are setting, you need to modify the getScope() method slightly and implement the second getScope() method, as well.

public Scope getScope(concept<> kind, node<> child)
  overrides ScopeProvider.getScope {
  if (kind.isExactly(Port)) {
    node<Instance> actualInstance = come from port_from_ref ? this.instance_from_ref : this.instance_to_ref;
    sequence<node<Port>> ports = actualInstance.ports.select({~it => it.port; });
    return new ListScope(ports) {
      public string getName(node<> child) {
        child : INamedConcept.name;
      }
    };
  } else {
 return super<ScopeProvider>.getScope(kind, child)
;
  }
}   

public Scope getScope(concept<> kind, string role, int index)
  overrides ScopeProvider.getScope {
  if (kind.isExactly(Port)) {
    node<Instance> actualInstance = role.equals(link name/Connector : port_from_ref/) ? this.instance_from_ref : this.instance_to_ref;
    sequence<node<Port>> ports = actualInstance.ports.select({~it => it.port; });
    return new ListScope(ports) {
      public string getName(node<> child) {
        child : INamedConcept.name;
      }
    };
  } else {
 return super<ScopeProvider>.getScope(kind, role, index)
;
  }
     
}

In the first method we're now checking the child of Connector that raised the request for scope of "Port". The "come from" construct can be used to query the asking child. The ComeFromExpression concept is in jetbrains.mps.lamg.scopes language, which you may need to import (Control + L).

In the second method we're handling the situation when the child that is being completed is null, so we can decide based on its desired role in the parent.

Note: The code is just a quick draft so it may need some polish. You could perhaps call the second getScope() from the first one, passing in the role retrieved through the "containingRole" concept - e.g.

if (come from port_from_ref) {
  role = this.port_from_ref.containingRole;
}
0
Avatar
Permanently deleted user
Thank you very much!! Code works fine. The "come from" construct is the very useful thing I was looking for. :-)
0

Please sign in to leave a comment.