internal/filter: add more tests, and adjust so they (mostly) pass

In particular:
- permit element-wise comparisons against an array
- correct time.Duration support
- permit fetching "seconds" field from a duration
- turn a.b on the right of a comparison into a string
- better though imperfect support for comparing interface values

Change-Id: I3c0a9792d755b5e2e24f05336f335c0cdf19ec1d
Reviewed-on: https://go-review.googlesource.com/c/oscar/+/630057
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/filter/eval.go b/internal/filter/eval.go
index 501efa5..32e5e58 100644
--- a/internal/filter/eval.go
+++ b/internal/filter/eval.go
@@ -9,6 +9,7 @@
 	"errors"
 	"fmt"
 	"iter"
+	"math"
 	"reflect"
 	"regexp"
 	"strconv"
@@ -111,6 +112,16 @@
 		return ev.has(e)
 	}
 
+	// Although it's not clearly documented,
+	// some existing implementations permit a > 3
+	// where a is a slice. This matches if any
+	// element of a is > 3. Since supporting this
+	// makes the evaluator less efficient and since
+	// it is not the common case, handle it specially.
+	if multi, _ := ev.isMultiple(e.left); multi {
+		return ev.multipleCompare(e)
+	}
+
 	left, leftType := ev.fieldValue(e.left)
 	if left == nil {
 		return ev.alwaysFalse()
@@ -218,6 +229,66 @@
 	return ev.alwaysFalse()
 }
 
+// isMultiple takes the left hand side of a comparison operation
+// and returns either true, nil if it contains multiple values,
+// or false, type if it does not. An expression contains
+// multiple values if it includes a reference to a slice or a map.
+func (ev *eval[T]) isMultiple(e Expr) (bool, reflect.Type) {
+	isMultipleType := func(typ reflect.Type) bool {
+		switch typ.Kind() {
+		case reflect.Map, reflect.Slice, reflect.Array:
+			return true
+		default:
+			return false
+		}
+	}
+
+	switch e := e.(type) {
+	case *nameExpr:
+		var typ reflect.Type
+		if sf, ok := fieldName(reflect.TypeFor[T](), e.name); ok {
+			typ = sf.Type
+		} else if cfn, ok := ev.functions[e.name]; ok {
+			typ = reflect.TypeOf(cfn).Out(0)
+		} else if e.isString {
+			typ = reflect.TypeFor[string]()
+		} else {
+			return false, nil
+		}
+
+		if isMultipleType(typ) {
+			return true, nil
+		}
+		return false, typ
+
+	case *memberExpr:
+		hmultiple, htype := ev.isMultiple(e.holder)
+		if hmultiple {
+			return true, nil
+		}
+		if htype == nil {
+			return false, nil
+		}
+		if htype.Kind() == reflect.Struct {
+			sf, ok := fieldName(htype, e.member)
+			if ok {
+				if isMultipleType(sf.Type) {
+					return true, nil
+				}
+				return false, sf.Type
+			}
+		}
+		return false, nil
+
+	case *functionExpr:
+		ev.msgs = append(ev.msgs, "isMultiple of functionExpr not implemented")
+		return false, nil
+
+	default:
+		return false, nil
+	}
+}
+
 // fieldValue takes the left hand side of a comparison operation.
 // It returns a function that evaluates to the value of the field,
 // and also returns the type of the field.
@@ -381,6 +452,20 @@
 		return fn, holderType.Elem()
 
 	default:
+		// Special case: we can get the "seconds" field of a
+		// time.Duration. For flexibility we permit that on
+		// any int64 field.
+		if strings.EqualFold(name, "seconds") && holderType.Kind() == reflect.Int64 {
+			fn := func(v reflect.Value) reflect.Value {
+				if !v.IsValid() {
+					return v
+				}
+				secs := time.Duration(v.Int()).Seconds()
+				return reflect.ValueOf(secs)
+			}
+			return fn, reflect.TypeFor[float64]()
+		}
+
 		ev.msgs = append(ev.msgs, fmt.Sprintf("%s: type %s has no fields (looking for field %s)", pos, holderType, name))
 		return nil, nil
 	}
@@ -446,14 +531,31 @@
 	case *functionExpr:
 		return ev.compareFunction(op, leftType, right)
 
+	case *memberExpr:
+		// An a.b on the right of a comparison is just the
+		// string "a.b".
+		var stringifyHolder func(e *memberExpr) string
+		stringifyHolder = func(e *memberExpr) string {
+			switch h := e.holder.(type) {
+			case *nameExpr:
+				return h.name
+			case *memberExpr:
+				return stringifyHolder(h) + "." + h.member
+			default:
+				panic("can't happen")
+			}
+		}
+		name := stringifyHolder(right) + "." + right.member
+		ne := &nameExpr{
+			name: name,
+			pos:  right.pos,
+		}
+		return ev.compareName(op, leftType, ne)
+
 	case *comparisonExpr:
 		ev.msgs = append(ev.msgs, fmt.Sprintf("%s: invalid comparison on right of comparison", right.pos))
 		return nil
 
-	case *memberExpr:
-		ev.msgs = append(ev.msgs, fmt.Sprintf("%s: invalid member expression on right of comparison", right.pos))
-		return nil
-
 	default:
 		panic("can't happen")
 	}
@@ -597,11 +699,11 @@
 		return fn
 	}
 
