1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-11-06 08:39:09 +02:00

Added namespace builder (#336)

* Added namespace builder

* Fixed linting issues

* Added extra check

* Updated e2e lib

* Renamed NamespaceBuilder to NamespaceContainer and changed func receivers

* Renamed NewLib to RegisterLib
This commit is contained in:
Tim Voronov
2019-07-22 17:21:20 -04:00
committed by GitHub
parent ddfb7a20e8
commit 22382a0f61
17 changed files with 290 additions and 132 deletions

View File

@@ -1,9 +1,6 @@
package compiler
import (
"regexp"
"strings"
"github.com/MontFerret/ferret/pkg/parser"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
@@ -11,14 +8,15 @@ import (
"github.com/pkg/errors"
)
var fnNameValidation = regexp.MustCompile("^[a-zA-Z]+[a-zA-Z0-9_]*(::[a-zA-Z]+[a-zA-Z0-9_]*)*$")
type FqlCompiler struct {
funcs map[string]core.Function
*NamespaceContainer
}
func New(setters ...Option) *FqlCompiler {
c := &FqlCompiler{}
c.NamespaceContainer = newRootNamespace()
c.funcs = make(map[string]core.Function)
opts := &Options{}
for _, setter := range setters {
@@ -26,67 +24,14 @@ func New(setters ...Option) *FqlCompiler {
}
if !opts.noStdlib {
c.funcs = stdlib.NewLib()
} else {
c.funcs = make(map[string]core.Function)
if err := stdlib.RegisterLib(c.NamespaceContainer); err != nil {
panic(err)
}
}
return c
}
func (c *FqlCompiler) RegisterFunction(name string, fun core.Function) error {
_, exists := c.funcs[name]
if exists {
return errors.Errorf("function already exists: %s", name)
}
// validation the name
if !fnNameValidation.MatchString(name) {
return errors.Errorf("invalid function name: %s", name)
}
c.funcs[strings.ToUpper(name)] = fun
return nil
}
func (c *FqlCompiler) RemoveFunction(name string) {
delete(c.funcs, strings.ToUpper(name))
}
func (c *FqlCompiler) RegisterFunctions(funcs map[string]core.Function) error {
for name, fun := range funcs {
if err := c.RegisterFunction(name, fun); err != nil {
return err
}
}
return nil
}
func (c *FqlCompiler) RegisteredFunctions() []string {
res := make([]string, 0, len(c.funcs))
for k := range c.funcs {
res = append(res, k)
}
return res
}
func (c *FqlCompiler) RegisteredFunctionsNS(namespace string) []string {
res := make([]string, 0, len(c.funcs))
for k := range c.funcs {
if strings.HasPrefix(k, namespace) {
res = append(res, k)
}
}
return res
}
func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error) {
if query == "" {
return nil, ErrEmptyQuery

View File

@@ -14,7 +14,7 @@ func TestFunctionNSCall(t *testing.T) {
c := compiler.New()
var counter int
err := c.RegisterFunction("T::SPY", func(_ context.Context, _ ...core.Value) (core.Value, error) {
err := c.Namespace("T").RegisterFunction("SPY", func(_ context.Context, _ ...core.Value) (core.Value, error) {
counter++
return values.None, nil
@@ -39,7 +39,7 @@ func TestFunctionNSCall(t *testing.T) {
c := compiler.New()
var counter int
err := c.RegisterFunction("T::UTILS::SPY", func(_ context.Context, _ ...core.Value) (core.Value, error) {
err := c.Namespace("T").Namespace("UTILS").RegisterFunction("SPY", func(_ context.Context, _ ...core.Value) (core.Value, error) {
counter++
return values.None, nil
@@ -64,7 +64,7 @@ func TestFunctionNSCall(t *testing.T) {
c := compiler.New()
var counter int
err := c.RegisterFunction("T::UTILS::SPY", func(_ context.Context, _ ...core.Value) (core.Value, error) {
err := c.Namespace("T").Namespace("UTILS").RegisterFunction("SPY", func(_ context.Context, _ ...core.Value) (core.Value, error) {
counter++
return values.None, nil
@@ -78,17 +78,4 @@ func TestFunctionNSCall(t *testing.T) {
So(err, ShouldNotBeNil)
})
Convey("Should NOT register RETURN T:UTILS::SPY", t, func() {
c := compiler.New()
var counter int
err := c.RegisterFunction("T::UTILS:SPY", func(_ context.Context, _ ...core.Value) (core.Value, error) {
counter++
return values.None, nil
})
So(err, ShouldNotBeNil)
})
}

View File

@@ -34,7 +34,7 @@ func TestRegexpOperator(t *testing.T) {
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) {
c.Namespace("T").RegisterFunction("REGEXP", func(_ context.Context, _ ...core.Value) (value core.Value, e error) {
return values.NewString("[a-z]+bar$"), nil
})

97
pkg/compiler/namespace.go Normal file
View File

@@ -0,0 +1,97 @@
package compiler
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/pkg/errors"
"regexp"
"strings"
)
var fnNameValidation = regexp.MustCompile("^[a-zA-Z]+[a-zA-Z0-9_]*(::[a-zA-Z]+[a-zA-Z0-9_]*)*$")
const emptyNS = ""
const separator = "::"
type NamespaceContainer struct {
funcs core.Functions
name string
}
func newRootNamespace() *NamespaceContainer {
ns := new(NamespaceContainer)
ns.funcs = make(core.Functions)
return ns
}
func newNamespace(funcs core.Functions, name string) *NamespaceContainer {
return &NamespaceContainer{funcs, strings.ToUpper(name)}
}
func (nc *NamespaceContainer) Namespace(name string) core.Namespace {
return newNamespace(nc.funcs, nc.makeFullName(name))
}
func (nc *NamespaceContainer) RegisterFunction(name string, fun core.Function) error {
nsName := nc.makeFullName(name)
_, exists := nc.funcs[nsName]
if exists {
return errors.Errorf("function already exists: %s", name)
}
// validation the name
if strings.Contains(name, separator) {
return errors.Errorf("invalid function name: %s", name)
}
if !fnNameValidation.MatchString(nsName) {
return errors.Errorf("invalid function or namespace name: %s", nsName)
}
nc.funcs[strings.ToUpper(nsName)] = fun
return nil
}
func (nc *NamespaceContainer) RemoveFunction(name string) {
delete(nc.funcs, strings.ToUpper(nc.makeFullName(name)))
}
func (nc *NamespaceContainer) RegisterFunctions(funcs core.Functions) error {
for name, fun := range funcs {
if err := nc.RegisterFunction(name, fun); err != nil {
return err
}
}
return nil
}
func (nc *NamespaceContainer) RegisteredFunctions() []string {
res := make([]string, 0, len(nc.funcs))
// root namespace, return all functions
if nc.name == emptyNS {
for k := range nc.funcs {
res = append(res, k)
}
} else {
nsPrefix := nc.name + separator
for k := range nc.funcs {
if strings.HasPrefix(k, nsPrefix) {
res = append(res, k)
}
}
}
return res
}
func (nc *NamespaceContainer) makeFullName(name string) string {
if nc.name == emptyNS {
return name
}
return nc.name + separator + name
}

View File

@@ -0,0 +1,96 @@
package compiler_test
import (
"context"
"github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestNamespaceBuilder(t *testing.T) {
Convey("Namespaces", t, func() {
Convey("Should return an error when a function name contains NS separator", func() {
c := compiler.New()
err := c.RegisterFunction("T::SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) {
return values.None, nil
})
So(err, ShouldNotBeNil)
})
Convey("Should successfully register a name within a namespace", func() {
c := compiler.New()
err := c.Namespace("T").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) {
return values.None, nil
})
So(err, ShouldBeNil)
funcs := c.RegisteredFunctions()
var exists bool
for _, name := range funcs {
exists = name == "T::SPY"
if exists {
break
}
}
So(exists, ShouldBeTrue)
})
Convey("Root namespace should return all registered functions", func() {
c := compiler.New()
err := c.Namespace("T").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) {
return values.None, nil
})
So(err, ShouldBeNil)
funcs := c.RegisteredFunctions()
So(len(funcs), ShouldBeGreaterThan, 1)
})
Convey("Namespace should return all registered functions", func() {
c := compiler.New()
err := c.Namespace("T").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) {
return values.None, nil
})
So(err, ShouldBeNil)
err = c.Namespace("T").Namespace("UTILS").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) {
return values.None, nil
})
So(err, ShouldBeNil)
funcs := c.Namespace("T").RegisteredFunctions()
So(funcs, ShouldHaveLength, 2)
funcs2 := c.Namespace("T").Namespace("UTILS").RegisteredFunctions()
So(funcs2, ShouldHaveLength, 1)
})
Convey("Namespace should return an error if namespace name is incorrect", func() {
c := compiler.New()
noop := func(ctx context.Context, args ...core.Value) (value core.Value, e error) {
return values.None, nil
}
err := c.Namespace("T::").RegisterFunction("SPY", noop)
So(err, ShouldNotBeNil)
err = c.Namespace("@F").RegisterFunction("SPY", noop)
So(err, ShouldNotBeNil)
})
})
}