1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-13 19:52:52 +02:00

Remove outdated compiler benchmarks, refactor function registration in namespace, and migrate math stdlib functions to simplified argument handling. Add new benchmarks for function calls and sorting scenarios.

This commit is contained in:
Tim Voronov
2025-06-30 17:02:09 -04:00
parent e09f981560
commit 223c28aa6b
56 changed files with 1025 additions and 706 deletions

View File

@@ -421,19 +421,47 @@ func (ec *ExprCompiler) CompileFunctionCallWith(ctx fql.IFunctionCallContext, pr
return seq[0]
default:
dest := ec.ctx.Registers.Allocate(core.Temp)
ec.ctx.Emitter.EmitLoadConst(dest, ec.ctx.Symbols.AddConstant(name))
if !protected {
ec.ctx.Emitter.EmitAs(vm.OpCall, dest, seq)
} else {
ec.ctx.Emitter.EmitAs(vm.OpProtectedCall, dest, seq)
}
return dest
return ec.compileUserFunctionCallWith(name, protected, seq)
}
}
func (ec *ExprCompiler) compileUserFunctionCallWith(name runtime.String, protected bool, seq core.RegisterSequence) vm.Operand {
dest := ec.ctx.Registers.Allocate(core.Temp)
ec.ctx.Emitter.EmitLoadConst(dest, ec.ctx.Symbols.AddConstant(name))
var opcode vm.Opcode
var protectedOpcode vm.Opcode
switch len(seq) {
case 0:
opcode = vm.OpCall0
protectedOpcode = vm.OpProtectedCall0
case 1:
opcode = vm.OpCall1
protectedOpcode = vm.OpProtectedCall1
case 2:
opcode = vm.OpCall2
protectedOpcode = vm.OpProtectedCall2
case 3:
opcode = vm.OpCall3
protectedOpcode = vm.OpProtectedCall3
case 4:
opcode = vm.OpCall4
protectedOpcode = vm.OpProtectedCall4
default:
opcode = vm.OpCall
protectedOpcode = vm.OpProtectedCall
}
if !protected {
ec.ctx.Emitter.EmitAs(opcode, dest, seq)
} else {
ec.ctx.Emitter.EmitAs(protectedOpcode, dest, seq)
}
return dest
}
func (ec *ExprCompiler) CompileArgumentList(ctx fql.IArgumentListContext) core.RegisterSequence {
var seq core.RegisterSequence

View File

@@ -35,7 +35,7 @@ func sortDirection(dir antlr.TerminalNode) runtime.SortDirection {
return runtime.SortDirectionAsc
}
func copyFromNamespace(fns *runtime.Functions, namespace string) error {
func copyFromNamespace(fns runtime.Functions, namespace string) error {
// In the name of the function "A::B::C", the namespace is "A::B",
// not "A::B::".
//
@@ -53,15 +53,28 @@ func copyFromNamespace(fns *runtime.Functions, namespace string) error {
noprefix := strings.Replace(name, namespace, "", 1)
if _, exists := fns.Get(noprefix); exists {
if exists := fns.Has(noprefix); exists {
return errors.Errorf(
`collision occurred: "%s" already registered`,
noprefix,
)
}
fn, _ := fns.Get(name)
fns.Set(noprefix, fn)
if fn, exists := fns.F().Get(name); exists {
fns.F().Unset(name).Set(noprefix, fn)
} else if fn, exists := fns.F0().Get(name); exists {
fns.F0().Unset(name).Set(noprefix, fn)
} else if fn, exists := fns.F1().Get(name); exists {
fns.F1().Unset(name).Set(noprefix, fn)
} else if fn, exists := fns.F2().Get(name); exists {
fns.F2().Unset(name).Set(noprefix, fn)
} else if fn, exists := fns.F3().Get(name); exists {
fns.F3().Unset(name).Set(noprefix, fn)
} else if fn, exists := fns.F4().Get(name); exists {
fns.F4().Unset(name).Set(noprefix, fn)
} else {
return errors.Errorf(`function "%s" not found`, name)
}
}
return nil

View File

@@ -5,8 +5,6 @@ import (
"strings"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/pkg/errors"
)
var fnNameValidation = regexp.MustCompile("^[a-zA-Z]+[a-zA-Z0-9_]*(::[a-zA-Z]+[a-zA-Z0-9_]*)*$")
@@ -34,53 +32,12 @@ func (nc *NamespaceContainer) Namespace(name string) runtime.Namespace {
return NewNamespace(nc.funcs, nc.makeFullName(name))
}
func (nc *NamespaceContainer) MustRegisterFunction(name string, fun runtime.Function) {
if err := nc.RegisterFunction(name, fun); err != nil {
panic(err)
}
}
func (nc *NamespaceContainer) RegisterFunction(name string, fun runtime.Function) error {
nsName := nc.makeFullName(name)
_, exists := nc.funcs.Get(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.Set(nsName, fun)
return nil
}
func (nc *NamespaceContainer) RemoveFunction(name string) {
nc.funcs.Unset(nc.makeFullName(name))
}
func (nc *NamespaceContainer) MustRegisterFunctions(funcs runtime.Functions) {
if err := nc.RegisterFunctions(funcs); err != nil {
panic(err)
}
}
func (nc *NamespaceContainer) RegisterFunctions(funcs runtime.Functions) error {
for _, name := range funcs.Names() {
fun, _ := funcs.Get(name)
if err := nc.RegisterFunction(name, fun); err != nil {
return err
}
}
nc.funcs.SetAll(funcs)
return nil
}

View File

@@ -2,118 +2,68 @@ package runtime
import (
"context"
"fmt"
)
// MaxArgs defines the maximum number of arguments that a function can accept.
const MaxArgs = 65536
type (
// Functions is a container for functions.
Functions map[string]Function
Functions interface {
Has(name string) bool
F() FunctionCollection[Function]
F0() FunctionCollection[Function0]
F1() FunctionCollection[Function1]
F2() FunctionCollection[Function2]
F3() FunctionCollection[Function3]
F4() FunctionCollection[Function4]
SetAll(otherFns Functions) Functions
Unset(name string) Functions
UnsetAll() Functions
Names() []string
}
// Function is a common interface for all functions of FQL.
FunctionsBuilder interface {
Set(name string, fn Function) FunctionsBuilder
Set0(name string, fn Function0) FunctionsBuilder
Set1(name string, fn Function1) FunctionsBuilder
Set2(name string, fn Function2) FunctionsBuilder
Set3(name string, fn Function3) FunctionsBuilder
Set4(name string, fn Function4) FunctionsBuilder
Build() Functions
}
FunctionConstraint interface {
Function | Function0 | Function1 | Function2 | Function3 | Function4
}
FunctionCollection[T FunctionConstraint] interface {
Has(name string) bool
Set(name string, fn T) FunctionCollection[T]
SetAll(otherFns FunctionCollection[T]) FunctionCollection[T]
Get(name string) (T, bool)
GetAll() map[string]T
Unset(name string) FunctionCollection[T]
UnsetAll() FunctionCollection[T]
Names() []string
Size() int
}
// Function is a common interface for functions with variable number of arguments.
// All functions receive a context and a slice of values, returning a value and an error.
Function = func(ctx context.Context, args ...Value) (Value, error)
// Function0 is a common interface for functions with no arguments.
Function0 = func(ctx context.Context) (Value, error)
// Function1 is a common interface for functions with a single argument.
Function1 = func(ctx context.Context, arg Value) (Value, error)
// Function2 is a common interface for functions with two arguments.
Function2 = func(ctx context.Context, arg1, arg2 Value) (Value, error)
// Function3 is a common interface for functions with three arguments.
Function3 = func(ctx context.Context, arg1, arg2, arg3 Value) (Value, error)
// Function4 is a common interface for functions with four arguments.
Function4 = func(ctx context.Context, arg1, arg2, arg3, arg4 Value) (Value, error)
)
func ErrorArg(err error, pos int) error {
return Errorf(
ErrInvalidArgumentType,
"expected argument %d to be: %s",
pos+1, err.Error(),
)
}
func ValidateArgs(args []Value, minimum, maximum int) error {
count := len(args)
if count < minimum || count > maximum {
return Error(
ErrInvalidArgumentNumber,
fmt.Sprintf(
"expected number of arguments %d-%d, but got %d",
minimum,
maximum,
len(args)))
}
return nil
}
func ValidateArgType(args []Value, pos int, assertion TypeAssertion) error {
if pos >= len(args) {
return nil
}
arg := args[pos]
err := assertion(arg)
if err == nil {
return nil
}
return ErrorArg(err, pos)
}
// NewFunctions returns new empty Functions.
func NewFunctions() Functions {
return make(map[string]Function)
}
// NewFunctionsFromMap creates new Functions from map, where
// key is the name of the function and value is the function.
func NewFunctionsFromMap(funcs map[string]Function) Functions {
fns := NewFunctions()
for name, fn := range funcs {
fns.Set(name, fn)
}
return fns
}
// Has returns true if the function with the given name exists.
func (fns Functions) Has(name string) bool {
_, exists := fns[name]
return exists
}
// Get returns the function with the given name. If the function
// does not exist it returns nil, false.
func (fns Functions) Get(name string) (Function, bool) {
fn, exists := fns[name]
return fn, exists
}
// MustGet returns the function with the given name. If the function
// does not exist it panics.
func (fns Functions) MustGet(name string) Function {
return fns[name]
}
// Set sets the function with the given name. If the function
// with the such name already exists it will be overwritten.
func (fns Functions) Set(name string, fn Function) {
fns[name] = fn
}
// Unset delete the function with the given name.
func (fns Functions) Unset(name string) {
delete(fns, name)
}
// Names returns the names of the internal functions.
func (fns Functions) Names() []string {
names := make([]string, 0, len(fns))
for name := range fns {
names = append(names, name)
}
return names
}
// Unwrap returns the internal map of functions.
func (fns Functions) Unwrap() map[string]Function {
return fns
}

View File

@@ -0,0 +1,51 @@
package runtime
type functionBuilder struct {
functions Functions
}
func NewFunctionsBuilder() FunctionsBuilder {
return &functionBuilder{
functions: NewFunctions(),
}
}
func (f *functionBuilder) Set(name string, fn Function) FunctionsBuilder {
f.functions.F().Set(name, fn)
return f
}
func (f *functionBuilder) Set0(name string, fn Function0) FunctionsBuilder {
f.functions.F0().Set(name, fn)
return f
}
func (f *functionBuilder) Set1(name string, fn Function1) FunctionsBuilder {
f.functions.F1().Set(name, fn)
return f
}
func (f *functionBuilder) Set2(name string, fn Function2) FunctionsBuilder {
f.functions.F2().Set(name, fn)
return f
}
func (f *functionBuilder) Set3(name string, fn Function3) FunctionsBuilder {
f.functions.F3().Set(name, fn)
return f
}
func (f *functionBuilder) Set4(name string, fn Function4) FunctionsBuilder {
f.functions.F4().Set(name, fn)
return f
}
func (f *functionBuilder) Build() Functions {
return f.functions
}

View File

@@ -0,0 +1,97 @@
package runtime
type functionCollection[T FunctionConstraint] struct {
values map[string]T
}
// NewFunctionCollection creates a new function collection of the specified type
func NewFunctionCollection[T FunctionConstraint]() FunctionCollection[T] {
return &functionCollection[T]{
values: make(map[string]T),
}
}
// NewFunctionCollectionFromMap creates a new function collection from an existing map
func NewFunctionCollectionFromMap[T FunctionConstraint](values map[string]T) FunctionCollection[T] {
fc := &functionCollection[T]{
values: make(map[string]T, len(values)),
}
for name, fn := range values {
fc.values[name] = fn
}
return fc
}
func (f *functionCollection[T]) Has(name string) bool {
_, exists := f.values[name]
return exists
}
func (f *functionCollection[T]) Set(name string, fn T) FunctionCollection[T] {
f.values[name] = fn
return f
}
func (f *functionCollection[T]) SetAll(otherFns FunctionCollection[T]) FunctionCollection[T] {
if otherFns == nil {
return f
}
for name, fn := range otherFns.GetAll() {
f.values[name] = fn
}
return f
}
func (f *functionCollection[T]) Get(name string) (T, bool) {
fn, exists := f.values[name]
return fn, exists
}
func (f *functionCollection[T]) GetAll() map[string]T {
// Return a copy to prevent external modification
result := make(map[string]T, len(f.values))
for name, fn := range f.values {
result[name] = fn
}
return result
}
func (f *functionCollection[T]) Unset(name string) FunctionCollection[T] {
delete(f.values, name)
return f
}
func (f *functionCollection[T]) UnsetAll() FunctionCollection[T] {
f.values = make(map[string]T)
return f
}
func (f *functionCollection[T]) Names() []string {
names := make([]string, 0, len(f.values))
for name := range f.values {
names = append(names, name)
}
return names
}
func (f *functionCollection[T]) Size() int {
return len(f.values)
}

View File

@@ -0,0 +1,50 @@
package runtime
import "fmt"
// ErrorArg creates an error for an invalid argument at the specified position.
// The position is 0-based internally but reported as 1-based to users.
func ErrorArg(err error, pos int) error {
return Errorf(
ErrInvalidArgumentType,
"expected argument %d to be: %s",
pos+1, err.Error(),
)
}
// ValidateArgs validates that the number of arguments is within the specified range.
// It returns an error if the argument count is outside the [minimum, maximum] range.
func ValidateArgs(args []Value, minimum, maximum int) error {
count := len(args)
if count < minimum || count > maximum {
return Error(
ErrInvalidArgumentNumber,
fmt.Sprintf(
"expected number of arguments %d-%d, but got %d",
minimum,
maximum,
len(args)))
}
return nil
}
// ValidateArgType validates that the argument at the specified position matches
// the given type assertion. If the position is beyond the arguments array,
// no validation is performed (returns nil).
func ValidateArgType(args []Value, pos int, assertion TypeAssertion) error {
if pos >= len(args) {
return nil
}
arg := args[pos]
err := assertion(arg)
if err == nil {
return nil
}
return ErrorArg(err, pos)
}

View File

@@ -0,0 +1,171 @@
package runtime
// Functions is a container for functions that organizes them by their argument count.
// It provides separate storage for functions with fixed argument counts (0-4) and
// functions with variable argument counts for optimal performance.
type functionRegistry struct {
variadic FunctionCollection[Function] // Functions with variable number of arguments
zero FunctionCollection[Function0] // Functions with no arguments
single FunctionCollection[Function1] // Functions with a single argument
double FunctionCollection[Function2] // Functions with two arguments
tripple FunctionCollection[Function3] // Functions with three arguments
quadruple FunctionCollection[Function4] // Functions with four arguments
}
// NewFunctions creates and returns a new empty Functions container.
func NewFunctions() Functions {
return &functionRegistry{}
}
func NewFunctionsFromMap(funcs map[string]Function) Functions {
return &functionRegistry{
variadic: NewFunctionCollectionFromMap(funcs),
zero: NewFunctionCollection[Function0](),
single: NewFunctionCollection[Function1](),
double: NewFunctionCollection[Function2](),
tripple: NewFunctionCollection[Function3](),
quadruple: NewFunctionCollection[Function4](),
}
}
func (f *functionRegistry) Has(name string) bool {
return f.F().Has(name) ||
f.F0().Has(name) ||
f.F1().Has(name) ||
f.F2().Has(name) ||
f.F3().Has(name) ||
f.F4().Has(name)
}
func (f *functionRegistry) F() FunctionCollection[Function] {
if f.variadic == nil {
f.variadic = NewFunctionCollection[Function]()
}
return f.variadic
}
func (f *functionRegistry) F0() FunctionCollection[Function0] {
if f.zero == nil {
f.zero = NewFunctionCollection[Function0]()
}
return f.zero
}
func (f *functionRegistry) F1() FunctionCollection[Function1] {
if f.single == nil {
f.single = NewFunctionCollection[Function1]()
}
return f.single
}
func (f *functionRegistry) F2() FunctionCollection[Function2] {
if f.double == nil {
f.double = NewFunctionCollection[Function2]()
}
return f.double
}
func (f *functionRegistry) F3() FunctionCollection[Function3] {
if f.tripple == nil {
f.tripple = NewFunctionCollection[Function3]()
}
return f.tripple
}
func (f *functionRegistry) F4() FunctionCollection[Function4] {
if f.quadruple == nil {
f.quadruple = NewFunctionCollection[Function4]()
}
return f.quadruple
}
func (f *functionRegistry) SetAll(otherFns Functions) Functions {
if otherFns == nil {
return f
}
// Copy functions from each collection
f.F().SetAll(otherFns.F())
f.F0().SetAll(otherFns.F0())
f.F1().SetAll(otherFns.F1())
f.F2().SetAll(otherFns.F2())
f.F3().SetAll(otherFns.F3())
f.F4().SetAll(otherFns.F4())
return f
}
func (f *functionRegistry) Unset(name string) Functions {
if f.F().Has(name) {
f.variadic.Unset(name)
} else if f.F0().Has(name) {
f.zero.Unset(name)
} else if f.F1().Has(name) {
f.single.Unset(name)
} else if f.F2().Has(name) {
f.double.Unset(name)
} else if f.F3().Has(name) {
f.tripple.Unset(name)
} else if f.F4().Has(name) {
f.quadruple.Unset(name)
}
return f
}
func (f *functionRegistry) UnsetAll() Functions {
if f.variadic != nil {
f.variadic.UnsetAll()
}
if f.zero != nil {
f.zero.UnsetAll()
}
if f.single != nil {
f.single.UnsetAll()
}
if f.double != nil {
f.double.UnsetAll()
}
if f.tripple != nil {
f.tripple.UnsetAll()
}
if f.quadruple != nil {
f.quadruple.UnsetAll()
}
return f
}
func (f *functionRegistry) Names() []string {
// Pre-calculate capacity to avoid reallocations
capacity := f.F().Size() +
f.F0().Size() +
f.F1().Size() +
f.F2().Size() +
f.F3().Size() +
f.F4().Size()
names := make([]string, 0, capacity)
// Collect names from all function collections
names = append(names, f.variadic.Names()...)
names = append(names, f.zero.Names()...)
names = append(names, f.single.Names()...)
names = append(names, f.double.Names()...)
names = append(names, f.tripple.Names()...)
names = append(names, f.quadruple.Names()...)
return names
}

View File

@@ -2,7 +2,6 @@ package runtime
type Namespace interface {
Namespace(name string) Namespace
RegisterFunction(name string, fun Function) error
RegisterFunctions(funs Functions) error
RegisteredFunctions() []string
RemoveFunction(name string)

View File

@@ -10,14 +10,10 @@ import (
// ABS returns the absolute value of a given number.
// @param {Int | Float} number - Input number.
// @return {Float} - The absolute value of a given number.
func Abs(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Abs(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Abs(toFloat(args[0]))), nil
return runtime.NewFloat(math.Abs(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// ACOS returns the arccosine, in radians, of a given number.
// @param {Int | Float} number - Input number.
// @return {Float} - The arccosine, in radians, of a given number.
func Acos(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Acos(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Acos(toFloat(args[0]))), nil
return runtime.NewFloat(math.Acos(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// ASIN returns the arcsine, in radians, of a given number.
// @param {Int | Float} number - Input number.
// @return {Float} - The arcsine, in radians, of a given number.
func Asin(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Asin(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Asin(toFloat(args[0]))), nil
return runtime.NewFloat(math.Asin(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// ATAN returns the arctangent, in radians, of a given number.
// @param {Int | Float} number - Input number.
// @return {Float} - The arctangent, in radians, of a given number.
func Atan(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Atan(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Atan(toFloat(args[0]))), nil
return runtime.NewFloat(math.Atan(toFloat(arg))), nil
}

View File

@@ -11,21 +11,17 @@ import (
// @param {Int | Float} number1 - Input number.
// @param {Int | Float} number2 - Input number.
// @return {Float} - The arc tangent of y/x, using the signs of the two to determine the quadrant of the return value.
func Atan2(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 2, 2); err != nil {
func Atan2(_ context.Context, arg1, arg2 runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg1); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
if err := runtime.AssertNumber(arg2); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[1]); err != nil {
return runtime.None, err
}
argf1 := toFloat(arg1)
argf2 := toFloat(arg2)
arg1 := toFloat(args[0])
arg2 := toFloat(args[1])
return runtime.NewFloat(math.Atan2(arg1, arg2)), nil
return runtime.NewFloat(math.Atan2(argf1, argf2)), nil
}

View File

@@ -9,12 +9,8 @@ import (
// AVERAGE Returns the average (arithmetic mean) of the values in array.
// @param {Int[] | Float[]} array - arrayList of numbers.
// @return {Float} - The average of the values in array.
func Average(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func Average(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -10,14 +10,10 @@ import (
// CEIL returns the least integer value greater than or equal to a given value.
// @param {Int | Float} number - Input number.
// @return {Int} - The least integer value greater than or equal to a given value.
func Ceil(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Ceil(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewInt(int(math.Ceil(toFloat(args[0])))), nil
return runtime.NewInt(int(math.Ceil(toFloat(arg)))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// COS returns the cosine of a given number.
// @param {Int | Float} number - Input number.
// @return {Float} - The cosine of a given number.
func Cos(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Cos(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Cos(toFloat(args[0]))), nil
return runtime.NewFloat(math.Cos(toFloat(arg))), nil
}

View File

@@ -9,16 +9,12 @@ import (
// DEGREES returns the angle converted from radians to degrees.
// @param {Int | Float} number - The input number.
// @return {Float} - The angle in degrees
func Degrees(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Degrees(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
r := toFloat(args[0])
r := toFloat(arg)
return runtime.NewFloat(r * RadToDeg), nil
}

View File

@@ -10,14 +10,10 @@ import (
// EXP returns Euler's constant (2.71828...) raised to the power of value.
// @param {Int | Float} number - Input number.
// @return {Float} - Euler's constant raised to the power of value.
func Exp(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Exp(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Exp(toFloat(args[0]))), nil
return runtime.NewFloat(math.Exp(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// EXP2 returns 2 raised to the power of value.
// @param {Int | Float} number - Input number.
// @return {Float} - 2 raised to the power of value.
func Exp2(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Exp2(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Exp2(toFloat(args[0]))), nil
return runtime.NewFloat(math.Exp2(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// FLOOR returns the greatest integer value less than or equal to a given value.
// @param {Int | Float} number - Input number.
// @return {Int} - The greatest integer value less than or equal to a given value.
func Floor(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Floor(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewInt(int(math.Floor(toFloat(args[0])))), nil
return runtime.NewInt(int(math.Floor(toFloat(arg)))), nil
}

View File

@@ -15,41 +15,43 @@ const (
func RegisterLib(ns runtime.Namespace) error {
return ns.RegisterFunctions(
runtime.NewFunctionsFromMap(map[string]runtime.Function{
"ABS": Abs,
"ACOS": Acos,
"ASIN": Asin,
"ATAN": Atan,
"ATAN2": Atan2,
"AVERAGE": Average,
"CEIL": Ceil,
"COS": Cos,
"DEGREES": Degrees,
"EXP": Exp,
"EXP2": Exp2,
"FLOOR": Floor,
"LOG": Log,
"LOG2": Log2,
"LOG10": Log10,
"MAX": Max,
"MEDIAN": Median,
"MIN": Min,
"PERCENTILE": Percentile,
"PI": Pi,
"POW": Pow,
"RADIANS": Radians,
"RAND": Rand,
"RANGE": Range,
"ROUND": Round,
"SIN": Sin,
"SQRT": Sqrt,
"STDDEV_POPULATION": StandardDeviationPopulation,
"STDDEV_SAMPLE": StandardDeviationSample,
"SUM": Sum,
"TAN": Tan,
"VARIANCE_POPULATION": PopulationVariance,
"VARIANCE_SAMPLE": SampleVariance,
}))
runtime.
NewFunctionsBuilder().
Set1("ABS", Abs).
Set1("ACOS", Acos).
Set1("ASIN", Asin).
Set1("ATAN", Atan).
Set2("ATAN2", Atan2).
Set1("AVERAGE", Average).
Set1("CEIL", Ceil).
Set1("COS", Cos).
Set1("DEGREES", Degrees).
Set1("EXP", Exp).
Set1("EXP2", Exp2).
Set1("FLOOR", Floor).
Set1("LOG", Log).
Set1("LOG2", Log2).
Set1("LOG10", Log10).
Set1("MAX", Max).
Set1("MEDIAN", Median).
Set1("MIN", Min).
Set("PERCENTILE", Percentile).
Set0("PI", Pi).
Set2("POW", Pow).
Set1("RADIANS", Radians).
Set("RAND", Rand).
Set("RANGE", Range).
Set1("ROUND", Round).
Set1("SIN", Sin).
Set1("SQRT", Sqrt).
Set1("STDDEV_POPULATION", StandardDeviationPopulation).
Set1("STDDEV_SAMPLE", StandardDeviationSample).
Set1("SUM", Sum).
Set1("TAN", Tan).
Set1("VARIANCE_POPULATION", PopulationVariance).
Set1("VARIANCE_SAMPLE", SampleVariance).
Build(),
)
}
func toFloat(arg runtime.Value) float64 {

View File

@@ -10,14 +10,10 @@ import (
// LOG returns the natural logarithm of a given value.
// @param {Int | Float} number - Input number.
// @return {Float} - The natural logarithm of a given value.
func Log(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Log(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Log(toFloat(args[0]))), nil
return runtime.NewFloat(math.Log(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// LOG10 returns the decimal logarithm of a given value.
// @param {Int | Float} number - Input number.
// @return {Float} - The decimal logarithm of a given value.
func Log10(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Log10(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Log10(toFloat(args[0]))), nil
return runtime.NewFloat(math.Log10(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// LOG2 returns the binary logarithm of a given value.
// @param {Int | Float} number - Input number.
// @return {Float} - The binary logarithm of a given value.
func Log2(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Log2(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Log2(toFloat(args[0]))), nil
return runtime.NewFloat(math.Log2(toFloat(arg))), nil
}

View File

@@ -9,12 +9,8 @@ import (
// MAX returns the greatest (arithmetic mean) of the values in array.
// @param {Int[] | Float[]} array - arrayList of numbers.
// @return {Float} - The greatest of the values in array.
func Max(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func Max(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -10,12 +10,8 @@ import (
// MEDIAN returns the median of the values in array.
// @param {Int[] | Float[]} array - arrayList of numbers.
// @return {Float} - The median of the values in array.
func Median(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func Median(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -9,12 +9,8 @@ import (
// MIN returns the smallest (arithmetic mean) of the values in array.
// @param {Int[] | Float[]} array - arrayList of numbers.
// @return {Float} - The smallest of the values in array.
func Min(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func Min(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -9,12 +9,6 @@ import (
// PI returns Pi value.
// @return {Float} - Pi value.
func Pi(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
err := runtime.ValidateArgs(args, 0, 0)
if err != nil {
return runtime.None, err
}
func Pi(_ context.Context) (runtime.Value, error) {
return runtime.NewFloat(math.Pi), nil
}

View File

@@ -11,18 +11,14 @@ import (
// @param {Int | Float} base - The base value.
// @param {Int | Float} exp - The exponent value.
// @return {Float} - The exponentiated value.
func Pow(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 2, 2); err != nil {
func Pow(_ context.Context, arg1, arg2 runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg1); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
if err := runtime.AssertNumber(arg2); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[1]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Pow(toFloat(args[0]), toFloat(args[1]))), nil
return runtime.NewFloat(math.Pow(toFloat(arg1), toFloat(arg2))), nil
}

View File

@@ -9,16 +9,12 @@ import (
// RADIANS returns the angle converted from degrees to radians.
// @param {Int | Float} number - The input number.
// @return {Float} - The angle in radians.
func Radians(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Radians(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
r := toFloat(args[0])
r := toFloat(arg)
return runtime.NewFloat(r * DegToRad), nil
}

View File

@@ -10,14 +10,10 @@ import (
// ROUND returns the nearest integer, rounding half away from zero.
// @param {Int | Float} number - Input number.
// @return {Int} - The nearest integer, rounding half away from zero.
func Round(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Round(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewInt(int(math.Round(toFloat(args[0])))), nil
return runtime.NewInt(int(math.Round(toFloat(arg)))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// SIN returns the sine of the radian argument.
// @param {Int | Float} number - Input number.
// @return {Float} - The sin, in radians, of a given number.
func Sin(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Sin(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Sin(toFloat(args[0]))), nil
return runtime.NewFloat(math.Sin(toFloat(arg))), nil
}

View File

@@ -10,14 +10,10 @@ import (
// SQRT returns the square root of a given number.
// @param {Int | Float} value - A number.
// @return {Float} - The square root.
func Sqrt(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Sqrt(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Sqrt(toFloat(args[0]))), nil
return runtime.NewFloat(math.Sqrt(toFloat(arg))), nil
}

View File

@@ -10,12 +10,8 @@ import (
// STDDEV_POPULATION returns the population standard deviation of the values in a given array.
// @param {Int[] | Float[]} numbers - arrayList of numbers.
// @return {Float} - The population standard deviation.
func StandardDeviationPopulation(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func StandardDeviationPopulation(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -10,12 +10,8 @@ import (
// STDDEV_SAMPLE returns the sample standard deviation of the values in a given array.
// @param {Int[] | Float[]} numbers - arrayList of numbers.
// @return {Float} - The sample standard deviation.
func StandardDeviationSample(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func StandardDeviationSample(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -9,12 +9,8 @@ import (
// SUM returns the sum of the values in a given array.
// @param {Int[] | Float[]} numbers - arrayList of numbers.
// @return {Float} - The sum of the values.
func Sum(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func Sum(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -10,14 +10,10 @@ import (
// TAN returns the tangent of a given number.
// @param {Int | Float} number - A number.
// @return {Float} - The tangent.
func Tan(_ context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
func Tan(_ context.Context, arg runtime.Value) (runtime.Value, error) {
if err := runtime.AssertNumber(arg); err != nil {
return runtime.None, err
}
if err := runtime.AssertNumber(args[0]); err != nil {
return runtime.None, err
}
return runtime.NewFloat(math.Tan(toFloat(args[0]))), nil
return runtime.NewFloat(math.Tan(toFloat(arg))), nil
}

View File

@@ -10,12 +10,8 @@ import (
// VARIANCE_POPULATION returns the population variance of the values in a given array.
// @param {Int[] | Float[]} numbers - arrayList of numbers.
// @return {Float} - The population variance.
func PopulationVariance(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func PopulationVariance(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -10,12 +10,8 @@ import (
// VARIANCE_SAMPLE returns the sample variance of the values in a given array.
// @param {Int[] | Float[]} numbers - arrayList of numbers.
// @return {Float} - The sample variance.
func SampleVariance(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if err := runtime.ValidateArgs(args, 1, 1); err != nil {
return runtime.None, err
}
arr, err := runtime.CastList(args[0])
func SampleVariance(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
arr, err := runtime.CastList(arg)
if err != nil {
return runtime.None, err

View File

@@ -12,14 +12,14 @@ type (
EnvironmentOption func(env *Environment)
Environment struct {
functions map[string]runtime.Function
functions runtime.Functions
params map[string]runtime.Value
logging logging.Options
}
)
var noopEnv = &Environment{
functions: make(map[string]runtime.Function),
functions: runtime.NewFunctions(),
params: make(map[string]runtime.Value),
}
@@ -29,7 +29,7 @@ func newEnvironment(opts []EnvironmentOption) *Environment {
}
env := &Environment{
functions: make(map[string]runtime.Function),
functions: runtime.NewFunctions(),
params: make(map[string]runtime.Value),
logging: logging.Options{
Writer: os.Stdout,
@@ -43,23 +43,3 @@ func newEnvironment(opts []EnvironmentOption) *Environment {
return env
}
func (env *Environment) GetFunction(name string) runtime.Function {
return env.functions[name]
}
func (env *Environment) HasFunction(name string) bool {
_, exists := env.functions[name]
return exists
}
func (env *Environment) GetParam(name string) runtime.Value {
return env.params[name]
}
func (env *Environment) HasParam(name string) bool {
_, exists := env.params[name]
return exists
}

View File

@@ -20,21 +20,23 @@ func WithParam(name string, value interface{}) EnvironmentOption {
}
}
func WithFunctions(functions map[string]runtime.Function) EnvironmentOption {
func WithFunctions(functions runtime.Functions) EnvironmentOption {
return func(env *Environment) {
if env.functions == nil {
env.functions = make(map[string]runtime.Function)
}
for name, function := range functions {
env.functions[name] = function
}
env.functions.SetAll(functions)
}
}
func WithFunction(name string, function runtime.Function) EnvironmentOption {
return func(env *Environment) {
env.functions[name] = function
env.functions.F().Set(name, function)
}
}
func WithFunctionSetter(setter func(fns runtime.Functions)) EnvironmentOption {
return func(env *Environment) {
if setter != nil {
setter(env.functions)
}
}
}

35
pkg/vm/helpers.go Normal file
View File

@@ -0,0 +1,35 @@
package vm
import (
"strings"
"github.com/MontFerret/ferret/pkg/runtime"
)
func validateParams(env *Environment, program *Program) error {
if len(program.Params) == 0 {
return nil
}
// There might be no errors.
// Thus, we allocate this slice lazily, on a first error.
var missedParams []string
for _, n := range program.Params {
_, exists := env.params[n]
if !exists {
if missedParams == nil {
missedParams = make([]string, 0, len(program.Params))
}
missedParams = append(missedParams, "@"+n)
}
}
if len(missedParams) > 0 {
return runtime.Error(ErrMissedParam, strings.Join(missedParams, ", "))
}
return nil
}

View File

@@ -73,6 +73,16 @@ const (
// Function Operations
OpCall
OpProtectedCall
OpCall0
OpProtectedCall0
OpCall1
OpProtectedCall1
OpCall2
OpProtectedCall2
OpCall3
OpProtectedCall3
OpCall4
OpProtectedCall4
// Collection Creation
OpList // Create an array

View File

@@ -3,7 +3,6 @@ package vm
import (
"context"
"io"
"strings"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm/internal"
@@ -25,19 +24,9 @@ func New(program *Program) *VM {
}
func (vm *VM) Run(ctx context.Context, opts []EnvironmentOption) (runtime.Value, error) {
tryCatch := func(pos int) (Catch, bool) {
for _, pair := range vm.program.CatchTable {
if pos >= pair[0] && pos <= pair[1] {
return pair, true
}
}
return Catch{}, false
}
env := newEnvironment(opts)
if err := vm.validateParams(env); err != nil {
if err := validateParams(env, vm.program); err != nil {
return nil, err
}
@@ -148,7 +137,7 @@ loop:
if err == nil {
reg[dst] = r.Match(reg[src1])
} else if _, catch := tryCatch(vm.pc); catch {
} else if _, catch := vm.tryCatch(vm.pc); catch {
reg[dst] = runtime.False
} else {
return nil, err
@@ -159,7 +148,7 @@ loop:
if err == nil {
reg[dst] = !r.Match(reg[src1])
} else if _, catch := tryCatch(vm.pc); catch {
} else if _, catch := vm.tryCatch(vm.pc); catch {
reg[dst] = runtime.False
} else {
return nil, err
@@ -250,40 +239,13 @@ loop:
}
case OpCall, OpProtectedCall:
fnName := reg[dst].String()
fn := vm.env.GetFunction(fnName)
if fn == nil {
if op == OpProtectedCall {
reg[dst] = runtime.None
continue
} else {
return nil, runtime.Error(ErrFunctionNotFound, fnName)
}
}
var size int
if src1 > 0 {
size = src2.Register() - src1.Register() + 1
}
start := int(src1)
end := int(src1) + size
args := make([]runtime.Value, size)
// Iterate over registers starting from src1 and up to the src2
for i := start; i < end; i++ {
args[i-start] = reg[i]
}
out, err := fn(ctx, args...)
out, err := vm.call(ctx, dst, src1, src2)
if err == nil {
reg[dst] = out
} else if op == OpProtectedCall {
reg[dst] = runtime.None
} else if catch, ok := tryCatch(vm.pc); ok {
} else if catch, ok := vm.tryCatch(vm.pc); ok {
reg[dst] = runtime.None
if catch[2] > 0 {
@@ -292,6 +254,91 @@ loop:
} else {
return nil, err
}
case OpCall0, OpProtectedCall0:
out, err := vm.call0(ctx, dst)
if err == nil {
reg[dst] = out
} else if op == OpProtectedCall0 {
reg[dst] = runtime.None
} else if catch, ok := vm.tryCatch(vm.pc); ok {
reg[dst] = runtime.None
if catch[2] > 0 {
vm.pc = catch[2]
}
} else {
return nil, err
}
case OpCall1, OpProtectedCall1:
out, err := vm.call1(ctx, dst, src1)
if err == nil {
reg[dst] = out
} else if op == OpProtectedCall1 {
reg[dst] = runtime.None
} else if catch, ok := vm.tryCatch(vm.pc); ok {
reg[dst] = runtime.None
if catch[2] > 0 {
vm.pc = catch[2]
}
} else {
return nil, err
}
case OpCall2, OpProtectedCall2:
out, err := vm.call2(ctx, dst, src1, src2)
if err == nil {
reg[dst] = out
} else if op == OpProtectedCall2 {
reg[dst] = runtime.None
} else if catch, ok := vm.tryCatch(vm.pc); ok {
reg[dst] = runtime.None
if catch[2] > 0 {
vm.pc = catch[2]
}
} else {
return nil, err
}
case OpCall3, OpProtectedCall3:
out, err := vm.call3(ctx, dst, src1, src2)
if err == nil {
reg[dst] = out
} else if op == OpProtectedCall3 {
reg[dst] = runtime.None
} else if catch, ok := vm.tryCatch(vm.pc); ok {
reg[dst] = runtime.None
if catch[2] > 0 {
vm.pc = catch[2]
}
} else {
return nil, err
}
case OpCall4, OpProtectedCall4:
out, err := vm.call4(ctx, dst, src1, src2)
if err == nil {
reg[dst] = out
} else if op == OpProtectedCall4 {
reg[dst] = runtime.None
} else if catch, ok := vm.tryCatch(vm.pc); ok {
reg[dst] = runtime.None
if catch[2] > 0 {
vm.pc = catch[2]
}
} else {
return nil, err
}
case OpLength:
val, ok := reg[src1].(runtime.Measurable)
@@ -299,7 +346,7 @@ loop:
length, err := val.Length(ctx)
if err != nil {
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
length = 0
} else {
return nil, err
@@ -307,7 +354,7 @@ loop:
}
reg[dst] = length
} else if _, catch := tryCatch(vm.pc); catch {
} else if _, catch := vm.tryCatch(vm.pc); catch {
reg[dst] = runtime.ZeroInt
} else {
return runtime.None, runtime.TypeErrorOf(reg[src1],
@@ -328,7 +375,7 @@ loop:
err := val.Close()
if err != nil {
if _, catch := tryCatch(vm.pc); !catch {
if _, catch := vm.tryCatch(vm.pc); !catch {
return nil, err
}
}
@@ -356,7 +403,7 @@ loop:
ds := reg[dst].(runtime.List)
if err := ds.Add(ctx, reg[src1]); err != nil {
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
continue
} else {
return nil, err
@@ -366,7 +413,7 @@ loop:
tr := reg[dst].(internal.Transformer)
if err := tr.Add(ctx, reg[src1], reg[src2]); err != nil {
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
continue
}
@@ -385,7 +432,7 @@ loop:
reg[dst] = internal.NewIterator(iterator)
default:
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
// Fall back to an empty iterator
reg[dst] = internal.NoopIter
} else {
@@ -438,7 +485,7 @@ loop:
observable, eventName, options, err := vm.castSubscribeArgs(reg[dst], reg[src1], reg[src2])
if err != nil {
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
continue
} else {
return nil, err
@@ -451,7 +498,7 @@ loop:
})
if err != nil {
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
continue
} else {
return nil, err
@@ -468,7 +515,7 @@ loop:
t, err := runtime.CastInt(reg[src1])
if err != nil {
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
continue
} else {
return nil, err
@@ -483,7 +530,7 @@ loop:
dur, err := runtime.ToInt(ctx, reg[dst])
if err != nil {
if _, catch := tryCatch(vm.pc); catch {
if _, catch := vm.tryCatch(vm.pc); catch {
continue
} else {
return nil, err
@@ -502,32 +549,109 @@ loop:
return vm.registers[NoopOperand], nil
}
func (vm *VM) validateParams(env *Environment) error {
if len(vm.program.Params) == 0 {
return nil
}
// There might be no errors.
// Thus, we allocate this slice lazily, on a first error.
var missedParams []string
for _, n := range vm.program.Params {
_, exists := env.params[n]
if !exists {
if missedParams == nil {
missedParams = make([]string, 0, len(vm.program.Params))
}
missedParams = append(missedParams, "@"+n)
func (vm *VM) tryCatch(pos int) (Catch, bool) {
for _, pair := range vm.program.CatchTable {
if pos >= pair[0] && pos <= pair[1] {
return pair, true
}
}
if len(missedParams) > 0 {
return runtime.Error(ErrMissedParam, strings.Join(missedParams, ", "))
return Catch{}, false
}
func (vm *VM) call(ctx context.Context, dst, src1, src2 Operand) (runtime.Value, error) {
fnName := vm.registers[dst].String()
fn, found := vm.env.functions.F().Get(fnName)
if !found {
return nil, runtime.Error(ErrFunctionNotFound, fnName)
}
return nil
var size int
if src1 > 0 {
size = src2.Register() - src1.Register() + 1
}
start := int(src1)
end := int(src1) + size
args := make([]runtime.Value, size)
// Iterate over registers starting from src1 and up to the src2
for i := start; i < end; i++ {
args[i-start] = vm.registers[i]
}
return fn(ctx, args...)
}
func (vm *VM) call0(ctx context.Context, dst Operand) (runtime.Value, error) {
fnName := vm.registers[dst].String()
fn, found := vm.env.functions.F0().Get(fnName)
if found {
return fn(ctx)
}
// Fall back to a variadic function call
return vm.call(ctx, dst, NoopOperand, NoopOperand)
}
func (vm *VM) call1(ctx context.Context, dst, src1 Operand) (runtime.Value, error) {
fnName := vm.registers[dst].String()
fn, found := vm.env.functions.F1().Get(fnName)
if found {
return fn(ctx, vm.registers[src1])
}
// Fall back to a variadic function call
return vm.call(ctx, dst, src1, src1)
}
func (vm *VM) call2(ctx context.Context, dst, src1, src2 Operand) (runtime.Value, error) {
fnName := vm.registers[dst].String()
fn, found := vm.env.functions.F2().Get(fnName)
if found {
return fn(ctx, vm.registers[src1], vm.registers[src2])
}
// Fall back to a variadic function call
return vm.call(ctx, dst, src1, src2)
}
func (vm *VM) call3(ctx context.Context, dst, src1, src2 Operand) (runtime.Value, error) {
fnName := vm.registers[dst].String()
fn, found := vm.env.functions.F3().Get(fnName)
if found {
arg1 := vm.registers[src1]
arg2 := vm.registers[src1+1]
arg3 := vm.registers[src1+2]
return fn(ctx, arg1, arg2, arg3)
}
// Fall back to a variadic function call
return vm.call(ctx, dst, src1, src2)
}
func (vm *VM) call4(ctx context.Context, dst, src1, src2 Operand) (runtime.Value, error) {
fnName := vm.registers[dst].String()
fn, found := vm.env.functions.F4().Get(fnName)
if found {
arg1 := vm.registers[src1]
arg2 := vm.registers[src1+1]
arg3 := vm.registers[src1+2]
arg4 := vm.registers[src1+3]
return fn(ctx, arg1, arg2, arg3, arg4)
}
// Fall back to a variadic function call
return vm.call(ctx, dst, src1, src2)
}
func (vm *VM) loadIndex(ctx context.Context, src, arg runtime.Value) (runtime.Value, error) {

View File

@@ -16,7 +16,7 @@ func RunBenchmarkWith(b *testing.B, c *compiler.Compiler, expression string, opt
}
options := []vm.EnvironmentOption{
vm.WithFunctions(c.Functions().Unwrap()),
vm.WithFunctions(c.Functions()),
}
options = append(options, opts...)
@@ -25,7 +25,7 @@ func RunBenchmarkWith(b *testing.B, c *compiler.Compiler, expression string, opt
b.ResetTimer()
for n := 0; n < b.N; n++ {
for b.Loop() {
_, err := instance.Run(ctx, opts)
if err != nil {

View File

@@ -0,0 +1,17 @@
package benchmarks_test
import (
"testing"
. "github.com/MontFerret/ferret/test/integration/base"
)
func BenchmarkForSort(b *testing.B) {
RunBenchmark(b, `
LET strs = ["foo", "bar", "qaz", "abc"]
FOR s IN strs
SORT s
RETURN s
`)
}

View File

@@ -0,0 +1,119 @@
package benchmarks_test
import (
"context"
"testing"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
. "github.com/MontFerret/ferret/test/integration/base"
)
func BenchmarkFunctionCall(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1,2,3,4,5,6)
`, vm.WithFunction("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.True, nil
}))
}
func BenchmarkFunctionCall0(b *testing.B) {
RunBenchmark(b, `
RETURN TEST()
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set0("TEST", func(ctx context.Context) (runtime.Value, error) {
return runtime.String("test0"), nil
}).
Build()))
}
func BenchmarkFunctionCall0Fallback(b *testing.B) {
RunBenchmark(b, `
RETURN TEST()
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall1(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set1("TEST", func(ctx context.Context, arg runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall1Fallback(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall2(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1, 1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set2("TEST", func(ctx context.Context, arg1, arg2 runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall2Fallback(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1, 1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall3(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1, 1, 1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set3("TEST", func(ctx context.Context, arg1, arg2, arg3 runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall3Fallback(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1, 1, 1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall4(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1, 1, 1, 1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set4("TEST", func(ctx context.Context, arg1, arg2, arg3, arg4 runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}
func BenchmarkFunctionCall4Fallback(b *testing.B) {
RunBenchmark(b, `
RETURN TEST(1, 1, 1, 1)
`, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Build()))
}

View File

@@ -1,182 +0,0 @@
package benchmarks_test
import (
"testing"
)
func BenchmarkStringLiteral(b *testing.B) {
test.RunBenchmark(b, `
RETURN "
FOO
BAR
"
`)
}
func BenchmarkEmptyArray(b *testing.B) {
test.RunBenchmark(b, `RETURN []`)
}
func BenchmarkStaticArray(b *testing.B) {
test.RunBenchmark(b, `RETURN [1,2,3,4,5,6,7,8,9,10]`)
}
func BenchmarkEmptyObject(b *testing.B) {
test.RunBenchmark(b, `RETURN {}`)
}
func BenchmarkUnaryOperatorExcl(b *testing.B) {
test.RunBenchmark(b, `RETURN !TRUE`)
}
func BenchmarkUnaryOperatorQ(b *testing.B) {
test.RunBenchmark(b, `
LET foo = TRUE
RETURN !foo ? TRUE : FALSE
`)
}
func BenchmarkUnaryOperatorN(b *testing.B) {
test.RunBenchmark(b, `
LET v = 1
RETURN -v
`)
}
func BenchmarkTernaryOperator(b *testing.B) {
test.RunBenchmark(b, `
LET a = "a"
LET b = "b"
LET c = FALSE
RETURN c ? a : b;
`)
}
func BenchmarkTernaryOperatorDef(b *testing.B) {
test.RunBenchmark(b, `
LET a = "a"
LET b = "b"
LET c = FALSE
RETURN c ? : a;
`)
}
func BenchmarkForEmpty(b *testing.B) {
test.RunBenchmark(b, `
FOR i IN []
RETURN i
`)
}
func BenchmarkForStaticArray(b *testing.B) {
test.RunBenchmark(b, `
FOR i IN [1,2,3,4,5,6,7,8,9,10]
RETURN i
`)
}
func BenchmarkForRange(b *testing.B) {
test.RunBenchmark(b, `
FOR i IN 1..10
RETURN i
`)
}
func BenchmarkForObject(b *testing.B) {
test.RunBenchmark(b, `
FOR i IN {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9":9, "10":10}
RETURN i
`)
}
func BenchmarkForNested(b *testing.B) {
test.RunBenchmark(b, `
FOR prop IN ["a"]
FOR val IN [1, 2, 3]
RETURN {[prop]: val}
`)
}
func BenchmarkForTernary(b *testing.B) {
test.RunBenchmark(b, `
LET foo = FALSE
RETURN foo ? TRUE : (FOR i IN 1..5 RETURN i*2)
`)
}
func BenchmarkForSort(b *testing.B) {
test.RunBenchmark(b, `
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR u IN users
SORT u.age
RETURN u
`)
}
func BenchmarkForSort2(b *testing.B) {
test.RunBenchmark(b, `
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR u IN users
SORT u.age, u.gender
RETURN u
`)
}
func BenchmarkForSortDesc(b *testing.B) {
test.RunBenchmark(b, `
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR u IN users
SORT u.age DESC
RETURN u
`)
}

View File

@@ -123,7 +123,7 @@ func RunUseCasesWith(t *testing.T, c *compiler.Compiler, useCases []UseCase, opt
}
options := []vm.EnvironmentOption{
vm.WithFunctions(c.Functions().Unwrap()),
vm.WithFunctions(c.Functions()),
}
options = append(options, opts...)

View File

@@ -272,7 +272,7 @@ LET users = [
COUNT_B()
RETURN i + x
`, []any{5, 6, 5}),
}, vm.WithFunctions(map[string]runtime.Function{
}, vm.WithFunctions(runtime.NewFunctionsFromMap(map[string]runtime.Function{
"COUNT_A": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counterA++
@@ -283,5 +283,5 @@ LET users = [
return runtime.None, nil
},
}))
})))
}

View File

@@ -1,8 +1,12 @@
package vm_test
import (
"context"
"testing"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
. "github.com/MontFerret/ferret/test/integration/base"
)
@@ -37,6 +41,20 @@ func TestFunctionCall(t *testing.T) {
})
}
func TestFunctionCall0(t *testing.T) {
RunUseCases(t, []UseCase{
Case("RETURN TEST0()", "test0", "Should call a function with no arguments"),
Case("RETURN TEST()", "test", "Should call a function with no arguments using fallback"),
}, vm.WithFunctions(runtime.NewFunctionsBuilder().
Set("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.String("test"), nil
}).
Set0("TEST0", func(ctx context.Context) (runtime.Value, error) {
return runtime.String("test0"), nil
}).
Build()))
}
func TestBuiltinFunctions(t *testing.T) {
RunUseCases(t, []UseCase{
Case("RETURN LENGTH([1,2,3])", 3),

View File

@@ -182,7 +182,7 @@ func TestMemberReservedWords(t *testing.T) {
So(err, ShouldBeNil)
out, err := Exec(prog, true, vm.WithFunctions(c.Functions().Unwrap()))
out, err := Exec(prog, true, vm.WithFunctions(c.Functions()))
So(err, ShouldBeNil)
So(out, ShouldEqual, expected.String())

View File

@@ -29,7 +29,7 @@ func TestForDoWhile(t *testing.T) {
FOR x IN 1..y
RETURN i * x
`, []any{0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 2, 4, 6, 8, 0, 3, 6, 9, 12, 0, 4, 8, 12, 16}),
}, vm.WithFunctions(map[string]runtime.Function{
}, vm.WithFunctions(runtime.NewFunctionsFromMap(map[string]runtime.Function{
"COUNTER": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter++
return runtime.NewInt(counter), nil
@@ -38,5 +38,5 @@ func TestForDoWhile(t *testing.T) {
counter2++
return runtime.NewInt(counter), nil
},
}))
})))
}

View File

@@ -26,10 +26,10 @@ func TestForTernaryWhileExpression(t *testing.T) {
LET foo = FALSE
RETURN foo ? TRUE : (FOR i WHILE COUNTER() < 10 RETURN i*2)`,
[]any{0, 2, 4, 6, 8, 10, 12, 14, 16, 18}),
}, vm.WithFunctions(map[string]runtime.Function{
}, vm.WithFunctions(runtime.NewFunctionsFromMap(map[string]runtime.Function{
"COUNTER": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter++
return runtime.NewInt(counter), nil
},
}))
})))
}

View File

@@ -23,7 +23,7 @@ func TestForWhile(t *testing.T) {
FOR x IN 1..y
RETURN i * x
`, []any{0, 1, 2, 2, 4, 6, 3, 6, 9, 12, 4, 8, 12, 16, 20}),
}, vm.WithFunctions(map[string]runtime.Function{
}, vm.WithFunctions(runtime.NewFunctionsFromMap(map[string]runtime.Function{
"UNTIL": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if untilCounter < int(runtime.ToIntSafe(ctx, args[0])) {
untilCounter++
@@ -37,5 +37,5 @@ func TestForWhile(t *testing.T) {
counter++
return runtime.NewInt(counter), nil
},
}))
})))
}