Skip to content

Commit

Permalink
Add support for Exp functional operator for Integers Decimals and Longs.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 659604138
  • Loading branch information
evan-gordon authored and copybara-github committed Aug 5, 2024
1 parent bcc03a2 commit 72ad288
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 1 deletion.
14 changes: 14 additions & 0 deletions interpreter/operator_arithmetic.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ func evalCeiling(_ model.IUnaryExpression, obj result.Value) (result.Value, erro
return result.New(int32(math.Ceil(val)))
}

// Exp(argument Decimal) Decimal
// https://cql.hl7.org/09-b-cqlreference.html#exp
// Integer and long overloads are implicitly converted to decimal.
func evalExpDecimal(_ model.IUnaryExpression, obj result.Value) (result.Value, error) {
if result.IsNull(obj) {
return result.New(nil)
}
val, err := result.ToFloat64(obj)
if err != nil {
return result.Value{}, err
}
return result.New(math.Exp(val))
}

// Floor(argument Decimal) Integer
// https://cql.hl7.org/09-b-cqlreference.html#floor
func evalFloor(_ model.IUnaryExpression, obj result.Value) (result.Value, error) {
Expand Down
7 changes: 7 additions & 0 deletions interpreter/operator_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ func (i *interpreter) unaryOverloads(m model.IUnaryExpression) ([]convert.Overlo
Result: evalCeiling,
},
}, nil
case *model.Exp:
return []convert.Overload[evalUnarySignature]{
{
Operands: []types.IType{types.Decimal},
Result: evalExpDecimal,
},
}, nil
case *model.Floor:
return []convert.Overload[evalUnarySignature]{
{
Expand Down
8 changes: 8 additions & 0 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ type Is struct {

var _ IUnaryExpression = &Is{}

// Exp is https://cql.hl7.org/04-logicalspecification.html#exp.
type Exp struct{ *UnaryExpression }

var _ IUnaryExpression = &Exp{}

// Negate is https://cql.hl7.org/04-logicalspecification.html#negate.
type Negate struct{ *UnaryExpression }

Expand Down Expand Up @@ -1242,6 +1247,9 @@ func (a *ToTime) GetName() string { return "ToTime" }
// GetName returns the name of the system operator.
func (a *CalculateAge) GetName() string { return "CalculateAge" }

// GetName returns the name of the system operator.
func (a *Exp) GetName() string { return "Exp" }

// GetName returns the name of the system operator.
func (a *Negate) GetName() string { return "Negate" }

Expand Down
12 changes: 12 additions & 0 deletions parser/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,18 @@ func (p *Parser) loadSystemOperators() error {
}
},
},
{
name: "Exp",
operands: [][]types.IType{{types.Decimal}},
model: func() model.IExpression {
return &model.Exp{
UnaryExpression: &model.UnaryExpression{
Expression: model.ResultType(types.Decimal),
},
}
},
},
// TODO: b/301606416 - Add support for Exp with Quantities, current behavior is ambiguous.
{
name: "Floor",
operands: [][]types.IType{{types.Decimal}},
Expand Down
10 changes: 10 additions & 0 deletions parser/operators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,16 @@ func TestBuiltInFunctions(t *testing.T) {
},
},
},
{
name: "Arithmetic Exp",
cql: "Exp(42.0)",
want: &model.Exp{
UnaryExpression: &model.UnaryExpression{
Operand: model.NewLiteral("42.0", types.Decimal),
Expression: model.ResultType(types.Decimal),
},
},
},
{
name: "Arithmetic Floor",
cql: "Floor(41.1)",
Expand Down
98 changes: 98 additions & 0 deletions tests/enginetests/operator_arithmetic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,104 @@ func TestCeiling(t *testing.T) {
}
}

func TestExp(t *testing.T) {
tests := []struct {
name string
cql string
wantModel model.IExpression
wantResult result.Value
}{
{
name: "Integer",
cql: "Exp(4)",
wantModel: &model.Exp{
UnaryExpression: &model.UnaryExpression{
Operand: &model.ToDecimal{
UnaryExpression: &model.UnaryExpression{
Operand: model.NewLiteral("4", types.Integer),
Expression: model.ResultType(types.Decimal),
},
},
Expression: model.ResultType(types.Decimal),
},
},
wantResult: newOrFatal(t, 54.598150033144236),
},
{
name: "Positive Integer",
cql: "Exp(2)",
wantResult: newOrFatal(t, 7.38905609893065),
},
{
name: "Minimum Integer",
cql: "Exp(-2147483648)",
wantResult: newOrFatal(t, 0.0),
},
{
name: "Long",
cql: "Exp(-4L)",
wantResult: newOrFatal(t, 0.01831563888873418),
},
{
name: "Positive Long",
cql: "Exp(2L)",
wantResult: newOrFatal(t, 7.38905609893065),
},
{
name: "Minimum Long",
cql: "Exp(-9223372036854775808L)",
wantResult: newOrFatal(t, 0.0),
},
{
name: "Decimal zero",
cql: "Exp(0.0)",
wantResult: newOrFatal(t, 1.0),
},
{
name: "Decimal negative one",
cql: "Exp(-1.0)",
wantResult: newOrFatal(t, 0.36787944117144233),
},
{
name: "Positive Decimal one",
cql: "Exp(1.0)",
wantResult: newOrFatal(t, 2.718281828459045),
},
{
name: "Minimum Decimal",
cql: "Exp(-99999999999999999999.99999999)",
wantResult: newOrFatal(t, 0.0),
},
{
name: "Null",
cql: "Exp(null as Decimal)",
wantResult: newOrFatal(t, nil),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p := newFHIRParser(t)
parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{})
if err != nil {
t.Fatalf("Parse returned unexpected error: %v", err)
}
if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" {
t.Errorf("Parse diff (-want +got):\n%s", diff)
}

results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p))
if err != nil {
t.Fatalf("Eval returned unexpected error: %v", err)
}

if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" {
t.Errorf("Eval diff (-want +got)\n%v", diff)
}
})
}
}

func TestFloor(t *testing.T) {
tests := []struct {
name string
Expand Down
3 changes: 2 additions & 1 deletion tests/spectests/exclusions/exclusions.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
"CqlArithmeticFunctionsTest.xml": XMLTestFileExclusions{
GroupExcludes: []string{
// TODO: b/342061715 - unsupported operators.
"Exp",
"HighBoundary",
"Log",
"LowBoundary",
Expand All @@ -69,6 +68,8 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
"Divide103",
"Multiply1CMBy2CM",
"Power2DToNeg2DEquivalence",
"Exp1", // Require Round support.
"ExpNeg1", // Require Round support.
"Ln1000", // Require Round support.
"Ln1000D", // Require Round support.
// TODO: b/342061606 - Unit conversion is not supported.
Expand Down

0 comments on commit 72ad288

Please sign in to comment.