-	var cmpfn func(v reflect.Value) int
+	var cmpFn func(v reflect.Value) int
 	switch leftType.Kind() {
 	case reflect.Bool:
 		rbval := rval.Bool()
-		cmpfn = func(v reflect.Value) int {
+		cmpFn = func(v reflect.Value) int {
 			if v.Bool() {
 				if rbval {
 					return 0
@@ -617,50 +719,73 @@
 
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		rival := rval.Int()
-		cmpfn = func(v reflect.Value) int {
+		cmpFn = func(v reflect.Value) int {
 			return cmp.Compare(v.Int(), rival)
 		}
 
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 		ruval := rval.Uint()
-		cmpfn = func(v reflect.Value) int {
+		cmpFn = func(v reflect.Value) int {
 			return cmp.Compare(v.Uint(), ruval)
 		}
 
 	case reflect.Float32, reflect.Float64:
 		rfval := rval.Float()
-		cmpfn = func(v reflect.Value) int {
+		cmpFn = func(v reflect.Value) int {
 			return cmp.Compare(v.Float(), rfval)
 		}
+		cmpOp := ev.compareOp(op, cmpFn)
+		return func(v reflect.Value) bool {
+			if !v.IsValid() {
+				return false
+			}
+			// Comparisons with NaN are always false,
+			// except that we treat NaN == NaN as true.
+			if math.IsNaN(v.Float()) {
+				return math.IsNaN(rfval)
+			}
+			return cmpOp(v)
+		}
 
 	case reflect.String:
 		rsval := rval.String()
-		cmpfn = func(v reflect.Value) int {
+		cmpFn = func(v reflect.Value) int {
 			return strings.Compare(v.String(), rsval)
 		}
 
+	case reflect.Interface:
+		// This call to Convert won't panic.
+		// rval comes from parseLiteral,
+		// which verified that the conversion
+		// is OK using CanConvert.
+		rival := rval.Convert(leftType).Interface()
+		rsval := fmt.Sprint(rival)
+		cmpFn = func(v reflect.Value) int {
+			// This comparison can't panic.
+			// rival comes from parseLiteral,
+			// which only returns comparable types.
+			if v.Interface() == rival {
+				return 0
+			}
+			// Compare as strings. Seems to be the best we can do.
+			return strings.Compare(fmt.Sprint(v.Interface()), rsval)
+		}
+
 	case reflect.Struct:
 		if leftType == reflect.TypeFor[time.Time]() {
 			rtval := rval.Interface().(time.Time)
-			cmpfn = func(v reflect.Value) int {
+			cmpFn = func(v reflect.Value) int {
 				return v.Interface().(time.Time).Compare(rtval)
 			}
 			break
 		}
-		if leftType == reflect.TypeFor[time.Duration]() {
-			rdval := rval.Interface().(time.Duration)
-			cmpfn = func(v reflect.Value) int {
-				return cmp.Compare(v.Interface().(time.Duration), rdval)
-			}
-			break
-		}
 
 		fallthrough
 	default:
 		panic("can't happen")
 	}
 
-	return ev.compareOp(op, cmpfn)
+	return ev.compareOp(op, cmpFn)
 }
 
 // compareOp returns a function that takes a comparison function
@@ -702,6 +827,26 @@
 	return func(reflect.Value) bool { return false }
 }
 
+// multipleCompare returns a function that returns the result of a
+// comparison, where the left hand side of the comparison contains
+// multiple values.
+func (ev *eval[T]) multipleCompare(e *comparisonExpr) func(T) bool {
+	switch el := e.left.(type) {
+	case *nameExpr:
+		left, leftType := ev.fieldValue(el)
+		if left == nil {
+			return ev.alwaysFalse()
+		}
+		return ev.multipleName(e.op, left, leftType, e.right)
+
+	case *memberExpr:
+		return ev.multipleMember(e.pos, e.op, el, e.right)
+
+	default:
+		panic("can't happen")
+	}
+}
+
 // match returns a function that takes a value of type typ
 // and reports whether it is equal to, or contains, val.
 // This returns nil if the types can't match.
@@ -752,6 +897,24 @@
 			return strings.Contains(strings.ToLower(v.String()), esval)
 		}
 
+	case reflect.Interface:
+		// This call to Convert won't panic.
+		// eval comes from parseLiteral,
+		// which verified that the conversion
+		// is OK using CanConvert.
+		eival := eval.Convert(typ).Interface()
+		esval := fmt.Sprint(eival)
+		return func(v reflect.Value) bool {
+			// This comparison can't panic.
+			// rival comes from parseLiteral,
+			// which only returns comparable types.
+			if v.Interface() == eival {
+				return true
+			}
+			// Compare as strings. Seems to be the best we can do.
+			return fmt.Sprint(v.Interface()) == esval
+		}
+
 	default:
 		panic("can't happen")
 	}
@@ -835,7 +998,7 @@
 	var rval reflect.Value
 	switch typ.Kind() {
 	case reflect.Bool:
-		bval, err := strconv.ParseBool(val)
+		bval, err := strconv.ParseBool(strings.ToLower(val))
 		if err != nil {
 			return badParse(err)
 		}
@@ -844,6 +1007,17 @@
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		ival, err := strconv.ParseInt(val, 0, 64)
 		if err != nil {
+			if (strings.HasSuffix(val, "s") || strings.HasSuffix(val, "S")) && typ.Kind() == reflect.Int64 {
+				// Try parsing this as a duration.
+				d, err := time.ParseDuration(strings.ToLower(val))
+				if err != nil {
+					return badParse(err)
+				}
+
+				rval = reflect.ValueOf(d)
+				break
+			}
+
 			return badParse(err)
 		}
 		rval = reflect.ValueOf(ival)
@@ -882,17 +1056,6 @@
 			rval = reflect.ValueOf(tval)
 			break
 		}
-		if typ == reflect.TypeFor[time.Duration]() {
-			s, ok := strings.CutSuffix(val, "s")
-			if !ok {
-				return badParse(errors.New(`duration does not end in "s"`))
-			}
-			ival, err := strconv.ParseInt(s, 0, 64)
-			if err != nil {
-				return badParse(err)
-			}
-			rval = reflect.ValueOf(time.Duration(ival) * time.Second)
-		}
 
 		fallthrough
 	default:
@@ -920,7 +1083,7 @@
 		return ev.hasName(el, e.right)
 
 	case *memberExpr:
-		return ev.hasMember(e.pos, el, e.right)
+		return ev.multipleMember(e.pos, tokenHas, el, e.right)
 
 	case *binaryExpr, *unaryExpr, *comparisonExpr, *functionExpr:
 		ev.msgs = append(ev.msgs, "invalid expression on left of has operator")
@@ -956,52 +1119,25 @@
 			}
 		}
 
-		cmpFn := ev.hasCompareVal(leftType.Elem(), er)
-		if cmpFn == nil {
-			return ev.alwaysFalse()
-		}
-
-		return func(v T) bool {
-			mval := left(v)
-			if !mval.IsValid() {
-				return false
-			}
-			seq := func(yield func(reflect.Value) bool) {
-				mi := mval.MapRange()
-				for mi.Next() {
-					if !yield(mi.Value()) {
-						return
-					}
-				}
-			}
-			return cmpFn(seq)
-		}
+		return ev.multipleName(tokenHas, left, leftType, er)
 
 	case reflect.Slice, reflect.Array:
 		// This reports whether the slice contains an element
 		// that matches er.
 
-		cmpFn := ev.hasCompareVal(leftType.Elem(), er)
-		if cmpFn == nil {
-			return ev.alwaysFalse()
-		}
-
-		return func(v T) bool {
-			sval := left(v)
-			if !sval.IsValid() {
-				return false
-			}
-			seq := func(yield func(reflect.Value) bool) {
-				for i := range sval.Len() {
-					if !yield(sval.Index(i)) {
-						return
-					}
-				}
-			}
-			return cmpFn(seq)
-		}
+		return ev.multipleName(tokenHas, left, leftType, er)
 
 	case reflect.Struct:
+		if leftType == reflect.TypeFor[time.Time]() {
+			cmpFn := ev.compareVal(tokenHas, leftType, er)
+			if cmpFn == nil {
+				return ev.alwaysFalse()
+			}
+			return func(v T) bool {
+				return cmpFn(left(v))
+			}
+		}
+
 		// This reports whether the struct has a member
 		// that matches er. We don't need an iter.Seq for this case.
 
@@ -1035,7 +1171,55 @@
 	}
 }
 
-// holderEmptyStringType tells hasMemberHolder whether we are
+// multipleVal returns a function that reports whether the value
+// left, of type leftType, matches the value in er.
+// leftType is expected to have multiple values.
+func (ev *eval[T]) multipleName(op tokenKind, left func(T) reflect.Value, leftType reflect.Type, er Expr) func(T) bool {
+	cmpFn := ev.multipleCompareVal(op, leftType.Elem(), er)
+	if cmpFn == nil {
+		return ev.alwaysFalse()
+	}
+
+	switch leftType.Kind() {
+	case reflect.Map:
+		return func(v T) bool {
+			mval := left(v)
+			if !mval.IsValid() {
+				return false
+			}
+			seq := func(yield func(reflect.Value) bool) {
+				mi := mval.MapRange()
+				for mi.Next() {
+					if !yield(mi.Value()) {
+						return
+					}
+				}
+			}
+			return cmpFn(seq)
+		}
+
+	case reflect.Slice, reflect.Array:
+		return func(v T) bool {
+			sval := left(v)
+			if !sval.IsValid() {
+				return false
+			}
+			seq := func(yield func(reflect.Value) bool) {
+				for i := range sval.Len() {
+					if !yield(sval.Index(i)) {
+						return
+					}
+				}
+			}
+			return cmpFn(seq)
+		}
+
+	default:
+		panic("can't happen")
+	}
+}
+
+// holderEmptyStringType tells multipleMemberHolder whether we are
 // comparing against an empty string.
 type holderEmptyStringType bool
 
@@ -1044,15 +1228,15 @@
 	holderEmptyString    holderEmptyStringType = true
 )
 
-// hasMember returns a function that returns whether the member el
-// has the value in er.
-func (ev *eval[T]) hasMember(pos position, el *memberExpr, er Expr) func(T) bool {
+// multipleMember returns a function that returns whether the member el
+// matches the value in er.
+func (ev *eval[T]) multipleMember(pos position, op tokenKind, el *memberExpr, er Expr) func(T) bool {
 	emptyString := holderNonEmptyString
 	if erName, ok := er.(*nameExpr); ok && erName.isString && erName.name == "" {
 		emptyString = holderEmptyString
 	}
 
-	hfn, htype := ev.hasMemberHolder(el, emptyString)
+	hfn, htype := ev.multipleMemberHolder(el, emptyString)
 	if hfn == nil {
 		return ev.alwaysFalse()
 	}
@@ -1062,7 +1246,7 @@
 
 	switch htype.Kind() {
 	case reflect.Map:
-		cmpFn = ev.hasCompareVal(htype.Elem(), er)
+		cmpFn = ev.multipleCompareVal(op, htype.Elem(), er)
 		if cmpFn == nil {
 			return ev.alwaysFalse()
 		}
@@ -1081,7 +1265,7 @@
 		}
 
 	case reflect.Slice, reflect.Array:
-		cmpFn = ev.hasCompareVal(htype.Elem(), er)
+		cmpFn = ev.multipleCompareVal(op, htype.Elem(), er)
 		if cmpFn == nil {
 			return ev.alwaysFalse()
 		}
@@ -1101,7 +1285,7 @@
 	case reflect.Struct:
 		cmpStructFn := ev.compareStructFields(htype,
 			func(ftype reflect.Type) func(reflect.Value) bool {
-				return ev.compareVal(tokenHas, ftype, er)
+				return ev.compareVal(op, ftype, er)
 			},
 		)
 		if cmpStructFn == nil {
@@ -1122,7 +1306,7 @@
 		}
 
 	default:
-		cmpFn = ev.hasCompareVal(htype, er)
+		cmpFn = ev.multipleCompareVal(op, htype, er)
 		if cmpFn == nil {
 			return ev.alwaysFalse()
 		}
@@ -1140,8 +1324,8 @@
 	}
 }
 
