Skip to content

Commit 7f05915

Browse files
evan-gordoncopybara-github
authored andcommitted
Add support for Width operator.
PiperOrigin-RevId: 767518139
1 parent 31bc59e commit 7f05915

File tree

8 files changed

+202
-44
lines changed

8 files changed

+202
-44
lines changed

interpreter/operator_dispatcher.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,29 @@ func (i *interpreter) unaryOverloads(m model.IUnaryExpression) ([]convert.Overlo
790790
Result: evalLower,
791791
},
792792
}, nil
793+
case *model.Width:
794+
return []convert.Overload[evalUnarySignature]{
795+
{
796+
Operands: []types.IType{&types.Interval{PointType: types.Any}},
797+
Result: evalWidthInterval,
798+
},
799+
{
800+
Operands: []types.IType{&types.Interval{PointType: types.Decimal}},
801+
Result: evalWidthInterval,
802+
},
803+
{
804+
Operands: []types.IType{&types.Interval{PointType: types.Integer}},
805+
Result: evalWidthInterval,
806+
},
807+
{
808+
Operands: []types.IType{&types.Interval{PointType: types.Long}},
809+
Result: evalWidthInterval,
810+
},
811+
{
812+
Operands: []types.IType{&types.Interval{PointType: types.Quantity}},
813+
Result: evalWidthInterval,
814+
},
815+
}, nil
793816
default:
794817
return nil, fmt.Errorf("unsupported Unary Expression %v", m.GetName())
795818
}

interpreter/operator_interval.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/google/cql/model"
2222
"github.com/google/cql/result"
2323
"github.com/google/cql/types"
24+
"github.com/google/cql/ucum"
2425
)
2526

2627
// INTERVAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#interval-operators-3
@@ -579,3 +580,64 @@ func inInterval(lowCompare, highCompare comparison, lowInclusive, highInclusive
579580
// Date(2020) in Interval[Date(2020, 3), Date(2020, 4)]
580581
return result.New(nil)
581582
}
583+
584+
// width of(argument Interval<T>) T
585+
// https://cql.hl7.org/09-b-cqlreference.html#width
586+
func evalWidthInterval(m model.IUnaryExpression, intervalObj result.Value) (result.Value, error) {
587+
if result.IsNull(intervalObj) {
588+
return result.New(nil)
589+
}
590+
interval, err := result.ToInterval(intervalObj)
591+
if err != nil {
592+
return result.Value{}, err
593+
}
594+
if interval.StaticType.PointType == types.Date || interval.StaticType.PointType == types.DateTime || interval.StaticType.PointType == types.Time {
595+
return result.Value{}, fmt.Errorf("width operator does not support Date or Time types")
596+
}
597+
start, err := start(intervalObj, nil)
598+
if err != nil {
599+
return result.Value{}, err
600+
}
601+
if result.IsNull(start) {
602+
return result.New(nil)
603+
}
604+
end, err := end(intervalObj, nil)
605+
if err != nil {
606+
return result.Value{}, err
607+
}
608+
if result.IsNull(end) {
609+
return result.New(nil)
610+
}
611+
switch start.RuntimeType() {
612+
case types.Decimal:
613+
startVal, endVal, err := applyToValues(start, end, result.ToFloat64)
614+
if err != nil {
615+
return result.Value{}, err
616+
}
617+
return result.New(endVal - startVal)
618+
case types.Integer:
619+
startVal, endVal, err := applyToValues(start, end, result.ToInt32)
620+
if err != nil {
621+
return result.Value{}, err
622+
}
623+
return result.New(endVal - startVal)
624+
case types.Long:
625+
startVal, endVal, err := applyToValues(start, end, result.ToInt64)
626+
if err != nil {
627+
return result.Value{}, err
628+
}
629+
return result.New(endVal - startVal)
630+
case types.Quantity:
631+
startVal, endVal, err := applyToValues(start, end, result.ToQuantity)
632+
if err != nil {
633+
return result.Value{}, err
634+
}
635+
// for now naively convery left unit to right unit.
636+
convertedStartVal, err := ucum.ConvertUnit(startVal.Value, string(startVal.Unit), string(endVal.Unit))
637+
if err != nil {
638+
return result.Value{}, err
639+
}
640+
return result.New(result.Quantity{Value: endVal.Value - convertedStartVal, Unit: endVal.Unit})
641+
}
642+
return result.Value{}, fmt.Errorf("internal error - unsupported point type in evalWidthInterval: %v", start.RuntimeType())
643+
}

