elk
elk copied to clipboard
More flexible 'requires' element in elkm files
The requires element should allow more complex predicates. E.g. it would be nice to be able to specify that an enum-based layout option is one of two values.
MdOptionDependency:
'requires' target=[MdOption|QualifiedName] ('==' value=XExpression)?;
Proposal 1:
requires myOption in { MyEnum.VALUE1, MyEnum.Value2 }
Proposal 2:
requires myOption == MyEnum.VALUE1 or myOption == MyEnum.VALUE2
Proposal 3:
requires myOption == MyEnum.VALUE1 || myOption == MyEnum.VALUE2
The second one is more flexible than the first, and the third is even more flexible. However, it's also quite tricky, and I'm not 100% sure whether it works (everything following requires would be an XExpression, so we have to generate a checker method with the option value as argument).
We could even go a step further. If both participating options are enums, one could require that for one value of the first option (some sort of guard), only a subset of the second option is allowed.
[this > 0 || this < 100] requires myOption == 20
It would be nice to have an unequal or negating expression too. For example if a single enum value is incompatible but additional enum values are expected to be added, one doesn't want to add the new values to the list of compatible values proposed by @spoenemann
Maybe we should think about some way to put arbitrary behavior into dependencies and validation of layout options (see #15). We could do it by expecting implementations of some interfaces. In Xbase such implementations can be written with the lambda syntax known from Xtend.
The main drawback of such an approach would be that you cannot reference layout options via their identifiers inside the function implementation, but only via their Java names, i.e. the generated constants.
I understand (and support) the desire to generalize lower and upper bounds, but I think allowing arbitrary implementations for dependencies takes a bit too far. After all, the use case here is quite limited, and mostly affects developers, not regular users.
It could help to identify erroneous configurations by the users.
True. My point is simply that I would not invest a lot of work designing such a feature that in my opinion has limited use.
What about implementing Proposal 1 then? It's the shortest of the proposed syntaxes.
For example if a single enum value is incompatible but additional enum values are expected to be added, one doesn't want to add the new values to the list of compatible values proposed by @spoenemann
I think it's ok if we have to reconsider and explicitly update constraints on layout options when we change the set of possible values.
That would solve the issue for enumerations only, but I guess that's good enough for the moment.
Of course I would allow arbitrary expressions, so you could also write
requires myOption in { 1, 2, 3 }
or
requires myOption in { "a", "b", "c", "x" }
A possible generalization could be to allow an arbitrary expression that return a Collection after the in keyword, so the syntax could be
requires myOption in newArrayList(1, 2, 3)
if you add import static extension com.google.common.collect.Lists.*, or
requires myOption in #[ 1, 2, 3 ]
if you're ok with having a dependency to the slim Xbase runtime library. That would even allow
requires myOption in #[ 1..3 ]
I think I would be fine with the in notation for the moment. Although I feel a not in and != is quite frequent as well.
I just realized that the in notation is (in theory) already supported: multiple requires are allowed and interpreted as logical OR.
Practically, this doesn't work yet since the code generator generates the same constant twice in a case like this:
requires strategy == NodePlacementStrategy.NETWORK_SIMPLEX
requires strategy == NodePlacementStrategy.BRANDES_KOEPF
e.g.:
private final static NodePlacementStrategy NODE_PLACEMENT_FAVOR_STRAIGHT_EDGES_DEP_NODE_PLACEMENT_STRATEGY = NodePlacementStrategy.NETWORK_SIMPLEX;
private final static NodePlacementStrategy NODE_PLACEMENT_FAVOR_STRAIGHT_EDGES_DEP_NODE_PLACEMENT_STRATEGY = NodePlacementStrategy.BRANDES_KOEPF;
For the moment, I adjusted the generator code to create unique constant names. That way it is possible to specify or-like requirements as illustrated in my previous comment.
Nevertheless, I still vote for more flexible expressions.