-// hasMemberHolder is called when we are using the has operator
-// on an aggregate A, and we are fetching a field F from that aggregate.
+// multipleMemberHolder is called when we are comparing against
+// an aggregate A, and we are fetching a field F from that aggregate.
 // If A is a map then F is a key in the map.
 // If A is a slice then F is checked against each element of the slice.
 // If A is a struct then F is a field of the struct.
@@ -1150,13 +1334,13 @@
 // takes the value A and returns a sequence of reflect.Value's.
 // We also return the type of those reflect.Value's.
 // This returns nil, nil on failure.
-func (ev *eval[T]) hasMemberHolder(e *memberExpr, emptyString holderEmptyStringType) (func(iter.Seq[reflect.Value]) iter.Seq[reflect.Value], reflect.Type) {
+func (ev *eval[T]) multipleMemberHolder(e *memberExpr, emptyString holderEmptyStringType) (func(iter.Seq[reflect.Value]) iter.Seq[reflect.Value], reflect.Type) {
 	var hfn func(iter.Seq[reflect.Value]) iter.Seq[reflect.Value]
 	var htype reflect.Type
 
 	switch holder := e.holder.(type) {
 	case *memberExpr:
-		hfn, htype = ev.hasMemberHolder(holder, emptyString)
+		hfn, htype = ev.multipleMemberHolder(holder, emptyString)
 		if hfn == nil {
 			return nil, nil
 		}
@@ -1238,7 +1422,7 @@
 		}
 		return rhfn, ftype
 
-	case reflect.Struct:
+	default:
 		ffn, ftype := ev.fieldAccessor(e.pos, htype, e.member)
 		if ffn == nil {
 			return nil, nil
@@ -1257,24 +1441,20 @@
 			}
 		}
 		return rhfn, ftype
-
-	default:
-		ev.msgs = append(ev.msgs, fmt.Sprintf("%s: request for member %s in non-aggregate type %s", e.pos, e.member, htype))
-		return nil, nil
 	}
 }
 
-// hasCompareVal returns a function that takes a sequence of reflect.Value,
+// multipleCompareVal returns a function that takes a sequence of reflect.Value,
 // where each element of the sequence has type leftType,
-// and evaluate that sequence with the parameter right.
+// and evaluates that sequence with the parameter right.
 // The function may evaluate the sequence multiple times.
 // The right value may be a logical expression with AND and OR.
 // This returns nil on error.
-func (ev *eval[T]) hasCompareVal(leftType reflect.Type, right Expr) func(iter.Seq[reflect.Value]) bool {
+func (ev *eval[T]) multipleCompareVal(op tokenKind, leftType reflect.Type, right Expr) func(iter.Seq[reflect.Value]) bool {
 	switch right := right.(type) {
 	case *binaryExpr:
-		lf := ev.hasCompareVal(leftType, right.left)
-		rf := ev.hasCompareVal(leftType, right.right)
+		lf := ev.multipleCompareVal(op, leftType, right.left)
+		rf := ev.multipleCompareVal(op, leftType, right.right)
 		if lf == nil || rf == nil {
 			return nil
 		}
@@ -1292,7 +1472,7 @@
 		}
 
 	case *unaryExpr:
-		uf := ev.hasCompareVal(leftType, right.expr)
+		uf := ev.multipleCompareVal(op, leftType, right.expr)
 		if uf == nil {
 			return nil
 		}
@@ -1306,7 +1486,7 @@
 		}
 
 	default:
-		cmpFn := ev.compareVal(tokenHas, leftType, right)
+		cmpFn := ev.compareVal(op, leftType, right)
 		if cmpFn == nil {
 			return nil
 		}
diff --git a/internal/filter/eval_test.go b/internal/filter/eval_test.go
index 3260b9d..9ab2316 100644
--- a/internal/filter/eval_test.go
+++ b/internal/filter/eval_test.go
@@ -10,6 +10,8 @@
 	"os"
 	"path/filepath"
 	"slices"
+	"strconv"
+	"strings"
 	"testing"
 	"time"
 
@@ -98,6 +100,121 @@
 	runTests(t, tests.Tests, data.Resources)
 }
 
+type filterMiscTestData struct {
+	Resources []testMessage `json:"resources"`
+}
+
+type testMessage struct {
+	Int32Field                 int32                    `json:"int32_field"`
+	Int64Field                 int64                    `json:"int64_field"`
+	Uint32Field                uint32                   `json:"uint32_field"`
+	Uint64Field                uint64                   `json:"uint64_field"`
+	FloatField                 float32                  `json:"float_field"`
+	FloatInfinity              float32Special           `json:"float_infinity"`
+	FloatNegativeInfinity      float32Special           `json:"float_negative_infinity"`
+	FloatNaN                   float32Special           `json:"float_nan"`
+	DoubleField                float64Special           `json:"double_field"`
+	DoubleInfinity             float64Special           `json:"double_infinity"`
+	DoubleNegativeInfinity     float64Special           `json:"double_negative_infinity"`
+	DoubleNaN                  float64Special           `json:"double_nan"`
+	BoolField                  bool                     `json:"bool_field"`
+	StringField                string                   `json:"string_field"`
+	EnumField                  string                   `json:"enum_field"`
+	OutOfOrderEnumField        string                   `json:"out_of_order_enum_field"`
+	BytesField                 string                   `json:"bytes_field"`
+	NoValueField               string                   `json:"no_value_field"`
+	Nested                     nestedMessage            `json:"nested"`
+	DeprecatedField            int64                    `json:"deprecated_field"`
+	InternalField              string                   `json:"internal_field"`
+	RepeatedInt32Field         []int32                  `json:"repeated_int32_field"`
+	RepeatedStringField        []string                 `json:"repeated_string_field"`
+	NonUTF8StringField         string                   `json:"non_utf8_string_field"`
+	NonUTF8RepeatedStringField []string                 `json:"non_utf8_repeated_string_field"`
+	RepeatedEnumField          []string                 `json:"repeated_enum_field"`
+	RepeatedMessageField       []nestedMessage          `json:"repeated_message_field"`
+	RepeatedEmptyMessageField  []nestedMessage          `json:"repeated_empty_message_field"`
+	MapStringIn32Field         map[string]int32         `json:"map_string_int32_field"`
+	MapInt32Int32Field         map[int32]int32          `json:"map_int32_int32_field"`
+	MapStringNestedField       map[string]nestedMessage `json:"map_string_nested_field"`
+	AnyField                   any                      `json:"any_field"`
+	RepeatedAnyField           []any                    `json:"repeated_any_field"`
+	Timestamp                  time.Time                `json:"timestamp"`
+	Duration                   duration                 `json:"duration"`
+	StructField                map[string]any           `json:"struct_field"`
+	JSON                       map[string]any           `json:"json"`
+	StringValue                string                   `json:"string_value"`
+	NestedValue                nestedMessage            `json:"nested_value"`
+	UnicodePathe               string                   `json:"unicode_pathe"`
+	UnicodeResume              string                   `json:"unicode_resume"`
+	UnicodeUnicode             string                   `json:"unicode_unicode"`
+	URLField                   string                   `json:"url_field"`
+}
+
+type nestedMessage struct {
+	Uint32Field         uint32              `json:"uint32_field"`
+	DeeperNest          deeperNestedMessage `json:"deeper_nest"`
+	RepeatedUint32Field []uint32            `json:"repeated_uint32_field"`
+	RepeatedEmptyUint32 []uint32            `json:"repeated_empty_uint32"`
+	StringField         string              `json:"string_field"`
+	NovalueField        string              `json:"no_value_field"`
+}
+
+type deeperNestedMessage struct {
+	NiceField  string `json:"nice_field"`
+	NicerField string `json:"nicer_field"`
+}
+
+// float32Special is a version of float32 that supports JSON
+// unmarshaling of Infinity and NaN.
+type float32Special float32
+
+func (pf *float32Special) UnmarshalJSON(text []byte) error {
+	str := strings.Trim(string(text), `"`)
+	f, err := strconv.ParseFloat(str, 32)
+	if err != nil {
+		return err
+	}
+	*pf = float32Special(f)
+	return nil
+}
+
+// float64Special is a version of float64 that supports JSON
+// unmarshaling of Infinity and NaN.
+type float64Special float64
+
+func (pf *float64Special) UnmarshalJSON(text []byte) error {
+	str := strings.Trim(string(text), `"`)
+	f, err := strconv.ParseFloat(str, 64)
+	if err != nil {
+		return err
+	}
+	*pf = float64Special(f)
+	return nil
+}
+
+// duration is a version of time.Duration that supports JSON unmarshaling.
+type duration time.Duration
+
+func (pd *duration) UnmarshalJSON(text []byte) error {
+	str := strings.Trim(string(text), `"`)
+	d, err := time.ParseDuration(str)
+	if err != nil {
+		return err
+	}
+	*pd = duration(d)
+	return nil
+}
+
+func TestEvalMisc(t *testing.T) {
+	var data filterMiscTestData
+	unmarshalJSON(t, "misc_test.json", &data)
+
+	var tests yamlTests
+	unmarshalYAML(t, "misc_test.yaml", &tests)
+
+	runTests(t, tests.Tests, data.Resources)
+}
+
 // runTests runs a set of YAML tests on a set of input data.
 func runTests[T any](t *testing.T, tests []yamlTest, data []T) {
 	var desc string
@@ -112,6 +229,23 @@
 		}
 
 		if test.Skip {
+			// Check whether the test passes.
+			e, err := ParseFilter(test.Expr)
+			if err == nil {
+				eval, msgs := Evaluator[T](e, nil)
+				if len(msgs) == 0 && test.Error == "" {
+					var matches []int
+					for i, d := range data {
+						if eval(d) {
+							matches = append(matches, i+1)
+						}
+					}
+					if slices.Equal(matches, test.Matches) {
+						t.Errorf("%s %d passes unexpectedly", desc, idx)
+					}
+				}
+			}
+
 			idx++
 			continue
 		}
