Skip to content

Commit

Permalink
Add support for IndexOf() functional operator.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 705618539
  • Loading branch information
evan-gordon authored and copybara-github committed Dec 12, 2024
1 parent 9cd34c6 commit 9e91ff1
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 1 deletion.
7 changes: 7 additions & 0 deletions interpreter/operator_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,13 @@ func (i *interpreter) binaryOverloads(m model.IBinaryExpression) ([]convert.Over
Result: i.evalIndexerList,
},
}, nil
case *model.IndexOf:
return []convert.Overload[evalBinarySignature]{
{
Operands: []types.IType{&types.List{ElementType: types.Any}, types.Any},
Result: evalIndexOf,
},
}, nil
default:
return nil, fmt.Errorf("unsupported Binary Expression %v", m.GetName())
}
Expand Down
22 changes: 22 additions & 0 deletions interpreter/operator_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@ func evalLast(m model.IUnaryExpression, listObj result.Value) (result.Value, err
return list[len(list)-1], nil
}

// IndexOf(argument List<T>, element T) Integer
// https://cql.hl7.org/09-b-cqlreference.html#indexof
func evalIndexOf(m model.IBinaryExpression, listObj, valueObj result.Value) (result.Value, error) {
if result.IsNull(listObj) || result.IsNull(valueObj) {
return result.New(nil)
}
list, err := result.ToSlice(listObj)
if err != nil {
return result.Value{}, err
}
if len(list) == 0 {
return result.New(int32(-1))
}

for i, elemObj := range list {
if valueObj.Equal(elemObj) {
return result.New(int32(i))
}
}
return result.New(int32(-1))
}

// singleton from(argument List<T>) T
// https://cql.hl7.org/09-b-cqlreference.html#singleton-from
func evalSingletonFrom(m model.IUnaryExpression, listObj result.Value) (result.Value, error) {
Expand Down
8 changes: 8 additions & 0 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,11 @@ type Split struct{ *BinaryExpression }
// Indexer ELM Expression https://cql.hl7.org/04-logicalspecification.html#indexer.
type Indexer struct{ *BinaryExpression }

// IndexOf ELM Expression https://cql.hl7.org/04-logicalspecification.html#indexof.
// IndexOf is an OperatorExpression in ELM, but we're modeling it as a BinaryExpression since in CQL
// it always takes two arguments.
type IndexOf struct{ *BinaryExpression }

// BinaryExpressionWithPrecision represents a BinaryExpression with a precision property.
type BinaryExpressionWithPrecision struct {
*BinaryExpression
Expand Down Expand Up @@ -1453,6 +1458,9 @@ func (a *Combine) GetName() string { return "Combine" }
// GetName returns the name of the system operator.
func (i *Indexer) GetName() string { return "Indexer" }

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

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

Expand Down
13 changes: 13 additions & 0 deletions parser/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,19 @@ func (p *Parser) loadSystemOperators() error {
}
},
},
{
name: "IndexOf",
operands: [][]types.IType{
{&types.List{ElementType: types.Any}, types.Any},
},
model: func() model.IExpression {
return &model.IndexOf{
BinaryExpression: &model.BinaryExpression{
Expression: model.ResultType(types.Integer),
},
}
},
},
{
name: "Last",
operands: [][]types.IType{
Expand Down
13 changes: 13 additions & 0 deletions parser/operators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,19 @@ func TestBuiltInFunctions(t *testing.T) {
},
},
},
{
name: "IndexOf",
cql: "IndexOf({1, 2, 3}, 1)",
want: &model.IndexOf{
BinaryExpression: &model.BinaryExpression{
Operands: []model.IExpression{
model.NewList([]string{"1", "2", "3"}, types.Integer),
model.NewLiteral("1", types.Integer),
},
Expression: model.ResultType(types.Integer),
},
},
},
{
name: "Last",
cql: "Last({1})",
Expand Down
78 changes: 78 additions & 0 deletions tests/enginetests/operator_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,84 @@ func TestLast(t *testing.T) {
}
}

func TestIndexOf(t *testing.T) {
tests := []struct {
name string
cql string
wantModel model.IExpression
wantResult result.Value
wantSourceExpression model.IExpression
wantSourceValues []result.Value
}{
{
name: "IndexOf({1, 2}, 2) = 1",
cql: "IndexOf({1, 2}, 2)",
wantModel: &model.IndexOf{
BinaryExpression: &model.BinaryExpression{
Operands: []model.IExpression{
model.NewList([]string{"1", "2"}, types.Integer),
model.NewLiteral("2", types.Integer),
},
Expression: model.ResultType(types.Integer),
},
},
wantResult: newOrFatal(t, int32(1)),
},
{
name: "IndexOf(List<Integer>{}, 1) = -1",
cql: "IndexOf(List<Integer>{}, 1)",
wantResult: newOrFatal(t, -1),
},
{
name: "IndexOf(null as List<Integer>, 1) = null",
cql: "IndexOf(null as List<Integer>, 1)",
wantResult: newOrFatal(t, nil),
},
{
name: "IndexOf({1, 2}, null as Integer) = null",
cql: "IndexOf({1, 2}, null as Integer)",
wantResult: newOrFatal(t, nil),
},
{
name: "IndexOf({1, 2}, 3) = -1",
cql: "IndexOf({1, 2}, 3)",
wantResult: newOrFatal(t, -1),
},
{
name: "IndexOf({@2010, @2011, @2012}, @2011) = 1",
cql: "IndexOf({@2010, @2011, @2012}, @2011)",
wantResult: newOrFatal(t, 1),
},
}
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)
}
gotResult := getTESTRESULTWithSources(t, results)
if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" {
t.Errorf("Eval returned diff (-want +got)\n%v", diff)
}
if diff := cmp.Diff(tc.wantSourceExpression, gotResult.SourceExpression(), protocmp.Transform()); tc.wantSourceExpression != nil && diff != "" {
t.Errorf("Eval SourceExpression diff (-want +got)\n%v", diff)
}
if diff := cmp.Diff(tc.wantSourceValues, gotResult.SourceValues(), protocmp.Transform()); tc.wantSourceValues != nil && diff != "" {
t.Errorf("Eval SourceValues diff (-want +got)\n%v", diff)
}
})
}
}

func TestSingletonFrom(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 @@ -344,7 +344,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
"Flatten",
"Includes",
"IncludedIn",
"IndexOf",
"Intersect",
"Length",
"ProperContains",
Expand Down

0 comments on commit 9e91ff1

Please sign in to comment.