Skip to content

Commit

Permalink
819 switch operation (finos#829)
Browse files Browse the repository at this point in the history
* save

* build switch without enum completeness awareness

* implement switch with enum or literal condition only

* fix validation

* add validation test for mismatched switch conditions

* validate missing enum values

* add validation test for missing enum values with default present

* fix test

* fix switch generate

* update documentation with switch and update operator precendance

* update docs with review comments

* Rename CaseStatement to SwitchCase

* inline default switch case statement

* renames SwitchOperation.values to SwitchOperation.cases

* Handle switch statement with only default and no cases

* fix typo

* fix spacing

* fix spacing

* fix typo

* neaten up recursion logic

* take switch return type from context

* Show missing enum values on validation

* Prevent multi cardinality inputs

* Only allow basic and enum type switch inputs

* Improve invalid switch argument message

* Rename switch conditions to guards

* Test for handling multi cardinality results

* fix formatting

* fix whitespace diffs
  • Loading branch information
davidalk authored Sep 13, 2024
1 parent 07fe1a6 commit b0676e5
Show file tree
Hide file tree
Showing 14 changed files with 697 additions and 78 deletions.
39 changes: 32 additions & 7 deletions docs/rune-modelling-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -912,20 +912,45 @@ Rune supports basic arithmetic operators

The `default` operator takes two values of matching type. If the value to the left of the default is empty then the value of to the right of the default will be returned. Note that the type and cardinality of both sides of the operator must match for the syntax to be valid.

### Switch Operator

The `switch` operator takes as its left hand input an argument on which to perform case analysis. The right side of the operator takes a set of case statements which define a return value for the expression when matching that case to the input.

```Haskell
"valueB" switch
"valueA" then "resultA"
"valueB" then "resultB"
default "resultC"
```

The `switch` operator can also operate over enumerations and in the case where all enumeration values are not provided as case statements then a syntax validation error will occur until either all enumeration values or a default value is provided.

```Haskell
"aCondition" switch
"aCondition" then SomeEnum -> A,
"bCondition" then SomeEnum -> B,
"cCondition" then SomeEnum -> C,
default SomeEnum -> D
```

{{< notice info "Note" >}}
The `default` case is optional, in case when there is no match then the `empty` value is returned.
{{< /notice >}}

#### Operator Precedence

Expressions are evaluated in Rune in the following order, from first to last - see [Operator Precedence](https://en.wikipedia.org/wiki/Order_of_operations)).

1. RosettaPathExpressions - e.g. `Lineage -> executionReference`
1. Brackets - e.g. `(1+2)`
1. if-then-else - e.g. `if (1=2) then 3`
1. only-element - e.g. `Lineage -> executionReference only-element`
1. count - e.g. `Lineage -> executionReference count`
1. Multiplicative operators `*`, `/` - e.g. `3*4`
1. Additive operators `+`, `-` - e.g. `3-4`
1. Comparison operators `>=`, `<=`, `>`, `<` - e.g. `3>4`
1. Existence operators `exists`,`is absent`, `contains`, `disjoint` - e.g. `Lineage -> executionReference exists`
1. Default operator `a default b`
1. Constructor expressions - e.g `MyType {attr1: "value 1"}`
1. Unary operators `->`, `->>`, `exists`, `is absent`, `only-element`, `flatten`, `distinct`, `reverse`, `first`, `last`, `sum`, `one-of`, `choice`, `to-string`, `to-number`, `to-int`, `to-time`, `to-enum`, `to-date`, `to-date-time`, `to-zoned-date-time`, `switch`, `sort`, `min`, `max`, `reduce`, `filter`, `map`, `extract`
1. Binary operators `contains`, `disjoint`, `default`, `join`
1. Multiplicative operators `*`, `/`
1. Additive operators `+`, `-`
1. Comparison operators `>=`, `<=`, `>`, `<`
1. Equality operators `=`, `<>`
1. and - e.g. `5>6 and true`
1. or - e.g. `5>6 or true`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
single -> single [[9, 14] .. [9, 14]]
sort -> sort [[9, 14] .. [9, 14]]
sum -> sum [[9, 14] .. [9, 14]]
switch -> switch [[9, 14] .. [9, 14]]
then -> then [[9, 14] .. [9, 14]]
to-date -> to-date [[9, 14] .. [9, 14]]
to-date-time -> to-date-time [[9, 14] .. [9, 14]]
Expand Down Expand Up @@ -154,6 +155,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
single -> single [[7, 27] .. [7, 27]]
sort -> sort [[7, 27] .. [7, 27]]
sum -> sum [[7, 27] .. [7, 27]]
switch -> switch [[7, 27] .. [7, 27]]
synonym -> synonym [[7, 27] .. [7, 27]]
then -> then [[7, 27] .. [7, 27]]
to-date -> to-date [[7, 27] .. [7, 27]]
Expand Down Expand Up @@ -231,6 +233,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
single -> single [[6, 25] .. [6, 25]]
sort -> sort [[6, 25] .. [6, 25]]
sum -> sum [[6, 25] .. [6, 25]]
switch -> switch [[6, 25] .. [6, 25]]
then -> then [[6, 25] .. [6, 25]]
to-date -> to-date [[6, 25] .. [6, 25]]
to-date-time -> to-date-time [[6, 25] .. [6, 25]]
Expand Down Expand Up @@ -425,6 +428,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest {
single -> single [[19, 2] .. [19, 2]]
sort -> sort [[19, 2] .. [19, 2]]
sum -> sum [[19, 2] .. [19, 2]]
switch -> switch [[19, 2] .. [19, 2]]
to-date -> to-date [[19, 2] .. [19, 2]]
to-date-time -> to-date-time [[19, 2] .. [19, 2]]
to-enum -> to-enum [[19, 2] .. [19, 2]]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,30 @@
package com.regnosys.rosetta.ide.tests

import org.eclipse.xtext.testing.AbstractLanguageServerTest
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtext.testing.TextDocumentPositionConfiguration
import org.eclipse.lsp4j.InlayHint
import com.google.inject.Module
import com.regnosys.rosetta.RosettaStandaloneSetup
import com.regnosys.rosetta.builtin.RosettaBuiltinsService
import com.regnosys.rosetta.ide.semantictokens.SemanticToken
import com.regnosys.rosetta.ide.server.RosettaLanguageServerImpl
import com.regnosys.rosetta.ide.server.RosettaServerModule
import com.regnosys.rosetta.ide.util.RangeUtils
import java.nio.charset.StandardCharsets
import java.util.HashMap
import java.util.List
import java.util.stream.Collectors
import javax.inject.Inject
import org.eclipse.lsp4j.DiagnosticSeverity
import org.eclipse.lsp4j.InlayHint
import org.eclipse.lsp4j.InlayHintParams
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.lsp4j.Range
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import org.eclipse.lsp4j.SemanticTokensParams
import javax.inject.Inject
import com.regnosys.rosetta.ide.semantictokens.SemanticToken
import com.regnosys.rosetta.ide.server.RosettaLanguageServerImpl
import org.eclipse.xtext.testing.TextDocumentConfiguration
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtext.testing.AbstractLanguageServerTest
import org.eclipse.xtext.testing.FileInfo
import java.nio.charset.StandardCharsets
import com.regnosys.rosetta.ide.util.RangeUtils
import java.util.stream.Collectors
import org.eclipse.xtext.testing.TextDocumentConfiguration
import org.eclipse.xtext.testing.TextDocumentPositionConfiguration
import org.junit.jupiter.api.Assertions
import com.regnosys.rosetta.builtin.RosettaBuiltinsService
import com.regnosys.rosetta.ide.server.RosettaServerModule
import com.google.inject.Module
import org.eclipse.lsp4j.DiagnosticSeverity
import com.regnosys.rosetta.RosettaStandaloneSetup
import java.util.HashMap
import org.eclipse.emf.ecore.EPackage
import org.eclipse.emf.ecore.EValidator
import com.regnosys.rosetta.rosetta.RosettaPackage
import com.regnosys.rosetta.rosetta.simple.SimplePackage
import com.regnosys.rosetta.rosetta.expression.ExpressionPackage

/**
* TODO: contribute to Xtext.
Expand Down
14 changes: 14 additions & 0 deletions rosetta-lang/model/RosettaExpression.xcore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.regnosys.rosetta.rosetta.RosettaMapTestExpression
import com.regnosys.rosetta.rosetta.RosettaTyped
import com.regnosys.rosetta.rosetta.simple.Attribute
import org.eclipse.emf.common.util.BasicEList
import com.regnosys.rosetta.rosetta.RosettaEnumValue


interface RosettaExpression {
Expand Down Expand Up @@ -406,3 +407,16 @@ class MinOperation extends ComparingFunctionalOperation, ListOperation {
class MaxOperation extends ComparingFunctionalOperation, ListOperation {
}


class SwitchOperation extends RosettaUnaryOperation {
contains SwitchCase[] cases opposite switchOperation
contains RosettaExpression ^default
}

class SwitchCase {
container SwitchOperation switchOperation opposite cases
contains RosettaLiteral literalGuard
refers RosettaEnumValue enumGuard
contains RosettaExpression expression
}

11 changes: 9 additions & 2 deletions rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext
Original file line number Diff line number Diff line change
Expand Up @@ -630,15 +630,19 @@ RosettaCalcBinary returns RosettaExpression:
)
;


/**
* List operations
* Unary operations
*/
enum ExistsModifier:
SINGLE='single'
| MULTIPLE='multiple'
;

SwitchCase:
(literalGuard=RosettaLiteral | enumGuard=[RosettaEnumValue|ValidID]) 'then' expression=RosettaCalcExpression
;


UnaryOperation returns RosettaExpression:
RosettaCalcPrimary
(
Expand All @@ -665,6 +669,7 @@ UnaryOperation returns RosettaExpression:
|({ToDateOperation.argument=current} operator='to-date')
|({ToDateTimeOperation.argument=current} operator='to-date-time')
|({ToZonedDateTimeOperation.argument=current} operator='to-zoned-date-time')
|({SwitchOperation.argument=current} operator='switch' ((cases+=SwitchCase =>(',' cases+=SwitchCase)* =>(',' 'default' default=RosettaCalcExpression)?) | ('default' default=RosettaCalcExpression)))
)
| =>(
({SortOperation.argument=current} operator='sort')
Expand Down Expand Up @@ -701,6 +706,7 @@ UnaryOperation returns RosettaExpression:
|({ToDateOperation} operator='to-date')
|({ToDateTimeOperation} operator='to-date-time')
|({ToZonedDateTimeOperation} operator='to-zoned-date-time')
|({SwitchOperation} operator='switch' ((cases+=SwitchCase =>(',' cases+=SwitchCase)* =>(',' 'default' default=RosettaCalcExpression)?) | ('default' default=RosettaCalcExpression)))
)
| (
({SortOperation} operator='sort')
Expand Down Expand Up @@ -738,6 +744,7 @@ UnaryOperation returns RosettaExpression:
|({ToDateOperation.argument=current} operator='to-date')
|({ToDateTimeOperation.argument=current} operator='to-date-time')
|({ToZonedDateTimeOperation.argument=current} operator='to-zoned-date-time')
|({SwitchOperation.argument=current} operator='switch' ((cases+=SwitchCase =>(',' cases+=SwitchCase)* =>(',' 'default' default=RosettaCalcExpression)?) | ('default' default=RosettaCalcExpression)))
)
| =>(
({SortOperation.argument=current} operator='sort')
Expand Down
Loading

0 comments on commit b0676e5

Please sign in to comment.