Skip to content

Commit

Permalink
Add support for Floor() functional operator.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 646569924
  • Loading branch information
evan-gordon authored and copybara-github committed Jun 25, 2024
1 parent 6c0bc31 commit d8a423a
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 1 deletion.
16 changes: 16 additions & 0 deletions interpreter/operator_arithmetic.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ func evalCeiling(_ model.IUnaryExpression, obj result.Value) (result.Value, erro
return result.New(int32(math.Ceil(val)))
}

// Floor(argument Decimal) Integer
// https://cql.hl7.org/09-b-cqlreference.html#floor
func evalFloor(_ 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
}
if val < math.MinInt32 || val >= math.MaxInt32+1 {
return result.New(nil)
}
return result.New(int32(math.Floor(val)))
}

// op(left Integer, right Integer) Integer
// https://cql.hl7.org/09-b-cqlreference.html#add
// https://cql.hl7.org/09-b-cqlreference.html#subtract
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.Floor:
return []convert.Overload[evalUnarySignature]{
{
Operands: []types.IType{types.Decimal},
Result: evalFloor,
},
}, nil
case *model.Exists:
return []convert.Overload[evalUnarySignature]{
{
Expand Down
6 changes: 6 additions & 0 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,9 @@ type Abs struct{ *UnaryExpression }
// Ceiling is https://cql.hl7.org/04-logicalspecification.html#ceiling.
type Ceiling struct{ *UnaryExpression }

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

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

Expand Down Expand Up @@ -1123,6 +1126,9 @@ func (a *Abs) GetName() string { return "Abs" }
// GetName returns the name of the system operator.
func (a *Ceiling) GetName() string { return "Ceiling" }

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

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

Expand Down
11 changes: 11 additions & 0 deletions parser/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,17 @@ func (p *Parser) loadSystemOperators() error {
}
},
},
{
name: "Floor",
operands: [][]types.IType{{types.Decimal}},
model: func() model.IExpression {
return &model.Floor{
UnaryExpression: &model.UnaryExpression{
Expression: model.ResultType(types.Integer),
},
}
},
},
{
name: "Negate",
operands: [][]types.IType{{types.Integer}},
Expand Down
10 changes: 10 additions & 0 deletions parser/operators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,16 @@ func TestBuiltInFunctions(t *testing.T) {
},
},
},
{
name: "Arithmetic Floor",
cql: "Floor(41.1)",
want: &model.Floor{
UnaryExpression: &model.UnaryExpression{
Operand: model.NewLiteral("41.1", types.Decimal),
Expression: model.ResultType(types.Integer),
},
},
},
{
name: "Arithmetic Addition",
cql: "Add(1, 2)",
Expand Down
103 changes: 103 additions & 0 deletions tests/enginetests/operator_arithmetic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,109 @@ func TestCeiling(t *testing.T) {
}
}

func TestFloor(t *testing.T) {
tests := []struct {
name string
cql string
wantModel model.IExpression
wantResult result.Value
}{
{
name: "Decimal",
cql: "Floor(42.1)",
wantModel: &model.Floor{
UnaryExpression: &model.UnaryExpression{
Operand: model.NewLiteral("42.1", types.Decimal),
Expression: model.ResultType(types.Integer),
},
},
wantResult: newOrFatal(t, 42),
},
{
name: "Negative",
cql: "Floor(-2.1)",
wantResult: newOrFatal(t, -3),
},
{
name: "Zero",
cql: "Floor(0.0)",
wantResult: newOrFatal(t, 0),
},
{
name: "Integer",
cql: "Floor(2)",
wantResult: newOrFatal(t, 2),
},
{
name: "Minimum Integer",
cql: "Floor(-2147483648)",
wantResult: newOrFatal(t, -2147483648),
},
{
name: "Minimum Decimal out of range",
cql: "Floor(-99999999999999999999.99999999)",
wantResult: newOrFatal(t, nil),
},
{
name: "Maximum Decimal out of range",
cql: "Floor(99999999999999999999.99999999)",
wantResult: newOrFatal(t, nil),
},
{
name: "Just less than min int32",
cql: "Floor(-2147483648.5)",
wantResult: newOrFatal(t, nil),
},
{
name: "Just more than max int32",
cql: "Floor(2147483647.5)",
wantResult: newOrFatal(t, math.MaxInt32),
},
{
name: "More than one more than max int32",
cql: "Floor(2147483648.5)",
wantResult: newOrFatal(t, nil),
},
{
name: "equal to min int32",
cql: "Floor(-2147483648.0)",
wantResult: newOrFatal(t, math.MinInt32),
},
{
name: "equal to max int32",
cql: "Floor(2147483647.0)",
wantResult: newOrFatal(t, math.MaxInt32),
},
{
name: "Null",
cql: "Floor(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 TestAdd(t *testing.T) {
tests := []struct {
name string
Expand Down
1 change: 0 additions & 1 deletion tests/spectests/exclusions/exclusions.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
"CqlArithmeticFunctionsTest.xml": XMLTestFileExclusions{
GroupExcludes: []string{
// TODO: b/342061715 - unsupported operators.
"Floor",
"Exp",
"HighBoundary",
"Log",
Expand Down

0 comments on commit d8a423a

Please sign in to comment.