mirror of
https://github.com/MontFerret/ferret.git
synced 2025-06-04 23:27:31 +02:00
Feature/#293 regular exp operator (#326)
* Added Regexp operator * Added simple type check * Fixed linting issue
This commit is contained in:
parent
cf8c55ab99
commit
cf43c7fdad
77
pkg/compiler/compiler_regexp_test.go
Normal file
77
pkg/compiler/compiler_regexp_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegexpOperator(t *testing.T) {
|
||||||
|
Convey("Should be possible to use positive regular expression operator", t, func() {
|
||||||
|
out := compiler.New().
|
||||||
|
MustCompile(`
|
||||||
|
RETURN "foo" =~ "^f[o].$"
|
||||||
|
`).
|
||||||
|
MustRun(context.Background())
|
||||||
|
|
||||||
|
So(string(out), ShouldEqual, `true`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be possible to use negative regular expression operator", t, func() {
|
||||||
|
out := compiler.New().
|
||||||
|
MustCompile(`
|
||||||
|
RETURN "foo" !~ "[a-z]+bar$"
|
||||||
|
`).
|
||||||
|
MustRun(context.Background())
|
||||||
|
|
||||||
|
So(string(out), ShouldEqual, `true`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be possible to use negative regular expression operator", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
c.RegisterFunction("T::REGEXP", func(_ context.Context, _ ...core.Value) (value core.Value, e error) {
|
||||||
|
return values.NewString("[a-z]+bar$"), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
out := c.
|
||||||
|
MustCompile(`
|
||||||
|
RETURN "foo" !~ T::REGEXP()
|
||||||
|
`).
|
||||||
|
MustRun(context.Background())
|
||||||
|
|
||||||
|
So(string(out), ShouldEqual, `true`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an error during compilation when a regexp string invalid", t, func() {
|
||||||
|
_, err := compiler.New().
|
||||||
|
Compile(`
|
||||||
|
RETURN "foo" !~ "[ ]\K(?<!\d )(?=(?: ?\d){8})(?!(?: ?\d){9})\d[ \d]+\d"
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an error during compilation when a regexp is not a string", t, func() {
|
||||||
|
right := []string{
|
||||||
|
"[]",
|
||||||
|
"{}",
|
||||||
|
"1",
|
||||||
|
"1.1",
|
||||||
|
"TRUE",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range right {
|
||||||
|
_, err := compiler.New().
|
||||||
|
Compile(fmt.Sprintf(`
|
||||||
|
RETURN "foo" !~ %s
|
||||||
|
`, r))
|
||||||
|
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -2,6 +2,7 @@ package compiler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -1187,6 +1188,36 @@ func (v *visitor) doVisitEqualityOperator(ctx *fql.ExpressionContext, scope *sco
|
|||||||
return operators.NewEqualityOperator(v.getSourceMap(equalityOp), left, right, equalityOp.GetText())
|
return operators.NewEqualityOperator(v.getSourceMap(equalityOp), left, right, equalityOp.GetText())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *visitor) doVisitRegexpOperator(ctx *fql.ExpressionContext, scope *scope) (core.Expression, error) {
|
||||||
|
regexpOp := ctx.RegexpOperator().(*fql.RegexpOperatorContext)
|
||||||
|
rawExps := ctx.AllExpression()
|
||||||
|
exps, err := v.doVisitAllExpressions(rawExps, scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
left := exps[0]
|
||||||
|
right := exps[1]
|
||||||
|
|
||||||
|
switch lit := right.(type) {
|
||||||
|
case literals.StringLiteral:
|
||||||
|
_, err := regexp.Compile(string(lit))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
src := v.getSourceMap(rawExps[1])
|
||||||
|
|
||||||
|
return nil, errors.Wrap(err, src.String())
|
||||||
|
}
|
||||||
|
case *literals.ArrayLiteral, *literals.ObjectLiteral, literals.BooleanLiteral, literals.FloatLiteral, literals.IntLiteral:
|
||||||
|
src := v.getSourceMap(rawExps[1])
|
||||||
|
|
||||||
|
return nil, errors.Wrap(errors.New("expected a string literal or a function call"), src.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return operators.NewRegexpOperator(v.getSourceMap(regexpOp), left, right, regexpOp.GetText())
|
||||||
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitInOperator(ctx *fql.ExpressionContext, scope *scope) (core.OperatorExpression, error) {
|
func (v *visitor) doVisitInOperator(ctx *fql.ExpressionContext, scope *scope) (core.OperatorExpression, error) {
|
||||||
exps, err := v.doVisitAllExpressions(ctx.AllExpression(), scope)
|
exps, err := v.doVisitAllExpressions(ctx.AllExpression(), scope)
|
||||||
|
|
||||||
@ -1333,6 +1364,12 @@ func (v *visitor) doVisitExpression(ctx *fql.ExpressionContext, scope *scope) (c
|
|||||||
return v.doVisitLogicalOperator(ctx, scope)
|
return v.doVisitLogicalOperator(ctx, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regexpOp := ctx.RegexpOperator()
|
||||||
|
|
||||||
|
if regexpOp != nil {
|
||||||
|
return v.doVisitRegexpOperator(ctx, scope)
|
||||||
|
}
|
||||||
|
|
||||||
variable := ctx.Variable()
|
variable := ctx.Variable()
|
||||||
|
|
||||||
if variable != nil {
|
if variable != nil {
|
||||||
|
@ -228,6 +228,7 @@ expression
|
|||||||
| expression arrayOperator (inOperator | equalityOperator) expression
|
| expression arrayOperator (inOperator | equalityOperator) expression
|
||||||
| expression inOperator expression
|
| expression inOperator expression
|
||||||
| expression equalityOperator expression
|
| expression equalityOperator expression
|
||||||
|
| expression regexpOperator expression
|
||||||
| expression logicalAndOperator expression
|
| expression logicalAndOperator expression
|
||||||
| expression logicalOrOperator expression
|
| expression logicalOrOperator expression
|
||||||
| expression QuestionMark expression? Colon expression
|
| expression QuestionMark expression? Colon expression
|
||||||
@ -270,6 +271,11 @@ equalityOperator
|
|||||||
| Neq
|
| Neq
|
||||||
;
|
;
|
||||||
|
|
||||||
|
regexpOperator
|
||||||
|
: RegexMatch
|
||||||
|
| RegexNotMatch
|
||||||
|
;
|
||||||
|
|
||||||
logicalAndOperator
|
logicalAndOperator
|
||||||
: And
|
: And
|
||||||
;
|
;
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -328,6 +328,12 @@ func (s *BaseFqlParserListener) EnterEqualityOperator(ctx *EqualityOperatorConte
|
|||||||
// ExitEqualityOperator is called when production equalityOperator is exited.
|
// ExitEqualityOperator is called when production equalityOperator is exited.
|
||||||
func (s *BaseFqlParserListener) ExitEqualityOperator(ctx *EqualityOperatorContext) {}
|
func (s *BaseFqlParserListener) ExitEqualityOperator(ctx *EqualityOperatorContext) {}
|
||||||
|
|
||||||
|
// EnterRegexpOperator is called when production regexpOperator is entered.
|
||||||
|
func (s *BaseFqlParserListener) EnterRegexpOperator(ctx *RegexpOperatorContext) {}
|
||||||
|
|
||||||
|
// ExitRegexpOperator is called when production regexpOperator is exited.
|
||||||
|
func (s *BaseFqlParserListener) ExitRegexpOperator(ctx *RegexpOperatorContext) {}
|
||||||
|
|
||||||
// EnterLogicalAndOperator is called when production logicalAndOperator is entered.
|
// EnterLogicalAndOperator is called when production logicalAndOperator is entered.
|
||||||
func (s *BaseFqlParserListener) EnterLogicalAndOperator(ctx *LogicalAndOperatorContext) {}
|
func (s *BaseFqlParserListener) EnterLogicalAndOperator(ctx *LogicalAndOperatorContext) {}
|
||||||
|
|
||||||
|
@ -211,6 +211,10 @@ func (v *BaseFqlParserVisitor) VisitEqualityOperator(ctx *EqualityOperatorContex
|
|||||||
return v.VisitChildren(ctx)
|
return v.VisitChildren(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *BaseFqlParserVisitor) VisitRegexpOperator(ctx *RegexpOperatorContext) interface{} {
|
||||||
|
return v.VisitChildren(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *BaseFqlParserVisitor) VisitLogicalAndOperator(ctx *LogicalAndOperatorContext) interface{} {
|
func (v *BaseFqlParserVisitor) VisitLogicalAndOperator(ctx *LogicalAndOperatorContext) interface{} {
|
||||||
return v.VisitChildren(ctx)
|
return v.VisitChildren(ctx)
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,9 @@ type FqlParserListener interface {
|
|||||||
// EnterEqualityOperator is called when entering the equalityOperator production.
|
// EnterEqualityOperator is called when entering the equalityOperator production.
|
||||||
EnterEqualityOperator(c *EqualityOperatorContext)
|
EnterEqualityOperator(c *EqualityOperatorContext)
|
||||||
|
|
||||||
|
// EnterRegexpOperator is called when entering the regexpOperator production.
|
||||||
|
EnterRegexpOperator(c *RegexpOperatorContext)
|
||||||
|
|
||||||
// EnterLogicalAndOperator is called when entering the logicalAndOperator production.
|
// EnterLogicalAndOperator is called when entering the logicalAndOperator production.
|
||||||
EnterLogicalAndOperator(c *LogicalAndOperatorContext)
|
EnterLogicalAndOperator(c *LogicalAndOperatorContext)
|
||||||
|
|
||||||
@ -328,6 +331,9 @@ type FqlParserListener interface {
|
|||||||
// ExitEqualityOperator is called when exiting the equalityOperator production.
|
// ExitEqualityOperator is called when exiting the equalityOperator production.
|
||||||
ExitEqualityOperator(c *EqualityOperatorContext)
|
ExitEqualityOperator(c *EqualityOperatorContext)
|
||||||
|
|
||||||
|
// ExitRegexpOperator is called when exiting the regexpOperator production.
|
||||||
|
ExitRegexpOperator(c *RegexpOperatorContext)
|
||||||
|
|
||||||
// ExitLogicalAndOperator is called when exiting the logicalAndOperator production.
|
// ExitLogicalAndOperator is called when exiting the logicalAndOperator production.
|
||||||
ExitLogicalAndOperator(c *LogicalAndOperatorContext)
|
ExitLogicalAndOperator(c *LogicalAndOperatorContext)
|
||||||
|
|
||||||
|
@ -160,6 +160,9 @@ type FqlParserVisitor interface {
|
|||||||
// Visit a parse tree produced by FqlParser#equalityOperator.
|
// Visit a parse tree produced by FqlParser#equalityOperator.
|
||||||
VisitEqualityOperator(ctx *EqualityOperatorContext) interface{}
|
VisitEqualityOperator(ctx *EqualityOperatorContext) interface{}
|
||||||
|
|
||||||
|
// Visit a parse tree produced by FqlParser#regexpOperator.
|
||||||
|
VisitRegexpOperator(ctx *RegexpOperatorContext) interface{}
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#logicalAndOperator.
|
// Visit a parse tree produced by FqlParser#logicalAndOperator.
|
||||||
VisitLogicalAndOperator(ctx *LogicalAndOperatorContext) interface{}
|
VisitLogicalAndOperator(ctx *LogicalAndOperatorContext) interface{}
|
||||||
|
|
||||||
|
85
pkg/runtime/expressions/operators/regexp.go
Normal file
85
pkg/runtime/expressions/operators/regexp.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package operators
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
RegexpOperatorType int
|
||||||
|
RegexpOperator struct {
|
||||||
|
*baseOperator
|
||||||
|
opType RegexpOperatorType
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RegexpOperatorTypeNegative RegexpOperatorType = 0
|
||||||
|
RegexpOperatorTypePositive RegexpOperatorType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var regexpOperators = map[string]RegexpOperatorType{
|
||||||
|
"!~": RegexpOperatorTypeNegative,
|
||||||
|
"=~": RegexpOperatorTypePositive,
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegexpOperator(
|
||||||
|
src core.SourceMap,
|
||||||
|
left core.Expression,
|
||||||
|
right core.Expression,
|
||||||
|
operator string,
|
||||||
|
) (*RegexpOperator, error) {
|
||||||
|
op, exists := regexpOperators[operator]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, core.Error(core.ErrInvalidArgument, "operator")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RegexpOperator{
|
||||||
|
&baseOperator{
|
||||||
|
src,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
},
|
||||||
|
op,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operator *RegexpOperator) Type() RegexpOperatorType {
|
||||||
|
return operator.opType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operator *RegexpOperator) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
|
left, err := operator.left.Exec(ctx, scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
right, err := operator.right.Exec(ctx, scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return operator.Eval(ctx, left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (operator *RegexpOperator) Eval(_ context.Context, left, right core.Value) (core.Value, error) {
|
||||||
|
leftStr := left.String()
|
||||||
|
rightStr := right.String()
|
||||||
|
|
||||||
|
r, err := regexp.Compile(rightStr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if operator.opType == RegexpOperatorTypePositive {
|
||||||
|
return values.NewBoolean(r.MatchString(leftStr)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.NewBoolean(!r.MatchString(leftStr)), nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user