Skip to content

Commit aa2071a

Browse files
committed
Add the matches/not matches operators to due regex matching on strings
A little surprised that this adds only about 300-400 ns per check over normal string equality. Seems reasonable for the power it adds.
1 parent fbf42d9 commit aa2071a

File tree

9 files changed

+405
-248
lines changed

9 files changed

+405
-248
lines changed

ast.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ const (
5757
MatchNotIn
5858
MatchIsEmpty
5959
MatchIsNotEmpty
60+
MatchMatches
61+
MatchNotMatches
6062
)
6163

6264
func (op MatchOperator) String() string {
@@ -73,6 +75,10 @@ func (op MatchOperator) String() string {
7375
return "Is Empty"
7476
case MatchIsNotEmpty:
7577
return "Is Not Empty"
78+
case MatchMatches:
79+
return "Matches"
80+
case MatchNotMatches:
81+
return "Not Matches"
7682
default:
7783
return "UNKNOWN"
7884
}

evaluate.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package bexpr
33
import (
44
"fmt"
55
"reflect"
6+
"regexp"
67
"strings"
78
)
89

10+
var byteSliceTyp reflect.Type = reflect.TypeOf([]byte{})
11+
912
var primitiveEqualityFns = map[reflect.Kind]func(first interface{}, second reflect.Value) bool{
1013
reflect.Bool: doEqualBool,
1114
reflect.Int: doEqualInt,
@@ -87,6 +90,16 @@ func derefType(rtype reflect.Type) reflect.Type {
8790
return rtype
8891
}
8992

93+
func doMatchMatches(expression *MatchExpression, value reflect.Value) (bool, error) {
94+
if !value.Type().ConvertibleTo(byteSliceTyp) {
95+
return false, fmt.Errorf("Value of type %s is not convertible to []byte", value.Type())
96+
}
97+
98+
re := expression.Value.Converted.(*regexp.Regexp)
99+
100+
return re.Match(value.Convert(byteSliceTyp).Interface().([]byte)), nil
101+
}
102+
90103
func doMatchEqual(expression *MatchExpression, value reflect.Value) (bool, error) {
91104
// NOTE: see preconditions in evaluateMatchExpressionRecurse
92105
eqFn := primitiveEqualityFns[value.Kind()]
@@ -186,6 +199,14 @@ func evaluateMatchExpressionRecurse(expression *MatchExpression, depth int, rval
186199
return !result, nil
187200
}
188201
return false, err
202+
case MatchMatches:
203+
return doMatchMatches(expression, rvalue)
204+
case MatchNotMatches:
205+
result, err := doMatchMatches(expression, rvalue)
206+
if err == nil {
207+
return !result, nil
208+
}
209+
return false, err
189210
default:
190211
return false, fmt.Errorf("Invalid match operation: %d", expression.Operator)
191212
}

evaluate_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ var evaluateTests map[string]expressionTest = map[string]expressionTest{
102102
{expression: "part not in String", result: true},
103103
{expression: "unexported == `unexported`", result: false, err: "Selector \"unexported\" is not valid"},
104104
{expression: "Hidden == false", result: false, err: "Selector \"Hidden\" is not valid"},
105+
{expression: "String matches `^ex.*`", result: true, benchQuick: true},
106+
{expression: "String not matches `^anchored.*`", result: true, benchQuick: true},
107+
{expression: "String matches `^anchored.*`", result: false},
108+
{expression: "String not matches `^ex.*`", result: false},
105109
},
106110
},
107111
"Flat Struct Alt Types": {

field_config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,11 @@ func generateFieldConfigurationInternal(rtype reflect.Type) (*FieldConfiguration
7272
// Handle primitive types
7373
if coerceFn, ok := primitiveCoercionFns[rtype.Kind()]; ok {
7474
ops := []MatchOperator{MatchEqual, MatchNotEqual}
75+
7576
if rtype.Kind() == reflect.String {
76-
ops = append(ops, MatchIn, MatchNotIn)
77+
ops = append(ops, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches)
7778
}
79+
7880
return &FieldConfiguration{
7981
CoerceFn: coerceFn,
8082
SupportedOperations: ops,

field_config_test.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
3030
"Float32": &FieldConfiguration{StructFieldName: "Float32", CoerceFn: CoerceFloat32, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
3131
"Float64": &FieldConfiguration{StructFieldName: "Float64", CoerceFn: CoerceFloat64, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
3232
"Bool": &FieldConfiguration{StructFieldName: "Bool", CoerceFn: CoerceBool, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
33-
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
33+
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
3434
},
3535
benchQuick: true,
3636
},
@@ -57,12 +57,11 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
5757
expected: FieldConfigurations{
5858
"Nested": &FieldConfiguration{StructFieldName: "Nested", SubFields: FieldConfigurations{
5959
"Map": &FieldConfiguration{StructFieldName: "Map", SupportedOperations: []MatchOperator{MatchIn, MatchNotIn, MatchIsEmpty, MatchIsNotEmpty}, SubFields: FieldConfigurations{
60-
FieldNameAny: &FieldConfiguration{StructFieldName: "", SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
61-
}},
60+
FieldNameAny: &FieldConfiguration{StructFieldName: "", SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}}}},
6261
"MapOfStructs": &FieldConfiguration{StructFieldName: "MapOfStructs", SupportedOperations: []MatchOperator{MatchIsEmpty, MatchIsNotEmpty, MatchIn, MatchNotIn}, SubFields: FieldConfigurations{
6362
FieldNameAny: &FieldConfiguration{StructFieldName: "", SubFields: FieldConfigurations{
6463
"Foo": &FieldConfiguration{StructFieldName: "Foo", CoerceFn: CoerceInt, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
65-
"Baz": &FieldConfiguration{StructFieldName: "Baz", SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
64+
"Baz": &FieldConfiguration{StructFieldName: "Baz", SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
6665
}},
6766
}},
6867
"MapInfInf": &FieldConfiguration{StructFieldName: "MapInfInf", SupportedOperations: []MatchOperator{MatchIsEmpty, MatchIsNotEmpty}},
@@ -94,7 +93,7 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
9493
"Float32": &FieldConfiguration{StructFieldName: "Float32", CoerceFn: CoerceFloat32, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
9594
"Float64": &FieldConfiguration{StructFieldName: "Float64", CoerceFn: CoerceFloat64, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
9695
"Bool": &FieldConfiguration{StructFieldName: "Bool", CoerceFn: CoerceBool, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
97-
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
96+
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
9897
}},
9998
"bar": &FieldConfiguration{SubFields: FieldConfigurations{
10099
"Int": &FieldConfiguration{StructFieldName: "Int", CoerceFn: CoerceInt, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
@@ -110,7 +109,7 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
110109
"Float32": &FieldConfiguration{StructFieldName: "Float32", CoerceFn: CoerceFloat32, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
111110
"Float64": &FieldConfiguration{StructFieldName: "Float64", CoerceFn: CoerceFloat64, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
112111
"Bool": &FieldConfiguration{StructFieldName: "Bool", CoerceFn: CoerceBool, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
113-
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
112+
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
114113
}},
115114
"baz": &FieldConfiguration{SubFields: FieldConfigurations{
116115
"Int": &FieldConfiguration{StructFieldName: "Int", CoerceFn: CoerceInt, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
@@ -126,7 +125,7 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
126125
"Float32": &FieldConfiguration{StructFieldName: "Float32", CoerceFn: CoerceFloat32, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
127126
"Float64": &FieldConfiguration{StructFieldName: "Float64", CoerceFn: CoerceFloat64, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
128127
"Bool": &FieldConfiguration{StructFieldName: "Bool", CoerceFn: CoerceBool, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
129-
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
128+
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
130129
}},
131130
},
132131
benchQuick: true,
@@ -149,7 +148,7 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
149148
"Float32": &FieldConfiguration{StructFieldName: "Float32", CoerceFn: CoerceFloat32, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
150149
"Float64": &FieldConfiguration{StructFieldName: "Float64", CoerceFn: CoerceFloat64, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
151150
"Bool": &FieldConfiguration{StructFieldName: "Bool", CoerceFn: CoerceBool, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
152-
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
151+
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
153152
}},
154153
"bar": &FieldConfiguration{SubFields: FieldConfigurations{
155154
"Int": &FieldConfiguration{StructFieldName: "Int", CoerceFn: CoerceInt, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
@@ -165,7 +164,7 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
165164
"Float32": &FieldConfiguration{StructFieldName: "Float32", CoerceFn: CoerceFloat32, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
166165
"Float64": &FieldConfiguration{StructFieldName: "Float64", CoerceFn: CoerceFloat64, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
167166
"Bool": &FieldConfiguration{StructFieldName: "Bool", CoerceFn: CoerceBool, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
168-
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
167+
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
169168
}},
170169
"baz": &FieldConfiguration{SubFields: FieldConfigurations{
171170
"Int": &FieldConfiguration{StructFieldName: "Int", CoerceFn: CoerceInt, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
@@ -181,7 +180,7 @@ var fieldConfigTests map[string]fieldConfigTest = map[string]fieldConfigTest{
181180
"Float32": &FieldConfiguration{StructFieldName: "Float32", CoerceFn: CoerceFloat32, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
182181
"Float64": &FieldConfiguration{StructFieldName: "Float64", CoerceFn: CoerceFloat64, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
183182
"Bool": &FieldConfiguration{StructFieldName: "Bool", CoerceFn: CoerceBool, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual}},
184-
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn}},
183+
"String": &FieldConfiguration{StructFieldName: "String", CoerceFn: CoerceString, SupportedOperations: []MatchOperator{MatchEqual, MatchNotEqual, MatchIn, MatchNotIn, MatchMatches, MatchNotMatches}},
185184
}},
186185
}},
187186
},

0 commit comments

Comments
 (0)