mirror of
				https://github.com/MontFerret/ferret.git
				synced 2025-10-30 23:37:40 +02:00 
			
		
		
		
	Feature/#293 regular exp operator (#326)
* Added Regexp operator * Added simple type check * Fixed linting issue
This commit is contained in:
		
							
								
								
									
										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 ( | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| @@ -1187,6 +1188,36 @@ func (v *visitor) doVisitEqualityOperator(ctx *fql.ExpressionContext, scope *sco | ||||
| 	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) { | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
| 	regexpOp := ctx.RegexpOperator() | ||||
|  | ||||
| 	if regexpOp != nil { | ||||
| 		return v.doVisitRegexpOperator(ctx, scope) | ||||
| 	} | ||||
|  | ||||
| 	variable := ctx.Variable() | ||||
|  | ||||
| 	if variable != nil { | ||||
|   | ||||
| @@ -228,6 +228,7 @@ expression | ||||
|     | expression arrayOperator (inOperator | equalityOperator) expression | ||||
|     | expression inOperator expression | ||||
|     | expression equalityOperator expression | ||||
|     | expression regexpOperator expression | ||||
|     | expression logicalAndOperator expression | ||||
|     | expression logicalOrOperator expression | ||||
|     | expression QuestionMark expression? Colon expression | ||||
| @@ -270,6 +271,11 @@ equalityOperator | ||||
|     | Neq | ||||
|     ; | ||||
|  | ||||
| regexpOperator | ||||
|     : RegexMatch | ||||
|     | RegexNotMatch | ||||
|     ; | ||||
|  | ||||
| logicalAndOperator | ||||
|     : 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. | ||||
| 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. | ||||
| func (s *BaseFqlParserListener) EnterLogicalAndOperator(ctx *LogicalAndOperatorContext) {} | ||||
|  | ||||
|   | ||||
| @@ -211,6 +211,10 @@ func (v *BaseFqlParserVisitor) VisitEqualityOperator(ctx *EqualityOperatorContex | ||||
| 	return v.VisitChildren(ctx) | ||||
| } | ||||
|  | ||||
| func (v *BaseFqlParserVisitor) VisitRegexpOperator(ctx *RegexpOperatorContext) interface{} { | ||||
| 	return v.VisitChildren(ctx) | ||||
| } | ||||
|  | ||||
| func (v *BaseFqlParserVisitor) VisitLogicalAndOperator(ctx *LogicalAndOperatorContext) interface{} { | ||||
| 	return v.VisitChildren(ctx) | ||||
| } | ||||
|   | ||||
| @@ -160,6 +160,9 @@ type FqlParserListener interface { | ||||
| 	// EnterEqualityOperator is called when entering the equalityOperator production. | ||||
| 	EnterEqualityOperator(c *EqualityOperatorContext) | ||||
|  | ||||
| 	// EnterRegexpOperator is called when entering the regexpOperator production. | ||||
| 	EnterRegexpOperator(c *RegexpOperatorContext) | ||||
|  | ||||
| 	// EnterLogicalAndOperator is called when entering the logicalAndOperator production. | ||||
| 	EnterLogicalAndOperator(c *LogicalAndOperatorContext) | ||||
|  | ||||
| @@ -328,6 +331,9 @@ type FqlParserListener interface { | ||||
| 	// ExitEqualityOperator is called when exiting the equalityOperator production. | ||||
| 	ExitEqualityOperator(c *EqualityOperatorContext) | ||||
|  | ||||
| 	// ExitRegexpOperator is called when exiting the regexpOperator production. | ||||
| 	ExitRegexpOperator(c *RegexpOperatorContext) | ||||
|  | ||||
| 	// ExitLogicalAndOperator is called when exiting the logicalAndOperator production. | ||||
| 	ExitLogicalAndOperator(c *LogicalAndOperatorContext) | ||||
|  | ||||
|   | ||||
| @@ -160,6 +160,9 @@ type FqlParserVisitor interface { | ||||
| 	// Visit a parse tree produced by FqlParser#equalityOperator. | ||||
| 	VisitEqualityOperator(ctx *EqualityOperatorContext) interface{} | ||||
|  | ||||
| 	// Visit a parse tree produced by FqlParser#regexpOperator. | ||||
| 	VisitRegexpOperator(ctx *RegexpOperatorContext) interface{} | ||||
|  | ||||
| 	// Visit a parse tree produced by FqlParser#logicalAndOperator. | ||||
| 	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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user