@@ -194,3 +328,64 @@
 		t.Fatal(err)
 	}
 }
+
+// Test that we don't panic on an incomparable literal.
+func TestIncomparable(t *testing.T) {
+	e1, err := ParseFilter("A:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	e2, err := ParseFilter("0")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	type Incomparable1 struct {
+		A [][]byte
+	}
+
+	// Building the evaluator should fail,
+	// because we can't compare 0 to []byte.
+	_, msgs := Evaluator[Incomparable1](e1, nil)
+	if len(msgs) == 0 {
+		t.Error("expected evaluator errors")
+	} else {
+		for _, msg := range msgs {
+			t.Log(msg)
+		}
+	}
+
+	eval, msgs := Evaluator[Incomparable1](e2, nil)
+	if len(msgs) != 0 {
+		t.Error("evaluator failed")
+		for _, msg := range msgs {
+			t.Log(msg)
+		}
+	} else {
+		// Running this should not panic.
+		if eval(Incomparable1{A: [][]byte{[]byte{1}}}) {
+			t.Error("unexpected match")
+		}
+	}
+
+	type Incomparable2 struct {
+		A []any
+	}
+
+	for i, expr := range []Expr{e1, e2} {
+		// This case should succeed, because we can compare 0 to any.
+		eval, msgs := Evaluator[Incomparable2](expr, nil)
+		if len(msgs) != 0 {
+			t.Errorf("%d: evaluator failed", i)
+			for _, msg := range msgs {
+				t.Logf("%d: %s", i, msg)
+			}
+		} else {
+			// Running this with an incomparable type in a
+			// should not panic.
+			if eval(Incomparable2{A: []any{[]byte{0}}}) {
+				t.Errorf("%d: unexpected match", i)
+			}
+		}
+	}
+}
diff --git a/internal/filter/lex.go b/internal/filter/lex.go
index 9069e82..8326b55 100644
--- a/internal/filter/lex.go
+++ b/internal/filter/lex.go
@@ -452,13 +452,14 @@
 	case '0', '1', '2', '3', '4', '5', '6', '7':
 		// A backslash sequence of 1-3 octal digits is an octal escape.
 		if len(lex.input) >= 3 && r <= '3' && isOctalDigit(rune(lex.input[1])) && isOctalDigit(rune(lex.input[2])) {
+			n1 := rune(lex.input[1])
+			n2 := rune(lex.input[2])
+			r = (r - '0') << 6
+			r += (n1 - '0') << 3
+			r += n2 - '0'
 			lex.advance(r, 1)
-			r -= '0'
-			r <<= 6
-			r += rune(lex.input[1]-'0') << 3
-			r += rune(lex.input[2] - '0')
-			lex.advance(rune(lex.input[1]), 1)
-			lex.advance(rune(lex.input[2]), 1)
+			lex.advance(n1, 1)
+			lex.advance(n2, 1)
 			sb.WriteRune(r)
 			return true
 		}
diff --git a/internal/filter/testdata/basic_test.yaml b/internal/filter/testdata/basic_test.yaml
index e938633..d61618d 100644
--- a/internal/filter/testdata/basic_test.yaml
+++ b/internal/filter/testdata/basic_test.yaml
@@ -854,7 +854,6 @@
 
   - expr: 'compound.str.array=ab'
     matches: []
-    skip: true
 
   - description: OR precedence over adjacent conjunction
 
@@ -1181,7 +1180,6 @@
 
   - expr: 'int_field:0 OR int_field:0 AND int_field:0'
     matches: [1]
-    skip: true
   - expr: 'int_field:0 OR int_field:0 AND int_field:1'
     matches: [1]
     skip: true
@@ -2085,7 +2083,6 @@
 
   - expr: 'compound.num.array!=(1 23)'
     matches: [1, 2, 3]
-    skip: true
 
   - expr: 'compound.num.array!=(-456 -789)'
     matches: [1, 2, 3]
@@ -2653,15 +2650,12 @@
 
   - expr: 'compound.str.dictionary=ab'
     matches: []
-    skip: true
 
   - expr: 'compound.num.dictionary=1'
     matches: []
-    skip: true
 
   - expr: 'compound.num.dictionary=(1)'
     matches: []
-    skip: true
 
   - expr: 'compound.num.dictionary=4'
     matches: []
@@ -2693,7 +2687,6 @@
 
   - expr: 'compound.num.dictionary=(3)'
     matches: []
-    skip: true
 
   - expr: 'compound.num.dictionary=(456)'
     matches: []
@@ -2701,15 +2694,12 @@
 
   - expr: 'compound.num.dictionary=(1 3 5)'
     matches: []
-    skip: true
 
   - expr: "compound.num.dictionary=(1\n3\n5)"
     matches: []
-    skip: true
 
   - expr: "compound.num.dictionary=(1\n-5\n5)"
     matches: []
-    skip: true
 
   - expr: 'compound.num.dictionary=(  aaa  3  zzz )'
     error: operand type mismatch
@@ -3000,7 +2990,6 @@
 
   - expr: 'timestampField<1980-01-01T00\:00\:00.000Z'
     matches: [1]
-    skip: true
   - expr: 'timestampField<1980-01-01T00\:00\:00\.000Z'
     matches: [1]
   - expr: 'timestampField<1980-01-01T00":"00":"00.000Z'
@@ -3011,7 +3000,6 @@
 
   - expr: 'timestampField>2010-01-01T00\:00\:00.000Z'
     matches: [3]
-    skip: true
   - expr: 'timestampField>2010-01-01T00\:00\:00\.000Z'
     matches: [3]
   - expr: 'timestampField>2010-01-01T00":"00":"00.000Z'
