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:
What I would like to have:
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
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
Please sign in to leave a comment.
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 {
}
}
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.
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?
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.
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 {
}
}
Nevertheless, the method returns all Ports of the instances... Any ideas what went wrong?
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 {
}
}
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 {
}
}
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
I can't find a mistake. Everything seems to be correct... :-\
Any additional ideas?
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?
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.
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
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)
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 {
}
}
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 {
}
}
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;
}