parser/operator_expressions.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -558,21 +558,11 @@ func (v *visitor) VisitTimeBoundaryExpressionTerm(ctx *cql.TimeBoundaryExpressio
558558
}
559559

560560
func (v *visitor) VisitWidthExpressionTerm(ctx *cql.WidthExpressionTermContext) model.IExpression {
561-
// Get the interval operand
562-
intervalExpr := v.VisitExpression(ctx.GetChild(2))
563-
564-
// Return a UnaryExpression for Width as expected by the tests
565-
resultType := types.Integer
566-
if intervalType, ok := intervalExpr.GetResultType().(*types.Interval); ok {
567-
if intervalType.PointType == types.Date || intervalType.PointType == types.DateTime {
568-
resultType = types.Quantity
569-
}
570-
}
571-
572-
return &model.UnaryExpression{
573-
Operand: intervalExpr,
574-
Expression: model.ResultType(resultType),
561+
m, err := v.parseFunction("", "Width", []antlr.Tree{ctx.GetChild(2)}, false)
562+
if err != nil {
563+
return v.badExpression(err.Error(), ctx)
575564
}
565+
return m
576566
}
577567

578568
func (v *visitor) VisitSetAggregateExpressionTerm(ctx *cql.SetAggregateExpressionTermContext) model.IExpression {

parser/operator_expressions_test.go

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,29 +1086,17 @@ func TestOperatorExpressions(t *testing.T) {
10861086
{
10871087
name: "Width Expression Term",
10881088
cql: "width of Interval[1, 10]",
1089-
want: &model.UnaryExpression{
1090-
Operand: &model.Interval{
1091-
Low: model.NewLiteral("1", types.Integer),
1092-
High: model.NewLiteral("10", types.Integer),
1093-
Expression: model.ResultType(&types.Interval{PointType: types.Integer}),
1094-
LowInclusive: true,
1095-
HighInclusive: true,
1096-
},
1097-
Expression: model.ResultType(types.Integer),
1098-
},
1099-
},
1100-
{
1101-
name: "Width Expression Term with Dates",
1102-
cql: "width of Interval[@2010-01-01, @2020-01-01]",
1103-
want: &model.UnaryExpression{
1104-
Operand: &model.Interval{
1105-
Low: model.NewLiteral("@2010-01-01", types.Date),
1106-
High: model.NewLiteral("@2020-01-01", types.Date),
1107-
Expression: model.ResultType(&types.Interval{PointType: types.Date}),
1108-
LowInclusive: true,
1109-
HighInclusive: true,
1089+
want: &model.Width{
1090+
UnaryExpression: &model.UnaryExpression{
1091+
Operand: &model.Interval{
1092+
Low: model.NewLiteral("1", types.Integer),
1093+
High: model.NewLiteral("10", types.Integer),
1094+
Expression: model.ResultType(&types.Interval{PointType: types.Integer}),
1095+
LowInclusive: true,
1096+
HighInclusive: true,
1097+
},
1098+
Expression: model.ResultType(types.Integer),
11101099
},
1111-
Expression: model.ResultType(types.Quantity),
11121100
},
11131101
},
11141102
{

parser/operators.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ func (v *visitor) resolveFunction(libraryName, funcName string, operands []model
120120
return nil, err
121121
}
122122
t.Expression = model.ResultType(&types.List{ElementType: listElemType})
123+
case *model.Width:
124+
pointType := resolved.WrappedOperands[0].GetResultType().(*types.Interval)
125+
t.Expression = model.ResultType(pointType.PointType)
123126
case *model.End:
124127
pointType := resolved.WrappedOperands[0].GetResultType().(*types.Interval)
125128
t.Expression = model.ResultType(pointType.PointType)
@@ -269,20 +272,15 @@ func (p *Parser) loadSystemOperators() error {
269272
{
270273
name: "Width",
271274
operands: [][]types.IType{
275+
{&types.Interval{PointType: types.Any}},
272276
{&types.Interval{PointType: types.Integer}},
273277
{&types.Interval{PointType: types.Long}},
274278
{&types.Interval{PointType: types.Decimal}},
275279
{&types.Interval{PointType: types.Quantity}},
276-
{&types.Interval{PointType: types.String}},
277-
{&types.Interval{PointType: types.Date}},
278-
{&types.Interval{PointType: types.DateTime}},
279-
{&types.Interval{PointType: types.Time}},
280280
},
281281
model: func() model.IExpression {
282282
return &model.Width{
283-
UnaryExpression: &model.UnaryExpression{
284-
Expression: model.ResultType(types.Integer),
285-
},
283+
UnaryExpression: &model.UnaryExpression{},
286284
}
287285
},
288286
},

parser/operators_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,22 @@ func TestBuiltInFunctions(t *testing.T) {
13241324
},
13251325
},
13261326
},
1327+
{
1328+
name: "Width",
1329+
cql: "Width(Interval[1, 4])",
1330+
want: &model.Width{
1331+
UnaryExpression: &model.UnaryExpression{
1332+
Operand: &model.Interval{
1333+
Low: model.NewLiteral("1", types.Integer),
1334+
High: model.NewLiteral("4", types.Integer),
1335+
Expression: model.ResultType(&types.Interval{PointType: types.Integer}),
1336+
LowInclusive: true,
1337+
HighInclusive: true,
1338+
},
1339+
Expression: model.ResultType(types.Integer),
1340+
},
1341+
},
1342+
},
13271343
// LIST OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#list-operators-2
13281344
{
13291345
name: "Except",

tests/enginetests/operator_interval_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,88 @@ func TestIntervalContains(t *testing.T) {
18481848
}
18491849
}
18501850

1851+
func TestIntervalWidth(t *testing.T) {
1852+
tests := []struct {
1853+
name string
1854+
cql string
1855+
wantModel model.IExpression
1856+
wantResult result.Value
1857+
}{
1858+
{
1859+
name: "width of inclusive interval",
1860+
cql: "width of Interval[1, 4]",
1861+
wantModel: &model.Width{
1862+
UnaryExpression: &model.UnaryExpression{
1863+
Operand: &model.Interval{
1864+
Low: model.NewLiteral("1", types.Integer),
1865+
High: model.NewLiteral("4", types.Integer),
1866+
LowInclusive: true,
1867+
HighInclusive: true,
1868+
Expression: model.ResultType(&types.Interval{PointType: types.Integer}),
1869+
},
1870+
Expression: model.ResultType(types.Integer),
1871+
},
1872+
},
1873+
wantResult: newOrFatal(t, 3),
1874+
},
1875+
{
1876+
name: "width of interval non-inclusive bounds",
1877+
cql: "width of Interval(1, 4)",
1878+
wantResult: newOrFatal(t, 1),
1879+
},
1880+
{
1881+
name: "width of interval with null bounds",
1882+
cql: "width of Interval(null, 4)",
1883+
wantResult: newOrFatal(t, nil),
1884+
},
1885+
{
1886+
name: "width of interval with null bounds",
1887+
cql: "width of Interval(1, null)",
1888+
wantResult: newOrFatal(t, nil),
1889+
},
1890+
{
1891+
name: "width of interval with null bounds",
1892+
cql: "width of Interval(null as Integer, null as Integer)",
1893+
wantResult: newOrFatal(t, nil),
1894+
},
1895+
{
1896+
name: "width of interval with decimal",
1897+
cql: "Round(width of Interval[1.0, 4.0], 1)",
1898+
wantResult: newOrFatal(t, 3.0),
1899+
},
1900+
{
1901+
name: "width of interval with long",
1902+
cql: "width of Interval[1L, 4L]",
1903+
wantResult: newOrFatal(t, int64(3)),
1904+
},
1905+
{
1906+
name: "width of interval with quantity",
1907+
cql: "width of Interval[1'cm', 4'cm']",
1908+
wantResult: newOrFatal(t, result.Quantity{Value: 3, Unit: "cm"}),
1909+
},
1910+
}
1911+
for _, tc := range tests {
1912+
t.Run(tc.name, func(t *testing.T) {
1913+
p := newFHIRParser(t)
1914+
parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{})
1915+
if err != nil {
1916+
t.Fatalf("Parse returned unexpected error: %v", err)
1917+
}
1918+
if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" {
1919+
t.Errorf("Parse diff (-want +got):\n%s", diff)
1920+
}
1921+
1922+
results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p))
1923+
if err != nil {
1924+
t.Fatalf("Eval returned unexpected error: %v", err)
1925+
}
1926+
if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" {
1927+
t.Errorf("Eval diff (-want +got)\n%v", diff)
1928+
}
1929+
})
1930+
}
1931+
}
1932+
18511933
func TestComparison_Error(t *testing.T) {
18521934
tests := []struct {
18531935
name string

tests/spectests/exclusions/exclusions.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
238238
"ProperlyIncludedIn",
239239
"Starts",
240240
"Union",
241-
"Width",
242241
},
243242
NamesExcludes: []string{
244243
// TODO: b/342061715 - unsupported operators.

0 commit comments

Comments
 (0)