Skip to content

Commit

Permalink
Deep path operator (#768)
Browse files Browse the repository at this point in the history
* Implemented choice type syntactic sugar

* WIP

* Enhanced expression parser

* Fixed

* Cleaned

* Cleaned

* Simplified

* WIP

* Added code generation for deep path operator

* Fixed casing

* Fixed test diffs

* Formatting and highlighting

* Cleaned

* Moved around Java structure

* Cleaned

* Corrected docs

* Fixed metadata deep feature call

* Fixed another metadata bug
  • Loading branch information
SimonCockx authored Jun 6, 2024
1 parent c61abce commit 0c75e69
Show file tree
Hide file tree
Showing 40 changed files with 1,047 additions and 265 deletions.
36 changes: 36 additions & 0 deletions docs/rune-modelling-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,38 @@ type Vehicle extends VehicleFeature:
For clarity purposes, the documentation snippets omit the annotations and definitions that are associated with the data types and attributes, unless the purpose of the snippet is to highlight some of those features.
{{< /notice >}}
### Choice Type
#### Purpose
A *choice type* let you describe a group of different types which are somehow related. It consists of a list of types, each of which is an option to choose from.
In other languages, a choice type is often called a *union type*.
#### Syntax
A choice type is defined using the keyword `choice`, followed by a name and a list of option types. Similar to data types, it can also comprise a description and annotations.
``` Haskell
choice <TypeName>: <"Description">
[<annotation1>]
[<annotation2>]
[...]
<Type1>
<Type2>
<...>
```
For example:
``` Haskell
choice Vehicle:
[metadata key]
Car
Bicycle
Motorcycle
```
### Enumeration
#### Purpose
Expand Down Expand Up @@ -792,6 +824,10 @@ In the above example, if `drivingLicense` is null, the final `penaltyPoints` att

A null value for an expression with multiple cardinality is treated as an empty [list](#list).

#### Deep Paths for Choice Types

For choice types, common attributes can be accessed using a *deep path operator* `->>`. Given a choice type called `Vehicle`, each of its options exposing an attribute `vehicleId`, this identifier can be accessed directly using `vehicle ->> vehicleId`.

### Operators

#### Purpose
Expand Down
27 changes: 27 additions & 0 deletions rosetta-ide/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,33 @@
</resources>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets combine.children="append">
<!-- vscode -->
<fileset>
<directory>${basedir}/vscode/src/rosetta</directory>
<includes>
<include>**/*</include>
</includes>
</fileset>
<fileset>
<directory>${basedir}/vscode/syntaxes</directory>
<includes>
<include>**/*</include>
</includes>
</fileset>
<fileset>
<directory>${basedir}/vscode/out</directory>
<includes>
<include>**/*</include>
</includes>
</fileset>
</filesets>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.xtend</groupId>
<artifactId>xtend-maven-plugin</artifactId>
Expand Down
40 changes: 38 additions & 2 deletions rosetta-ide/rosetta.tmLanguage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ variables:
# - keywords that are unambiguous, i.e., all other keywords indicating the start of a root element.
identifiersConflictingWithRootStart: '{{wordStart}}(version){{wordEnd}}'
ambiguousRootStart: '{{wordStart}}(synonym|rule){{wordEnd}}'
unambiguousRootStart: '{{wordStart}}(namespace|import|isEvent|isProduct|body|corpus|segment|basicType|recordType|typeAlias|library|reporting|eligibility|metaType|report|annotation|enum|type|func){{wordEnd}}'
unambiguousRootStart: '{{wordStart}}(namespace|import|isEvent|isProduct|body|corpus|segment|basicType|recordType|typeAlias|library|reporting|eligibility|metaType|report|annotation|enum|type|choice|func){{wordEnd}}'
rootStart: '{{identifiersConflictingWithRootStart}}|{{ambiguousRootStart}}|{{unambiguousRootStart}}'
strictRootEnd: (?={{rootStart}})
rootEnd: (?={{ambiguousRootStart}}|{{unambiguousRootStart}})
Expand All @@ -39,7 +39,7 @@ variables:
sectionEnd: (?={{sectionStart}}|{{rootStart}})(?!\bcondition\b)|(?=\bcondition\b\s*({{identifier}})?:)
functionalOperation: '{{wordStart}}(reduce|filter|map|extract|sort|min|max){{wordEnd}}'
listOperationWord: '{{functionalOperation}}|{{wordStart}}(single|multiple|exists|is|absent|only-element|count|flatten|distinct|reverse|first|last|sum){{wordEnd}}'
listOperation: ->|{{listOperationWord}}
listOperation: ->>|->|{{listOperationWord}}
synonymAnnotationSimpleSection: '{{wordStart}}(value|meta|definition|pattern|removeHtml|dateFormat|mapper|hint|merge|condition-func|condition-path){{wordEnd}}'
synonymAnnotationSection: '{{synonymAnnotationSimpleSection}}|{{wordStart}}(set){{wordEnd}}'
synonymAnnotationSectionEnd: (?={{synonymAnnotationSection}})|(?=\])|{{rootEnd}}
Expand Down Expand Up @@ -152,6 +152,7 @@ repository:
- include: '#enumerationDeclaration'
- include: '#annotationDeclaration'
- include: '#typeDeclaration'
- include: '#choiceDeclaration'
- include: '#functionDeclaration'

rosettaBody:
Expand Down Expand Up @@ -881,6 +882,32 @@ repository:
match: '{{identifier}}'
- include: '#comment'
- include: '#documentationAndAnnotationsFollowedByExpression'

choiceDeclaration:
name: meta.choice.rosetta
begin: '{{wordStart}}choice{{wordEnd}}'
beginCaptures:
0: { name: storage.type.choice.rosetta keyword.declaration.type.rosetta }
end: '{{rootEnd}}'
patterns:
- begin: (?<={{wordStart}}choice{{wordEnd}})
end: (:)|{{rootEnd}}
endCaptures:
1: { name: punctuation.separator.colon.rosetta }
patterns:
- include: '#comment'
- name: entity.name.type.choice.rosetta
match: '{{identifier}}'
- name: punctuation.separator.dot.rosetta
match: \.
- include: '#comment'
- include: '#documentation'
- include: '#annotation'
- include: '#choiceOption'

choiceOption:
patterns:
- include: '#typeCall'

functionDeclaration:
name: meta.function.rosetta
Expand Down Expand Up @@ -1081,6 +1108,15 @@ repository:
- include: '#comment'
- include: '#expression'
- include: '#expression'
- name: meta.deep-projection.rosetta
begin: ->>
beginCaptures:
0: { name: keyword.operator.rosetta }
end: '{{identifier}}'
endCaptures:
0: { name: variable.other.rosetta } # Need semantic tokens for meta members
patterns:
- include: '#comment'
- name: meta.projection.rosetta
begin: ->
beginCaptures:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference;
import com.regnosys.rosetta.rosetta.simple.AnnotationRef;
import com.regnosys.rosetta.rosetta.simple.Attribute;
import com.regnosys.rosetta.rosetta.simple.ChoiceOption;
import com.regnosys.rosetta.rosetta.simple.Data;
import com.regnosys.rosetta.rosetta.simple.Function;
import com.regnosys.rosetta.rosetta.simple.Operation;
Expand Down Expand Up @@ -158,6 +159,10 @@ public SemanticToken markRuleReference(RosettaRuleReference ruleRef) {
}

private SemanticToken markAttribute(EObject objectToMark, EStructuralFeature featureToMark, Attribute attribute) {
if (attribute instanceof ChoiceOption) {
return null;
}

RosettaSemanticTokenTypesEnum tokenType = null;
EReference containmentFeature = attribute.eContainmentFeature();
if (containmentFeature.equals(FUNCTION__INPUTS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
+ -> + [[9, 14] .. [9, 14]]
- -> - [[9, 14] .. [9, 14]]
-> -> -> [[9, 14] .. [9, 14]]
->> -> ->> [[9, 14] .. [9, 14]]
/ -> / [[9, 14] .. [9, 14]]
< -> < [[9, 14] .. [9, 14]]
<= -> <= [[9, 14] .. [9, 14]]
Expand Down Expand Up @@ -111,6 +112,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
basicType -> basicType [[7, 27] .. [7, 27]]
body -> body [[7, 27] .. [7, 27]]
calculationType -> calculationType [[7, 27] .. [7, 27]]
choice -> choice [[7, 27] .. [7, 27]]
condition -> condition [[7, 27] .. [7, 27]]
contains -> contains [[7, 27] .. [7, 27]]
corpus -> corpus [[7, 27] .. [7, 27]]
Expand Down Expand Up @@ -168,6 +170,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
+ -> + [[7, 27] .. [7, 27]]
- -> - [[7, 27] .. [7, 27]]
-> -> -> [[7, 27] .. [7, 27]]
->> -> ->> [[7, 27] .. [7, 27]]
/ -> / [[7, 27] .. [7, 27]]
< -> < [[7, 27] .. [7, 27]]
<= -> <= [[7, 27] .. [7, 27]]
Expand Down Expand Up @@ -241,6 +244,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
+ -> + [[6, 25] .. [6, 25]]
- -> - [[6, 25] .. [6, 25]]
-> -> -> [[6, 25] .. [6, 25]]
->> -> ->> [[6, 25] .. [6, 25]]
/ -> / [[6, 25] .. [6, 25]]
< -> < [[6, 25] .. [6, 25]]
<= -> <= [[6, 25] .. [6, 25]]
Expand Down
5 changes: 5 additions & 0 deletions rosetta-lang/model/RosettaExpression.xcore
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ class RosettaFeatureCall extends RosettaExpression {
refers RosettaFeature feature
}

class RosettaDeepFeatureCall extends RosettaExpression {
contains RosettaExpression receiver
refers Attribute feature
}

class RosettaConditionalExpression extends RosettaExpression {
contains RosettaExpression ^if
contains RosettaExpression ifthen
Expand Down
46 changes: 46 additions & 0 deletions rosetta-lang/model/RosettaSimple.xcore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import org.eclipse.emf.common.util.BasicEList
import com.regnosys.rosetta.rosetta.expression.RosettaExpression
import com.regnosys.rosetta.rosetta.RosettaAttributeReferenceSegment
import com.regnosys.rosetta.rosetta.RosettaCardinality
import com.regnosys.rosetta.rosetta.RosettaFactory
import com.regnosys.rosetta.rosetta.expression.ExpressionFactory

import org.eclipse.xtext.nodemodel.util.NodeModelUtils
import com.regnosys.rosetta.rosetta.RosettaPackage.Literals

abstract class RootElement extends RosettaRootElement, RosettaNamed, RosettaDefinable, Annotated {
}
Expand Down Expand Up @@ -70,6 +75,47 @@ class Data extends RosettaType, RootElement, References {
}
}


class Choice extends Data {
contains Condition[] _hardcodedConditions

op Condition[] getConditions() {
if (_hardcodedConditions.empty) {
val cond = SimpleFactory.eINSTANCE.createCondition
cond.name = "Choice"

val oneOf = ExpressionFactory.eINSTANCE.createOneOfOperation
oneOf.generated = true
cond.expression = oneOf

val item = ExpressionFactory.eINSTANCE.createRosettaImplicitVariable
item.name = "item"
item.generated = true
oneOf.argument = item

_hardcodedConditions.add(cond)
}
return _hardcodedConditions
}
}

class ChoiceOption extends Attribute {
contains RosettaCardinality _hardcodedCardinality

op String getName() {
NodeModelUtils.findNodesForFeature(typeCall, Literals.TYPE_CALL__TYPE).head.text.strip
}

op RosettaCardinality getCard() {
if (_hardcodedCardinality === null) {
_hardcodedCardinality = RosettaFactory.eINSTANCE.createRosettaCardinality
_hardcodedCardinality.inf = 0
_hardcodedCardinality.sup = 1
}
return _hardcodedCardinality
}
}

class Function extends RootElement, RosettaNamed, RosettaCallableWithArgs, References {
contains Attribute[] inputs
contains Attribute output
Expand Down
14 changes: 14 additions & 0 deletions rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ QualifiedNameWithWildcard:
RootElement:
Annotation
| Data
| Choice
| Function
;

Expand Down Expand Up @@ -79,6 +80,17 @@ Data:
conditions += Condition*
;

Choice:
'choice' RosettaNamed ':' RosettaDefinable?
(Annotations|ClassSynonyms)*

attributes += ChoiceOption*
;

ChoiceOption:
RosettaTyped
;

Attribute:
// @Compat remove `override`
(override ?= 'override')? RosettaNamed RosettaTyped card=RosettaCardinality RosettaDefinable?
Expand Down Expand Up @@ -623,6 +635,7 @@ ListOperation returns RosettaExpression:
(
=>(
({RosettaFeatureCall.receiver=current} '->' (=>feature=[RosettaFeature|ValidID])?)
|({RosettaDeepFeatureCall.receiver=current} '->>' (=>feature=[Attribute|ValidID])?)
|({RosettaExistsExpression.argument=current} (modifier=ExistsModifier)? operator='exists')
|({RosettaAbsentExpression.argument=current} 'is' operator='absent')
|({RosettaOnlyElement.argument=current} operator='only-element')
Expand Down Expand Up @@ -695,6 +708,7 @@ ListOperation returns RosettaExpression:
(
=>(
({RosettaFeatureCall.receiver=current} '->' (=>feature=[RosettaFeature|ValidID])?)
|({RosettaDeepFeatureCall.receiver=current} '->>' (=>feature=[Attribute|ValidID])?)
|({RosettaExistsExpression.argument=current} (modifier=ExistsModifier)? operator='exists')
|({RosettaAbsentExpression.argument=current} 'is' operator='absent')
|({RosettaOnlyElement.argument=current} operator='only-element')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.regnosys.rosetta.rosetta.expression.ComparisonOperation
import com.regnosys.rosetta.rosetta.expression.RosettaOperation
import com.regnosys.rosetta.rosetta.expression.ThenOperation
import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression
import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall

class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {

Expand Down Expand Up @@ -214,6 +215,11 @@ class RosettaExpressionFormatter extends AbstractRosettaFormatter2 {
expr.receiver.formatExpression(document, mode.stopChain)
}

private def dispatch void unsafeFormatExpression(RosettaDeepFeatureCall expr, extension IFormattableDocument document, FormattingMode mode) {
expr.regionFor.keyword('->>').surround[oneSpace]
expr.receiver.formatExpression(document, mode.stopChain)
}

private def dispatch void unsafeFormatExpression(RosettaLiteral expr, extension IFormattableDocument document, FormattingMode mode) {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ import com.regnosys.rosetta.rosetta.RosettaRule
import com.regnosys.rosetta.rosetta.RosettaReport
import com.regnosys.rosetta.generator.java.validator.ValidatorGenerator
import com.regnosys.rosetta.config.RosettaGeneratorsConfiguration
import com.regnosys.rosetta.generator.java.expression.DeepPathUtilGenerator
import com.regnosys.rosetta.utils.DeepFeatureCallUtil
import com.regnosys.rosetta.types.RDataType

/**
* Generates code from your model files on save.
Expand All @@ -63,6 +66,9 @@ class RosettaGenerator implements IGenerator2 {
@Inject extension RosettaFunctionExtensions
@Inject FunctionGenerator funcGenerator
@Inject ReportGenerator reportGenerator
@Inject DeepPathUtilGenerator deepPathUtilGenerator

@Inject DeepFeatureCallUtil deepFeatureCallUtil

@Inject
ResourceAwareFSAFactory fsaFactory;
Expand Down Expand Up @@ -166,6 +172,9 @@ class RosettaGenerator implements IGenerator2 {
// new
// validatorGenerator.generate(packages, fsa, it, version)
tabulatorGenerator.generate(fsa, it, Optional.empty)
if (deepFeatureCallUtil.isEligibleForDeepFeatureCall(new RDataType(it))) {
deepPathUtilGenerator.generate(fsa, it, version)
}
}
Function: {
if (!isDispatchingFunction) {
Expand Down
Loading

0 comments on commit 0c75e69

Please sign in to comment.