diff --git a/internal/filter/testdata/misc_test.json b/internal/filter/testdata/misc_test.json
new file mode 100644
index 0000000..0078c73
--- /dev/null
+++ b/internal/filter/testdata/misc_test.json
@@ -0,0 +1,101 @@
+{
+  "resources": [
+    {
+      "bytes_field": "øùúûüýþÿ",
+      "int32_field": -1001,
+      "int64_field": -1001,
+      "uint32_field": 1001,
+      "uint64_field": 1001,
+      "float_field": 1.7976e-38,
+      "float_infinity": "Infinity",
+      "float_negative_infinity": "-Infinity",
+      "float_nan": "NaN",
+      "double_field": -1.7976e+308,
+      "double_infinity": "Infinity",
+      "double_negative_infinity": "-Infinity",
+      "double_nan": "NaN",
+      "bool_field": true,
+      "string_field": "Hello beautiful world.",
+      "enum_field": "VALUE_2",
+      "out_of_order_enum_field": "VALUE_A",
+      "nested": {
+        "uint32_field": 123435,
+        "deeper_nest": {
+          "nice_field": "Foo Bar",
+          "nicer_field": "FooBar Baz"
+        }
+      },
+      "internal_field": "topsecret",
+      "deprecated_field": 200,
+      "repeated_int32_field": [100, 101],
+      "repeated_string_field": [
+        "Apple",
+        "Ball",
+        "Cat Dog Elephant",
+        "sl\\as\\\\h",
+        "qu\"ot\"es",
+        "t,e:s=[tin]+g~o.nly",
+        "t,e:s=t<i>n+g~o.nly",
+        "t,e:s=tin+g~m.at*ch",
+        "/",
+        "//",
+        "//foo.bar/baz",
+        "//foo.bar/baz*"
+      ],
+      "repeated_enum_field": ["VALUE_3", "VALUE_4"],
+      "repeated_message_field": [
+        {
+          "uint32_field": 5555,
+          "deeper_nest": {
+            "nice_field": "nice55",
+            "nicer_field": "nicer55"
+          },
+          "repeated_uint32_field": [6666, 6667]
+        },
+        {
+          "uint32_field": 7777,
+          "deeper_nest": {
+            "nice_field": "nice66",
+            "nicer_field": "nicer66"
+          },
+          "repeated_uint32_field": [8888, 8889],
+          "string_field": "red"
+        }
+      ],
+      "map_string_int32_field": {
+        "foobar": 9876,
+        "foobar.abcd": 9877,
+        "barFoo": 1234
+      },
+      "map_int32_int32_field": {
+        "1234": 4321,
+        "1235": 5321
+      },
+      "map_string_nested_field": {
+        "foobar": {
+          "uint32_field": 1111,
+          "deeper_nest": {
+            "nice_field": "nice11",
+            "nicer_field": "nicer11"
+          },
+          "repeated_uint32_field": [2222, 2223]
+        }
+      },
+      "timestamp": "1970-01-01T00:00:10.000Z",
+      "duration": "5.0s",
+      "string_value": "OneOfAKind",
+      "struct_field": {
+        "foo": "bar",
+        "fooBar": "xyz",
+        "foo.foo": "bar.bar",
+        "foo_bool": true,
+        "foo_number": 12345,
+        "sports": "soccer"
+      },
+      "unicode_pathe": "Path\u00E9",
+      "unicode_resume": "Résumé",
+      "unicode_unicode": "Ṳᾔḯ¢◎ⅾℯ",
+      "url_field": "http://foo.bar/baz*"
+    }
+  ]
+}
diff --git a/internal/filter/testdata/misc_test.yaml b/internal/filter/testdata/misc_test.yaml
new file mode 100644
index 0000000..e2bc6d3
--- /dev/null
+++ b/internal/filter/testdata/misc_test.yaml
@@ -0,0 +1,1196 @@
+tests:
+
+  - description: empty expressions
+
+  - expr: ""
+    matches: [1]
+  - expr: " "
+    matches: [1]
+  - expr: "\n"
+    matches: [1]
+
+  - description: unknown fields
+
+  - expr: "fooField:3"
+    error: unknown field
+
+  - expr: "nested.foo:3"
+    error: unknown field
+
+  - description: Int32Field
+
+  - expr: "int32Field = -1001"
+    matches: [1]
+  - expr: "int32Field != 1001"
+    matches: [1]
+  - expr: "int32Field > -2000"
+    matches: [1]
+  - expr: "int32Field < 0"
+    matches: [1]
+  - expr: "int32Field >= -1001"
+    matches: [1]
+  - expr: "int32Field <= -1001"
+    matches: [1]
+
+  - expr: "int32Field = -1002"
+    matches: []
+  - expr: "int32Field != -1001"
+    matches: []
+  - expr: "int32Field > -1001"
+    matches: []
+  - expr: "int32Field < -2000"
+    matches: []
+  - expr: "int32Field >= -1000"
+    matches: []
+  - expr: "int32Field <= -1002"
+    matches: []
+
+  - expr: "int32Field > 0.0001"
+    error: operand type mismatch
+
+  - description: Int64Field
+
+  - expr: "int64Field = -1001"
+    matches: [1]
+  - expr: "int64Field != 1001"
+    matches: [1]
+  - expr: "int64Field > -2000"
+    matches: [1]
+  - expr: "int64Field < 0"
+    matches: [1]
+  - expr: "int64Field >= -1001"
+    matches: [1]
+  - expr: "int64Field <= -1001"
+    matches: [1]
+
+  - expr: "int64Field = -1002"
+    matches: []
+  - expr: "int64Field != -1001"
+    matches: []
+  - expr: "int64Field > -1001"
+    matches: []
+  - expr: "int64Field < -2000"
+    matches: []
+  - expr: "int64Field >= -1000"
+    matches: []
+  - expr: "int64Field <= -1002"
+    matches: []
+
+  - description: Uint32Field
+
+  - expr: "uint32Field = 1001"
+    matches: [1]
+  - expr: "uint32Field != 1002"
+    matches: [1]
+  - expr: "uint32Field > 1000"
+    matches: [1]
+  - expr: "uint32Field < 2000"
+    matches: [1]
+  - expr: "uint32Field >= 1001"
+    matches: [1]
+  - expr: "uint32Field <= 1001"
+    matches: [1]
+
+  - expr: "uint32Field = 1002"
+    matches: []
+  - expr: "uint32Field != 1001"
+    matches: []
+  - expr: "uint32Field > 1001"
+    matches: []
+  - expr: "uint32Field < 1001"
+    matches: []
+  - expr: "uint32Field >= 1002"
+    matches: []
+  - expr: "uint32Field <= 1000"
+    matches: []
+
+  - expr: "uint32Field <= -1001"
+    error: operand type mismatch
+
+  - description: Uint64Field
+
+  - expr: "uint64Field = 1001"
+    matches: [1]
+  - expr: "uint64Field != 1002"
+    matches: [1]
+  - expr: "uint64Field > 1000"
+    matches: [1]
+  - expr: "uint64Field < 2000"
+    matches: [1]
+  - expr: "uint64Field >= 1001"
+    matches: [1]
+  - expr: "uint64Field <= 1001"
+    matches: [1]
+
+  - expr: "uint64Field = 1002"
+    matches: []
+  - expr: "uint64Field != 1001"
+    matches: []
+  - expr: "uint64Field > 1001"
+    matches: []
+  - expr: "uint64Field < 1001"
+    matches: []
+  - expr: "uint64Field >= 1002"
+    matches: []
+  - expr: "uint64Field <= 1000"
+    matches: []
+
+  - expr: "uint64Field <= -1001"
+    error: operand type mismatch
+
+  - description: FloatField
+
+  - expr: "floatField = 1.7976e-38"
+    matches: [1]
+
+  - expr: "floatField != 1.7976e-37"
+    matches: [1]
+  - expr: "floatField != 1.79761e-38"
+    matches: [1]
+  - expr: "floatField > 1.7976e-39"
+    matches: [1]
+  - expr: "floatField < 1.7976e-37"
+    matches: [1]
+  - expr: "floatField >= 1.797e-38"
+    matches: [1]
+  - expr: "floatField <= 1.7977e-38"
+    matches: [1]
+
+  - expr: "floatField = 1.7976e-37"
+    matches: []
+  - expr: "floatField != 1.7976e-38"
+    matches: []
+
+  - expr: "floatField > 1.7976e-38"
+    matches: []
+  - expr: "floatField < 1.7976e-39"
+    matches: []
+  - expr: "floatField >= 1.79761e-38"
+    matches: []
+  - expr: "floatField <= 1.7976e-39"
+    matches: []
+  - expr: "floatField <= -20"
+    matches: []
+
+  - expr: "floatField =  NaN"
+    matches: []
+  - expr: "floatField =  Infinity"
+    matches: []
+
+  - description: float extremes
+
+  - expr: "float_infinity = Infinity"
+    matches: [1]
+  - expr: "float_infinity: infinity"
+    matches: [1]
+
+  - expr: "float_infinity > 100"
+    matches: [1]
+  - expr: "float_infinity != -Infinity"
+    matches: [1]
+    skip: true
+
+  - expr: "float_infinity = -Infinity"
+    matches: []
+    skip: true
+
+  - expr: "float_infinity = NaN"
+    matches: []
+  - expr: 'float_infinity = "infinity"'
+    matches: [1]
+
+  - expr: 'float_infinity != "-Infinity"'
+    matches: [1]
+  - expr: 'float_infinity = "-infinity"'
+    matches: []
+
+  - expr: 'float_infinity = "NaN"'
+    matches: []
+
+  - expr: "float_negative_infinity = -Infinity"
+    matches: [1]
+    skip: true
+
+  - expr: "float_negative_infinity: -infinity"
+    matches: [1]
+    skip: true
+
+  - expr: "float_negative_infinity < 100"
+    matches: [1]
+  - expr: "float_negative_infinity != Infinity"
+    matches: [1]
+  - expr: "float_negative_infinity = Infinity"
+    matches: []
+  - expr: "float_negative_infinity = NaN"
+    matches: []
+  - expr: 'float_negative_infinity = "-Infinity"'
+    matches: [1]
+  - expr: 'float_negative_infinity != "Infinity"'
+    matches: [1]
+  - expr: 'float_negative_infinity = "infinity"'
+    matches: []
+
+  - expr: 'float_negative_infinity = "NaN"'
+    matches: []
+
+  - expr: "float_nan:nan"
+    matches: [1]
+
+  - expr: "float_nan = NaN"
+    matches: [1]
+  - expr: "float_nan < 100"
+    matches: []
+  - expr: "float_nan = 100"
+    matches: []
+  - expr: "float_nan > 100"
+    matches: []
+
+  - description: DoubleField
+
+  - expr: "doubleField =  -1.7976e+308"
+    matches: [1]
+  - expr: "doubleField != -1.7976e+307"
+    matches: [1]
+  - expr: "doubleField != -1.7975e+308"
+    matches: [1]
+  - expr: "doubleField >  -1.79761e+308"
+    matches: [1]
+  - expr: "doubleField < 0"
+    matches: [1]
+  - expr: "doubleField >= -1.79761e+308"
+    matches: [1]
+  - expr: "doubleField <= -1"
+    matches: [1]
+
+  - expr: "doubleField =  -1.79762e+308"
+    matches: []
+  - expr: "doubleField != -1.7976e+308"
+    matches: []
+  - expr: "doubleField > 1"
+    matches: []
+  - expr: "doubleField < -1.79761e+308"
+    matches: []
+  - expr: "doubleField >= -1"
+    matches: []
+  - expr: "doubleField <= -1.79761e+308"
+    matches: []
+
+  - expr: "doubleField =  NaN"
+    matches: []
+  - expr: "doubleField =  Infinity"
+    matches: []
+
+  - description: double extremes
+
+  - expr: "double_infinity = Infinity"
+    matches: [1]
+  - expr: "double_infinity: infinity"
+    matches: [1]
+
+  - expr: "double_infinity > 100"
+    matches: [1]
+  - expr: "double_infinity != -Infinity"
+    matches: [1]
+    skip: true
+
+  - expr: "double_infinity = -Infinity"
+    matches: []
+    skip: true
+
+  - expr: "double_infinity = NaN"
+    matches: []
+  - expr: 'double_infinity = "infinity"'
+    matches: [1]
+
+  - expr: 'double_infinity != "-Infinity"'
+    matches: [1]
+  - expr: 'double_infinity = "-infinity"'
+    matches: []
+
+  - expr: 'double_infinity = "NaN"'
+    matches: []
+
+  - expr: "double_negative_infinity = -Infinity"
+    matches: [1]
+    skip: true
+
+  - expr: "double_negative_infinity: -infinity"
+    matches: [1]
+    skip: true
+
+  - expr: "double_negative_infinity < 100"
+    matches: [1]
+  - expr: "double_negative_infinity != Infinity"
+    matches: [1]
+  - expr: "double_negative_infinity = Infinity"
+    matches: []
+  - expr: "double_negative_infinity = NaN"
+    matches: []
+  - expr: 'double_negative_infinity = "-Infinity"'
+    matches: [1]
+  - expr: 'double_negative_infinity != "Infinity"'
+    matches: [1]
+  - expr: 'double_negative_infinity = "infinity"'
+    matches: []
+
+  - expr: 'double_negative_infinity = "NaN"'
+    matches: []
+
+  - expr: "double_nan:nan"
+    matches: [1]
+
+  - expr: "double_nan = NaN"
+    matches: [1]
+  - expr: "double_nan < 100"
+    matches: []
+  - expr: "double_nan = 100"
+    matches: []
+  - expr: "double_nan > 100"
+    matches: []
+
+  - description: BoolField
+
+  - expr: "boolField = true"
+    matches: [1]
+  - expr: "boolField > false"
+    matches: [1]
+  - expr: "boolField >= false"
+    matches: [1]
+  - expr: "boolField <= true"
+    matches: [1]
+  - expr: "boolField = tRUe"
+    matches: [1]
+  - expr: "boolField = TRUE"
+    matches: [1]
+  - expr: "boolField:True"
+    matches: [1]
+  - expr: "boolField != False"
+    matches: [1]
+
+  - expr: "boolField = false"
+    matches: []
+  - expr: "boolField = faLSe"
+    matches: []
+  - expr: "boolField = FALSE"
+    matches: []
+  - expr: "boolField < false"
+    matches: []
+  - expr: "boolField:False"
+    matches: []
+  - expr: "boolField != True"
+    matches: []
+
+  - expr: "boolField = 0"
+    matches: []
+  - expr: "boolField = 1"
+    matches: [1]
+
+  - expr: "boolField = x"
+    error: operand type mismatch
+
+  - description: StringField
+
+  - expr: "stringField : Hello"
+    matches: [1]
+
+  - expr: "stringField :Hello"
+    matches: [1]
+
+  - expr: "stringField: Hello"
+    matches: [1]
+
+  - expr: "stringField:Hello"
+    matches: [1]
+
+  - expr: "stringField:hello"
+    matches: [1]
+
+  - expr: "stringField:bye OR stringField:hello"
+    matches: [1]
+
+  - expr: "stringField:world"
+    matches: [1]
+
+  - expr: "stringField:hello AND stringField:world"
+    matches: [1]
+
+  - expr: 'stringField="Hello beautiful world."'
+    matches: [1]
+  - expr: 'stringField = "Hello beautiful world."'
+    matches: [1]
+  - expr: 'stringField=  "Hello beautiful world."'
+    matches: [1]
+  - expr: "stringField != Google"
+    matches: [1]
+  - expr: "stringField!= Google"
+    matches: [1]
+  - expr: "stringField !=Google"
+    matches: [1]
+  - expr: 'stringField >= "Hello beautiful world."'
+    matches: [1]
+  - expr: 'stringField <= "Hello beautiful world."'
+    matches: [1]
+  - expr: 'stringField:"\150\145\154\154\157"'
+    matches: [1]
+
+  - expr: 'stringField:"\x48\x65\x6C\x6C\x6F"'
+    matches: [1]
+
+  - expr: 'stringField: "\150\145\u013E\u013E\157"'
+    matches: [1]
+    skip: true
+
+  - expr: 'stringField:"Hello World"'
+    matches: []
+  - expr: "stringField:foo"
+    matches: []
+  - expr: "stringField:hello AND stringField:bar"
+    matches: []
+  - expr: "stringField:bye OR stringField:foo"
+    matches: []
+  - expr: 'stringField!= "Hello beautiful world."'
+    matches: []
+  - expr: 'stringField !=  "Hello beautiful world."'
+    matches: []
+  - expr: "stringField = Hello"
+    matches: []
+  - expr: 'stringField < "Hello beautiful world."'
+    matches: []
+  - expr: 'stringField > "Hello beautiful world."'
+    matches: []
+
+  - description: unicode
+
+  - expr: 'unicode_pathe: Pathe\u0301'
+    matches: []
+  - expr: 'unicode_pathe: "Pathe\u0301"'
+    matches: [1]
+    skip: true
+  - expr: "unicode_pathe: Pathé"
+    matches: [1]
+    skip: true
+  - expr: "unicode_pathe: Pathe"
+    matches: [1]
+    skip: true
+
+  - expr: "unicode_pathe: pAtHe"
+    matches: [1]
+    skip: true
+
+  - expr: "unicode_resume: Résumé"
+    matches: [1]
+  - expr: "unicode_resume: Resume"
+    matches: [1]
+    skip: true
+
+  - expr: "unicode_resume: rEsUmE"
+    matches: [1]
+    skip: true
+
+  - expr: "unicode_unicode: Ṳᾔḯ¢◎ⅾℯ"
+    matches: [1]
+  - expr: "unicode_unicode: U*"
+    matches: [1]
+    skip: true
+
+  - expr: "unicode_unicode: u*"
+    matches: [1]
+    skip: true
+
+  - description: invalid UTF-8 (TBD)
+
+  - description: EscapedValues
+
+  - expr: 'bytes_field:"\370\371\372\373\374\375\376\377"'
+    matches: [1]
+
+  - expr: 'bytes_field="\370\371\372\373\374\375\376\377"'
+    matches: [1]
+
+  - expr: 'bytes_field:"\370"'
+    matches: [1]
+    skip: true
+
+  - expr: 'bytes_field="\370"'
+    matches: []
+  - expr: 'bytes_field: "\372\373"'
+    matches: [1]
+    skip: true
+
+  - expr: 'bytes_field: "\374"'
+    matches: [1]
+    skip: true
+
+  - expr: 'bytes_field: "\360"'
+    matches: []
+  - expr: 'bytes_field:"\370\371\372\373\374\375\376"'
+    matches: [1]
+    skip: true
+
+  - expr: 'bytes_field="\370\371\372\373\374\375\376"'
+    matches: []
+
+  - description: escaped values
+
+  - expr: 'repeatedStringField:Apple'
+    matches: [1]
+  - expr: 'repeatedStringField:"Apple"'
+    matches: [1]
+  - expr: 'repeatedStringField:apple'
+    matches: [1]
+  - expr: 'repeatedStringField:"apple"'
+    matches: [1]
+  - expr: 'repeatedStringField:cat'
+    matches: [1]
+
+  - expr: 'repeatedStringField:dog'
+    matches: [1]
+
+  - expr: 'repeatedStringField:elephant'
+    matches: [1]
+
+  - expr: 'repeatedStringField:ant'
+    matches: []
+
+  - expr: 'repeatedStringField:"sl\\as\\\\h"'
+    matches: [1]
+  - expr: 'repeatedStringField:"qu\"ot\"es"'
+    matches: [1]
+
+  - expr: 'repeatedStringField:"t,e:s=[tin]+g~o.nly"'
+    matches: [1]
+  - expr: 'repeatedStringField:"t,e:s=+g~o.nly"'
+    matches: []
+  - expr: 'repeatedStringField:"t,e:s=[tin]+g~o.n*"'
+    matches: [1]
+
+  - expr: 'repeatedStringField:"t,e:s=t<i>n+g~o.nly"'
+    matches: [1]
+  - expr: 'repeatedStringField:"t,e:s=t<i>n+g~o.n*"'
+    matches: [1]
+
+  - expr: 'repeatedStringField:"t,e:s=tn+g~o.nly"'
+    matches: [1]
+    skip: true
+
+  - expr: 'repeatedStringField:"t,e:s=tn+g~o.n*"'
+    matches: [1]
+    skip: true
+
+  - expr: 'repeatedStringField:"t,e:s=tin+g~m.at*"'
+    matches: [1]
+  - expr: 'repeatedStringField:"t,e:s=tin+g~m.at*ch"'
+    error: suffix matching not supported
+    skip: true
+
+  - description: Any (TBD)
+
+  - description: repeated Any (TBD)
+
+  - description: OneOf
+
+  - expr: "kind.stringValue: OneOfAKind"
+    matches: [1]
+    skip: true
+
+  - expr: "kind.string_value=OneOfAKind"
+    matches: [1]
+    skip: true
+
+  - expr: "OneOfAKind"
+    matches: [1]
+
+  - expr: "string_value:*"
+    matches: [1]
+
+  - expr: "stringValue:*"
+    matches: [1]
+
+  - expr: "nested_value:*"
+    matches: []
+
+  - expr: "nestedValue:*"
+    matches: []
+
+  - description: Negation
+
+  - expr: "-stringField:Google"
+    matches: [1]
+  - expr: "NOT stringField:Google"
+    matches: [1]
+  - expr: "stringField : (NOT Google)"
+    matches: [1]
+
+  - expr: "-stringField:Hello"
+    matches: []
+
+  - expr: "NOT stringField:Hello"
+    matches: []
+
+  - expr: "stringField:(NOT Hello)"
+    matches: []
+
+  - description: ComplexRHS
+
+  - expr: "stringField:(Hello World)"
+    matches: [1]
+
+  - expr: "stringField:(Hello OR Goodbye)"
+    matches: [1]
+
+  - expr: "stringField:(Hello OR (Goodbye OR Bye))"
+    matches: [1]
+
+  - expr: "enumField = (VALUE_2 OR VALUE_3)"
+    matches: [1]
+
+  - expr: "stringField:(Hello AND Goodbye)"
+    matches: []
+
+  - expr: "stringField:(Hello Goodbye)"
+    matches: []
+
+  - expr: "stringField:(Hello:Goodbye)"
+    error: terms not supported in operand subexpression
+
+  - expr: "stringField=(A OR foo=bar)"
+    error: terms not supported in operand subexpression
+
+  - description: EnumField
+
+  - expr: "enumField = VALUE_2"
+    matches: [1]
+  - expr: "enumField = value_2"
+    matches: [1]
+  - expr: "enumField != VALUE_3"
+    matches: [1]
+  - expr: "enumField < VALUE_MAX"
+    matches: [1]
+  - expr: "enumField > VALUE_1"
+    matches: [1]
+  - expr: "enumField > VALUE_1 AND enumField < VALUE_MAX"
+    matches: [1]
+
+  - expr: "enumField=VALUE_1"
+    matches: []
+  - expr: "enumField=VALUE_X"
+    error: unknown enum identifier
+
+  - description: OutOfOrderEnumField
+
+  - expr: "outOfOrderEnumField = VALUE_A"
+    matches: [1]
+  - expr: "outOfOrderEnumField = value_a"
+    matches: [1]
+  - expr: "outOfOrderEnumField != VALUE_B"
+    matches: [1]
+
+  - expr: "outOfOrderEnumField < VALUE_B"
+    matches: []
+    skip: true
+
+  - expr: "outOfOrderEnumField <= VALUE_B"
+    matches: []
+    skip: true
+
+  - expr: "outOfOrderEnumField > VALUE_B"
+    matches: [1]
+    skip: true
+
+  - expr: "outOfOrderEnumField >= VALUE_B"
+    matches: [1]
+    skip: true
+
+  - expr: "outOfOrderEnumField < VALUE_A"
+    matches: []
+  - expr: "outOfOrderEnumField <= VALUE_A"
+    matches: [1]
+  - expr: "outOfOrderEnumField > VALUE_A"
+    matches: []
+  - expr: "outOfOrderEnumField >= VALUE_A"
+    matches: [1]
+
+  - description: GlobalSearch
+
+  - expr: "hello world"
+    matches: [1]
+
+  - expr: "hello bar"
+    matches: [1]
+
+  - expr: "BAZ"
+    matches: [1]
+
+  - expr: "1001"
+    matches: [1]
+
+  - expr: "123435"
+    matches: [1]
+
+  - expr: "1002"
+    matches: []
+
+  - expr: "123436"
+    matches: []
+
+  - expr: "nested"
+    matches: []
+
+  - expr: "Googley"
+    matches: []
+
+  - description: NestedFields
+
+  - expr: "nested.uint32Field = 123435"
+    matches: [1]
+  - expr: "nested.uint32Field > 123434"
+    matches: [1]
+  - expr: "nested.uint32Field < 123436"
+    matches: [1]
+
+  - expr: "nested:*"
+    matches: [1]
+
+  - expr: "nested=12345"
+    error: only :* supported on structs
+
+  - expr: "nested.deeperNest:*"
+    matches: [1]
+
+  - expr: "nested.deeperNest=12345"
+    error: only :* supported on structs
+
+  - description: DeepNestedFields
+
+  - expr: "nested.deeperNest.niceField:foo"
+    matches: [1]
+
+  - expr: "nested.deeperNest.niceField:bAr"
+    matches: [1]
+
+  - expr: 'nested.deeperNest.niceField:"foo bar"'
+    matches: [1]
+  - expr: 'nested.deeperNest.nicerField:"foobar"'
+    matches: [1]
+
+  - expr: "nested.deeperNest.niceField.nonexistent:foo"
+    error: unknown field
+
+  - description: RepeatedFields
+
+  - expr: "repeatedInt32Field > 100"
+    matches: [1]
+
+  - expr: "repeatedInt32Field: *"
+    matches: [1]
+  - expr: "repeatedStringField:Elephant"
+    matches: [1]
+
+  - expr: "dog"
+    matches: [1]
+
+  - expr: "apple dog"
+    matches: [1]
+
+  - expr: '"cat dog"'
+    matches: [1]
+
+  - expr: "repeatedEnumField=(VALUE_3 AND VALUE_4)"
+    matches: [1]
+
+  - expr: "repeatedEnumField != VALUE_2"
+    matches: [1]
+
+  - expr: "repeatedEnumField=VALUE_3"
+    matches: [1]
+
+  - expr: "repeatedEnumField!=VALUE_3"
+    matches: [1]
+
+  - expr: "repeatedMessageField.uint32_field = 5555"
+    matches: [1]
+
+  - expr: "repeatedMessageField.uint32_field = 7777"
+    matches: [1]
+
+  - expr: "repeatedMessageField.deeperNest.niceField = nice55"
+    matches: [1]
+
+  - expr: "repeatedMessageField.deeperNest.niceField = nice66"
+    matches: [1]
+
+  - expr: "repeatedInt32Field > 101"
+    matches: []
+
+  - expr: "repeatedStringField:Fish"
+    matches: []
+  - expr: "dogs"
+    matches: []
+
+  - expr: "apples dog"
+    matches: []
+
+  - expr: '"ball dog"'
+    matches: []
+
+  - expr: "repeatedEnumField=(VALUE_3 AND NOT VALUE_4)"
+    matches: []
+
+  - expr: "NOT repeatedEnumField=VALUE_3"
+    matches: []
+
+  - expr: "nested.repeatedEmptyUint32: 100"
+    matches: []
+  - expr: "nested.repeatedEmptyUint32: *"
+    matches: []
+  - expr: "repeatedEmptyMessageField.uint32Field: 100"
+    matches: []
+  - expr: "repeatedEmptyMessageField.repeatedEmptyUint32:*"
+    matches: []
+  - expr: "repeatedEmptyMessageField.repeatedEmptyUint32: 100"
+    matches: []
+  - expr: "repeatedMessageField.deeperNest.niceField = nice77"
+    matches: []
+
+  - expr: "repeatedMessageField:*"
+    matches: [1]
+
+  - expr: "repeatedMessageField = 5555"
+    error: only :* supported on structs
+
+  - expr: "repeatedMessageField.deeperNest = 5555"
+    error: only :* supported on structs
+
+  - expr: "repeatedMessageField.uint32_field = foo(5555)"
+    error: operand functions not supported
+
+  - description: PresenceTest
+
+  - expr: "stringField: *"
+    matches: [1]
+  - expr: "repeatedMessageField.stringField: *"
+    matches: [1]
+  - expr: "map_int32_int32_field.1234:*"
+    matches: [1]
+  - expr: "map_string_int32_field.foobar:*"
+    matches: [1]
+
+  - expr: "noValueField: *"
+    matches: []
+
+  - expr: "repeatedMessageField.noValueField: *"
+    matches: []
+
+  - expr: "map_string_int32_field.foobaz:*"
+    matches: []
+  - expr: "map_int32_int32_field.1236:*"
+    matches: []
+
+  - description: EmptyValue
+
+  - expr: 'noValueField=""'
+    matches: [1]
+  - expr: "noValueField!=foo"
+    matches: [1]
+  - expr: "noValueField=foo"
+    matches: []
+
+  - description: MapFields
+
+  - expr: "map_string_int32_field.foobar>9800"
+    matches: [1]
+  - expr: "map_string_int32_field.foobar<9877"
+    matches: [1]
+  - expr: "map_string_int32_field.barFoo>1200"
+    matches: [1]
+  - expr: 'map_string_int32_field."foobar.abcd":9877'
+    matches: [1]
+
+  - expr: "map_int32_int32_field.1234 = 4321"
+    matches: [1]
+  - expr: 'map_int32_int32_field."1235" = 5321'
+    matches: [1]
+
+  - expr: "map_string_nested_field.foobar.uint32_field = 1111"
+    matches: [1]
+
+  - expr: "map_string_nested_field.foobar.deeper_nest.nice_field = nice11"
+    matches: [1]
+
+  - expr: "9876"
+    matches: [1]
+
+  - expr: "nice11"
+    matches: [1]
+
+  - expr: "map_string_int32_field.foobar>9876"
+    matches: []
+  - expr: "map_string_int32_field.foobar!=9876"
+    matches: []
+  - expr: "map_string_int32_field.barfoo>1200"
+    matches: []
+
+  - expr: 'map_string_int32_field."foobar.abcd"<9877'
+    matches: []
+
+  - expr: "map_int32_int32_field.1236 = 4321"
+    matches: []
+  - expr: "map_int32_int32_field.1234 = 4320"
+    matches: []
+  - expr: 'map_int32_int32_field."1235" = 5320'
+    matches: []
+
+  - description:  Label keys are case-sensitive.
+
+  - expr: "map_string_int32_field.fooBAR>9800"
+    matches: []
+
+  - expr: "map_string_int32_field = 5555"
+    error: only :* supported on structs
+
+  - description: TimeStampTests
+
+  - expr: 'timestamp = "1970-01-01T00:00:10Z"'
+    matches: [1]
+
+  - expr: 'timestamp = "1970-01-01t00:00:10z"'
+    matches: [1]
+    skip: true
+
+  - expr: 'timestamp = "1970-01-01T05:45:10+05:45"'
+    matches: [1]
+
+  - expr: 'timestamp = "1970-01-01T00:00:10.000000000Z"'
+    matches: [1]
+
+  - expr: 'timestamp < "1970-01-01T00:00:10.000000001Z"'
+    matches: [1]
+
+  - expr: 'timestamp != "1970-01-01T00:00:11Z"'
+    matches: [1]
+
+  - expr: 'timestamp > "1970-01-01T00:00:09Z"'
+    matches: [1]
+
+  - expr: 'timestamp >= "1970-01-01T00:00:10Z"'
+    matches: [1]
+
+  - expr: 'timestamp < "1970-01-01T00:00:11Z"'
+    matches: [1]
+
+  - expr: 'timestamp <= "1970-01-01T00:00:10Z"'
+    matches: [1]
+
+  - expr: 'timestamp:"1970-01-01T00:00:10Z"'
+    matches: [1]
+
+  - expr: 'timestamp = "1970-01-01T00:00:11Z"'
+    matches: []
+
+  - expr: 'timestamp != "1970-01-01T00:00:10Z"'
+    matches: []
+
+  - expr: 'timestamp > "1970-01-01T00:00:10Z"'
+    matches: []
+
+  - expr: 'timestamp >= "1970-01-01T00:00:11Z"'
+    matches: []
+
+  - expr: 'timestamp < "1970-01-01T00:00:10Z"'
+    matches: []
+
+  - expr: 'timestamp <= "1970-01-01T00:00:09Z"'
+    matches: []
+
+  - expr: 'timestamp:"1970-01-01T00:00:01Z"'
+    matches: []
+
+  - expr: 'timestamp < 1970-01-01T00:00:11Z'
+    matches: [1]
+    skip: true
+
+  - expr: 'timestamp <= 1970-01-01T00:00:10Z'
+    matches: [1]
+    skip: true
+
+  - expr: 'timestamp = 1970-01-01T00:00:10Z'
+    matches: [1]
+    skip: true
+
+  - expr: 'timestamp >= 1970-01-01T00:00:10Z'
+    matches: [1]
+    skip: true
+
+  - expr: 'timestamp > 1970-01-01T00:00:09Z'
+    matches: [1]
+    skip: true
+
+  - expr: 'timestamp.seconds = 10'
+    matches: [1]
+    skip: true
+
+  - description: DurationTests
+
+  - expr: "duration = 5s"
+    matches: [1]
+
+  - expr: "duration = 5S"
+    matches: [1]
+
+  - expr: "duration = 5.0s"
+    matches: [1]
+
+  - expr: "duration = 5.s"
+    matches: [1]
+
+  - expr: "duration < 5.000000001s"
+    matches: [1]
+
+  - expr: "duration = 5.0000000001s"
+    matches: [1]
+
+  - expr: "duration != 4s"
+    matches: [1]
+
+  - expr: "duration > 4s"
+    matches: [1]
+
+  - expr: "duration >= 5s"
+    matches: [1]
+
+  - expr: "duration < 6s"
+    matches: [1]
+
+  - expr: "duration > -6s"
+    matches: [1]
+
+  - expr: "duration > .1s"
+    matches: [1]
+
+  - expr: "duration <= 5s"
+    matches: [1]
+
+  - expr: "duration:5s"
+    matches: [1]
+
+  - expr: "duration = 5000.0ms"
+    matches: [1]
+
+  - expr: "duration > 10ms"
+    matches: [1]
+
+  - expr: "duration = -5.0s"
+    matches: []
+
+  - expr: "duration = 6s"
+    matches: []
+
+  - expr: "duration != 5s"
+    matches: []
+
+  - expr: "duration > 5s"
+    matches: []
+
+  - expr: "duration >= 6s"
+    matches: []
+
+  - expr: "duration < 5s"
+    matches: []
+
+  - expr: "duration <= 4s"
+    matches: []
+
+  - expr: "duration:1s"
+    matches: []
+
+  - expr: "duration:5"
+    error: invalid duration literal
+
+  - expr: "duration.seconds:5"
+    matches: [1]
+  - expr: "duration.seconds:1"
+    matches: []
+  - expr: 'duration = "1970-01-01T00:00:10Z"'
+    error: invalid duration literal
+
+  - description: StructFields
+
+  - expr: "struct_field.foo = bar"
+    matches: [1]
+
+  - expr: "struct_field.fooBar = xyz"
+    matches: [1]
+
+  - expr: 'struct_field."foo.foo" = bar.bar'
+    matches: [1]
+
+  - expr: "struct_field.foo_number = 12345"
+    matches: [1]
+
+  - expr: "struct_field.foo_bool = true"
+    matches: [1]
+
+  - expr: "soccer"
+    matches: [1]
+
+  - expr: "struct_field.foo = barz"
+    matches: []
+
+  - expr: "struct_field.foobar = xyz"
+    matches: []
+
+  - expr: 'struct_field."foo.foo" = bar'
+    matches: []
+
+  - expr: "struct_field.foo_number > 12345"
+    matches: []
+
+  - expr: "struct_field.foo_bool = false"
+    matches: []
+
+  - description: Global restriction.
+
+  - expr: "beautiful"
+    matches: [1]
+
+  - description: restricted field data not present client side
+
+  - expr: "internalField:*"
+    error: cannot access restricted field
+    skip: true
+
+  - expr: "topsecret"
+    matches: []
+    skip: true
+
+  - description: deprecated fields are visible
+
+  - expr: "deprecatedField:*"
+    matches: [1]
+
+  - expr: "/"
+    matches: [1]
+
+  - expr: "//"
+    matches: [1]
+
+  - expr: "repeated_string_field:/"
+    matches: [1]
+
+  - expr: "repeated_string_field://"
+    matches: [1]
+
+  - expr: "repeated_string_field:\"//foo.bar/baz\""
+    matches: [1]
+
+  - expr: "repeated_string_field:\"//foo.bar/baz*\""
+    matches: [1]
+
+  - expr: "url_field:\"//foo.bar/baz\""
+    matches: [1]
+
+  - expr: "url_field: \"http://foo.bar/baz*\""
+    matches: [1]
+
+  - expr: "/foo AND \"/foo.bar/\""
+    matches: [1]