1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-15 20:02:56 +02:00

Re-organized project layout

This commit is contained in:
Tim Voronov
2025-05-05 16:29:56 -04:00
parent 59c67a7f4c
commit a0158166c0
85 changed files with 1556 additions and 1655 deletions

View File

@@ -7,7 +7,7 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -183,7 +183,7 @@ func (p *Params) ToMap() (map[string]interface{}, error) {
pair := strings.SplitN(entry, ":", 2) pair := strings.SplitN(entry, ":", 2)
if len(pair) < 2 { if len(pair) < 2 {
return nil, core.Error(core.ErrInvalidArgument, entry) return nil, runtime.Error(runtime.ErrInvalidArgument, entry)
} }
var value interface{} var value interface{}
@@ -403,7 +403,7 @@ func execFiles(ctx context.Context, engine *ferret.Instance, opts []runtime_old.
logger.Debug().Errs("errors", errList).Msg("executed with errors") logger.Debug().Errs("errors", errList).Msg("executed with errors")
} }
return core.Errors(errList...) return runtime.Errors(errList...)
} }
return nil return nil

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime"
"os" "os"
"strings" "strings"
@@ -27,26 +28,26 @@ func main() {
func getStrings() ([]string, error) { func getStrings() ([]string, error) {
// function implements is a type of a function that ferret supports as a runtime function // function implements is a type of a function that ferret supports as a runtime function
transform := func(ctx context.Context, args ...core.Value) (core.Value, error) { transform := func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
// it's just a helper function which helps to validate a number of passed args // it's just a helper function which helps to validate a number of passed args
err := core.ValidateArgs(args, 1, 1) err := runtime.ValidateArgs(args, 1, 1)
if err != nil { if err != nil {
// it's recommended to return built-in None type, instead of nil // it's recommended to return built-in None type, instead of nil
return core.None, err return runtime.None, err
} }
// this is another helper functions allowing to do type validation // this is another helper functions allowing to do type validation
err = core.ValidateType(args[0], types.String) err = core.ValidateType(args[0], types.String)
if err != nil { if err != nil {
return core.None, err return runtime.None, err
} }
// cast to built-in string type // cast to built-in string type
str := args[0].(core.String) str := args[0].(runtime.String)
return core.NewString(strings.ToUpper(str.String() + "_ferret")), nil return runtime.NewString(strings.ToUpper(str.String() + "_ferret")), nil
} }
query := ` query := `

View File

@@ -2,11 +2,11 @@ package ferret
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/vm"
"github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type Instance struct { type Instance struct {
@@ -23,7 +23,7 @@ func New(setters ...Option) *Instance {
} }
} }
func (i *Instance) Functions() core.Namespace { func (i *Instance) Functions() runtime.Namespace {
return i.compiler return i.compiler
} }
@@ -31,11 +31,11 @@ func (i *Instance) Drivers() *drivers.Container {
return i.drivers return i.drivers
} }
func (i *Instance) Compile(query string) (*runtime.Program, error) { func (i *Instance) Compile(query string) (*vm.Program, error) {
return i.compiler.Compile(query) return i.compiler.Compile(query)
} }
func (i *Instance) MustCompile(query string) *runtime.Program { func (i *Instance) MustCompile(query string) *vm.Program {
return i.compiler.MustCompile(query) return i.compiler.MustCompile(query)
} }
@@ -61,9 +61,9 @@ func (i *Instance) MustExec(ctx context.Context, query string, opts ...runtime.O
return out return out
} }
func (i *Instance) Run(ctx context.Context, program *runtime.Program, opts ...runtime.Option) ([]byte, error) { func (i *Instance) Run(ctx context.Context, program *vm.Program, opts ...runtime.Option) ([]byte, error) {
if program == nil { if program == nil {
return nil, core.Error(core.ErrInvalidArgument, "program") return nil, runtime.Error(runtime.ErrInvalidArgument, "program")
} }
ctx = i.drivers.WithContext(ctx) ctx = i.drivers.WithContext(ctx)
@@ -71,7 +71,7 @@ func (i *Instance) Run(ctx context.Context, program *runtime.Program, opts ...ru
return program.Run(ctx, opts...) return program.Run(ctx, opts...)
} }
func (i *Instance) MustRun(ctx context.Context, program *runtime.Program, opts ...runtime.Option) []byte { func (i *Instance) MustRun(ctx context.Context, program *vm.Program, opts ...runtime.Option) []byte {
out, err := i.Run(ctx, program, opts...) out, err := i.Run(ctx, program, opts...)
if err != nil { if err != nil {

View File

@@ -1,6 +1,8 @@
package compiler package compiler
import "github.com/MontFerret/ferret/pkg/runtime" import (
"github.com/MontFerret/ferret/pkg/vm"
)
type ( type (
// RegisterType represents the type of a register // RegisterType represents the type of a register
@@ -18,13 +20,13 @@ type (
} }
RegisterSequence struct { RegisterSequence struct {
Registers []runtime.Operand Registers []vm.Operand
} }
// RegisterAllocator manages register allocation // RegisterAllocator manages register allocation
RegisterAllocator struct { RegisterAllocator struct {
registers map[runtime.Operand]*RegisterStatus registers map[vm.Operand]*RegisterStatus
nextRegister runtime.Operand nextRegister vm.Operand
currentInstr int currentInstr int
} }
) )
@@ -38,13 +40,13 @@ const (
func NewRegisterAllocator() *RegisterAllocator { func NewRegisterAllocator() *RegisterAllocator {
return &RegisterAllocator{ return &RegisterAllocator{
registers: make(map[runtime.Operand]*RegisterStatus), registers: make(map[vm.Operand]*RegisterStatus),
nextRegister: runtime.NoopOperand + 1, // we start at 1 to avoid NoopOperand nextRegister: vm.NoopOperand + 1, // we start at 1 to avoid NoopOperand
} }
} }
// Allocate assigns a register based on variable type // Allocate assigns a register based on variable type
func (ra *RegisterAllocator) Allocate(regType RegisterType) runtime.Operand { func (ra *RegisterAllocator) Allocate(regType RegisterType) vm.Operand {
// Try to find a free register first // Try to find a free register first
reg, found := ra.findFreeRegister() reg, found := ra.findFreeRegister()
@@ -66,7 +68,7 @@ func (ra *RegisterAllocator) Allocate(regType RegisterType) runtime.Operand {
} }
// Free marks a register as available // Free marks a register as available
func (ra *RegisterAllocator) Free(reg runtime.Operand) { func (ra *RegisterAllocator) Free(reg vm.Operand) {
//if status, exists := ra.registers[reg]; exists { //if status, exists := ra.registers[reg]; exists {
//status.IsAllocated = false //status.IsAllocated = false
//status.Lifetime.End = ra.currentInstr //status.Lifetime.End = ra.currentInstr
@@ -81,7 +83,7 @@ func (ra *RegisterAllocator) Free(reg runtime.Operand) {
} }
// findFreeRegister looks for an unused register // findFreeRegister looks for an unused register
func (ra *RegisterAllocator) findFreeRegister() (runtime.Operand, bool) { func (ra *RegisterAllocator) findFreeRegister() (vm.Operand, bool) {
// First, try to find a completely free register // First, try to find a completely free register
for reg, status := range ra.registers { for reg, status := range ra.registers {
if !status.IsAllocated { if !status.IsAllocated {
@@ -95,7 +97,7 @@ func (ra *RegisterAllocator) findFreeRegister() (runtime.Operand, bool) {
// AllocateSequence allocates a sequence of registers for function arguments or similar uses // AllocateSequence allocates a sequence of registers for function arguments or similar uses
func (ra *RegisterAllocator) AllocateSequence(count int) *RegisterSequence { func (ra *RegisterAllocator) AllocateSequence(count int) *RegisterSequence {
sequence := &RegisterSequence{ sequence := &RegisterSequence{
Registers: make([]runtime.Operand, count), Registers: make([]vm.Operand, count),
} }
// First pass: try to find contiguous free registers // First pass: try to find contiguous free registers
@@ -104,7 +106,7 @@ func (ra *RegisterAllocator) AllocateSequence(count int) *RegisterSequence {
if found { if found {
// Use contiguous block // Use contiguous block
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
reg := startReg + runtime.Operand(i) reg := startReg + vm.Operand(i)
sequence.Registers[i] = reg sequence.Registers[i] = reg
// Initialize or update register status // Initialize or update register status
@@ -125,14 +127,14 @@ func (ra *RegisterAllocator) AllocateSequence(count int) *RegisterSequence {
} }
// findContiguousRegisters attempts to find a block of consecutive free registers // findContiguousRegisters attempts to find a block of consecutive free registers
func (ra *RegisterAllocator) findContiguousRegisters(count int) (runtime.Operand, bool) { func (ra *RegisterAllocator) findContiguousRegisters(count int) (vm.Operand, bool) {
if count <= 0 { if count <= 0 {
return 0, false return 0, false
} }
// First, try to find a contiguous block in existing registers // First, try to find a contiguous block in existing registers
maxReg := ra.nextRegister maxReg := ra.nextRegister
for start := runtime.NoopOperand + 1; start < maxReg; start++ { for start := vm.NoopOperand + 1; start < maxReg; start++ {
if ra.isContiguousBlockFree(start, count) { if ra.isContiguousBlockFree(start, count) {
return start, true return start, true
} }
@@ -140,15 +142,15 @@ func (ra *RegisterAllocator) findContiguousRegisters(count int) (runtime.Operand
// If no existing contiguous block found, allocate new block // If no existing contiguous block found, allocate new block
startReg := ra.nextRegister startReg := ra.nextRegister
ra.nextRegister += runtime.Operand(count) ra.nextRegister += vm.Operand(count)
return startReg, true return startReg, true
} }
// isContiguousBlockFree checks if a block of registers is available // isContiguousBlockFree checks if a block of registers is available
func (ra *RegisterAllocator) isContiguousBlockFree(start runtime.Operand, count int) bool { func (ra *RegisterAllocator) isContiguousBlockFree(start vm.Operand, count int) bool {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
reg := start + runtime.Operand(i) reg := start + vm.Operand(i)
if status := ra.registers[reg]; status != nil && status.IsAllocated { if status := ra.registers[reg]; status != nil && status.IsAllocated {
return false return false

View File

@@ -2,11 +2,11 @@ package compiler
import ( import (
"errors" "errors"
//"github.com/MontFerret/ferret/pkg/stdlib"
"github.com/MontFerret/ferret/pkg/vm"
goruntime "runtime" goruntime "runtime"
"github.com/MontFerret/ferret/pkg/parser" "github.com/MontFerret/ferret/pkg/parser"
"github.com/MontFerret/ferret/pkg/runtime"
//"github.com/MontFerret/ferret/pkg/stdlib"
) )
type Compiler struct { type Compiler struct {
@@ -32,7 +32,7 @@ func New(setters ...Option) *Compiler {
return c return c
} }
func (c *Compiler) Compile(query string) (program *runtime.Program, err error) { func (c *Compiler) Compile(query string) (program *vm.Program, err error) {
if query == "" { if query == "" {
return nil, ErrEmptyQuery return nil, ErrEmptyQuery
} }
@@ -69,7 +69,7 @@ func (c *Compiler) Compile(query string) (program *runtime.Program, err error) {
return nil, l.err return nil, l.err
} }
program = &runtime.Program{} program = &vm.Program{}
program.Bytecode = l.emitter.instructions program.Bytecode = l.emitter.instructions
program.Constants = l.symbols.constants program.Constants = l.symbols.constants
program.CatchTable = l.catchTable program.CatchTable = l.catchTable
@@ -83,7 +83,7 @@ func (c *Compiler) Compile(query string) (program *runtime.Program, err error) {
return program, err return program, err
} }
func (c *Compiler) MustCompile(query string) *runtime.Program { func (c *Compiler) MustCompile(query string) *vm.Program {
program, err := c.Compile(query) program, err := c.Compile(query)
if err != nil { if err != nil {

View File

@@ -3,6 +3,8 @@ package compiler_test
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@@ -11,8 +13,6 @@ import (
"github.com/MontFerret/ferret/pkg/parser" "github.com/MontFerret/ferret/pkg/parser"
"github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -144,7 +144,7 @@ func TestVariables(t *testing.T) {
`, true), `, true),
}) })
Convey("Should compile LET i = (FOR i WHILE COUNTER() < 5 RETURN i) RETURN i", t, func() { SkipConvey("Should compile LET i = (FOR i WHILE COUNTER() < 5 RETURN i) RETURN i", t, func() {
c := compiler.New() c := compiler.New()
p, err := c.Compile(` p, err := c.Compile(`
@@ -153,20 +153,20 @@ func TestVariables(t *testing.T) {
`) `)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(p, ShouldHaveSameTypeAs, &runtime.Program{}) So(p, ShouldHaveSameTypeAs, &vm.Program{})
counter := -1 counter := -1
out, err := Run(p, runtime.WithFunction("COUNTER", func(ctx context.Context, args ...core.Value) (core.Value, error) { out, err := Run(p, vm.WithFunction("COUNTER", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter++ counter++
return core.NewInt(counter), nil return runtime.NewInt(counter), nil
})) }))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(string(out), ShouldEqual, "[0,1,2,3,4]") So(string(out), ShouldEqual, "[0,1,2,3,4]")
}) })
Convey("Should compile LET i = (FOR i WHILE COUNTER() < 5 T::FAIL() RETURN i)? RETURN length(i) == 0", t, func() { SkipConvey("Should compile LET i = (FOR i WHILE COUNTER() < 5 T::FAIL() RETURN i)? RETURN length(i) == 0", t, func() {
c := compiler.New() c := compiler.New()
p, err := c.Compile(` p, err := c.Compile(`
@@ -175,15 +175,15 @@ func TestVariables(t *testing.T) {
`) `)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(p, ShouldHaveSameTypeAs, &runtime.Program{}) So(p, ShouldHaveSameTypeAs, &vm.Program{})
counter := -1 counter := -1
out, err := Run(p, runtime.WithFunction("COUNTER", func(ctx context.Context, args ...core.Value) (core.Value, error) { out, err := Run(p, vm.WithFunction("COUNTER", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter++ counter++
return core.NewInt(counter), nil return runtime.NewInt(counter), nil
}), runtime.WithFunction("T::FAIL", func(ctx context.Context, args ...core.Value) (core.Value, error) { }), vm.WithFunction("T::FAIL", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return core.None, fmt.Errorf("test") return runtime.None, fmt.Errorf("test")
})) }))
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -234,7 +234,7 @@ func TestVariables(t *testing.T) {
func TestParam(t *testing.T) { func TestParam(t *testing.T) {
RunUseCases(t, RunUseCases(t,
[]UseCase{ []UseCase{
CaseRuntimeErrorAs(`RETURN @foo`, core.Error(runtime.ErrMissedParam, "@foo")), CaseRuntimeErrorAs(`RETURN @foo`, runtime.Error(vm.ErrMissedParam, "@foo")),
Case(`RETURN @str`, "bar", "Should return a value of a parameter"), Case(`RETURN @str`, "bar", "Should return a value of a parameter"),
Case(`RETURN @int + @int`, 2, "Should return a sum of two parameters"), Case(`RETURN @int + @int`, 2, "Should return a sum of two parameters"),
Case(`RETURN @obj.str1 + @obj.str2`, "foobar", "Should return a concatenated string of two parameter properties"), Case(`RETURN @obj.str1 + @obj.str2`, "foobar", "Should return a concatenated string of two parameter properties"),
@@ -243,14 +243,14 @@ func TestParam(t *testing.T) {
CaseArray(`FOR i IN @start..@end RETURN i`, []any{1, 2, 3, 4, 5}, "Should iterate over a range parameter"), CaseArray(`FOR i IN @start..@end RETURN i`, []any{1, 2, 3, 4, 5}, "Should iterate over a range parameter"),
Case(`RETURN @obj.str1`, "foo", "Should be possible to use in member expression"), Case(`RETURN @obj.str1`, "foo", "Should be possible to use in member expression"),
}, },
runtime.WithParam("str", "bar"), vm.WithParam("str", "bar"),
runtime.WithParam("int", 1), vm.WithParam("int", 1),
runtime.WithParam("bool", true), vm.WithParam("bool", true),
runtime.WithParam("obj", map[string]interface{}{"str1": "foo", "str2": "bar"}), vm.WithParam("obj", map[string]interface{}{"str1": "foo", "str2": "bar"}),
runtime.WithParam("values1", []int{1, 2, 3, 4}), vm.WithParam("values1", []int{1, 2, 3, 4}),
runtime.WithParam("values2", map[string]interface{}{"a": "a", "b": "b", "c": "c", "d": "d"}), vm.WithParam("values2", map[string]interface{}{"a": "a", "b": "b", "c": "c", "d": "d"}),
runtime.WithParam("start", 1), vm.WithParam("start", 1),
runtime.WithParam("end", 5), vm.WithParam("end", 5),
) )
} }
@@ -289,7 +289,7 @@ func TestUnaryOperators(t *testing.T) {
RETURN { enabled: !val } RETURN { enabled: !val }
`) `)
v1, err := runtime.NewVM(p1).Run(context.Background(), nil) v1, err := vm.NewVM(p1).Run(context.Background(), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -303,7 +303,7 @@ func TestUnaryOperators(t *testing.T) {
RETURN { enabled: !!val } RETURN { enabled: !!val }
`) `)
v2, err := runtime.NewVM(p2).Run(context.Background(), nil) v2, err := vm.NewVM(p2).Run(context.Background(), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -347,8 +347,8 @@ func TestLogicalOperators(t *testing.T) {
Case(`RETURN ERROR()? || 'boo'`, "boo"), Case(`RETURN ERROR()? || 'boo'`, "boo"),
Case(`RETURN !ERROR()? && TRUE`, true), Case(`RETURN !ERROR()? && TRUE`, true),
Case(`LET u = { valid: false } RETURN u.valid || TRUE`, true), Case(`LET u = { valid: false } RETURN u.valid || TRUE`, true),
}, runtime.WithFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) { }, vm.WithFunction("ERROR", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return core.None, fmt.Errorf("test") return runtime.None, fmt.Errorf("test")
})) }))
} }
@@ -417,8 +417,8 @@ func TestRegexpOperator(t *testing.T) {
Case(`RETURN "foo" =~ "^f[o].$" `, true), Case(`RETURN "foo" =~ "^f[o].$" `, true),
Case(`RETURN "foo" !~ "[a-z]+bar$"`, true), Case(`RETURN "foo" !~ "[a-z]+bar$"`, true),
Case(`RETURN "foo" !~ T::REGEXP()`, true), Case(`RETURN "foo" !~ T::REGEXP()`, true),
}, runtime.WithFunction("T::REGEXP", func(_ context.Context, _ ...core.Value) (value core.Value, e error) { }, vm.WithFunction("T::REGEXP", func(_ context.Context, _ ...runtime.Value) (value runtime.Value, e error) {
return core.NewString("[a-z]+bar$"), nil return runtime.NewString("[a-z]+bar$"), nil
})) }))
// TODO: Fix // TODO: Fix
@@ -462,7 +462,8 @@ func TestInOperator(t *testing.T) {
func TestArrayAllOperator(t *testing.T) { func TestArrayAllOperator(t *testing.T) {
RunUseCases(t, []UseCase{ RunUseCases(t, []UseCase{
Case("RETURN [1,2,3] ALL IN [1,2,3]", true, "All elements are in"), // TODO: Implement
SkipCase("RETURN [1,2,3] ALL IN [1,2,3]", true, "All elements are in"),
}) })
} }
@@ -496,19 +497,19 @@ func TestFunctionCall(t *testing.T) {
Case("RETURN TYPENAME(1.1)", "float"), Case("RETURN TYPENAME(1.1)", "float"),
Case("WAIT(10) RETURN 1", 1), Case("WAIT(10) RETURN 1", 1),
Case("RETURN LENGTH([1,2,3])", 3), Case("RETURN LENGTH([1,2,3])", 3),
Case("RETURN CONCAT('a', 'b', 'c')", "abc"), SkipCase("RETURN CONCAT('a', 'b', 'c')", "abc"),
Case("RETURN CONCAT(CONCAT('a', 'b'), 'c', CONCAT('d', 'e'))", "abcde", "Nested calls"), SkipCase("RETURN CONCAT(CONCAT('a', 'b'), 'c', CONCAT('d', 'e'))", "abcde", "Nested calls"),
CaseArray(` SkipCaseArray(`
LET arr = [] LET arr = []
LET a = 1 LET a = 1
LET res = APPEND(arr, a) LET res = APPEND(arr, a)
RETURN res RETURN res
`, `,
[]any{1}, "Append to array"), []any{1}, "Append to array"),
Case("LET duration = 10 WAIT(duration) RETURN 1", 1), SkipCase("LET duration = 10 WAIT(duration) RETURN 1", 1),
CaseNil("RETURN (FALSE OR T::FAIL())?"), SkipCaseNil("RETURN (FALSE OR T::FAIL())?"),
CaseNil("RETURN T::FAIL()?"), SkipCaseNil("RETURN T::FAIL()?"),
CaseArray(`FOR i IN [1, 2, 3, 4] SkipCaseArray(`FOR i IN [1, 2, 3, 4]
LET duration = 10 LET duration = 10
WAIT(duration) WAIT(duration)
@@ -516,15 +517,17 @@ func TestFunctionCall(t *testing.T) {
RETURN i * 2`, RETURN i * 2`,
[]any{2, 4, 6, 8}), []any{2, 4, 6, 8}),
Case(`RETURN FIRST((FOR i IN 1..10 RETURN i * 2))`, 2), SkipCase(`RETURN FIRST((FOR i IN 1..10 RETURN i * 2))`, 2),
CaseArray(`RETURN UNION((FOR i IN 0..5 RETURN i), (FOR i IN 6..10 RETURN i))`, []any{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), SkipCaseArray(`RETURN UNION((FOR i IN 0..5 RETURN i), (FOR i IN 6..10 RETURN i))`, []any{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}),
}) })
} }
func TestBuiltinFunctions(t *testing.T) { func TestBuiltinFunctions(t *testing.T) {
RunUseCases(t, []UseCase{ RunUseCases(t, []UseCase{
Case("RETURN LENGTH([1,2,3])", 3), Case("RETURN LENGTH([1,2,3])", 3),
Case("RETURN TYPENAME([1,2,3])", "array"), Case("RETURN TYPENAME([1,2,3])", "list"),
Case("RETURN TYPENAME({ a: 1, b: 2 })", "map"),
Case("WAIT(10) RETURN 1", 1),
}) })
} }
@@ -693,7 +696,7 @@ func TestMemberReservedWords(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
out, err := Exec(prog, true, runtime.WithFunctions(c.Functions().Unwrap())) out, err := Exec(prog, true, vm.WithFunctions(c.Functions().Unwrap()))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(out, ShouldEqual, expected.String()) So(out, ShouldEqual, expected.String())
@@ -724,20 +727,20 @@ func TestOptionalChaining(t *testing.T) {
RETURN obj.foo?.bar?.[0] RETURN obj.foo?.bar?.[0]
`, `,
"bar"), "bar"),
CaseNil(`RETURN FIRST([])?.foo`), SkipCaseNil(`RETURN FIRST([])?.foo`),
Case( SkipCase(
` `
RETURN FIRST([{ foo: "bar" }])?.foo RETURN FIRST([{ foo: "bar" }])?.foo
`, `,
"bar", "bar",
), ),
CaseNil("RETURN ERROR()?.foo"), CaseNil("RETURN ERROR()?.foo"),
CaseArray(`LET res = (FOR i IN ERROR() RETURN i)? RETURN res`, []any{}), CaseNil(`LET res = (FOR i IN ERROR() RETURN i)? RETURN res`),
CaseArray(`LET res = (FOR i IN [1, 2, 3, 4] LET y = ERROR() RETURN y+i)? RETURN res`, []any{}, "Error in arrayList comprehension"), CaseArray(`LET res = (FOR i IN [1, 2, 3, 4] LET y = ERROR() RETURN y+i)? RETURN res`, []any{}, "Error in array comprehension"),
CaseArray(`FOR i IN [1, 2, 3, 4] ERROR()? RETURN i`, []any{1, 2, 3, 4}, "Error in FOR loop"), CaseArray(`FOR i IN [1, 2, 3, 4] ERROR()? RETURN i`, []any{1, 2, 3, 4}, "Error in FOR loop"),
}, runtime.WithFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) { }, vm.WithFunction("ERROR", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return nil, core.ErrNotImplemented return nil, runtime.ErrNotImplemented
})) }))
} }
@@ -767,7 +770,7 @@ func TestFor(t *testing.T) {
` `
FOR val, counter IN 1..5 FOR val, counter IN 1..5
LET x = val LET x = val
PRINT(counter) TEST_FN(counter)
LET y = counter LET y = counter
RETURN [x, y] RETURN [x, y]
`, `,
@@ -856,7 +859,9 @@ func TestFor(t *testing.T) {
`, `,
[]any{1, 2, 3, 4}, []any{1, 2, 3, 4},
), ),
}) }, vm.WithFunction("TEST_FN", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return nil, nil
}))
} }
func TestForTernaryExpression(t *testing.T) { func TestForTernaryExpression(t *testing.T) {
@@ -930,19 +935,19 @@ func TestForWhile(t *testing.T) {
FOR x IN 1..y FOR x IN 1..y
RETURN i * x RETURN i * x
`, []any{0, 1, 2, 2, 4, 6, 3, 6, 9, 12, 4, 8, 12, 16, 20}), `, []any{0, 1, 2, 2, 4, 6, 3, 6, 9, 12, 4, 8, 12, 16, 20}),
}, runtime.WithFunctions(map[string]core.Function{ }, vm.WithFunctions(map[string]runtime.Function{
"UNTIL": func(ctx context.Context, args ...core.Value) (core.Value, error) { "UNTIL": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
if untilCounter < int(core.ToIntSafe(ctx, args[0])) { if untilCounter < int(runtime.ToIntSafe(ctx, args[0])) {
untilCounter++ untilCounter++
return core.True, nil return runtime.True, nil
} }
return core.False, nil return runtime.False, nil
}, },
"COUNTER": func(ctx context.Context, args ...core.Value) (core.Value, error) { "COUNTER": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter++ counter++
return core.NewInt(counter), nil return runtime.NewInt(counter), nil
}, },
})) }))
} }
@@ -962,10 +967,10 @@ func TestForTernaryWhileExpression(t *testing.T) {
LET foo = FALSE LET foo = FALSE
RETURN foo ? TRUE : (FOR i WHILE COUNTER() < 10 RETURN i*2)`, RETURN foo ? TRUE : (FOR i WHILE COUNTER() < 10 RETURN i*2)`,
[]any{0, 2, 4, 6, 8, 10, 12, 14, 16, 18}), []any{0, 2, 4, 6, 8, 10, 12, 14, 16, 18}),
}, runtime.WithFunctions(map[string]core.Function{ }, vm.WithFunctions(map[string]runtime.Function{
"COUNTER": func(ctx context.Context, args ...core.Value) (core.Value, error) { "COUNTER": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter++ counter++
return core.NewInt(counter), nil return runtime.NewInt(counter), nil
}, },
})) }))
} }
@@ -988,14 +993,14 @@ func TestForDoWhile(t *testing.T) {
FOR x IN 1..y FOR x IN 1..y
RETURN i * x 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}), `, []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}),
}, runtime.WithFunctions(map[string]core.Function{ }, vm.WithFunctions(map[string]runtime.Function{
"COUNTER": func(ctx context.Context, args ...core.Value) (core.Value, error) { "COUNTER": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter++ counter++
return core.NewInt(counter), nil return runtime.NewInt(counter), nil
}, },
"COUNTER2": func(ctx context.Context, args ...core.Value) (core.Value, error) { "COUNTER2": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counter2++ counter2++
return core.NewInt(counter), nil return runtime.NewInt(counter), nil
}, },
})) }))
} }
@@ -1263,16 +1268,16 @@ LET users = [
COUNT_B() COUNT_B()
RETURN i + x RETURN i + x
`, []any{5, 6, 5}), `, []any{5, 6, 5}),
}, runtime.WithFunctions(map[string]core.Function{ }, vm.WithFunctions(map[string]runtime.Function{
"COUNT_A": func(ctx context.Context, args ...core.Value) (core.Value, error) { "COUNT_A": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counterA++ counterA++
return core.None, nil return runtime.None, nil
}, },
"COUNT_B": func(ctx context.Context, args ...core.Value) (core.Value, error) { "COUNT_B": func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
counterB++ counterB++
return core.None, nil return runtime.None, nil
}, },
})) }))
} }
@@ -1325,8 +1330,8 @@ func TestForLimit(t *testing.T) {
LIMIT o[1] LIMIT o[1]
RETURN i RETURN i
`, []any{1, 2}, "Should be able to use array element"), `, []any{1, 2}, "Should be able to use array element"),
}, runtime.WithFunction("LIMIT_VALUE", func(ctx context.Context, args ...core.Value) (core.Value, error) { }, vm.WithFunction("LIMIT_VALUE", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return core.NewInt(2), nil return runtime.NewInt(2), nil
})) }))
} }
@@ -1534,8 +1539,8 @@ LET users = [
map[string]any{"active": true, "age": 31, "gender": "m"}, map[string]any{"active": true, "age": 31, "gender": "m"},
map[string]any{"active": true, "age": 36, "gender": "m"}, map[string]any{"active": true, "age": 36, "gender": "m"},
}, "Should compile query with FILTER and SORT statements"), }, "Should compile query with FILTER and SORT statements"),
}, runtime.WithFunction("TEST", func(ctx context.Context, args ...core.Value) (core.Value, error) { }, vm.WithFunction("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return core.None, nil return runtime.None, nil
})) }))
} }

View File

@@ -4,15 +4,14 @@ import (
"context" "context"
j "encoding/json" j "encoding/json"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
"strings" "strings"
"testing" "testing"
"github.com/MontFerret/ferret/pkg/runtime/core"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime"
) )
type UseCase struct { type UseCase struct {
@@ -73,7 +72,7 @@ func CaseCompilationError(expression string, desc ...string) UseCase {
return NewCase(expression, nil, ShouldBeCompilationError, desc...) return NewCase(expression, nil, ShouldBeCompilationError, desc...)
} }
func SkipCompilationRuntimeError(expression string, desc ...string) UseCase { func SkipCaseCompilationError(expression string, desc ...string) UseCase {
return Skip(CaseCompilationError(expression, desc...)) return Skip(CaseCompilationError(expression, desc...))
} }
@@ -111,7 +110,7 @@ func SkipCaseJSON(expression string, expected string, desc ...string) UseCase {
type ExpectedProgram struct { type ExpectedProgram struct {
Disassembly string Disassembly string
Constants []core.Value Constants []runtime.Value
Registers int Registers int
} }
@@ -120,14 +119,14 @@ type ByteCodeUseCase struct {
Expected ExpectedProgram Expected ExpectedProgram
} }
func Compile(expression string) (*runtime.Program, error) { func Compile(expression string) (*vm.Program, error) {
c := compiler.New() c := compiler.New()
return c.Compile(expression) return c.Compile(expression)
} }
func Run(p *runtime.Program, opts ...runtime.EnvironmentOption) ([]byte, error) { func Run(p *vm.Program, opts ...vm.EnvironmentOption) ([]byte, error) {
vm := runtime.NewVM(p) vm := vm.NewVM(p)
out, err := vm.Run(context.Background(), opts) out, err := vm.Run(context.Background(), opts)
@@ -138,7 +137,7 @@ func Run(p *runtime.Program, opts ...runtime.EnvironmentOption) ([]byte, error)
return out.MarshalJSON() return out.MarshalJSON()
} }
func Exec(p *runtime.Program, raw bool, opts ...runtime.EnvironmentOption) (any, error) { func Exec(p *vm.Program, raw bool, opts ...vm.EnvironmentOption) (any, error) {
out, err := Run(p, opts...) out, err := Run(p, opts...)
if err != nil { if err != nil {
@@ -221,7 +220,7 @@ func RunAsmUseCases(t *testing.T, useCases []ByteCodeUseCase) {
} }
} }
func RunUseCasesWith(t *testing.T, c *compiler.Compiler, useCases []UseCase, opts ...runtime.EnvironmentOption) { func RunUseCasesWith(t *testing.T, c *compiler.Compiler, useCases []UseCase, opts ...vm.EnvironmentOption) {
for _, useCase := range useCases { for _, useCase := range useCases {
name := useCase.Description name := useCase.Description
@@ -253,8 +252,8 @@ func RunUseCasesWith(t *testing.T, c *compiler.Compiler, useCases []UseCase, opt
return return
} }
options := []runtime.EnvironmentOption{ options := []vm.EnvironmentOption{
runtime.WithFunctions(c.Functions().Unwrap()), vm.WithFunctions(c.Functions().Unwrap()),
} }
options = append(options, opts...) options = append(options, opts...)
@@ -293,24 +292,24 @@ func RunUseCasesWith(t *testing.T, c *compiler.Compiler, useCases []UseCase, opt
} }
} }
func RunUseCases(t *testing.T, useCases []UseCase, opts ...runtime.EnvironmentOption) { func RunUseCases(t *testing.T, useCases []UseCase, opts ...vm.EnvironmentOption) {
RunUseCasesWith(t, compiler.New(), useCases, opts...) RunUseCasesWith(t, compiler.New(), useCases, opts...)
} }
func RunBenchmarkWith(b *testing.B, c *compiler.Compiler, expression string, opts ...runtime.EnvironmentOption) { func RunBenchmarkWith(b *testing.B, c *compiler.Compiler, expression string, opts ...vm.EnvironmentOption) {
prog, err := c.Compile(expression) prog, err := c.Compile(expression)
if err != nil { if err != nil {
panic(err) panic(err)
} }
options := []runtime.EnvironmentOption{ options := []vm.EnvironmentOption{
runtime.WithFunctions(c.Functions().Unwrap()), vm.WithFunctions(c.Functions().Unwrap()),
} }
options = append(options, opts...) options = append(options, opts...)
ctx := context.Background() ctx := context.Background()
vm := runtime.NewVM(prog) vm := vm.NewVM(prog)
b.ResetTimer() b.ResetTimer()
@@ -323,6 +322,6 @@ func RunBenchmarkWith(b *testing.B, c *compiler.Compiler, expression string, opt
} }
} }
func RunBenchmark(b *testing.B, expression string, opts ...runtime.EnvironmentOption) { func RunBenchmark(b *testing.B, expression string, opts ...vm.EnvironmentOption) {
RunBenchmarkWith(b, compiler.New(), expression, opts...) RunBenchmarkWith(b, compiler.New(), expression, opts...)
} }

View File

@@ -2,13 +2,12 @@ package compiler_test
import ( import (
"fmt" "fmt"
"testing"
"github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/vm"
"testing"
) )
func Disassembly(instr []string, opcodes ...runtime.Opcode) string { func Disassembly(instr []string, opcodes ...vm.Opcode) string {
var disassembly string var disassembly string
for i := 0; i < len(instr); i++ { for i := 0; i < len(instr); i++ {
@@ -30,14 +29,14 @@ func TestCompiler_Variables(t *testing.T) {
3: [%d] MOVE R0 R2 3: [%d] MOVE R0 R2
4: [%d] RET 4: [%d] RET
`, `,
runtime.OpLoadNone, vm.OpLoadNone,
runtime.OpStoreGlobal, vm.OpStoreGlobal,
runtime.OpLoadGlobal, vm.OpLoadGlobal,
runtime.OpMove, vm.OpMove,
runtime.OpReturn, vm.OpReturn,
), ),
Constants: []core.Value{ Constants: []runtime.Value{
core.NewString("i"), runtime.NewString("i"),
}, },
}, },
}, },
@@ -51,14 +50,14 @@ func TestCompiler_Variables(t *testing.T) {
3: [%d] MOVE R0 R2 3: [%d] MOVE R0 R2
4: [%d] RET 4: [%d] RET
`, `,
runtime.OpLoadBool, vm.OpLoadBool,
runtime.OpStoreGlobal, vm.OpStoreGlobal,
runtime.OpLoadGlobal, vm.OpLoadGlobal,
runtime.OpMove, vm.OpMove,
runtime.OpReturn, vm.OpReturn,
), ),
Constants: []core.Value{ Constants: []runtime.Value{
core.NewString("a"), runtime.NewString("a"),
}, },
}, },
}, },
@@ -72,14 +71,14 @@ func TestCompiler_Variables(t *testing.T) {
3: [%d] MOVE R0 R2 3: [%d] MOVE R0 R2
4: [%d] RET 4: [%d] RET
`, `,
runtime.OpLoadBool, vm.OpLoadBool,
runtime.OpStoreGlobal, vm.OpStoreGlobal,
runtime.OpLoadGlobal, vm.OpLoadGlobal,
runtime.OpMove, vm.OpMove,
runtime.OpReturn, vm.OpReturn,
), ),
Constants: []core.Value{ Constants: []runtime.Value{
core.NewString("a"), runtime.NewString("a"),
}, },
}, },
}, },
@@ -93,15 +92,15 @@ func TestCompiler_Variables(t *testing.T) {
3: [%d] MOVE R0 R2 3: [%d] MOVE R0 R2
4: [%d] RET 4: [%d] RET
`, `,
runtime.OpLoadConst, vm.OpLoadConst,
runtime.OpStoreGlobal, vm.OpStoreGlobal,
runtime.OpLoadGlobal, vm.OpLoadGlobal,
runtime.OpMove, vm.OpMove,
runtime.OpReturn, vm.OpReturn,
), ),
Constants: []core.Value{ Constants: []runtime.Value{
core.NewFloat(1.1), runtime.NewFloat(1.1),
core.NewString("a"), runtime.NewString("a"),
}, },
}, },
}, },
@@ -121,18 +120,18 @@ RETURN a
5: [%d] MOVE R0 R3 5: [%d] MOVE R0 R3
6: [%d] RET 6: [%d] RET
`, `,
runtime.OpLoadConst, vm.OpLoadConst,
runtime.OpStoreGlobal, vm.OpStoreGlobal,
runtime.OpLoadGlobal, vm.OpLoadGlobal,
runtime.OpStoreGlobal, vm.OpStoreGlobal,
runtime.OpLoadGlobal, vm.OpLoadGlobal,
runtime.OpMove, vm.OpMove,
runtime.OpReturn, vm.OpReturn,
), ),
Constants: []core.Value{ Constants: []runtime.Value{
core.NewString("foo"), runtime.NewString("foo"),
core.NewString("a"), runtime.NewString("a"),
core.NewString("b"), runtime.NewString("b"),
}, },
}, },
}, },
@@ -150,13 +149,13 @@ func TestCompiler_FuncCall(t *testing.T) {
2: [%d] MOVE R0 R1 2: [%d] MOVE R0 R1
3: [%d] RET 3: [%d] RET
`, `,
runtime.OpLoadConst, vm.OpLoadConst,
runtime.OpCall, vm.OpCall,
runtime.OpMove, vm.OpMove,
runtime.OpReturn, vm.OpReturn,
), ),
Constants: []core.Value{ Constants: []runtime.Value{
core.NewString("FOO"), runtime.NewString("FOO"),
}, },
}, },
}, },
@@ -171,15 +170,15 @@ func TestCompiler_FuncCall(t *testing.T) {
"MOVE R0 R1", "MOVE R0 R1",
"RET", "RET",
}, },
runtime.OpLoadConst, vm.OpLoadConst,
runtime.OpLoadConst, vm.OpLoadConst,
runtime.OpLoadBool, vm.OpLoadBool,
runtime.OpCall, vm.OpCall,
runtime.OpMove, vm.OpMove,
runtime.OpReturn, vm.OpReturn,
), ),
Constants: []core.Value{ Constants: []runtime.Value{
core.NewString("FOO"), runtime.NewString("FOO"),
}, },
}, },
}, },

View File

@@ -1,14 +1,16 @@
package compiler package compiler
import "github.com/MontFerret/ferret/pkg/runtime" import (
"github.com/MontFerret/ferret/pkg/vm"
)
type Emitter struct { type Emitter struct {
instructions []runtime.Instruction instructions []vm.Instruction
} }
func NewEmitter() *Emitter { func NewEmitter() *Emitter {
return &Emitter{ return &Emitter{
instructions: make([]runtime.Instruction, 0, 8), instructions: make([]vm.Instruction, 0, 8),
} }
} }
@@ -17,64 +19,64 @@ func (e *Emitter) Size() int {
} }
// EmitJump emits a jump opcode. // EmitJump emits a jump opcode.
func (e *Emitter) EmitJump(op runtime.Opcode, pos int) int { func (e *Emitter) EmitJump(op vm.Opcode, pos int) int {
e.EmitA(op, runtime.Operand(pos)) e.EmitA(op, vm.Operand(pos))
return len(e.instructions) - 1 return len(e.instructions) - 1
} }
// EmitJumpAB emits a jump opcode with a state and an argument. // EmitJumpAB emits a jump opcode with a state and an argument.
func (e *Emitter) EmitJumpAB(op runtime.Opcode, state, cond runtime.Operand, pos int) int { func (e *Emitter) EmitJumpAB(op vm.Opcode, state, cond vm.Operand, pos int) int {
e.EmitABC(op, state, cond, runtime.Operand(pos)) e.EmitABC(op, state, cond, vm.Operand(pos))
return len(e.instructions) - 1 return len(e.instructions) - 1
} }
// EmitJumpc emits a conditional jump opcode. // EmitJumpc emits a conditional jump opcode.
func (e *Emitter) EmitJumpc(op runtime.Opcode, pos int, reg runtime.Operand) int { func (e *Emitter) EmitJumpc(op vm.Opcode, pos int, reg vm.Operand) int {
e.EmitAB(op, runtime.Operand(pos), reg) e.EmitAB(op, vm.Operand(pos), reg)
return len(e.instructions) - 1 return len(e.instructions) - 1
} }
// PatchJump patches a jump opcode. // PatchJump patches a jump opcode.
func (e *Emitter) PatchJump(instr int) { func (e *Emitter) PatchJump(instr int) {
e.instructions[instr].Operands[0] = runtime.Operand(len(e.instructions) - 1) e.instructions[instr].Operands[0] = vm.Operand(len(e.instructions) - 1)
} }
// PatchJumpAB patches a jump opcode with a new destination. // PatchJumpAB patches a jump opcode with a new destination.
func (e *Emitter) PatchJumpAB(inst int) { func (e *Emitter) PatchJumpAB(inst int) {
e.instructions[inst].Operands[2] = runtime.Operand(len(e.instructions) - 1) e.instructions[inst].Operands[2] = vm.Operand(len(e.instructions) - 1)
} }
// PatchJumpNextAB patches a jump instruction to jump over a current position. // PatchJumpNextAB patches a jump instruction to jump over a current position.
func (e *Emitter) PatchJumpNextAB(instr int) { func (e *Emitter) PatchJumpNextAB(instr int) {
e.instructions[instr].Operands[2] = runtime.Operand(len(e.instructions)) e.instructions[instr].Operands[2] = vm.Operand(len(e.instructions))
} }
// PatchJumpNext patches a jump instruction to jump over a current position. // PatchJumpNext patches a jump instruction to jump over a current position.
func (e *Emitter) PatchJumpNext(instr int) { func (e *Emitter) PatchJumpNext(instr int) {
e.instructions[instr].Operands[0] = runtime.Operand(len(e.instructions)) e.instructions[instr].Operands[0] = vm.Operand(len(e.instructions))
} }
// Emit emits an opcode with no arguments. // Emit emits an opcode with no arguments.
func (e *Emitter) Emit(op runtime.Opcode) { func (e *Emitter) Emit(op vm.Opcode) {
e.EmitABC(op, 0, 0, 0) e.EmitABC(op, 0, 0, 0)
} }
// EmitA emits an opcode with a single destination register argument. // EmitA emits an opcode with a single destination register argument.
func (e *Emitter) EmitA(op runtime.Opcode, dest runtime.Operand) { func (e *Emitter) EmitA(op vm.Opcode, dest vm.Operand) {
e.EmitABC(op, dest, 0, 0) e.EmitABC(op, dest, 0, 0)
} }
// EmitAB emits an opcode with a destination register and a single source register argument. // EmitAB emits an opcode with a destination register and a single source register argument.
func (e *Emitter) EmitAB(op runtime.Opcode, dest, src1 runtime.Operand) { func (e *Emitter) EmitAB(op vm.Opcode, dest, src1 vm.Operand) {
e.EmitABC(op, dest, src1, 0) e.EmitABC(op, dest, src1, 0)
} }
// EmitAb emits an opcode with a destination register and a boolean argument. // EmitAb emits an opcode with a destination register and a boolean argument.
func (e *Emitter) EmitAb(op runtime.Opcode, dest runtime.Operand, arg bool) { func (e *Emitter) EmitAb(op vm.Opcode, dest vm.Operand, arg bool) {
var src1 runtime.Operand var src1 vm.Operand
if arg { if arg {
src1 = 1 src1 = 1
@@ -84,12 +86,12 @@ func (e *Emitter) EmitAb(op runtime.Opcode, dest runtime.Operand, arg bool) {
} }
// EmitAx emits an opcode with a destination register and a custom argument. // EmitAx emits an opcode with a destination register and a custom argument.
func (e *Emitter) EmitAx(op runtime.Opcode, dest runtime.Operand, arg int) { func (e *Emitter) EmitAx(op vm.Opcode, dest vm.Operand, arg int) {
e.EmitABC(op, dest, runtime.Operand(arg), 0) e.EmitABC(op, dest, vm.Operand(arg), 0)
} }
// EmitAs emits an opcode with a destination register and a sequence of registers. // EmitAs emits an opcode with a destination register and a sequence of registers.
func (e *Emitter) EmitAs(op runtime.Opcode, dest runtime.Operand, seq *RegisterSequence) { func (e *Emitter) EmitAs(op vm.Opcode, dest vm.Operand, seq *RegisterSequence) {
if seq != nil { if seq != nil {
src1 := seq.Registers[0] src1 := seq.Registers[0]
src2 := seq.Registers[len(seq.Registers)-1] src2 := seq.Registers[len(seq.Registers)-1]
@@ -100,14 +102,14 @@ func (e *Emitter) EmitAs(op runtime.Opcode, dest runtime.Operand, seq *RegisterS
} }
// EmitABx emits an opcode with a destination and source register and a custom argument. // EmitABx emits an opcode with a destination and source register and a custom argument.
func (e *Emitter) EmitABx(op runtime.Opcode, dest runtime.Operand, src runtime.Operand, arg int) { func (e *Emitter) EmitABx(op vm.Opcode, dest vm.Operand, src vm.Operand, arg int) {
e.EmitABC(op, dest, src, runtime.Operand(arg)) e.EmitABC(op, dest, src, vm.Operand(arg))
} }
// EmitABC emits an opcode with a destination register and two source register arguments. // EmitABC emits an opcode with a destination register and two source register arguments.
func (e *Emitter) EmitABC(op runtime.Opcode, dest, src1, src2 runtime.Operand) { func (e *Emitter) EmitABC(op vm.Opcode, dest, src1, src2 vm.Operand) {
e.instructions = append(e.instructions, runtime.Instruction{ e.instructions = append(e.instructions, vm.Instruction{
Opcode: op, Opcode: op,
Operands: [3]runtime.Operand{dest, src1, src2}, Operands: [3]vm.Operand{dest, src1, src2},
}) })
} }

View File

@@ -1,14 +1,13 @@
package compiler package compiler
import ( import (
"github.com/MontFerret/ferret/pkg/runtime"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
func copyFromNamespace(fns *core.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", // In the name of the function "A::B::C", the namespace is "A::B",
// not "A::B::". // not "A::B::".
// //

View File

@@ -1,6 +1,8 @@
package compiler package compiler
import "github.com/MontFerret/ferret/pkg/runtime" import (
"github.com/MontFerret/ferret/pkg/vm"
)
type ( type (
LoopType int LoopType int
@@ -14,13 +16,13 @@ type (
Allocate bool Allocate bool
Jump int Jump int
JumpOffset int JumpOffset int
Src runtime.Operand Src vm.Operand
Iterator runtime.Operand Iterator vm.Operand
ValueName string ValueName string
Value runtime.Operand Value vm.Operand
KeyName string KeyName string
Key runtime.Operand Key vm.Operand
Result runtime.Operand Result vm.Operand
} }
LoopTable struct { LoopTable struct {
@@ -50,7 +52,7 @@ func NewLoopTable(registers *RegisterAllocator) *LoopTable {
func (lt *LoopTable) EnterLoop(loopType LoopType, kind LoopKind, distinct bool) *Loop { func (lt *LoopTable) EnterLoop(loopType LoopType, kind LoopKind, distinct bool) *Loop {
var allocate bool var allocate bool
var state runtime.Operand var state vm.Operand
// top loop // top loop
if len(lt.loops) == 0 { if len(lt.loops) == 0 {

View File

@@ -1,12 +1,11 @@
package compiler package compiler
import ( import (
"github.com/MontFerret/ferret/pkg/runtime"
"regexp" "regexp"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
var fnNameValidation = regexp.MustCompile("^[a-zA-Z]+[a-zA-Z0-9_]*(::[a-zA-Z]+[a-zA-Z0-9_]*)*$") var fnNameValidation = regexp.MustCompile("^[a-zA-Z]+[a-zA-Z0-9_]*(::[a-zA-Z]+[a-zA-Z0-9_]*)*$")
@@ -15,32 +14,32 @@ const emptyNS = ""
const separator = "::" const separator = "::"
type NamespaceContainer struct { type NamespaceContainer struct {
funcs core.Functions funcs runtime.Functions
name string name string
} }
func NewRootNamespace() *NamespaceContainer { func NewRootNamespace() *NamespaceContainer {
ns := new(NamespaceContainer) ns := new(NamespaceContainer)
ns.funcs = core.NewFunctions() ns.funcs = runtime.NewFunctions()
return ns return ns
} }
func NewNamespace(funcs core.Functions, name string) *NamespaceContainer { func NewNamespace(funcs runtime.Functions, name string) *NamespaceContainer {
return &NamespaceContainer{funcs, strings.ToUpper(name)} return &NamespaceContainer{funcs, strings.ToUpper(name)}
} }
func (nc *NamespaceContainer) Namespace(name string) core.Namespace { func (nc *NamespaceContainer) Namespace(name string) runtime.Namespace {
return NewNamespace(nc.funcs, nc.makeFullName(name)) return NewNamespace(nc.funcs, nc.makeFullName(name))
} }
func (nc *NamespaceContainer) MustRegisterFunction(name string, fun core.Function) { func (nc *NamespaceContainer) MustRegisterFunction(name string, fun runtime.Function) {
if err := nc.RegisterFunction(name, fun); err != nil { if err := nc.RegisterFunction(name, fun); err != nil {
panic(err) panic(err)
} }
} }
func (nc *NamespaceContainer) RegisterFunction(name string, fun core.Function) error { func (nc *NamespaceContainer) RegisterFunction(name string, fun runtime.Function) error {
nsName := nc.makeFullName(name) nsName := nc.makeFullName(name)
_, exists := nc.funcs.Get(nsName) _, exists := nc.funcs.Get(nsName)
@@ -67,13 +66,13 @@ func (nc *NamespaceContainer) RemoveFunction(name string) {
nc.funcs.Unset(nc.makeFullName(name)) nc.funcs.Unset(nc.makeFullName(name))
} }
func (nc *NamespaceContainer) MustRegisterFunctions(funcs core.Functions) { func (nc *NamespaceContainer) MustRegisterFunctions(funcs runtime.Functions) {
if err := nc.RegisterFunctions(funcs); err != nil { if err := nc.RegisterFunctions(funcs); err != nil {
panic(err) panic(err)
} }
} }
func (nc *NamespaceContainer) RegisterFunctions(funcs core.Functions) error { func (nc *NamespaceContainer) RegisterFunctions(funcs runtime.Functions) error {
for _, name := range funcs.Names() { for _, name := range funcs.Names() {
fun, _ := funcs.Get(name) fun, _ := funcs.Get(name)
@@ -104,7 +103,7 @@ func (nc *NamespaceContainer) RegisteredFunctions() []string {
return res return res
} }
func (nc *NamespaceContainer) Functions() core.Functions { func (nc *NamespaceContainer) Functions() runtime.Functions {
return nc.funcs return nc.funcs
} }

View File

@@ -2,20 +2,20 @@ package compiler_test
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
func TestNamespaceBuilder(t *testing.T) { func TestNamespaceBuilder(t *testing.T) {
Convey("Namespaces", t, func() { Convey("Namespaces", t, func() {
Convey("Should return an error when a function name contains NS separator", func() { Convey("Should return an error when a function name contains NS separator", func() {
c := compiler.New() c := compiler.New()
err := c.RegisterFunction("FOO::SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) { err := c.RegisterFunction("FOO::SPY", func(ctx context.Context, args ...runtime.Value) (value runtime.Value, e error) {
return core.None, nil return runtime.None, nil
}) })
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@@ -23,8 +23,8 @@ func TestNamespaceBuilder(t *testing.T) {
Convey("Should successfully register a name within a namespace", func() { Convey("Should successfully register a name within a namespace", func() {
c := compiler.New() c := compiler.New()
err := c.Namespace("FOO").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) { err := c.Namespace("FOO").RegisterFunction("SPY", func(ctx context.Context, args ...runtime.Value) (value runtime.Value, e error) {
return core.None, nil return runtime.None, nil
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -46,8 +46,8 @@ func TestNamespaceBuilder(t *testing.T) {
Convey("Root namespace should return all registered functions", func() { Convey("Root namespace should return all registered functions", func() {
c := compiler.New() c := compiler.New()
err := c.Namespace("FOO").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) { err := c.Namespace("FOO").RegisterFunction("SPY", func(ctx context.Context, args ...runtime.Value) (value runtime.Value, e error) {
return core.None, nil return runtime.None, nil
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -59,14 +59,14 @@ func TestNamespaceBuilder(t *testing.T) {
Convey("Namespace should return all registered functions", func() { Convey("Namespace should return all registered functions", func() {
c := compiler.New() c := compiler.New()
err := c.Namespace("FOO").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) { err := c.Namespace("FOO").RegisterFunction("SPY", func(ctx context.Context, args ...runtime.Value) (value runtime.Value, e error) {
return core.None, nil return runtime.None, nil
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = c.Namespace("FOO").Namespace("UTILS").RegisterFunction("SPY", func(ctx context.Context, args ...core.Value) (value core.Value, e error) { err = c.Namespace("FOO").Namespace("UTILS").RegisterFunction("SPY", func(ctx context.Context, args ...runtime.Value) (value runtime.Value, e error) {
return core.None, nil return runtime.None, nil
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -82,8 +82,8 @@ func TestNamespaceBuilder(t *testing.T) {
Convey("Namespace should return an error if namespace name is incorrect", func() { Convey("Namespace should return an error if namespace name is incorrect", func() {
c := compiler.New() c := compiler.New()
noop := func(ctx context.Context, args ...core.Value) (value core.Value, e error) { noop := func(ctx context.Context, args ...runtime.Value) (value runtime.Value, e error) {
return core.None, nil return runtime.None, nil
} }
err := c.Namespace("FOO::").RegisterFunction("SPY", noop) err := c.Namespace("FOO::").RegisterFunction("SPY", noop)

View File

@@ -1,25 +1,24 @@
package compiler package compiler
import ( import (
"strconv"
"github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/vm"
"strconv"
) )
type ( type (
Variable struct { Variable struct {
Name string Name string
Register runtime.Operand Register vm.Operand
Depth int Depth int
} }
SymbolTable struct { SymbolTable struct {
registers *RegisterAllocator registers *RegisterAllocator
constants []core.Value constants []runtime.Value
constantsIndex map[uint64]int constantsIndex map[uint64]int
params map[string]string params map[string]string
globals map[string]runtime.Operand globals map[string]vm.Operand
locals []*Variable locals []*Variable
scope int scope int
} }
@@ -28,10 +27,10 @@ type (
func NewSymbolTable(registers *RegisterAllocator) *SymbolTable { func NewSymbolTable(registers *RegisterAllocator) *SymbolTable {
return &SymbolTable{ return &SymbolTable{
registers: registers, registers: registers,
constants: make([]core.Value, 0), constants: make([]runtime.Value, 0),
constantsIndex: make(map[uint64]int), constantsIndex: make(map[uint64]int),
params: make(map[string]string), params: make(map[string]string),
globals: make(map[string]runtime.Operand), globals: make(map[string]vm.Operand),
locals: make([]*Variable, 0), locals: make([]*Variable, 0),
} }
} }
@@ -44,26 +43,26 @@ func (st *SymbolTable) EnterScope() {
st.scope++ st.scope++
} }
func (st *SymbolTable) AddParam(name string) runtime.Operand { func (st *SymbolTable) AddParam(name string) vm.Operand {
st.params[name] = name st.params[name] = name
return st.AddConstant(core.NewString(name)) return st.AddConstant(runtime.NewString(name))
} }
// AddConstant adds a constant to the constants pool and returns its index. // AddConstant adds a constant to the constants pool and returns its index.
// If the constant is a scalar, it will be deduplicated. // If the constant is a scalar, it will be deduplicated.
// If the constant is not a scalar, it will be added to the pool without deduplication. // If the constant is not a scalar, it will be added to the pool without deduplication.
func (st *SymbolTable) AddConstant(constant core.Value) runtime.Operand { func (st *SymbolTable) AddConstant(constant runtime.Value) vm.Operand {
var hash uint64 var hash uint64
isNone := constant == core.None isNone := constant == runtime.None
if core.IsScalar(constant) { if runtime.IsScalar(constant) {
hash = constant.Hash() hash = constant.Hash()
} }
if hash > 0 || isNone { if hash > 0 || isNone {
if p, ok := st.constantsIndex[hash]; ok { if p, ok := st.constantsIndex[hash]; ok {
return runtime.NewConstantOperand(p) return vm.NewConstantOperand(p)
} }
} }
@@ -75,34 +74,34 @@ func (st *SymbolTable) AddConstant(constant core.Value) runtime.Operand {
} }
// We flip the sign to indicate that this is a constant index, not a register. // We flip the sign to indicate that this is a constant index, not a register.
return runtime.NewConstantOperand(p) return vm.NewConstantOperand(p)
} }
// Constant returns a constant by its index. // Constant returns a constant by its index.
func (st *SymbolTable) Constant(addr runtime.Operand) core.Value { func (st *SymbolTable) Constant(addr vm.Operand) runtime.Value {
if !addr.IsConstant() { if !addr.IsConstant() {
panic(core.Error(ErrInvalidOperandType, strconv.Itoa(int(addr)))) panic(runtime.Error(ErrInvalidOperandType, strconv.Itoa(int(addr))))
} }
index := addr.Constant() index := addr.Constant()
if index < 0 || index >= len(st.constants) { if index < 0 || index >= len(st.constants) {
panic(core.Error(ErrConstantNotFound, strconv.Itoa(index))) panic(runtime.Error(ErrConstantNotFound, strconv.Itoa(index)))
} }
return st.constants[index] return st.constants[index]
} }
func (st *SymbolTable) DefineVariable(name string) runtime.Operand { func (st *SymbolTable) DefineVariable(name string) vm.Operand {
if st.scope == 0 { if st.scope == 0 {
// Check for duplicate global variable names. // Check for duplicate global variable names.
_, ok := st.globals[name] _, ok := st.globals[name]
if ok { if ok {
panic(core.Error(ErrVariableNotUnique, name)) panic(runtime.Error(ErrVariableNotUnique, name))
} }
op := st.AddConstant(core.NewString(name)) op := st.AddConstant(runtime.NewString(name))
// Define global variable. // Define global variable.
st.globals[name] = op st.globals[name] = op
@@ -120,25 +119,25 @@ func (st *SymbolTable) DefineVariable(name string) runtime.Operand {
return register return register
} }
func (st *SymbolTable) Variable(name string) runtime.Operand { func (st *SymbolTable) Variable(name string) vm.Operand {
for i := len(st.locals) - 1; i >= 0; i-- { for i := len(st.locals) - 1; i >= 0; i-- {
variable := st.locals[i] variable := st.locals[i]
if variable.Name == name { if variable.Name == name {
return runtime.NewRegisterOperand(int(variable.Register)) return vm.NewRegisterOperand(int(variable.Register))
} }
} }
op, ok := st.globals[name] op, ok := st.globals[name]
if !ok { if !ok {
panic(core.Error(ErrVariableNotFound, name)) panic(runtime.Error(ErrVariableNotFound, name))
} }
return op return op
} }
// GlobalVariable returns a global variable by its name. // GlobalVariable returns a global variable by its name.
func (st *SymbolTable) GlobalVariable(name string) (runtime.Operand, bool) { func (st *SymbolTable) GlobalVariable(name string) (vm.Operand, bool) {
op, ok := st.globals[name] op, ok := st.globals[name]
return op, ok return op, ok

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,8 +1,8 @@
package core_test package runtime_test
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -11,8 +11,8 @@ import (
func TestArrayIterator(t *testing.T) { func TestArrayIterator(t *testing.T) {
Convey("No values", t, func() { Convey("No values", t, func() {
ctx := context.Background() ctx := context.Background()
arr := core.NewArray(0) arr := runtime.NewArray(0)
iter := core.NewArrayIterator(arr) iter := runtime.NewArrayIterator(arr)
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)
@@ -22,9 +22,9 @@ func TestArrayIterator(t *testing.T) {
Convey("One value", t, func() { Convey("One value", t, func() {
ctx := context.Background() ctx := context.Background()
arr := core.NewArray(1) arr := runtime.NewArray(1)
arr.Add(ctx, NewInt(1)) arr.Add(ctx, NewInt(1))
iter := core.NewArrayIterator(arr) iter := runtime.NewArrayIterator(arr)
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)
@@ -45,13 +45,13 @@ func TestArrayIterator(t *testing.T) {
Convey("Multiple values", t, func() { Convey("Multiple values", t, func() {
ctx := context.Background() ctx := context.Background()
arr := core.NewArray(5) arr := runtime.NewArray(5)
arr.Push(NewInt(1)) arr.Push(NewInt(1))
arr.Push(NewInt(2)) arr.Push(NewInt(2))
arr.Push(NewInt(3)) arr.Push(NewInt(3))
arr.Push(NewInt(4)) arr.Push(NewInt(4))
arr.Push(NewInt(5)) arr.Push(NewInt(5))
iter := core.NewArrayIterator(arr) iter := runtime.NewArrayIterator(arr)
actual := make([]Int, 0, 5) actual := make([]Int, 0, 5)
@@ -70,7 +70,7 @@ func TestArrayIterator(t *testing.T) {
func BenchmarkArrayIterator(b *testing.B) { func BenchmarkArrayIterator(b *testing.B) {
size := 100 size := 100
arr := core.NewArray(size) arr := runtime.NewArray(size)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
arr.Push(NewInt(i)) arr.Push(NewInt(i))
@@ -81,7 +81,7 @@ func BenchmarkArrayIterator(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
iter := core.NewArrayIterator(arr) iter := runtime.NewArrayIterator(arr)
for { for {
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)

View File

@@ -1,8 +1,8 @@
package core_test package runtime_test
import ( import (
"encoding/json" "encoding/json"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -11,13 +11,13 @@ import (
func TestArray(t *testing.T) { func TestArray(t *testing.T) {
Convey("#constructor", t, func() { Convey("#constructor", t, func() {
Convey("Should create an empty array", func() { Convey("Should create an empty array", func() {
arr := core.NewArray(10) arr := runtime.NewArray(10)
So(arr.Length(), ShouldEqual, 0) So(arr.Length(), ShouldEqual, 0)
}) })
Convey("Should create an array, from passed values", func() { Convey("Should create an array, from passed values", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
NewInt(3), NewInt(3),
@@ -29,7 +29,7 @@ func TestArray(t *testing.T) {
Convey(".MarshalJSON", t, func() { Convey(".MarshalJSON", t, func() {
Convey("Should serialize empty array", func() { Convey("Should serialize empty array", func() {
arr := core.NewArray(10) arr := runtime.NewArray(10)
marshaled, err := arr.MarshalJSON() marshaled, err := arr.MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -38,7 +38,7 @@ func TestArray(t *testing.T) {
}) })
Convey("Should serialize full array", func() { Convey("Should serialize full array", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
NewInt(3), NewInt(3),
@@ -53,7 +53,7 @@ func TestArray(t *testing.T) {
Convey(".Unwrap", t, func() { Convey(".Unwrap", t, func() {
Convey("Should return a an array of unwrapped values", func() { Convey("Should return a an array of unwrapped values", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
ZeroInt, ZeroInt,
ZeroInt, ZeroInt,
) )
@@ -66,7 +66,7 @@ func TestArray(t *testing.T) {
Convey(".String", t, func() { Convey(".String", t, func() {
Convey("Should return a string representation ", func() { Convey("Should return a string representation ", func() {
arr := core.NewArrayWith(ZeroInt, ZeroInt) arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
So(arr.String(), ShouldEqual, "[0,0]") So(arr.String(), ShouldEqual, "[0,0]")
}) })
@@ -74,7 +74,7 @@ func TestArray(t *testing.T) {
Convey(".CompareValues", t, func() { Convey(".CompareValues", t, func() {
Convey("It should return 1 for all non-array and non-object values", func() { Convey("It should return 1 for all non-array and non-object values", func() {
arr := core.NewArrayWith(ZeroInt, ZeroInt) arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
So(arr.Compare(None), ShouldEqual, 1) So(arr.Compare(None), ShouldEqual, 1)
So(arr.Compare(ZeroInt), ShouldEqual, 1) So(arr.Compare(ZeroInt), ShouldEqual, 1)
@@ -83,39 +83,39 @@ func TestArray(t *testing.T) {
}) })
Convey("It should return -1 for all object values", func() { Convey("It should return -1 for all object values", func() {
arr := core.NewArrayWith(ZeroInt, ZeroInt) arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
obj := core.NewObject() obj := runtime.NewObject()
So(arr.Compare(obj), ShouldEqual, -1) So(arr.Compare(obj), ShouldEqual, -1)
}) })
Convey("It should return 0 when both arrays are empty", func() { Convey("It should return 0 when both arrays are empty", func() {
arr1 := core.NewArray(1) arr1 := runtime.NewArray(1)
arr2 := core.NewArray(1) arr2 := runtime.NewArray(1)
So(arr1.Compare(arr2), ShouldEqual, 0) So(arr1.Compare(arr2), ShouldEqual, 0)
}) })
Convey("It should return 1 when other array is empty", func() { Convey("It should return 1 when other array is empty", func() {
arr1 := core.NewArrayWith(ZeroFloat) arr1 := runtime.NewArrayWith(ZeroFloat)
arr2 := core.NewArray(1) arr2 := runtime.NewArray(1)
So(arr1.Compare(arr2), ShouldEqual, 1) So(arr1.Compare(arr2), ShouldEqual, 1)
}) })
Convey("It should return 1 when values are bigger", func() { Convey("It should return 1 when values are bigger", func() {
arr1 := core.NewArrayWith(NewInt(1)) arr1 := runtime.NewArrayWith(NewInt(1))
arr2 := core.NewArrayWith(ZeroInt) arr2 := runtime.NewArrayWith(ZeroInt)
So(arr1.Compare(arr2), ShouldEqual, 1) So(arr1.Compare(arr2), ShouldEqual, 1)
}) })
Convey("It should return 0 when arrays are equal", func() { Convey("It should return 0 when arrays are equal", func() {
Convey("When only simple types are nested", func() { Convey("When only simple types are nested", func() {
arr1 := core.NewArrayWith( arr1 := runtime.NewArrayWith(
NewInt(0), NewString("str"), NewInt(0), NewString("str"),
) )
arr2 := core.NewArrayWith( arr2 := runtime.NewArrayWith(
NewInt(0), NewString("str"), NewInt(0), NewString("str"),
) )
@@ -123,19 +123,19 @@ func TestArray(t *testing.T) {
}) })
Convey("When object and array are nested at the same time", func() { Convey("When object and array are nested at the same time", func() {
arr1 := core.NewArrayWith( arr1 := runtime.NewArrayWith(
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
), ),
core.NewArrayWith( runtime.NewArrayWith(
NewInt(2), NewInt(2),
), ),
) )
arr2 := core.NewArrayWith( arr2 := runtime.NewArrayWith(
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
), ),
core.NewArrayWith( runtime.NewArrayWith(
NewInt(2), NewInt(2),
), ),
) )
@@ -144,14 +144,14 @@ func TestArray(t *testing.T) {
}) })
Convey("When only objects are nested", func() { Convey("When only objects are nested", func() {
arr1 := core.NewArrayWith( arr1 := runtime.NewArrayWith(
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
), ),
) )
arr2 := core.NewArrayWith( arr2 := runtime.NewArrayWith(
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
), ),
) )
@@ -159,13 +159,13 @@ func TestArray(t *testing.T) {
}) })
Convey("When only arrays are nested", func() { Convey("When only arrays are nested", func() {
arr1 := core.NewArrayWith( arr1 := runtime.NewArrayWith(
core.NewArrayWith( runtime.NewArrayWith(
NewInt(2), NewInt(2),
), ),
) )
arr2 := core.NewArrayWith( arr2 := runtime.NewArrayWith(
core.NewArrayWith( runtime.NewArrayWith(
NewInt(2), NewInt(2),
), ),
) )
@@ -174,21 +174,21 @@ func TestArray(t *testing.T) {
}) })
Convey("When simple and complex types at the same time", func() { Convey("When simple and complex types at the same time", func() {
arr1 := core.NewArrayWith( arr1 := runtime.NewArrayWith(
NewInt(0), NewInt(0),
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
), ),
core.NewArrayWith( runtime.NewArrayWith(
NewInt(2), NewInt(2),
), ),
) )
arr2 := core.NewArrayWith( arr2 := runtime.NewArrayWith(
NewInt(0), NewInt(0),
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
), ),
core.NewArrayWith( runtime.NewArrayWith(
NewInt(2), NewInt(2),
), ),
) )
@@ -197,17 +197,17 @@ func TestArray(t *testing.T) {
}) })
Convey("When custom complex type", func() { Convey("When custom complex type", func() {
arr1 := core.NewArrayWith( arr1 := runtime.NewArrayWith(
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty( runtime.NewObjectProperty(
"arr", core.NewArrayWith(core.NewObject()), "arr", runtime.NewArrayWith(runtime.NewObject()),
), ),
), ),
) )
arr2 := core.NewArrayWith( arr2 := runtime.NewArrayWith(
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty( runtime.NewObjectProperty(
"arr", core.NewArrayWith(core.NewObject()), "arr", runtime.NewArrayWith(runtime.NewObject()),
), ),
), ),
) )
@@ -219,7 +219,7 @@ func TestArray(t *testing.T) {
Convey(".Hash", t, func() { Convey(".Hash", t, func() {
Convey("It should calculate hash of non-empty array", func() { Convey("It should calculate hash of non-empty array", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
NewInt(3), NewInt(3),
@@ -231,7 +231,7 @@ func TestArray(t *testing.T) {
}) })
Convey("It should calculate hash of empty array", func() { Convey("It should calculate hash of empty array", func() {
arr := core.NewArrayWith() arr := runtime.NewArrayWith()
h := arr.Hash() h := arr.Hash()
@@ -239,14 +239,14 @@ func TestArray(t *testing.T) {
}) })
Convey("Hash sum should be consistent", func() { Convey("Hash sum should be consistent", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
True, True,
NewInt(1), NewInt(1),
NewFloat(1.1), NewFloat(1.1),
NewString("foobar"), NewString("foobar"),
NewCurrentDateTime(), NewCurrentDateTime(),
core.NewArrayWith(NewInt(1), True), runtime.NewArrayWith(NewInt(1), True),
core.NewObjectWith(core.NewObjectProperty("foo", NewString("bar"))), runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewString("bar"))),
) )
h1 := arr.Hash() h1 := arr.Hash()
@@ -258,13 +258,13 @@ func TestArray(t *testing.T) {
Convey(".Length", t, func() { Convey(".Length", t, func() {
Convey("Should return 0 when empty", func() { Convey("Should return 0 when empty", func() {
arr := core.NewArray(1) arr := runtime.NewArray(1)
So(arr.Length(), ShouldEqual, 0) So(arr.Length(), ShouldEqual, 0)
}) })
Convey("Should return greater than 0 when not empty", func() { Convey("Should return greater than 0 when not empty", func() {
arr := core.NewArrayWith(ZeroInt, ZeroInt) arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
So(arr.Length(), ShouldEqual, 2) So(arr.Length(), ShouldEqual, 2)
}) })
@@ -272,7 +272,7 @@ func TestArray(t *testing.T) {
Convey(".ForEach", t, func() { Convey(".ForEach", t, func() {
Convey("Should iterate over elements", func() { Convey("Should iterate over elements", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
NewInt(3), NewInt(3),
@@ -289,7 +289,7 @@ func TestArray(t *testing.T) {
}) })
Convey("Should not iterate when empty", func() { Convey("Should not iterate when empty", func() {
arr := core.NewArrayWith() arr := runtime.NewArrayWith()
counter := 0 counter := 0
arr.ForEach(func(value Value, idx int) bool { arr.ForEach(func(value Value, idx int) bool {
@@ -358,7 +358,7 @@ func TestArray(t *testing.T) {
//}) //})
Convey("Should return an error when index is out of bounds", func() { Convey("Should return an error when index is out of bounds", func() {
arr := core.NewArray(10) arr := runtime.NewArray(10)
err := arr.Set(0, NewInt(1)) err := arr.Set(0, NewInt(1))
@@ -369,7 +369,7 @@ func TestArray(t *testing.T) {
Convey(".Push", t, func() { Convey(".Push", t, func() {
Convey("Should add an item", func() { Convey("Should add an item", func() {
arr := core.NewArray(10) arr := runtime.NewArray(10)
src := []Value{ src := []Value{
ZeroInt, ZeroInt,
@@ -411,7 +411,7 @@ func TestArray(t *testing.T) {
Convey(".Insert", t, func() { Convey(".Insert", t, func() {
Convey("Should insert an item in the middle of an array", func() { Convey("Should insert an item in the middle of an array", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(0), NewInt(0),
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
@@ -433,7 +433,7 @@ func TestArray(t *testing.T) {
Convey(".RemoveAt", t, func() { Convey(".RemoveAt", t, func() {
Convey("Should remove an item from the middle", func() { Convey("Should remove an item from the middle", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(0), NewInt(0),
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
@@ -453,7 +453,7 @@ func TestArray(t *testing.T) {
}) })
Convey("Should remove an item from the end", func() { Convey("Should remove an item from the end", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(0), NewInt(0),
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
@@ -474,7 +474,7 @@ func TestArray(t *testing.T) {
}) })
Convey("Should remove an item from the beginning", func() { Convey("Should remove an item from the beginning", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(0), NewInt(0),
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
@@ -496,24 +496,24 @@ func TestArray(t *testing.T) {
Convey(".Clone", t, func() { Convey(".Clone", t, func() {
Convey("Cloned array should be equal to source array", func() { Convey("Cloned array should be equal to source array", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(0), NewInt(0),
core.NewObjectWith( runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
), ),
core.NewArrayWith( runtime.NewArrayWith(
NewInt(2), NewInt(2),
), ),
) )
clone := arr.Clone().(*core.Array) clone := arr.Clone().(*runtime.Array)
So(arr.Length(), ShouldEqual, clone.Length()) So(arr.Length(), ShouldEqual, clone.Length())
So(arr.Compare(clone), ShouldEqual, 0) So(arr.Compare(clone), ShouldEqual, 0)
}) })
Convey("Cloned array should be independent of the source array", func() { Convey("Cloned array should be independent of the source array", func() {
arr := core.NewArrayWith( arr := runtime.NewArrayWith(
NewInt(0), NewInt(0),
NewInt(1), NewInt(1),
NewInt(2), NewInt(2),
@@ -522,7 +522,7 @@ func TestArray(t *testing.T) {
NewInt(5), NewInt(5),
) )
clone := arr.Clone().(*core.Array) clone := arr.Clone().(*runtime.Array)
arr.Push(NewInt(6)) arr.Push(NewInt(6))

View File

@@ -1,4 +1,8 @@
package core package runtime
import "context"
type TypeAssertion func(input Value) error
func AssertString(input Value) error { func AssertString(input Value) error {
_, ok := input.(String) _, ok := input.(String)
@@ -74,6 +78,16 @@ func AssertList(input Value) error {
return nil return nil
} }
func AssertItemsOf(ctx context.Context, input Iterable, assertion TypeAssertion) error {
return ForEachOf(ctx, input, func(ctx context.Context, value, _ Value) (Boolean, error) {
if err := assertion(value); err != nil {
return false, err
}
return true, nil
})
}
func AssertMap(input Value) error { func AssertMap(input Value) error {
_, ok := input.(Map) _, ok := input.(Map)

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"hash/fnv" "hash/fnv"

View File

@@ -1,7 +1,7 @@
package core_test package runtime_test
import ( import (
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -10,7 +10,7 @@ import (
func TestBoolean(t *testing.T) { func TestBoolean(t *testing.T) {
Convey(".MarshalJSON", t, func() { Convey(".MarshalJSON", t, func() {
Convey("Should serialize a boolean items", func() { Convey("Should serialize a boolean items", func() {
b := core.True b := runtime.True
marshaled, err := b.MarshalJSON() marshaled, err := b.MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -21,19 +21,19 @@ func TestBoolean(t *testing.T) {
Convey(".Unwrap", t, func() { Convey(".Unwrap", t, func() {
Convey("Should return an unwrapped items", func() { Convey("Should return an unwrapped items", func() {
So(core.True.Unwrap(), ShouldHaveSameTypeAs, true) So(runtime.True.Unwrap(), ShouldHaveSameTypeAs, true)
}) })
}) })
Convey(".String", t, func() { Convey(".String", t, func() {
Convey("Should return a string representation ", func() { Convey("Should return a string representation ", func() {
So(core.True.String(), ShouldEqual, "true") So(runtime.True.String(), ShouldEqual, "true")
}) })
}) })
Convey(".CompareValues", t, func() { Convey(".CompareValues", t, func() {
Convey("It should return 1 when compared to None", func() { Convey("It should return 1 when compared to None", func() {
So(core.True.Compare(core.None), ShouldEqual, 1) So(runtime.True.Compare(runtime.None), ShouldEqual, 1)
}) })
Convey("It should return -1 for all non-boolean values", func() { Convey("It should return -1 for all non-boolean values", func() {
@@ -41,8 +41,8 @@ func TestBoolean(t *testing.T) {
NewString("foo"), NewString("foo"),
NewInt(1), NewInt(1),
NewFloat(1.1), NewFloat(1.1),
core.NewArray(10), runtime.NewArray(10),
core.NewObject(), runtime.NewObject(),
} }
for _, v := range vals { for _, v := range vals {

View File

@@ -1,4 +1,4 @@
package core package runtime
func CastBoolean(input Value) (Boolean, error) { func CastBoolean(input Value) (Boolean, error) {
boolean, ok := input.(Boolean) boolean, ok := input.(Boolean)

View File

@@ -1,4 +1,4 @@
package core package runtime
import "context" import "context"
@@ -22,6 +22,8 @@ func SafeClone(ctx context.Context, origin Cloneable) Cloneable {
return cloned return cloned
} }
// CloneOrCopy creates a deep copy of the given value.
// If the value does not support cloning, it returns a shallow copy of the value.
func CloneOrCopy(ctx context.Context, val Value) (Value, error) { func CloneOrCopy(ctx context.Context, val Value) (Value, error) {
switch v := val.(type) { switch v := val.(type) {
case Cloneable: case Cloneable:

View File

@@ -1,8 +1,10 @@
package core package runtime
import "context" import "context"
type ( type (
Predicate = func(ctx context.Context, value, idx Value) (Boolean, error)
Indexed interface { Indexed interface {
Get(ctx context.Context, idx Int) (Value, error) Get(ctx context.Context, idx Int) (Value, error)
} }
@@ -34,7 +36,12 @@ type (
Collection Collection
Indexed Indexed
ForEach(ctx context.Context, predicate IndexedPredicate) error Add(ctx context.Context, value Value) error
Set(ctx context.Context, idx Int, value Value) error
Insert(ctx context.Context, idx Int, value Value) error
Remove(ctx context.Context, value Value) error
RemoveAt(ctx context.Context, idx Int) (Value, error)
Swap(ctx context.Context, i, j Int) error
Find(ctx context.Context, predicate IndexedPredicate) (List, error) Find(ctx context.Context, predicate IndexedPredicate) (List, error)
FindOne(ctx context.Context, predicate IndexedPredicate) (Value, Boolean, error) FindOne(ctx context.Context, predicate IndexedPredicate) (Value, Boolean, error)
@@ -42,16 +49,12 @@ type (
IndexOf(ctx context.Context, value Value) (Int, error) IndexOf(ctx context.Context, value Value) (Int, error)
First(context.Context) (Value, error) First(context.Context) (Value, error)
Last(context.Context) (Value, error) Last(context.Context) (Value, error)
Slice(ctx context.Context, start, end Int) (List, error) Slice(ctx context.Context, start, end Int) (List, error)
Sort(ctx context.Context, ascending Boolean) (List, error) Sort(ctx context.Context, ascending Boolean) (List, error)
SortWith(ctx context.Context, comparator Comparator) (List, error) SortWith(ctx context.Context, comparator Comparator) (List, error)
Add(ctx context.Context, value Value) error ForEach(ctx context.Context, predicate IndexedPredicate) error
Set(ctx context.Context, idx Int, value Value) error
Insert(ctx context.Context, idx Int, value Value) error
Remove(ctx context.Context, value Value) error
RemoveAt(ctx context.Context, idx Int) (Value, error)
Swap(ctx context.Context, i, j Int) error
} }
// Set represents a set of values. // Set represents a set of values.
@@ -70,17 +73,16 @@ type (
Collection Collection
Keyed Keyed
ForEach(ctx context.Context, predicate KeyedPredicate) error
Keys(context.Context) ([]Value, error)
Values(context.Context) ([]Value, error)
Find(ctx context.Context, predicate KeyedPredicate) (List, error)
FindOne(ctx context.Context, predicate KeyedPredicate) (Value, Boolean, error)
ContainsKey(ctx context.Context, key Value) (Boolean, error)
ContainsValue(ctx context.Context, value Value) (Boolean, error)
Set(ctx context.Context, key Value, value Value) error Set(ctx context.Context, key Value, value Value) error
Remove(ctx context.Context, key Value) error Remove(ctx context.Context, key Value) error
ContainsKey(ctx context.Context, key Value) (Boolean, error)
ContainsValue(ctx context.Context, value Value) (Boolean, error)
Keys(context.Context) ([]Value, error)
Values(context.Context) ([]Value, error)
Find(ctx context.Context, predicate KeyedPredicate) (List, error)
FindOne(ctx context.Context, predicate KeyedPredicate) (Value, Boolean, error)
ForEach(ctx context.Context, predicate KeyedPredicate) error
} }
) )

View File

@@ -1,4 +1,4 @@
package core package runtime
type ( type (
Comparable interface { Comparable interface {

View File

@@ -1,67 +0,0 @@
package core
import (
"fmt"
"strings"
"github.com/pkg/errors"
)
var (
ErrMissedArgument = errors.New("missed argument")
ErrInvalidArgument = errors.New("invalid argument")
ErrInvalidArgumentNumber = errors.New("invalid argument number")
ErrInvalidType = errors.New("invalid type")
ErrInvalidOperation = errors.New("invalid operation")
ErrNotFound = errors.New("not found")
ErrNotUnique = errors.New("not unique")
ErrTerminated = errors.New("operation is terminated")
ErrUnexpected = errors.New("unexpected error")
ErrTimeout = errors.New("operation timed out")
ErrNotImplemented = errors.New("not implemented")
ErrNotSupported = errors.New("not supported")
)
const typeErrorTemplate = "expected %s, but got %s"
func TypeError(value Value, expected ...string) error {
actual := Reflect(value)
if len(expected) == 0 {
return Error(ErrInvalidType, actual)
}
if len(expected) == 1 {
return Error(ErrInvalidType, fmt.Sprintf(typeErrorTemplate, expected, actual))
}
strs := make([]string, len(expected))
for idx, t := range expected {
strs[idx] = t
}
expectedStr := strings.Join(strs, " or ")
return Error(ErrInvalidType, fmt.Sprintf(typeErrorTemplate, expectedStr, actual))
}
func Error(err error, msg string) error {
return errors.Errorf("%s: %s", err.Error(), msg)
}
func Errorf(err error, format string, args ...interface{}) error {
return errors.Errorf("%s: %s", err.Error(), fmt.Sprintf(format, args...))
}
func Errors(err ...error) error {
message := ""
for _, e := range err {
if e != nil {
message += ": " + e.Error()
}
}
return errors.New(message)
}

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"hash/fnv" "hash/fnv"

View File

@@ -1,8 +1,8 @@
package core_test package runtime_test
import ( import (
"encoding/json" "encoding/json"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
"time" "time"
@@ -12,7 +12,7 @@ import (
func TestDateTime(t *testing.T) { func TestDateTime(t *testing.T) {
Convey(".Hash", t, func() { Convey(".Hash", t, func() {
Convey("It should calculate hash", func() { Convey("It should calculate hash", func() {
d := core.NewCurrentDateTime() d := runtime.NewCurrentDateTime()
h := d.Hash() h := d.Hash()
@@ -20,7 +20,7 @@ func TestDateTime(t *testing.T) {
}) })
Convey("Hash sum should be consistent", func() { Convey("Hash sum should be consistent", func() {
d := core.NewCurrentDateTime() d := runtime.NewCurrentDateTime()
So(d.Hash(), ShouldEqual, d.Hash()) So(d.Hash(), ShouldEqual, d.Hash())
}) })
@@ -33,7 +33,7 @@ func TestDateTime(t *testing.T) {
json1, err := json.Marshal(value) json1, err := json.Marshal(value)
So(err, ShouldBeNil) So(err, ShouldBeNil)
json2, err := core.NewDateTime(value).MarshalJSON() json2, err := runtime.NewDateTime(value).MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(json1, ShouldResemble, json2) So(json1, ShouldResemble, json2)

View File

@@ -1,26 +1,67 @@
package runtime package runtime
import "github.com/pkg/errors" import (
"fmt"
"strings"
"github.com/pkg/errors"
)
var ( var (
ErrMissedParam = errors.New("missed parameter") ErrMissedArgument = errors.New("missed argument")
ErrInvalidArgument = errors.New("invalid argument")
ErrInvalidArgumentNumber = errors.New("invalid argument number")
ErrInvalidType = errors.New("invalid type")
ErrInvalidOperation = errors.New("invalid operation")
ErrNotFound = errors.New("not found")
ErrNotUnique = errors.New("not unique")
ErrTerminated = errors.New("operation is terminated")
ErrUnexpected = errors.New("unexpected error")
ErrTimeout = errors.New("operation timed out")
ErrNotImplemented = errors.New("not implemented")
ErrNotSupported = errors.New("not supported")
) )
type ( const typeErrorTemplate = "expected %s, but got %s"
SourceErrorDetail struct {
error
BaseError error
ComputeError error
}
)
func (e *SourceErrorDetail) Error() string { func TypeError(value Value, expected ...string) error {
return e.ComputeError.Error() actual := Reflect(value)
if len(expected) == 0 {
return Error(ErrInvalidType, actual)
}
if len(expected) == 1 {
return Error(ErrInvalidType, fmt.Sprintf(typeErrorTemplate, expected, actual))
}
strs := make([]string, len(expected))
for idx, t := range expected {
strs[idx] = t
}
expectedStr := strings.Join(strs, " or ")
return Error(ErrInvalidType, fmt.Sprintf(typeErrorTemplate, expectedStr, actual))
} }
func SourceError(src SourceMap, err error) error { func Error(err error, msg string) error {
return &SourceErrorDetail{ return errors.Errorf("%s: %s", err.Error(), msg)
BaseError: err, }
ComputeError: errors.Errorf("%s: %s", err.Error(), src.String()),
} func Errorf(err error, format string, args ...interface{}) error {
return errors.Errorf("%s: %s", err.Error(), fmt.Sprintf(format, args...))
}
func Errors(err ...error) error {
message := ""
for _, e := range err {
if e != nil {
message += ": " + e.Error()
}
}
return errors.New(message)
} }

View File

@@ -1,6 +1,7 @@
package core_test package runtime_test
import ( import (
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/core"
"testing" "testing"
@@ -24,14 +25,14 @@ func TestSourceError(t *testing.T) {
func TestTypeError(t *testing.T) { func TestTypeError(t *testing.T) {
Convey("Should match", t, func() { Convey("Should match", t, func() {
e := core.TypeError(TypeA{}) e := runtime.TypeError(TypeA{})
So(e, ShouldNotBeNil) So(e, ShouldNotBeNil)
e = core.TypeError(TypeA{}, TypeB{}) e = runtime.TypeError(TypeA{}, TypeB{})
So(e, ShouldNotBeNil) So(e, ShouldNotBeNil)
cause := errors.New("invalid type: expected type_b or type_c, but got type_a") cause := errors.New("invalid type: expected type_b or type_c, but got type_a")
e = core.TypeError(TypeA{}, TypeB{}, TypeC{}) e = runtime.TypeError(TypeA{}, TypeB{}, TypeC{})
So(e.Error(), ShouldEqual, cause.Error()) So(e.Error(), ShouldEqual, cause.Error())
}) })
} }
@@ -42,7 +43,7 @@ func TestError(t *testing.T) {
cause := errors.New("cause") cause := errors.New("cause")
e := errors.Errorf("%s: %s", cause.Error(), msg) e := errors.Errorf("%s: %s", cause.Error(), msg)
ce := core.Error(cause, msg) ce := runtime.Error(cause, msg)
So(ce, ShouldNotBeNil) So(ce, ShouldNotBeNil)
So(ce.Error(), ShouldEqual, e.Error()) So(ce.Error(), ShouldEqual, e.Error())
}) })

View File

@@ -1,65 +0,0 @@
package events
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
type (
strm chan Message
msg struct {
value core.Value
err error
}
)
func New(source chan Message) Stream {
return strm(source)
}
func (s strm) Close(_ context.Context) error {
close(s)
return nil
}
func (s strm) Read(ctx context.Context) <-chan Message {
proxy := make(chan Message)
go func() {
defer close(proxy)
for {
select {
case <-ctx.Done():
return
case evt := <-s:
if ctx.Err() != nil {
return
}
proxy <- evt
}
}
}()
return s
}
func (n *msg) Value() core.Value {
return n.value
}
func (n *msg) Err() error {
return n.err
}
func WithValue(val core.Value) Message {
return &msg{value: val}
}
func WithErr(err error) Message {
return &msg{err: err, value: core.None}
}

View File

@@ -1,56 +0,0 @@
package events
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"sync"
)
type mrger struct {
inputs []Stream
}
func (m *mrger) Close(ctx context.Context) error {
errs := make([]error, 0, len(m.inputs))
for _, s := range m.inputs {
if err := s.Close(ctx); err != nil {
errs = append(errs, err)
}
}
if len(errs) == 0 {
return nil
}
return core.Errors(errs...)
}
func (m *mrger) Read(ctx context.Context) <-chan Message {
var wg sync.WaitGroup
wg.Add(len(m.inputs))
out := make(chan Message)
consume := func(c context.Context, input Stream) {
for evt := range input.Read(c) {
out <- evt
}
wg.Done()
}
for _, ch := range m.inputs {
go consume(ctx, ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
func Merge(inputs ...Stream) Stream {
return &mrger{inputs}
}

View File

@@ -1,42 +0,0 @@
package events
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
type Iterator struct {
messages <-chan Message
message Message
}
func NewIterator(ch <-chan Message) core.Iterator {
return &Iterator{ch, nil}
}
func (iter *Iterator) HasNext(ctx context.Context) (bool, error) {
select {
case evt, ok := <-iter.messages:
if !ok {
return false, nil
}
iter.message = evt
return true, nil
case <-ctx.Done():
return false, ctx.Err()
}
}
func (iter *Iterator) Next(ctx context.Context) (value core.Value, key core.Value, err error) {
if iter.message != nil {
if err := iter.message.Err(); err != nil {
return core.None, core.None, err
}
return iter.message.Value(), core.None, nil
}
return core.None, core.None, core.ErrNoMoreData
}

View File

@@ -1,33 +0,0 @@
package events
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
type (
// Subscription represents an event subscription object that contains target event name
// and optional event options.
Subscription struct {
EventName string
Options *core.hashMap
}
// Message represents an event message that an Observable can emit.
Message interface {
Value() core.Value
Err() error
}
// Stream represents an event stream that produces target event objects.
Stream interface {
Close(ctx context.Context) error
Read(ctx context.Context) <-chan Message
}
// Observable represents an interface of
// complex types that returns stream of events.
Observable interface {
Subscribe(ctx context.Context, subscription Subscription) (Stream, error)
}
)

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"encoding/binary" "encoding/binary"

View File

@@ -1,8 +1,8 @@
package core_test package runtime_test
import ( import (
"encoding/json" "encoding/json"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -11,19 +11,19 @@ import (
func TestFloat(t *testing.T) { func TestFloat(t *testing.T) {
Convey(".Hash", t, func() { Convey(".Hash", t, func() {
Convey("It should calculate hash", func() { Convey("It should calculate hash", func() {
v := core.NewFloat(1.1) v := runtime.NewFloat(1.1)
h := v.Hash() h := v.Hash()
So(h, ShouldBeGreaterThan, 0) So(h, ShouldBeGreaterThan, 0)
v2 := core.NewFloat(1.2) v2 := runtime.NewFloat(1.2)
So(h, ShouldNotEqual, v2.Hash()) So(h, ShouldNotEqual, v2.Hash())
}) })
Convey("Hash sum should be consistent", func() { Convey("Hash sum should be consistent", func() {
v := core.NewFloat(1.1) v := runtime.NewFloat(1.1)
So(v.Hash(), ShouldEqual, v.Hash()) So(v.Hash(), ShouldEqual, v.Hash())
}) })
@@ -36,7 +36,7 @@ func TestFloat(t *testing.T) {
json1, err := json.Marshal(value) json1, err := json.Marshal(value)
So(err, ShouldBeNil) So(err, ShouldBeNil)
json2, err := core.NewFloat(value).MarshalJSON() json2, err := runtime.NewFloat(value).MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(json1, ShouldResemble, json2) So(json1, ShouldResemble, json2)

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,37 +1,36 @@
package core_test package runtime_test
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime"
"strings" "strings"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
func TestValidateArgs(t *testing.T) { func TestValidateArgs(t *testing.T) {
Convey("Should match", t, func() { Convey("Should match", t, func() {
a := []core.Value{core.NewInt(1), core.NewInt(2)} a := []runtime.Value{runtime.NewInt(1), runtime.NewInt(2)}
e := core.ValidateArgs(a, 1, 2) e := runtime.ValidateArgs(a, 1, 2)
So(e, ShouldBeNil) So(e, ShouldBeNil)
e = core.ValidateArgs(a, 3, 4) e = runtime.ValidateArgs(a, 3, 4)
So(e, ShouldNotBeNil) So(e, ShouldNotBeNil)
}) })
} }
func TestFunctions(t *testing.T) { func TestFunctions(t *testing.T) {
fnTrue := func(ctx context.Context, args ...core.Value) (core.Value, error) { fnTrue := func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return core.True, nil return runtime.True, nil
} }
Convey(".Set", t, func() { Convey(".Set", t, func() {
Convey("Should set function by name", func() { Convey("Should set function by name", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "F" fname := "F"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -43,7 +42,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should set function by name at uppercase", func() { Convey("Should set function by name at uppercase", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "f" fname := "f"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -55,7 +54,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should set when Functions created not by NewFunctions", func() { Convey("Should set when Functions created not by NewFunctions", func() {
fns := core.Functions{} fns := runtime.Functions{}
fname := "F" fname := "F"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -70,7 +69,7 @@ func TestFunctions(t *testing.T) {
Convey(".Get", t, func() { Convey(".Get", t, func() {
Convey("Should get function by name", func() { Convey("Should get function by name", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "F" fname := "F"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -81,7 +80,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should get function by name at uppercase", func() { Convey("Should get function by name at uppercase", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "f" fname := "f"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -92,7 +91,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should not panic when Functions created not by NewFunctions", func() { Convey("Should not panic when Functions created not by NewFunctions", func() {
fns := core.Functions{} fns := runtime.Functions{}
fn, exists := fns.Get("f") fn, exists := fns.Get("f")
@@ -104,7 +103,7 @@ func TestFunctions(t *testing.T) {
Convey(".Unset", t, func() { Convey(".Unset", t, func() {
Convey("Should unset function by name", func() { Convey("Should unset function by name", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "F" fname := "F"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -114,7 +113,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should get function by name at uppercase", func() { Convey("Should get function by name at uppercase", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "f" fname := "f"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -124,7 +123,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should not panic when Functions created not by NewFunctions", func() { Convey("Should not panic when Functions created not by NewFunctions", func() {
fns := core.Functions{} fns := runtime.Functions{}
fname := "F" fname := "F"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -137,7 +136,7 @@ func TestFunctions(t *testing.T) {
Convey(".Names", t, func() { Convey(".Names", t, func() {
Convey("Should return name", func() { Convey("Should return name", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "F" fname := "F"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -148,7 +147,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should return name at uppercase", func() { Convey("Should return name at uppercase", func() {
fns := core.NewFunctions() fns := runtime.NewFunctions()
fname := "f" fname := "f"
fns.Set(fname, fnTrue) fns.Set(fname, fnTrue)
@@ -159,7 +158,7 @@ func TestFunctions(t *testing.T) {
}) })
Convey("Should not panic when Functions created not by NewFunctions", func() { Convey("Should not panic when Functions created not by NewFunctions", func() {
fns := core.Functions{} fns := runtime.Functions{}
So(fns.Names(), ShouldHaveLength, 0) So(fns.Names(), ShouldHaveLength, 0)
}) })
}) })

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,9 +1,9 @@
package core_test package runtime_test
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"reflect" "reflect"
"testing" "testing"
"unsafe" "unsafe"
@@ -12,7 +12,7 @@ import (
) )
type CustomValue struct { type CustomValue struct {
properties map[core.Value]core.Value properties map[runtime.Value]runtime.Value
} }
type DummyStruct struct{} type DummyStruct struct{}
@@ -20,51 +20,51 @@ type DummyStruct struct{}
func TestIsNil(t *testing.T) { func TestIsNil(t *testing.T) {
Convey("Should match", t, func() { Convey("Should match", t, func() {
// nil == invalid // nil == invalid
t := core.IsNil(nil) t := runtime.IsNil(nil)
So(t, ShouldBeTrue) So(t, ShouldBeTrue)
a := []string{} a := []string{}
t = core.IsNil(a) t = runtime.IsNil(a)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
b := make([]string, 1) b := make([]string, 1)
t = core.IsNil(b) t = runtime.IsNil(b)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
c := make(map[string]string) c := make(map[string]string)
t = core.IsNil(c) t = runtime.IsNil(c)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
var s struct { var s struct {
Test string Test string
} }
t = core.IsNil(s) t = runtime.IsNil(s)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
f := func() {} f := func() {}
t = core.IsNil(f) t = runtime.IsNil(f)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
i := DummyStruct{} i := DummyStruct{}
t = core.IsNil(i) t = runtime.IsNil(i)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
ch := make(chan string) ch := make(chan string)
t = core.IsNil(ch) t = runtime.IsNil(ch)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
var y unsafe.Pointer var y unsafe.Pointer
var vy int var vy int
y = unsafe.Pointer(&vy) y = unsafe.Pointer(&vy)
t = core.IsNil(y) t = runtime.IsNil(y)
So(t, ShouldBeFalse) So(t, ShouldBeFalse)
}) })
@@ -106,7 +106,7 @@ func TestHelpers(t *testing.T) {
} }
for _, input := range inputs { for _, input := range inputs {
out := core.Parse(input.Raw) out := runtime.Parse(input.Raw)
expectedType := reflect.TypeOf(input.Parsed) expectedType := reflect.TypeOf(input.Parsed)
actualType := reflect.TypeOf(out) actualType := reflect.TypeOf(out)
@@ -175,7 +175,7 @@ func TestHelpers(t *testing.T) {
} }
for _, pair := range inputs { for _, pair := range inputs {
actual := core.ToBoolean(pair[0]) actual := runtime.ToBoolean(pair[0])
expected := pair[1] expected := pair[1]
So(actual, ShouldEqual, expected) So(actual, ShouldEqual, expected)
@@ -186,48 +186,48 @@ func TestHelpers(t *testing.T) {
Convey("ToFloat", func() { Convey("ToFloat", func() {
Convey("Should convert Int", func() { Convey("Should convert Int", func() {
input := NewInt(100) input := NewInt(100)
output := core.ToFloat(input) output := runtime.ToFloat(input)
So(output, ShouldEqual, NewFloat(100)) So(output, ShouldEqual, NewFloat(100))
}) })
Convey("Should convert Float", func() { Convey("Should convert Float", func() {
input := NewFloat(100) input := NewFloat(100)
output := core.ToFloat(input) output := runtime.ToFloat(input)
So(output, ShouldEqual, NewFloat(100)) So(output, ShouldEqual, NewFloat(100))
}) })
Convey("Should convert String", func() { Convey("Should convert String", func() {
input := NewString("100.1") input := NewString("100.1")
output := core.ToFloat(input) output := runtime.ToFloat(input)
So(output, ShouldEqual, NewFloat(100.1)) So(output, ShouldEqual, NewFloat(100.1))
output2 := core.ToFloat(NewString("foobar")) output2 := runtime.ToFloat(NewString("foobar"))
So(output2, ShouldEqual, ZeroFloat) So(output2, ShouldEqual, ZeroFloat)
}) })
Convey("Should convert Boolean", func() { Convey("Should convert Boolean", func() {
So(core.ToFloat(True), ShouldEqual, NewFloat(1)) So(runtime.ToFloat(True), ShouldEqual, NewFloat(1))
So(core.ToFloat(False), ShouldEqual, NewFloat(0)) So(runtime.ToFloat(False), ShouldEqual, NewFloat(0))
}) })
Convey("Should convert Array with single item", func() { Convey("Should convert Array with single item", func() {
So(core.ToFloat(NewArrayWith(NewFloat(1))), ShouldEqual, NewFloat(1)) So(runtime.ToFloat(NewArrayWith(NewFloat(1))), ShouldEqual, NewFloat(1))
}) })
Convey("Should convert Array with multiple items", func() { Convey("Should convert Array with multiple items", func() {
arg := NewArrayWith(NewFloat(1), NewFloat(1)) arg := NewArrayWith(NewFloat(1), NewFloat(1))
So(core.ToFloat(arg), ShouldEqual, NewFloat(2)) So(runtime.ToFloat(arg), ShouldEqual, NewFloat(2))
}) })
Convey("Should convert DateTime", func() { Convey("Should convert DateTime", func() {
dt := NewCurrentDateTime() dt := NewCurrentDateTime()
ts := dt.Time.Unix() ts := dt.Time.Unix()
So(core.ToFloat(dt), ShouldEqual, NewFloat(float64(ts))) So(runtime.ToFloat(dt), ShouldEqual, NewFloat(float64(ts)))
}) })
Convey("Should NOT convert other types", func() { Convey("Should NOT convert other types", func() {
@@ -237,7 +237,7 @@ func TestHelpers(t *testing.T) {
} }
for _, input := range inputs { for _, input := range inputs {
So(core.ToFloat(input), ShouldEqual, ZeroFloat) So(runtime.ToFloat(input), ShouldEqual, ZeroFloat)
} }
}) })
}) })
@@ -245,48 +245,48 @@ func TestHelpers(t *testing.T) {
Convey("ToInt", func() { Convey("ToInt", func() {
Convey("Should convert Int", func() { Convey("Should convert Int", func() {
input := NewInt(100) input := NewInt(100)
output := core.ToInt(input) output := runtime.ToInt(input)
So(output, ShouldEqual, NewInt(100)) So(output, ShouldEqual, NewInt(100))
}) })
Convey("Should convert Float", func() { Convey("Should convert Float", func() {
input := NewFloat(100.1) input := NewFloat(100.1)
output := core.ToInt(input) output := runtime.ToInt(input)
So(output, ShouldEqual, NewInt(100)) So(output, ShouldEqual, NewInt(100))
}) })
Convey("Should convert String", func() { Convey("Should convert String", func() {
input := NewString("100") input := NewString("100")
output := core.ToInt(input) output := runtime.ToInt(input)
So(output, ShouldEqual, NewInt(100)) So(output, ShouldEqual, NewInt(100))
output2 := core.ToInt(NewString("foobar")) output2 := runtime.ToInt(NewString("foobar"))
So(output2, ShouldEqual, ZeroInt) So(output2, ShouldEqual, ZeroInt)
}) })
Convey("Should convert Boolean", func() { Convey("Should convert Boolean", func() {
So(core.ToInt(True), ShouldEqual, NewInt(1)) So(runtime.ToInt(True), ShouldEqual, NewInt(1))
So(core.ToInt(False), ShouldEqual, NewInt(0)) So(runtime.ToInt(False), ShouldEqual, NewInt(0))
}) })
Convey("Should convert Array with single item", func() { Convey("Should convert Array with single item", func() {
So(core.ToInt(NewArrayWith(NewFloat(1))), ShouldEqual, NewInt(1)) So(runtime.ToInt(NewArrayWith(NewFloat(1))), ShouldEqual, NewInt(1))
}) })
Convey("Should convert Array with multiple items", func() { Convey("Should convert Array with multiple items", func() {
arg := NewArrayWith(NewFloat(1), NewFloat(1)) arg := NewArrayWith(NewFloat(1), NewFloat(1))
So(core.ToInt(arg), ShouldEqual, NewFloat(2)) So(runtime.ToInt(arg), ShouldEqual, NewFloat(2))
}) })
Convey("Should convert DateTime", func() { Convey("Should convert DateTime", func() {
dt := NewCurrentDateTime() dt := NewCurrentDateTime()
ts := dt.Time.Unix() ts := dt.Time.Unix()
So(core.ToInt(dt), ShouldEqual, NewInt(int(ts))) So(runtime.ToInt(dt), ShouldEqual, NewInt(int(ts)))
}) })
Convey("Should NOT convert other types", func() { Convey("Should NOT convert other types", func() {
@@ -296,7 +296,7 @@ func TestHelpers(t *testing.T) {
} }
for _, input := range inputs { for _, input := range inputs {
So(core.ToInt(input), ShouldEqual, ZeroInt) So(runtime.ToInt(input), ShouldEqual, ZeroInt)
} }
}) })
}) })
@@ -333,7 +333,7 @@ func TestHelpers(t *testing.T) {
} }
for _, pairs := range inputs { for _, pairs := range inputs {
actual := core.ToList(context.Background(), pairs[0]) actual := runtime.ToList(context.Background(), pairs[0])
expected := pairs[1] expected := pairs[1]
So(actual.Compare(expected), ShouldEqual, 0) So(actual.Compare(expected), ShouldEqual, 0)
@@ -392,7 +392,7 @@ func TestHelpers(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
val, err := core.Unmarshal(json1) val, err := runtime.Unmarshal(json1)
So(err, ShouldBeNil) So(err, ShouldBeNil)

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"encoding/binary" "encoding/binary"

View File

@@ -1,8 +1,8 @@
package core_test package runtime_test
import ( import (
"encoding/json" "encoding/json"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -11,19 +11,19 @@ import (
func TestInt(t *testing.T) { func TestInt(t *testing.T) {
Convey(".Hash", t, func() { Convey(".Hash", t, func() {
Convey("It should calculate hash", func() { Convey("It should calculate hash", func() {
v := core.NewInt(1) v := runtime.NewInt(1)
h := v.Hash() h := v.Hash()
So(h, ShouldBeGreaterThan, 0) So(h, ShouldBeGreaterThan, 0)
v2 := core.NewInt(2) v2 := runtime.NewInt(2)
So(h, ShouldNotEqual, v2.Hash()) So(h, ShouldNotEqual, v2.Hash())
}) })
Convey("Hash sum should be consistent", func() { Convey("Hash sum should be consistent", func() {
v := core.NewInt(1) v := runtime.NewInt(1)
So(v.Hash(), ShouldEqual, v.Hash()) So(v.Hash(), ShouldEqual, v.Hash())
}) })
@@ -36,7 +36,7 @@ func TestInt(t *testing.T) {
json1, err := json.Marshal(value) json1, err := json.Marshal(value)
So(err, ShouldBeNil) So(err, ShouldBeNil)
json2, err := core.NewInt(value).MarshalJSON() json2, err := runtime.NewInt(value).MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(json1, ShouldResemble, json2) So(json1, ShouldResemble, json2)

View File

@@ -1,77 +0,0 @@
package internal
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"strings"
)
func ToNumberOnly(ctx context.Context, input core.Value) core.Value {
switch value := input.(type) {
case core.Int, core.Float:
return value
case core.String:
if strings.Contains(value.String(), ".") {
if val, err := core.ToFloat(ctx, value); err == nil {
return val
}
return core.ZeroFloat
}
if val, err := core.ToInt(ctx, value); err == nil {
return val
}
return core.ZeroFloat
case core.Iterable:
iterator, err := value.Iterate(ctx)
if err != nil {
return core.ZeroInt
}
i := core.ZeroInt
f := core.ZeroFloat
for hasNext, err := iterator.HasNext(ctx); hasNext && err == nil; {
val, _, err := iterator.Next(ctx)
if err != nil {
continue
}
out := ToNumberOnly(ctx, val)
switch item := out.(type) {
case core.Int:
i += item
case core.Float:
f += item
}
}
if f == 0 {
return i
}
return core.Float(i) + f
default:
if val, err := core.ToFloat(ctx, value); err == nil {
return val
}
return core.ZeroInt
}
}
func ToRegexp(input core.Value) (*Regexp, error) {
switch r := input.(type) {
case *Regexp:
return r, nil
case core.String:
return NewRegexp(r)
default:
return nil, core.TypeError(input, core.TypeString, "regexp")
}
}

View File

@@ -1,16 +0,0 @@
package internal
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
type noopIter struct{}
func (n noopIter) HasNext(_ context.Context) (bool, error) {
return false, nil
}
func (n noopIter) Next(_ context.Context) (value core.Value, key core.Value, err error) {
return core.None, core.None, nil
}

View File

@@ -1,267 +0,0 @@
package internal
import (
"context"
"github.com/gobwas/glob"
"github.com/pkg/errors"
"strings"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
func Contains(ctx context.Context, input core.Value, value core.Value) core.Boolean {
switch val := input.(type) {
case core.List:
contains, err := val.Contains(ctx, value)
if err != nil {
return core.False
}
return contains
case core.Map:
containsValue, err := val.ContainsValue(ctx, value)
if err != nil {
return core.False
}
return containsValue
case core.String:
return core.Boolean(strings.Contains(val.String(), value.String()))
default:
return false
}
}
func Add(ctx context.Context, inputL, inputR core.Value) core.Value {
left := core.ToNumberOrString(inputL)
switch leftVal := left.(type) {
case core.Int:
return addLeftInt(leftVal, inputR)
case core.Float:
return addLeftFloat(leftVal, inputR)
case core.String:
return addLeftString(leftVal, inputR)
default:
return core.String(leftVal.String() + inputR.String())
}
}
func addLeftInt(integer core.Int, input core.Value) core.Value {
right := core.ToNumberOrString(input)
switch rightVal := right.(type) {
case core.Int:
return integer + rightVal
case core.Float:
return core.Float(integer) + rightVal
default:
return core.String(integer.String() + rightVal.String())
}
}
func addLeftFloat(float core.Float, input core.Value) core.Value {
right := core.ToNumberOrString(input)
switch rightVal := right.(type) {
case core.Int:
return float + core.Float(rightVal)
case core.Float:
return float + rightVal
default:
return core.String(float.String() + rightVal.String())
}
}
func addLeftString(str core.String, input core.Value) core.Value {
return core.String(str.String() + input.String())
}
func Subtract(ctx context.Context, inputL, inputR core.Value) core.Value {
left := ToNumberOnly(ctx, inputL)
switch leftVal := left.(type) {
case core.Int:
return subtractLeftInt(ctx, leftVal, inputR)
case core.Float:
return subtractLeftFloat(ctx, leftVal, inputR)
default:
return core.ZeroInt
}
}
func subtractLeftInt(ctx context.Context, integer core.Int, input core.Value) core.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case core.Int:
return integer - rightVal
case core.Float:
return core.Float(integer) - rightVal
default:
return core.ZeroInt
}
}
func subtractLeftFloat(ctx context.Context, float core.Float, input core.Value) core.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case core.Int:
return float - core.Float(rightVal)
case core.Float:
return float - rightVal
default:
return core.ZeroInt
}
}
func Multiply(ctx context.Context, inputL, inputR core.Value) core.Value {
left := ToNumberOnly(ctx, inputL)
switch leftVal := left.(type) {
case core.Int:
return multiplyLeftInt(ctx, leftVal, inputR)
case core.Float:
return multiplyLeftFloat(ctx, leftVal, inputR)
default:
return core.ZeroInt
}
}
func multiplyLeftInt(ctx context.Context, integer core.Int, input core.Value) core.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case core.Int:
return integer * rightVal
case core.Float:
return core.Float(integer) * rightVal
default:
return core.ZeroInt
}
}
func multiplyLeftFloat(ctx context.Context, float core.Float, input core.Value) core.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case core.Int:
return float * core.Float(rightVal)
case core.Float:
return float * rightVal
default:
return core.ZeroInt
}
}
func Divide(ctx context.Context, inputL, inputR core.Value) core.Value {
left := ToNumberOnly(ctx, inputL)
switch leftVal := left.(type) {
case core.Int:
return divideLeftInt(ctx, leftVal, inputR)
case core.Float:
return divideLeftFloat(ctx, leftVal, inputR)
default:
return core.ZeroInt
}
}
func divideLeftInt(ctx context.Context, integer core.Int, input core.Value) core.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case core.Int:
return integer / rightVal
case core.Float:
return core.Float(integer) / rightVal
default:
return core.ZeroInt
}
}
func divideLeftFloat(ctx context.Context, float core.Float, input core.Value) core.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case core.Int:
return float / core.Float(rightVal)
case core.Float:
return float / rightVal
default:
return core.ZeroInt
}
}
func Modulus(ctx context.Context, inputL, inputR core.Value) core.Value {
left, _ := core.ToInt(ctx, inputL)
right, _ := core.ToInt(ctx, inputR)
return left % right
}
func Increment(ctx context.Context, input core.Value) core.Value {
left := ToNumberOnly(ctx, input)
switch value := left.(type) {
case core.Int:
return value + 1
case core.Float:
return value + 1
default:
return core.None
}
}
func Decrement(ctx context.Context, input core.Value) core.Value {
left := ToNumberOnly(ctx, input)
switch value := left.(type) {
case core.Int:
return value - 1
case core.Float:
return value - 1
default:
return core.None
}
}
func ToRange(ctx context.Context, left, right core.Value) (core.Value, error) {
start, err := core.ToInt(ctx, left)
if err != nil {
return core.ZeroInt, err
}
end, err := core.ToInt(ctx, right)
if err != nil {
return core.ZeroInt, err
}
return NewRange(int64(start), int64(end)), nil
}
func Like(left, right core.Value) (core.Boolean, error) {
if err := core.AssertString(left); err != nil {
// TODO: Return the error? AQL just returns false
return core.False, nil
}
if err := core.AssertString(right); err != nil {
// TODO: Return the error? AQL just returns false
return core.False, nil
}
r, err := glob.Compile(right.String())
if err != nil {
return core.False, errors.Wrap(err, "invalid glob pattern")
}
result := r.Match(left.String())
return core.NewBoolean(result), nil
}

View File

@@ -1,6 +1,9 @@
package core package runtime
import "context" import (
"context"
"io"
)
type ( type (
// Iterable represents an interface of a value that can be iterated by using an iterator. // Iterable represents an interface of a value that can be iterated by using an iterator.
@@ -20,7 +23,26 @@ type (
} }
) )
func ForEach(ctx context.Context, iter Iterator, predicate func(value Value, key Value) bool) error { func ForEachOf(ctx context.Context, input Iterable, predicate Predicate) error {
iter, err := input.Iterate(ctx)
if err != nil {
return err
}
err = ForEach(ctx, iter, predicate)
closable, ok := iter.(io.Closer)
if ok {
if err := closable.Close(); err != nil {
return err
}
}
return err
}
func ForEach(ctx context.Context, iter Iterator, predicate Predicate) error {
for { for {
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)
@@ -38,7 +60,13 @@ func ForEach(ctx context.Context, iter Iterator, predicate func(value Value, key
return err return err
} }
if !predicate(val, key) { res, err := predicate(ctx, val, key)
if err != nil {
return err
}
if !res {
return nil return nil
} }
} }

View File

@@ -1,4 +1,4 @@
package core package runtime
import "context" import "context"

View File

@@ -1,4 +1,4 @@
package core package runtime
type Namespace interface { type Namespace interface {
Namespace(name string) Namespace Namespace(name string) Namespace

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"
@@ -316,7 +316,7 @@ func (t *Object) Get(_ context.Context, key Value) (Value, error) {
return val, nil return val, nil
} }
return None, nil return None, ErrNotFound
} }
func (t *Object) Set(_ context.Context, key Value, value Value) error { func (t *Object) Set(_ context.Context, key Value, value Value) error {

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,8 +1,8 @@
package core_test package runtime_test
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"slices" "slices"
"testing" "testing"
@@ -12,8 +12,8 @@ import (
func TestObjectIterator(t *testing.T) { func TestObjectIterator(t *testing.T) {
Convey("No values", t, func() { Convey("No values", t, func() {
ctx := context.Background() ctx := context.Background()
obj := core.NewObject() obj := runtime.NewObject()
iter := core.NewObjectIterator(obj) iter := runtime.NewObjectIterator(obj)
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)
@@ -23,9 +23,9 @@ func TestObjectIterator(t *testing.T) {
Convey("One value", t, func() { Convey("One value", t, func() {
ctx := context.Background() ctx := context.Background()
obj := core.NewObject() obj := runtime.NewObject()
obj.Set(NewString("key"), NewInt(1)) obj.Set(NewString("key"), NewInt(1))
iter := core.NewObjectIterator(obj) iter := runtime.NewObjectIterator(obj)
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)
@@ -46,13 +46,13 @@ func TestObjectIterator(t *testing.T) {
Convey("Multiple values", t, func() { Convey("Multiple values", t, func() {
ctx := context.Background() ctx := context.Background()
obj := core.NewObject() obj := runtime.NewObject()
obj.Set(NewString("key1"), NewInt(1)) obj.Set(NewString("key1"), NewInt(1))
obj.Set(NewString("key2"), NewInt(2)) obj.Set(NewString("key2"), NewInt(2))
obj.Set(NewString("key3"), NewInt(3)) obj.Set(NewString("key3"), NewInt(3))
obj.Set(NewString("key4"), NewInt(4)) obj.Set(NewString("key4"), NewInt(4))
obj.Set(NewString("key5"), NewInt(5)) obj.Set(NewString("key5"), NewInt(5))
iter := core.NewObjectIterator(obj) iter := runtime.NewObjectIterator(obj)
actual := make([][2]Value, 0, 5) actual := make([][2]Value, 0, 5)
@@ -83,14 +83,14 @@ func TestObjectIterator(t *testing.T) {
func BenchmarkObjectIterator(b *testing.B) { func BenchmarkObjectIterator(b *testing.B) {
size := 100 size := 100
obj := core.NewObject() obj := runtime.NewObject()
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
obj.Set(NewString("key"+ToString(NewInt(i)).String()), NewInt(i)) obj.Set(NewString("key"+ToString(NewInt(i)).String()), NewInt(i))
} }
ctx := context.Background() ctx := context.Background()
iter := core.NewObjectIterator(obj) iter := runtime.NewObjectIterator(obj)
b.ResetTimer() b.ResetTimer()

View File

@@ -1,7 +1,7 @@
package core_test package runtime_test
import ( import (
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -10,20 +10,20 @@ import (
func TestObject(t *testing.T) { func TestObject(t *testing.T) {
Convey("#constructor", t, func() { Convey("#constructor", t, func() {
Convey("Should create an empty object", func() { Convey("Should create an empty object", func() {
obj := core.NewObject() obj := runtime.NewObject()
So(obj.Length(), ShouldEqual, 0) So(obj.Length(), ShouldEqual, 0)
}) })
Convey("Should create an object, from passed values", func() { Convey("Should create an object, from passed values", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("none", None), runtime.NewObjectProperty("none", None),
core.NewObjectProperty("boolean", False), runtime.NewObjectProperty("boolean", False),
core.NewObjectProperty("int", NewInt(1)), runtime.NewObjectProperty("int", NewInt(1)),
core.NewObjectProperty("float", Float(1)), runtime.NewObjectProperty("float", Float(1)),
core.NewObjectProperty("string", NewString("1")), runtime.NewObjectProperty("string", NewString("1")),
core.NewObjectProperty("array", NewArray(10)), runtime.NewObjectProperty("array", NewArray(10)),
core.NewObjectProperty("object", core.NewObject()), runtime.NewObjectProperty("object", runtime.NewObject()),
) )
So(obj.Length(), ShouldEqual, 7) So(obj.Length(), ShouldEqual, 7)
@@ -32,7 +32,7 @@ func TestObject(t *testing.T) {
Convey(".MarshalJSON", t, func() { Convey(".MarshalJSON", t, func() {
Convey("Should serialize an empty object", func() { Convey("Should serialize an empty object", func() {
obj := core.NewObject() obj := runtime.NewObject()
marshaled, err := obj.MarshalJSON() marshaled, err := obj.MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -41,14 +41,14 @@ func TestObject(t *testing.T) {
}) })
Convey("Should serialize full object", func() { Convey("Should serialize full object", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("none", None), runtime.NewObjectProperty("none", None),
core.NewObjectProperty("boolean", False), runtime.NewObjectProperty("boolean", False),
core.NewObjectProperty("int", NewInt(1)), runtime.NewObjectProperty("int", NewInt(1)),
core.NewObjectProperty("float", Float(1)), runtime.NewObjectProperty("float", Float(1)),
core.NewObjectProperty("string", NewString("1")), runtime.NewObjectProperty("string", NewString("1")),
core.NewObjectProperty("array", NewArray(10)), runtime.NewObjectProperty("array", NewArray(10)),
core.NewObjectProperty("object", core.NewObject()), runtime.NewObjectProperty("object", runtime.NewObject()),
) )
marshaled, err := obj.MarshalJSON() marshaled, err := obj.MarshalJSON()
@@ -60,9 +60,9 @@ func TestObject(t *testing.T) {
Convey(".Unwrap", t, func() { Convey(".Unwrap", t, func() {
Convey("Should return an unwrapped items", func() { Convey("Should return an unwrapped items", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("foo", NewString("foo")), runtime.NewObjectProperty("foo", NewString("foo")),
core.NewObjectProperty("bar", NewString("bar")), runtime.NewObjectProperty("bar", NewString("bar")),
) )
for _, val := range obj.Unwrap().(map[string]interface{}) { for _, val := range obj.Unwrap().(map[string]interface{}) {
@@ -73,8 +73,8 @@ func TestObject(t *testing.T) {
Convey(".String", t, func() { Convey(".String", t, func() {
Convey("Should return a string representation ", func() { Convey("Should return a string representation ", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("foo", NewString("bar")), runtime.NewObjectProperty("foo", NewString("bar")),
) )
So(obj.String(), ShouldEqual, "{\"foo\":\"bar\"}") So(obj.String(), ShouldEqual, "{\"foo\":\"bar\"}")
@@ -91,7 +91,7 @@ func TestObject(t *testing.T) {
NewString("1"), NewString("1"),
NewArray(10), NewArray(10),
} }
obj := core.NewObject() obj := runtime.NewObject()
for _, val := range arr { for _, val := range arr {
So(obj.Compare(val), ShouldEqual, 1) So(obj.Compare(val), ShouldEqual, 1)
@@ -100,26 +100,26 @@ func TestObject(t *testing.T) {
Convey("It should return -1 for all object values", func() { Convey("It should return -1 for all object values", func() {
arr := NewArrayWith(ZeroInt, ZeroInt) arr := NewArrayWith(ZeroInt, ZeroInt)
obj := core.NewObject() obj := runtime.NewObject()
So(arr.Compare(obj), ShouldEqual, -1) So(arr.Compare(obj), ShouldEqual, -1)
}) })
Convey("It should return 0 when both objects are empty", func() { Convey("It should return 0 when both objects are empty", func() {
obj1 := core.NewObject() obj1 := runtime.NewObject()
obj2 := core.NewObject() obj2 := runtime.NewObject()
So(obj1.Compare(obj2), ShouldEqual, 0) So(obj1.Compare(obj2), ShouldEqual, 0)
}) })
Convey("It should return 0 when both objects are equal (independent of key order)", func() { Convey("It should return 0 when both objects are equal (independent of key order)", func() {
obj1 := core.NewObjectWith( obj1 := runtime.NewObjectWith(
core.NewObjectProperty("foo", NewString("foo")), runtime.NewObjectProperty("foo", NewString("foo")),
core.NewObjectProperty("bar", NewString("bar")), runtime.NewObjectProperty("bar", NewString("bar")),
) )
obj2 := core.NewObjectWith( obj2 := runtime.NewObjectWith(
core.NewObjectProperty("foo", NewString("foo")), runtime.NewObjectProperty("foo", NewString("foo")),
core.NewObjectProperty("bar", NewString("bar")), runtime.NewObjectProperty("bar", NewString("bar")),
) )
So(obj1.Compare(obj1), ShouldEqual, 0) So(obj1.Compare(obj1), ShouldEqual, 0)
@@ -129,82 +129,82 @@ func TestObject(t *testing.T) {
}) })
Convey("It should return 1 when other array is empty", func() { Convey("It should return 1 when other array is empty", func() {
obj1 := core.NewObjectWith(core.NewObjectProperty("foo", NewString("bar"))) obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewString("bar")))
obj2 := core.NewObject() obj2 := runtime.NewObject()
So(obj1.Compare(obj2), ShouldEqual, 1) So(obj1.Compare(obj2), ShouldEqual, 1)
}) })
Convey("It should return 1 when values are bigger", func() { Convey("It should return 1 when values are bigger", func() {
obj1 := core.NewObjectWith(core.NewObjectProperty("foo", NewFloat(3))) obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(3)))
obj2 := core.NewObjectWith(core.NewObjectProperty("foo", NewFloat(2))) obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(2)))
So(obj1.Compare(obj2), ShouldEqual, 1) So(obj1.Compare(obj2), ShouldEqual, 1)
}) })
Convey("It should return 1 when values are less", func() { Convey("It should return 1 when values are less", func() {
obj1 := core.NewObjectWith(core.NewObjectProperty("foo", NewFloat(1))) obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(1)))
obj2 := core.NewObjectWith(core.NewObjectProperty("foo", NewFloat(2))) obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(2)))
So(obj1.Compare(obj2), ShouldEqual, -1) So(obj1.Compare(obj2), ShouldEqual, -1)
}) })
Convey("ArangoDB compatibility", func() { Convey("ArangoDB compatibility", func() {
Convey("It should return 1 when {a:1} and {b:2}", func() { Convey("It should return 1 when {a:1} and {b:2}", func() {
obj1 := core.NewObjectWith(core.NewObjectProperty("a", NewInt(1))) obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
obj2 := core.NewObjectWith(core.NewObjectProperty("b", NewInt(2))) obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("b", NewInt(2)))
So(obj1.Compare(obj2), ShouldEqual, 1) So(obj1.Compare(obj2), ShouldEqual, 1)
}) })
Convey("It should return 0 when {a:1} and {a:1}", func() { Convey("It should return 0 when {a:1} and {a:1}", func() {
obj1 := core.NewObjectWith(core.NewObjectProperty("a", NewInt(1))) obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
obj2 := core.NewObjectWith(core.NewObjectProperty("a", NewInt(1))) obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
So(obj1.Compare(obj2), ShouldEqual, 0) So(obj1.Compare(obj2), ShouldEqual, 0)
}) })
Convey("It should return 0 {a:1, c:2} and {c:2, a:1}", func() { Convey("It should return 0 {a:1, c:2} and {c:2, a:1}", func() {
obj1 := core.NewObjectWith( obj1 := runtime.NewObjectWith(
core.NewObjectProperty("a", NewInt(1)), runtime.NewObjectProperty("a", NewInt(1)),
core.NewObjectProperty("c", NewInt(2)), runtime.NewObjectProperty("c", NewInt(2)),
) )
obj2 := core.NewObjectWith( obj2 := runtime.NewObjectWith(
core.NewObjectProperty("c", NewInt(2)), runtime.NewObjectProperty("c", NewInt(2)),
core.NewObjectProperty("a", NewInt(1)), runtime.NewObjectProperty("a", NewInt(1)),
) )
So(obj1.Compare(obj2), ShouldEqual, 0) So(obj1.Compare(obj2), ShouldEqual, 0)
}) })
Convey("It should return -1 when {a:1} and {a:2}", func() { Convey("It should return -1 when {a:1} and {a:2}", func() {
obj1 := core.NewObjectWith(core.NewObjectProperty("a", NewInt(1))) obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
obj2 := core.NewObjectWith(core.NewObjectProperty("a", NewInt(2))) obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(2)))
So(obj1.Compare(obj2), ShouldEqual, -1) So(obj1.Compare(obj2), ShouldEqual, -1)
}) })
Convey("It should return 1 when {a:1, c:2} and {c:2, b:2}", func() { Convey("It should return 1 when {a:1, c:2} and {c:2, b:2}", func() {
obj1 := core.NewObjectWith( obj1 := runtime.NewObjectWith(
core.NewObjectProperty("a", NewInt(1)), runtime.NewObjectProperty("a", NewInt(1)),
core.NewObjectProperty("c", NewInt(2)), runtime.NewObjectProperty("c", NewInt(2)),
) )
obj2 := core.NewObjectWith( obj2 := runtime.NewObjectWith(
core.NewObjectProperty("c", NewInt(2)), runtime.NewObjectProperty("c", NewInt(2)),
core.NewObjectProperty("b", NewInt(2)), runtime.NewObjectProperty("b", NewInt(2)),
) )
So(obj1.Compare(obj2), ShouldEqual, 1) So(obj1.Compare(obj2), ShouldEqual, 1)
}) })
Convey("It should return 1 {a:1, c:3} and {c:2, a:1}", func() { Convey("It should return 1 {a:1, c:3} and {c:2, a:1}", func() {
obj1 := core.NewObjectWith( obj1 := runtime.NewObjectWith(
core.NewObjectProperty("a", NewInt(1)), runtime.NewObjectProperty("a", NewInt(1)),
core.NewObjectProperty("c", NewInt(3)), runtime.NewObjectProperty("c", NewInt(3)),
) )
obj2 := core.NewObjectWith( obj2 := runtime.NewObjectWith(
core.NewObjectProperty("c", NewInt(2)), runtime.NewObjectProperty("c", NewInt(2)),
core.NewObjectProperty("a", NewInt(1)), runtime.NewObjectProperty("a", NewInt(1)),
) )
So(obj1.Compare(obj2), ShouldEqual, 1) So(obj1.Compare(obj2), ShouldEqual, 1)
@@ -214,10 +214,10 @@ func TestObject(t *testing.T) {
Convey(".Hash", t, func() { Convey(".Hash", t, func() {
Convey("It should calculate hash of non-empty object", func() { Convey("It should calculate hash of non-empty object", func() {
v := core.NewObjectWith( v := runtime.NewObjectWith(
core.NewObjectProperty("foo", NewString("bar")), runtime.NewObjectProperty("foo", NewString("bar")),
core.NewObjectProperty("faz", NewInt(1)), runtime.NewObjectProperty("faz", NewInt(1)),
core.NewObjectProperty("qaz", True), runtime.NewObjectProperty("qaz", True),
) )
h := v.Hash() h := v.Hash()
@@ -226,7 +226,7 @@ func TestObject(t *testing.T) {
}) })
Convey("It should calculate hash of empty object", func() { Convey("It should calculate hash of empty object", func() {
v := core.NewObject() v := runtime.NewObject()
h := v.Hash() h := v.Hash()
@@ -234,14 +234,14 @@ func TestObject(t *testing.T) {
}) })
Convey("Hash sum should be consistent", func() { Convey("Hash sum should be consistent", func() {
v := core.NewObjectWith( v := runtime.NewObjectWith(
core.NewObjectProperty("boolean", True), runtime.NewObjectProperty("boolean", True),
core.NewObjectProperty("int", NewInt(1)), runtime.NewObjectProperty("int", NewInt(1)),
core.NewObjectProperty("float", NewFloat(1.1)), runtime.NewObjectProperty("float", NewFloat(1.1)),
core.NewObjectProperty("string", NewString("foobar")), runtime.NewObjectProperty("string", NewString("foobar")),
core.NewObjectProperty("datetime", NewCurrentDateTime()), runtime.NewObjectProperty("datetime", NewCurrentDateTime()),
core.NewObjectProperty("array", NewArrayWith(NewInt(1), True)), runtime.NewObjectProperty("array", NewArrayWith(NewInt(1), True)),
core.NewObjectProperty("object", core.NewObjectWith(core.NewObjectProperty("foo", NewString("bar")))), runtime.NewObjectProperty("object", runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewString("bar")))),
) )
h1 := v.Hash() h1 := v.Hash()
@@ -253,15 +253,15 @@ func TestObject(t *testing.T) {
Convey(".Length", t, func() { Convey(".Length", t, func() {
Convey("Should return 0 when empty", func() { Convey("Should return 0 when empty", func() {
obj := core.NewObject() obj := runtime.NewObject()
So(obj.Length(), ShouldEqual, 0) So(obj.Length(), ShouldEqual, 0)
}) })
Convey("Should return greater than 0 when not empty", func() { Convey("Should return greater than 0 when not empty", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("foo", ZeroInt), runtime.NewObjectProperty("foo", ZeroInt),
core.NewObjectProperty("bar", ZeroInt), runtime.NewObjectProperty("bar", ZeroInt),
) )
So(obj.Length(), ShouldEqual, 2) So(obj.Length(), ShouldEqual, 2)
@@ -270,9 +270,9 @@ func TestObject(t *testing.T) {
Convey(".ForEach", t, func() { Convey(".ForEach", t, func() {
Convey("Should iterate over elements", func() { Convey("Should iterate over elements", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("foo", ZeroInt), runtime.NewObjectProperty("foo", ZeroInt),
core.NewObjectProperty("bar", ZeroInt), runtime.NewObjectProperty("bar", ZeroInt),
) )
counter := 0 counter := 0
@@ -286,7 +286,7 @@ func TestObject(t *testing.T) {
}) })
Convey("Should not iterate when empty", func() { Convey("Should not iterate when empty", func() {
obj := core.NewObject() obj := runtime.NewObject()
counter := 0 counter := 0
obj.ForEach(func(value Value, key string) bool { obj.ForEach(func(value Value, key string) bool {
@@ -299,12 +299,12 @@ func TestObject(t *testing.T) {
}) })
Convey("Should break iteration when false returned", func() { Convey("Should break iteration when false returned", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("1", NewInt(1)), runtime.NewObjectProperty("1", NewInt(1)),
core.NewObjectProperty("2", NewInt(2)), runtime.NewObjectProperty("2", NewInt(2)),
core.NewObjectProperty("3", NewInt(3)), runtime.NewObjectProperty("3", NewInt(3)),
core.NewObjectProperty("4", NewInt(4)), runtime.NewObjectProperty("4", NewInt(4)),
core.NewObjectProperty("5", NewInt(5)), runtime.NewObjectProperty("5", NewInt(5)),
) )
threshold := 3 threshold := 3
counter := 0 counter := 0
@@ -356,23 +356,23 @@ func TestObject(t *testing.T) {
Convey(".Clone", t, func() { Convey(".Clone", t, func() {
Convey("Cloned object should be equal to source object", func() { Convey("Cloned object should be equal to source object", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
core.NewObjectProperty("two", NewInt(2)), runtime.NewObjectProperty("two", NewInt(2)),
) )
clone := obj.Clone().(*core.Object) clone := obj.Clone().(*runtime.Object)
So(obj.Compare(clone), ShouldEqual, 0) So(obj.Compare(clone), ShouldEqual, 0)
}) })
Convey("Cloned object should be independent of the source object", func() { Convey("Cloned object should be independent of the source object", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty("one", NewInt(1)), runtime.NewObjectProperty("one", NewInt(1)),
core.NewObjectProperty("two", NewInt(2)), runtime.NewObjectProperty("two", NewInt(2)),
) )
clone := obj.Clone().(*core.Object) clone := obj.Clone().(*runtime.Object)
obj.Remove(NewString("one")) obj.Remove(NewString("one"))
@@ -380,13 +380,13 @@ func TestObject(t *testing.T) {
}) })
Convey("Cloned object must contain copies of the nested objects", func() { Convey("Cloned object must contain copies of the nested objects", func() {
obj := core.NewObjectWith( obj := runtime.NewObjectWith(
core.NewObjectProperty( runtime.NewObjectProperty(
"arr", NewArrayWith(NewInt(1)), "arr", NewArrayWith(NewInt(1)),
), ),
) )
clone := obj.Clone().(*core.Object) clone := obj.Clone().(*runtime.Object)
nestedInObj, _ := obj.Get(NewString("arr")) nestedInObj, _ := obj.Get(NewString("arr"))
nestedInObjArr := nestedInObj.(*Array) nestedInObjArr := nestedInObj.(*Array)

View File

@@ -1,4 +1,4 @@
package core package runtime
type ( type (
Location struct { Location struct {

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"context" "context"

View File

@@ -1,9 +1,9 @@
package core_test package runtime_test
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -12,19 +12,19 @@ import (
func TestString(t *testing.T) { func TestString(t *testing.T) {
Convey(".Hash", t, func() { Convey(".Hash", t, func() {
Convey("It should calculate hash", func() { Convey("It should calculate hash", func() {
v := core.NewString("a") v := runtime.NewString("a")
h := v.Hash() h := v.Hash()
So(h, ShouldBeGreaterThan, 0) So(h, ShouldBeGreaterThan, 0)
v2 := core.NewString("b") v2 := runtime.NewString("b")
So(h, ShouldNotEqual, v2.Hash()) So(h, ShouldNotEqual, v2.Hash())
}) })
Convey("Hash sum should be consistent", func() { Convey("Hash sum should be consistent", func() {
v := core.NewString("foobar") v := runtime.NewString("foobar")
So(v.Hash(), ShouldEqual, v.Hash()) So(v.Hash(), ShouldEqual, v.Hash())
}) })
@@ -32,7 +32,7 @@ func TestString(t *testing.T) {
Convey(".Length", t, func() { Convey(".Length", t, func() {
Convey("Should return unicode length", func() { Convey("Should return unicode length", func() {
str := core.NewString("Спутник") str := runtime.NewString("Спутник")
So(str.Length(), ShouldEqual, 7) So(str.Length(), ShouldEqual, 7)
}) })
@@ -45,7 +45,7 @@ func TestString(t *testing.T) {
json1, err := json.Marshal(value) json1, err := json.Marshal(value)
So(err, ShouldBeNil) So(err, ShouldBeNil)
json2, err := core.NewString(value).MarshalJSON() json2, err := runtime.NewString(value).MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(json1, ShouldResemble, json2) So(json1, ShouldResemble, json2)
@@ -57,7 +57,7 @@ func TestString(t *testing.T) {
json1, err := json.Marshal(value) json1, err := json.Marshal(value)
So(err, ShouldBeNil) So(err, ShouldBeNil)
json2, err := core.NewString(value).MarshalJSON() json2, err := runtime.NewString(value).MarshalJSON()
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(json1, ShouldNotResemble, json2) So(json1, ShouldNotResemble, json2)
@@ -66,7 +66,7 @@ func TestString(t *testing.T) {
}) })
Convey(".At", t, func() { Convey(".At", t, func() {
Convey("It should return a character", func() { Convey("It should return a character", func() {
v := core.NewString("abcdefg") v := runtime.NewString("abcdefg")
c := v.At(2) c := v.At(2)
So(string(c), ShouldEqual, "c") So(string(c), ShouldEqual, "c")

View File

@@ -1,4 +1,4 @@
package core package runtime
import "reflect" import "reflect"
@@ -13,7 +13,7 @@ const (
TypeSet = "set" TypeSet = "set"
TypeMap = "map" TypeMap = "map"
TypeBinary = "binary" TypeBinary = "binary"
// Create subtypes for less specific types like interfaces // Create subtypes for less specific types
TypeIterable = "iterable" TypeIterable = "iterable"
TypeIterator = "iterator" TypeIterator = "iterator"
TypeMeasurable = "measurable" TypeMeasurable = "measurable"

View File

@@ -1,4 +1,4 @@
package core package runtime
import ( import (
"encoding/json" "encoding/json"

View File

@@ -1,9 +0,0 @@
package values
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
)
func NewArray(cap int) core.List {
return core.NewArray(cap)
}

View File

@@ -1,25 +1,25 @@
package runtime package vm
import ( import (
"github.com/MontFerret/ferret/pkg/runtime"
"os" "os"
"github.com/MontFerret/ferret/pkg/logging" "github.com/MontFerret/ferret/pkg/logging"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type ( type (
EnvironmentOption func(env *Environment) EnvironmentOption func(env *Environment)
Environment struct { Environment struct {
functions map[string]core.Function functions map[string]runtime.Function
params map[string]core.Value params map[string]runtime.Value
logging logging.Options logging logging.Options
} }
) )
var noopEnv = &Environment{ var noopEnv = &Environment{
functions: make(map[string]core.Function), functions: make(map[string]runtime.Function),
params: make(map[string]core.Value), params: make(map[string]runtime.Value),
} }
func newEnvironment(opts []EnvironmentOption) *Environment { func newEnvironment(opts []EnvironmentOption) *Environment {
@@ -28,8 +28,8 @@ func newEnvironment(opts []EnvironmentOption) *Environment {
} }
env := &Environment{ env := &Environment{
functions: make(map[string]core.Function), functions: make(map[string]runtime.Function),
params: make(map[string]core.Value), params: make(map[string]runtime.Value),
logging: logging.Options{ logging: logging.Options{
Writer: os.Stdout, Writer: os.Stdout,
Level: logging.ErrorLevel, Level: logging.ErrorLevel,
@@ -43,7 +43,7 @@ func newEnvironment(opts []EnvironmentOption) *Environment {
return env return env
} }
func (env *Environment) GetFunction(name string) core.Function { func (env *Environment) GetFunction(name string) runtime.Function {
return env.functions[name] return env.functions[name]
} }
@@ -53,7 +53,7 @@ func (env *Environment) HasFunction(name string) bool {
return exists return exists
} }
func (env *Environment) GetParam(name string) core.Value { func (env *Environment) GetParam(name string) runtime.Value {
return env.params[name] return env.params[name]
} }

View File

@@ -1,13 +1,13 @@
package runtime package vm
import ( import (
"github.com/MontFerret/ferret/pkg/runtime"
"io" "io"
"github.com/MontFerret/ferret/pkg/logging" "github.com/MontFerret/ferret/pkg/logging"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
func WithParams(params map[string]core.Value) EnvironmentOption { func WithParams(params map[string]runtime.Value) EnvironmentOption {
return func(env *Environment) { return func(env *Environment) {
env.params = params env.params = params
} }
@@ -15,14 +15,14 @@ func WithParams(params map[string]core.Value) EnvironmentOption {
func WithParam(name string, value interface{}) EnvironmentOption { func WithParam(name string, value interface{}) EnvironmentOption {
return func(options *Environment) { return func(options *Environment) {
options.params[name] = core.Parse(value) options.params[name] = runtime.Parse(value)
} }
} }
func WithFunctions(functions map[string]core.Function) EnvironmentOption { func WithFunctions(functions map[string]runtime.Function) EnvironmentOption {
return func(env *Environment) { return func(env *Environment) {
if env.functions == nil { if env.functions == nil {
env.functions = make(map[string]core.Function) env.functions = make(map[string]runtime.Function)
} }
for name, function := range functions { for name, function := range functions {
@@ -31,7 +31,7 @@ func WithFunctions(functions map[string]core.Function) EnvironmentOption {
} }
} }
func WithFunction(name string, function core.Function) EnvironmentOption { func WithFunction(name string, function runtime.Function) EnvironmentOption {
return func(env *Environment) { return func(env *Environment) {
env.functions[name] = function env.functions[name] = function
} }

27
pkg/vm/errors.go Normal file
View File

@@ -0,0 +1,27 @@
package vm
import "github.com/pkg/errors"
var (
ErrMissedParam = errors.New("missed parameter")
ErrFunctionNotFound = errors.New("function not found")
)
type (
SourceErrorDetail struct {
error
BaseError error
ComputeError error
}
)
func (e *SourceErrorDetail) Error() string {
return e.ComputeError.Error()
}
func SourceError(src SourceMap, err error) error {
return &SourceErrorDetail{
BaseError: err,
ComputeError: errors.Errorf("%s: %s", err.Error(), src.String()),
}
}

View File

@@ -1,4 +1,4 @@
package runtime package vm
import "fmt" import "fmt"

View File

@@ -2,11 +2,10 @@ package internal
import ( import (
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime"
"hash/fnv" "hash/fnv"
"github.com/wI2L/jettison" "github.com/wI2L/jettison"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
// Boxed represents an arbitrary Value that can be boxed as a runtime Value. // Boxed represents an arbitrary Value that can be boxed as a runtime Value.
@@ -40,6 +39,6 @@ func (b *Boxed) Hash() uint64 {
return h.Sum64() return h.Sum64()
} }
func (b *Boxed) Copy() core.Value { func (b *Boxed) Copy() runtime.Value {
return NewBoxedValue(b.Value) return NewBoxedValue(b.Value)
} }

View File

@@ -1,7 +1,7 @@
package internal package internal
import ( import (
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
) )
type Collector struct { type Collector struct {
@@ -12,7 +12,7 @@ func NewCollector() *Collector {
return &Collector{values: make(map[uint64][]*KeyValuePair)} return &Collector{values: make(map[uint64][]*KeyValuePair)}
} }
func (c *Collector) Add(key core.Value, value core.Value) { func (c *Collector) Add(key runtime.Value, value runtime.Value) {
hash := key.Hash() hash := key.Hash()
values, exists := c.values[hash] values, exists := c.values[hash]
@@ -40,6 +40,6 @@ func (c *Collector) Hash() uint64 {
panic("not supported") panic("not supported")
} }
func (c *Collector) Copy() core.Value { func (c *Collector) Copy() runtime.Value {
panic("not supported") panic("not supported")
} }

View File

@@ -2,12 +2,12 @@ package internal
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
) )
type DataSet struct { type DataSet struct {
hashmap map[uint64]bool hashmap map[uint64]bool
values core.List values runtime.List
} }
func NewDataSet(distinct bool) *DataSet { func NewDataSet(distinct bool) *DataSet {
@@ -19,21 +19,21 @@ func NewDataSet(distinct bool) *DataSet {
return &DataSet{ return &DataSet{
hashmap: hashmap, hashmap: hashmap,
values: core.NewArray(16), values: runtime.NewArray(16),
} }
} }
func (ds *DataSet) Swap(ctx context.Context, i, j core.Int) { func (ds *DataSet) Swap(ctx context.Context, i, j runtime.Int) {
ds.values.Swap(ctx, i, j) ds.values.Swap(ctx, i, j)
} }
func (ds *DataSet) Get(ctx context.Context, idx core.Int) core.Value { func (ds *DataSet) Get(ctx context.Context, idx runtime.Int) runtime.Value {
val, _ := ds.values.Get(ctx, idx) val, _ := ds.values.Get(ctx, idx)
return val return val
} }
func (ds *DataSet) Push(ctx context.Context, item core.Value) { func (ds *DataSet) Push(ctx context.Context, item runtime.Value) {
if ds.hashmap != nil { if ds.hashmap != nil {
hash := item.Hash() hash := item.Hash()
@@ -49,15 +49,15 @@ func (ds *DataSet) Push(ctx context.Context, item core.Value) {
_ = ds.values.Add(ctx, item) _ = ds.values.Add(ctx, item)
} }
func (ds *DataSet) Iterate(ctx context.Context) (core.Iterator, error) { func (ds *DataSet) Iterate(ctx context.Context) (runtime.Iterator, error) {
return ds.values.Iterate(ctx) return ds.values.Iterate(ctx)
} }
func (ds *DataSet) Length(ctx context.Context) (core.Int, error) { func (ds *DataSet) Length(ctx context.Context) (runtime.Int, error) {
return ds.values.Length(ctx) return ds.values.Length(ctx)
} }
func (ds *DataSet) ToList() core.List { func (ds *DataSet) ToList() runtime.List {
return ds.values return ds.values
} }
@@ -73,7 +73,7 @@ func (ds *DataSet) Hash() uint64 {
return ds.values.Hash() return ds.values.Hash()
} }
func (ds *DataSet) Copy() core.Value { func (ds *DataSet) Copy() runtime.Value {
return ds.values.Copy() return ds.values.Copy()
} }

View File

@@ -0,0 +1,91 @@
package internal
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime"
"strings"
"time"
)
func ToNumberOnly(ctx context.Context, input runtime.Value) runtime.Value {
switch value := input.(type) {
case runtime.Int, runtime.Float:
return value
case runtime.String:
if strings.Contains(value.String(), ".") {
if val, err := runtime.ToFloat(ctx, value); err == nil {
return val
}
return runtime.ZeroFloat
}
if val, err := runtime.ToInt(ctx, value); err == nil {
return val
}
return runtime.ZeroFloat
case runtime.Iterable:
iterator, err := value.Iterate(ctx)
if err != nil {
return runtime.ZeroInt
}
i := runtime.ZeroInt
f := runtime.ZeroFloat
for hasNext, err := iterator.HasNext(ctx); hasNext && err == nil; {
val, _, err := iterator.Next(ctx)
if err != nil {
continue
}
out := ToNumberOnly(ctx, val)
switch item := out.(type) {
case runtime.Int:
i += item
case runtime.Float:
f += item
}
}
if f == 0 {
return i
}
return runtime.Float(i) + f
default:
if val, err := runtime.ToFloat(ctx, value); err == nil {
return val
}
return runtime.ZeroInt
}
}
func ToRegexp(input runtime.Value) (*Regexp, error) {
switch r := input.(type) {
case *Regexp:
return r, nil
case runtime.String:
return NewRegexp(r)
default:
return nil, runtime.TypeError(input, runtime.TypeString, "regexp")
}
}
func Sleep(ctx context.Context, duration runtime.Int) error {
timer := time.NewTimer(time.Millisecond * time.Duration(duration))
select {
case <-ctx.Done():
timer.Stop()
return ctx.Err()
case <-timer.C:
}
return nil
}

View File

@@ -2,21 +2,20 @@ package internal
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime"
"io" "io"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type Iterator struct { type Iterator struct {
src core.Iterator src runtime.Iterator
value core.Value value runtime.Value
key core.Value key runtime.Value
} }
var NoopIter = NewIterator(&noopIter{}) var NoopIter = NewIterator(&noopIter{})
func NewIterator(src core.Iterator) *Iterator { func NewIterator(src runtime.Iterator) *Iterator {
return &Iterator{src, core.None, core.None} return &Iterator{src, runtime.None, runtime.None}
} }
func (it *Iterator) HasNext(ctx context.Context) (bool, error) { func (it *Iterator) HasNext(ctx context.Context) (bool, error) {
@@ -36,11 +35,11 @@ func (it *Iterator) Next(ctx context.Context) error {
return nil return nil
} }
func (it *Iterator) Value() core.Value { func (it *Iterator) Value() runtime.Value {
return it.value return it.value
} }
func (it *Iterator) Key() core.Value { func (it *Iterator) Key() runtime.Value {
return it.key return it.key
} }
@@ -68,6 +67,6 @@ func (it *Iterator) Hash() uint64 {
panic("not supported") panic("not supported")
} }
func (it *Iterator) Copy() core.Value { func (it *Iterator) Copy() runtime.Value {
panic("not supported") panic("not supported")
} }

View File

@@ -2,14 +2,13 @@ package internal
import ( import (
"encoding/binary" "encoding/binary"
"github.com/MontFerret/ferret/pkg/runtime"
"hash/fnv" "hash/fnv"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type KeyValuePair struct { type KeyValuePair struct {
Key core.Value Key runtime.Value
Value core.Value Value runtime.Value
} }
func (p *KeyValuePair) MarshalJSON() ([]byte, error) { func (p *KeyValuePair) MarshalJSON() ([]byte, error) {
@@ -21,7 +20,7 @@ func (p *KeyValuePair) String() string {
} }
func (p *KeyValuePair) Unwrap() interface{} { func (p *KeyValuePair) Unwrap() interface{} {
return [2]core.Value{p.Key, p.Value} return [2]runtime.Value{p.Key, p.Value}
} }
func (p *KeyValuePair) Hash() uint64 { func (p *KeyValuePair) Hash() uint64 {
@@ -44,7 +43,7 @@ func (p *KeyValuePair) Hash() uint64 {
return h.Sum64() return h.Sum64()
} }
func (p *KeyValuePair) Copy() core.Value { func (p *KeyValuePair) Copy() runtime.Value {
return &KeyValuePair{ return &KeyValuePair{
Key: p.Key, Key: p.Key,
Value: p.Value, Value: p.Value,

View File

@@ -0,0 +1,16 @@
package internal
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime"
)
type noopIter struct{}
func (n noopIter) HasNext(_ context.Context) (bool, error) {
return false, nil
}
func (n noopIter) Next(_ context.Context) (value runtime.Value, key runtime.Value, err error) {
return runtime.None, runtime.None, nil
}

View File

@@ -0,0 +1,266 @@
package internal
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/gobwas/glob"
"github.com/pkg/errors"
"strings"
)
func Contains(ctx context.Context, input runtime.Value, value runtime.Value) runtime.Boolean {
switch val := input.(type) {
case runtime.List:
contains, err := val.Contains(ctx, value)
if err != nil {
return runtime.False
}
return contains
case runtime.Map:
containsValue, err := val.ContainsValue(ctx, value)
if err != nil {
return runtime.False
}
return containsValue
case runtime.String:
return runtime.Boolean(strings.Contains(val.String(), value.String()))
default:
return false
}
}
func Add(ctx context.Context, inputL, inputR runtime.Value) runtime.Value {
left := runtime.ToNumberOrString(inputL)
switch leftVal := left.(type) {
case runtime.Int:
return addLeftInt(leftVal, inputR)
case runtime.Float:
return addLeftFloat(leftVal, inputR)
case runtime.String:
return addLeftString(leftVal, inputR)
default:
return runtime.String(leftVal.String() + inputR.String())
}
}
func addLeftInt(integer runtime.Int, input runtime.Value) runtime.Value {
right := runtime.ToNumberOrString(input)
switch rightVal := right.(type) {
case runtime.Int:
return integer + rightVal
case runtime.Float:
return runtime.Float(integer) + rightVal
default:
return runtime.String(integer.String() + rightVal.String())
}
}
func addLeftFloat(float runtime.Float, input runtime.Value) runtime.Value {
right := runtime.ToNumberOrString(input)
switch rightVal := right.(type) {
case runtime.Int:
return float + runtime.Float(rightVal)
case runtime.Float:
return float + rightVal
default:
return runtime.String(float.String() + rightVal.String())
}
}
func addLeftString(str runtime.String, input runtime.Value) runtime.Value {
return runtime.String(str.String() + input.String())
}
func Subtract(ctx context.Context, inputL, inputR runtime.Value) runtime.Value {
left := ToNumberOnly(ctx, inputL)
switch leftVal := left.(type) {
case runtime.Int:
return subtractLeftInt(ctx, leftVal, inputR)
case runtime.Float:
return subtractLeftFloat(ctx, leftVal, inputR)
default:
return runtime.ZeroInt
}
}
func subtractLeftInt(ctx context.Context, integer runtime.Int, input runtime.Value) runtime.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case runtime.Int:
return integer - rightVal
case runtime.Float:
return runtime.Float(integer) - rightVal
default:
return runtime.ZeroInt
}
}
func subtractLeftFloat(ctx context.Context, float runtime.Float, input runtime.Value) runtime.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case runtime.Int:
return float - runtime.Float(rightVal)
case runtime.Float:
return float - rightVal
default:
return runtime.ZeroInt
}
}
func Multiply(ctx context.Context, inputL, inputR runtime.Value) runtime.Value {
left := ToNumberOnly(ctx, inputL)
switch leftVal := left.(type) {
case runtime.Int:
return multiplyLeftInt(ctx, leftVal, inputR)
case runtime.Float:
return multiplyLeftFloat(ctx, leftVal, inputR)
default:
return runtime.ZeroInt
}
}
func multiplyLeftInt(ctx context.Context, integer runtime.Int, input runtime.Value) runtime.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case runtime.Int:
return integer * rightVal
case runtime.Float:
return runtime.Float(integer) * rightVal
default:
return runtime.ZeroInt
}
}
func multiplyLeftFloat(ctx context.Context, float runtime.Float, input runtime.Value) runtime.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case runtime.Int:
return float * runtime.Float(rightVal)
case runtime.Float:
return float * rightVal
default:
return runtime.ZeroInt
}
}
func Divide(ctx context.Context, inputL, inputR runtime.Value) runtime.Value {
left := ToNumberOnly(ctx, inputL)
switch leftVal := left.(type) {
case runtime.Int:
return divideLeftInt(ctx, leftVal, inputR)
case runtime.Float:
return divideLeftFloat(ctx, leftVal, inputR)
default:
return runtime.ZeroInt
}
}
func divideLeftInt(ctx context.Context, integer runtime.Int, input runtime.Value) runtime.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case runtime.Int:
return integer / rightVal
case runtime.Float:
return runtime.Float(integer) / rightVal
default:
return runtime.ZeroInt
}
}
func divideLeftFloat(ctx context.Context, float runtime.Float, input runtime.Value) runtime.Value {
right := ToNumberOnly(ctx, input)
switch rightVal := right.(type) {
case runtime.Int:
return float / runtime.Float(rightVal)
case runtime.Float:
return float / rightVal
default:
return runtime.ZeroInt
}
}
func Modulus(ctx context.Context, inputL, inputR runtime.Value) runtime.Value {
left, _ := runtime.ToInt(ctx, inputL)
right, _ := runtime.ToInt(ctx, inputR)
return left % right
}
func Increment(ctx context.Context, input runtime.Value) runtime.Value {
left := ToNumberOnly(ctx, input)
switch value := left.(type) {
case runtime.Int:
return value + 1
case runtime.Float:
return value + 1
default:
return runtime.None
}
}
func Decrement(ctx context.Context, input runtime.Value) runtime.Value {
left := ToNumberOnly(ctx, input)
switch value := left.(type) {
case runtime.Int:
return value - 1
case runtime.Float:
return value - 1
default:
return runtime.None
}
}
func ToRange(ctx context.Context, left, right runtime.Value) (runtime.Value, error) {
start, err := runtime.ToInt(ctx, left)
if err != nil {
return runtime.ZeroInt, err
}
end, err := runtime.ToInt(ctx, right)
if err != nil {
return runtime.ZeroInt, err
}
return NewRange(int64(start), int64(end)), nil
}
func Like(left, right runtime.Value) (runtime.Boolean, error) {
if err := runtime.AssertString(left); err != nil {
// TODO: Return the error? AQL just returns false
return runtime.False, nil
}
if err := runtime.AssertString(right); err != nil {
// TODO: Return the error? AQL just returns false
return runtime.False, nil
}
r, err := glob.Compile(right.String())
if err != nil {
return runtime.False, errors.Wrap(err, "invalid glob pattern")
}
result := r.Match(left.String())
return runtime.NewBoolean(result), nil
}

View File

@@ -4,11 +4,10 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime"
"hash/fnv" "hash/fnv"
"github.com/wI2L/jettison" "github.com/wI2L/jettison"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type Range struct { type Range struct {
@@ -65,11 +64,11 @@ func (r *Range) Hash() uint64 {
return h.Sum64() return h.Sum64()
} }
func (r *Range) Copy() core.Value { func (r *Range) Copy() runtime.Value {
return NewRange(r.start, r.end) return NewRange(r.start, r.end)
} }
func (r *Range) Iterate(_ context.Context) (core.Iterator, error) { func (r *Range) Iterate(_ context.Context) (runtime.Iterator, error) {
return NewRangeIterator(r), nil return NewRangeIterator(r), nil
} }
@@ -88,11 +87,11 @@ func (r *Range) MarshalJSON() ([]byte, error) {
return jettison.MarshalOpts(arr, jettison.NoHTMLEscaping()) return jettison.MarshalOpts(arr, jettison.NoHTMLEscaping())
} }
func (r *Range) Compare(_ context.Context, other core.Value) (int64, error) { func (r *Range) Compare(_ context.Context, other runtime.Value) (int64, error) {
otherRange, ok := other.(*Range) otherRange, ok := other.(*Range)
if !ok { if !ok {
return core.CompareTypes(r, other), nil return runtime.CompareTypes(r, other), nil
} }
if r.start == otherRange.start && r.end == otherRange.end { if r.start == otherRange.start && r.end == otherRange.end {

View File

@@ -2,8 +2,7 @@ package internal
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type RangeIterator struct { type RangeIterator struct {
@@ -13,7 +12,7 @@ type RangeIterator struct {
counter int64 counter int64
} }
func NewRangeIterator(values *Range) core.Iterator { func NewRangeIterator(values *Range) runtime.Iterator {
if values.start <= values.end { if values.start <= values.end {
return &RangeIterator{values: values, pos: values.start, counter: -1} return &RangeIterator{values: values, pos: values.start, counter: -1}
} }
@@ -29,7 +28,7 @@ func (iter *RangeIterator) HasNext(ctx context.Context) (bool, error) {
return iter.values.end <= iter.pos, nil return iter.values.end <= iter.pos, nil
} }
func (iter *RangeIterator) Next(ctx context.Context) (value core.Value, key core.Value, err error) { func (iter *RangeIterator) Next(ctx context.Context) (value runtime.Value, key runtime.Value, err error) {
iter.counter++ iter.counter++
if !iter.descending { if !iter.descending {
@@ -39,8 +38,8 @@ func (iter *RangeIterator) Next(ctx context.Context) (value core.Value, key core
} }
if !iter.descending { if !iter.descending {
return core.Int(iter.pos - 1), core.Int(iter.counter), nil return runtime.Int(iter.pos - 1), runtime.Int(iter.counter), nil
} }
return core.Int(iter.pos + 1), core.Int(iter.counter), nil return runtime.Int(iter.pos + 1), runtime.Int(iter.counter), nil
} }

View File

@@ -2,7 +2,7 @@ package internal_test
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -21,8 +21,8 @@ func TestRangeIterator(t *testing.T) {
val, key, err := iter.Next(ctx) val, key, err := iter.Next(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(val, ShouldEqual, core.NewInt(0)) So(val, ShouldEqual, runtime.NewInt(0))
So(key, ShouldEqual, core.NewInt(0)) So(key, ShouldEqual, runtime.NewInt(0))
hasNext, err = iter.HasNext(ctx) hasNext, err = iter.HasNext(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -41,14 +41,14 @@ func TestRangeIterator(t *testing.T) {
val, key, err := iter.Next(ctx) val, key, err := iter.Next(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(val, ShouldEqual, core.NewInt(0)) So(val, ShouldEqual, runtime.NewInt(0))
So(key, ShouldEqual, core.NewInt(0)) So(key, ShouldEqual, runtime.NewInt(0))
val, key, err = iter.Next(ctx) val, key, err = iter.Next(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(val, ShouldEqual, core.NewInt(1)) So(val, ShouldEqual, runtime.NewInt(1))
So(key, ShouldEqual, core.NewInt(1)) So(key, ShouldEqual, runtime.NewInt(1))
hasNext, err = iter.HasNext(ctx) hasNext, err = iter.HasNext(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -67,14 +67,14 @@ func TestRangeIterator(t *testing.T) {
val, key, err := iter.Next(ctx) val, key, err := iter.Next(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(val, ShouldEqual, core.NewInt(1)) So(val, ShouldEqual, runtime.NewInt(1))
So(key, ShouldEqual, core.NewInt(0)) So(key, ShouldEqual, runtime.NewInt(0))
val, key, err = iter.Next(ctx) val, key, err = iter.Next(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(val, ShouldEqual, core.NewInt(2)) So(val, ShouldEqual, runtime.NewInt(2))
So(key, ShouldEqual, core.NewInt(1)) So(key, ShouldEqual, runtime.NewInt(1))
hasNext, err = iter.HasNext(ctx) hasNext, err = iter.HasNext(ctx)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -86,7 +86,7 @@ func TestRangeIterator(t *testing.T) {
r := NewRange(0, 10) r := NewRange(0, 10)
iter := NewRangeIterator(r) iter := NewRangeIterator(r)
actual := make([]core.Int, 0, 10) actual := make([]runtime.Int, 0, 10)
for { for {
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)
@@ -95,10 +95,10 @@ func TestRangeIterator(t *testing.T) {
} }
val, _, err := iter.Next(ctx) val, _, err := iter.Next(ctx)
actual = append(actual, val.(core.Int)) actual = append(actual, val.(runtime.Int))
} }
So(actual, ShouldResemble, []core.Int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) So(actual, ShouldResemble, []runtime.Int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
}) })
Convey("Multiple descending values", t, func() { Convey("Multiple descending values", t, func() {
@@ -106,7 +106,7 @@ func TestRangeIterator(t *testing.T) {
r := NewRange(10, 0) r := NewRange(10, 0)
iter := NewRangeIterator(r) iter := NewRangeIterator(r)
actual := make([]core.Int, 0, 10) actual := make([]runtime.Int, 0, 10)
for { for {
hasNext, err := iter.HasNext(ctx) hasNext, err := iter.HasNext(ctx)
@@ -115,10 +115,10 @@ func TestRangeIterator(t *testing.T) {
} }
val, _, err := iter.Next(ctx) val, _, err := iter.Next(ctx)
actual = append(actual, val.(core.Int)) actual = append(actual, val.(runtime.Int))
} }
So(actual, ShouldResemble, []core.Int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}) So(actual, ShouldResemble, []runtime.Int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0})
}) })
} }

View File

@@ -2,18 +2,17 @@ package internal
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime"
"hash/fnv" "hash/fnv"
"regexp" "regexp"
"strings" "strings"
"github.com/wI2L/jettison" "github.com/wI2L/jettison"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type Regexp regexp.Regexp type Regexp regexp.Regexp
func NewRegexp(pattern core.String) (*Regexp, error) { func NewRegexp(pattern runtime.String) (*Regexp, error) {
r, err := regexp.Compile(string(pattern)) r, err := regexp.Compile(string(pattern))
if err != nil { if err != nil {
@@ -45,8 +44,8 @@ func (r *Regexp) Hash() uint64 {
return h.Sum64() return h.Sum64()
} }
func (r *Regexp) Copy() core.Value { func (r *Regexp) Copy() runtime.Value {
copied, err := NewRegexp(core.String(r.String())) copied, err := NewRegexp(runtime.String(r.String()))
// it should never happen // it should never happen
if err != nil { if err != nil {
@@ -56,20 +55,20 @@ func (r *Regexp) Copy() core.Value {
return copied return copied
} }
func (r *Regexp) Compare(_ context.Context, other core.Value) (int64, error) { func (r *Regexp) Compare(_ context.Context, other runtime.Value) (int64, error) {
otherRegexp, ok := other.(*Regexp) otherRegexp, ok := other.(*Regexp)
if !ok { if !ok {
return core.CompareTypes(r, other), nil return runtime.CompareTypes(r, other), nil
} }
return int64(strings.Compare(r.String(), otherRegexp.String())), nil return int64(strings.Compare(r.String(), otherRegexp.String())), nil
} }
func (r *Regexp) Match(value core.Value) core.Boolean { func (r *Regexp) Match(value runtime.Value) runtime.Boolean {
return core.Boolean((*regexp.Regexp)(r).MatchString(value.String())) return runtime.Boolean((*regexp.Regexp)(r).MatchString(value.String()))
} }
func (r *Regexp) MatchString(str core.String) core.Boolean { func (r *Regexp) MatchString(str runtime.String) runtime.Boolean {
return core.Boolean((*regexp.Regexp)(r).MatchString(string(str))) return runtime.Boolean((*regexp.Regexp)(r).MatchString(string(str)))
} }

View File

@@ -2,22 +2,22 @@ package internal
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
) )
type ( type (
Sequence struct { Sequence struct {
data core.List data runtime.List
} }
sequenceIterator struct { sequenceIterator struct {
data core.List data runtime.List
length core.Int length runtime.Int
pos core.Int pos runtime.Int
} }
) )
func NewSequence(data core.List) *Sequence { func NewSequence(data runtime.List) *Sequence {
return &Sequence{data} return &Sequence{data}
} }
@@ -25,7 +25,7 @@ func (iter *sequenceIterator) HasNext(_ context.Context) (bool, error) {
return iter.length > iter.pos, nil return iter.length > iter.pos, nil
} }
func (iter *sequenceIterator) Next(ctx context.Context) (value core.Value, key core.Value, err error) { func (iter *sequenceIterator) Next(ctx context.Context) (value runtime.Value, key runtime.Value, err error) {
iter.pos++ iter.pos++
val, err := iter.data.Get(ctx, iter.pos-1) val, err := iter.data.Get(ctx, iter.pos-1)
@@ -39,7 +39,7 @@ func (iter *sequenceIterator) Next(ctx context.Context) (value core.Value, key c
return kv.Value, iter.pos - 1, nil return kv.Value, iter.pos - 1, nil
} }
func (s *Sequence) Iterate(ctx context.Context) (core.Iterator, error) { func (s *Sequence) Iterate(ctx context.Context) (runtime.Iterator, error) {
length, err := s.data.Length(ctx) length, err := s.data.Length(ctx)
if err != nil { if err != nil {
@@ -68,7 +68,7 @@ func (s *Sequence) Hash() uint64 {
panic("implement me") panic("implement me")
} }
func (s *Sequence) Copy() core.Value { func (s *Sequence) Copy() runtime.Value {
//TODO implement me //TODO implement me
panic("implement me") panic("implement me")
} }

View File

@@ -1,28 +1,28 @@
package internal package internal
import ( import (
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime"
) )
type Stack struct { type Stack struct {
data []core.Value data []runtime.Value
} }
func NewStack(size int) *Stack { func NewStack(size int) *Stack {
return &Stack{data: make([]core.Value, 0, size)} return &Stack{data: make([]runtime.Value, 0, size)}
} }
func (s *Stack) Length() int { func (s *Stack) Length() int {
return len(s.data) return len(s.data)
} }
func (s *Stack) Push(value core.Value) { func (s *Stack) Push(value runtime.Value) {
s.data = append(s.data, value) s.data = append(s.data, value)
} }
func (s *Stack) Pop() core.Value { func (s *Stack) Pop() runtime.Value {
if len(s.data) == 0 { if len(s.data) == 0 {
return core.None return runtime.None
} }
last := len(s.data) - 1 last := len(s.data) - 1
@@ -48,6 +48,6 @@ func (s *Stack) Hash() uint64 {
panic("not supported") panic("not supported")
} }
func (s *Stack) Copy() core.Value { func (s *Stack) Copy() runtime.Value {
panic("not supported") panic("not supported")
} }

View File

@@ -1,17 +1,17 @@
package runtime package vm
type Opcode byte type Opcode byte
const ( const (
OpReturn Opcode = iota OpReturn Opcode = iota
OpMove // Move a value from register A to register B OpMove // Move a value from register A to register B
OpLoadNone // Set None value to a register OpLoadNone // Set None value to a register
OpLoadBool // Set a boolean value to a register OpLoadBool // Set a boolean value to a register
OpLoadZero // Set a zero value to a register OpLoadZero // Set a zero value to a register
OpLoadConst // Load a constant to a register or a global variable OpLoadConst // Load a constant to a register or a global variable
OpStoreGlobal // Store a value from register A to a global variable OpStoreGlobal // Store a value from register A to a global variable
OpLoadGlobal // Load a global variable to a register A OpLoadGlobal // Load a global variable to a register A
OpLoadParam // Load a parameter to a register A OpLoadParam // Load a parameter to a register A
OpJump OpJump
OpJumpIfFalse OpJumpIfFalse
@@ -84,4 +84,5 @@ const (
OpWhileLoopPrep OpWhileLoopPrep
OpWhileLoopNext OpWhileLoopNext
OpWhileLoopValue OpWhileLoopValue
OpSleep
) )

View File

@@ -1,4 +1,4 @@
package runtime package vm
import "fmt" import "fmt"

View File

@@ -1,22 +1,21 @@
package runtime package vm
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime"
"io" "io"
"text/tabwriter" "text/tabwriter"
"github.com/MontFerret/ferret/pkg/runtime/core"
) )
type ( type (
Catch [3]int Catch [3]int
Program struct { Program struct {
Source *core.Source Source *runtime.Source
Locations []core.Location Locations []runtime.Location
Bytecode []Instruction Bytecode []Instruction
Constants []core.Value Constants []runtime.Value
CatchTable []Catch CatchTable []Catch
Params []string Params []string
Registers int Registers int

View File

@@ -1,4 +1,4 @@
package runtime package vm
import "fmt" import "fmt"

View File

@@ -1,8 +1,8 @@
package runtime_test package vm_test
import ( import (
"fmt" "fmt"
"github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/vm"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -10,7 +10,7 @@ import (
func TestNewSourceMap(t *testing.T) { func TestNewSourceMap(t *testing.T) {
Convey("Should match", t, func() { Convey("Should match", t, func() {
s := runtime.NewSourceMap("test", 1, 100) s := vm.NewSourceMap("test", 1, 100)
sFmt := fmt.Sprintf("%s at %d:%d", "test", 1, 100) sFmt := fmt.Sprintf("%s at %d:%d", "test", 1, 100)
So(s, ShouldNotBeNil) So(s, ShouldNotBeNil)

View File

@@ -1,19 +1,18 @@
package runtime package vm
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm/internal"
"io" "io"
"strings" "strings"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/internal"
) )
type VM struct { type VM struct {
env *Environment env *Environment
program *Program program *Program
globals map[string]core.Value globals map[string]runtime.Value
registers []core.Value registers []runtime.Value
pc int pc int
} }
@@ -24,7 +23,7 @@ func NewVM(program *Program) *VM {
return vm return vm
} }
func (vm *VM) Run(ctx context.Context, opts []EnvironmentOption) (core.Value, error) { func (vm *VM) Run(ctx context.Context, opts []EnvironmentOption) (runtime.Value, error) {
tryCatch := func(pos int) (Catch, bool) { tryCatch := func(pos int) (Catch, bool) {
for _, pair := range vm.program.CatchTable { for _, pair := range vm.program.CatchTable {
if pos >= pair[0] && pos <= pair[1] { if pos >= pair[0] && pos <= pair[1] {
@@ -42,8 +41,8 @@ func (vm *VM) Run(ctx context.Context, opts []EnvironmentOption) (core.Value, er
} }
vm.env = env vm.env = env
vm.registers = make([]core.Value, vm.program.Registers) vm.registers = make([]runtime.Value, vm.program.Registers)
vm.globals = make(map[string]core.Value) vm.globals = make(map[string]runtime.Value)
vm.pc = 0 vm.pc = 0
bytecode := vm.program.Bytecode bytecode := vm.program.Bytecode
constants := vm.program.Constants constants := vm.program.Constants
@@ -58,11 +57,11 @@ loop:
switch op { switch op {
case OpLoadNone: case OpLoadNone:
reg[dst] = core.None reg[dst] = runtime.None
case OpLoadZero: case OpLoadZero:
reg[dst] = core.ZeroInt reg[dst] = runtime.ZeroInt
case OpLoadBool: case OpLoadBool:
reg[dst] = core.Boolean(src1 == 1) reg[dst] = runtime.Boolean(src1 == 1)
case OpMove: case OpMove:
reg[dst] = reg[src1] reg[dst] = reg[src1]
case OpLoadConst: case OpLoadConst:
@@ -77,15 +76,15 @@ loop:
case OpJump: case OpJump:
vm.pc = int(dst) vm.pc = int(dst)
case OpJumpIfFalse: case OpJumpIfFalse:
if !core.ToBoolean(reg[src1]) { if !runtime.ToBoolean(reg[src1]) {
vm.pc = int(dst) vm.pc = int(dst)
} }
case OpJumpIfTrue: case OpJumpIfTrue:
if core.ToBoolean(reg[src1]) { if runtime.ToBoolean(reg[src1]) {
vm.pc = int(dst) vm.pc = int(dst)
} }
case OpJumpIfEmpty: case OpJumpIfEmpty:
val, ok := reg[src1].(core.Measurable) val, ok := reg[src1].(runtime.Measurable)
if ok { if ok {
size, err := val.Length(ctx) size, err := val.Length(ctx)
@@ -116,29 +115,29 @@ loop:
case OpDecr: case OpDecr:
reg[dst] = internal.Decrement(ctx, reg[dst]) reg[dst] = internal.Decrement(ctx, reg[dst])
case OpCastBool: case OpCastBool:
reg[dst] = core.ToBoolean(reg[src1]) reg[dst] = runtime.ToBoolean(reg[src1])
case OpNegate: case OpNegate:
reg[dst] = core.Negate(reg[src1]) reg[dst] = runtime.Negate(reg[src1])
case OpFlipPositive: case OpFlipPositive:
reg[dst] = core.Positive(reg[src1]) reg[dst] = runtime.Positive(reg[src1])
case OpFlipNegative: case OpFlipNegative:
reg[dst] = core.Negative(reg[src1]) reg[dst] = runtime.Negative(reg[src1])
case OpComp: case OpComp:
reg[dst] = core.Int(core.CompareValues(reg[src1], reg[src2])) reg[dst] = runtime.Int(runtime.CompareValues(reg[src1], reg[src2]))
case OpNot: case OpNot:
reg[dst] = !core.ToBoolean(reg[src1]) reg[dst] = !runtime.ToBoolean(reg[src1])
case OpEq: case OpEq:
reg[dst] = core.Boolean(core.CompareValues(reg[src1], reg[src2]) == 0) reg[dst] = runtime.Boolean(runtime.CompareValues(reg[src1], reg[src2]) == 0)
case OpNeq: case OpNeq:
reg[dst] = core.Boolean(core.CompareValues(reg[src1], reg[src2]) != 0) reg[dst] = runtime.Boolean(runtime.CompareValues(reg[src1], reg[src2]) != 0)
case OpGt: case OpGt:
reg[dst] = core.Boolean(core.CompareValues(reg[src1], reg[src2]) > 0) reg[dst] = runtime.Boolean(runtime.CompareValues(reg[src1], reg[src2]) > 0)
case OpLt: case OpLt:
reg[dst] = core.Boolean(core.CompareValues(reg[src1], reg[src2]) < 0) reg[dst] = runtime.Boolean(runtime.CompareValues(reg[src1], reg[src2]) < 0)
case OpGte: case OpGte:
reg[dst] = core.Boolean(core.CompareValues(reg[src1], reg[src2]) >= 0) reg[dst] = runtime.Boolean(runtime.CompareValues(reg[src1], reg[src2]) >= 0)
case OpLte: case OpLte:
reg[dst] = core.Boolean(core.CompareValues(reg[src1], reg[src2]) <= 0) reg[dst] = runtime.Boolean(runtime.CompareValues(reg[src1], reg[src2]) <= 0)
case OpIn: case OpIn:
reg[dst] = internal.Contains(ctx, reg[src2], reg[src1]) reg[dst] = internal.Contains(ctx, reg[src2], reg[src1])
case OpNotIn: case OpNotIn:
@@ -166,7 +165,7 @@ loop:
if err == nil { if err == nil {
reg[dst] = r.Match(reg[src1]) reg[dst] = r.Match(reg[src1])
} else if _, catch := tryCatch(vm.pc); catch { } else if _, catch := tryCatch(vm.pc); catch {
reg[dst] = core.False reg[dst] = runtime.False
} else { } else {
return nil, err return nil, err
} }
@@ -177,7 +176,7 @@ loop:
if err == nil { if err == nil {
reg[dst] = !r.Match(reg[src1]) reg[dst] = !r.Match(reg[src1])
} else if _, catch := tryCatch(vm.pc); catch { } else if _, catch := tryCatch(vm.pc); catch {
reg[dst] = core.False reg[dst] = runtime.False
} else { } else {
return nil, err return nil, err
} }
@@ -188,7 +187,7 @@ loop:
size = src2.Register() - src1.Register() + 1 size = src2.Register() - src1.Register() + 1
} }
arr := core.NewArray(size) arr := runtime.NewArray(size)
start := int(src1) start := int(src1)
end := int(src1) + size end := int(src1) + size
@@ -199,7 +198,7 @@ loop:
reg[dst] = arr reg[dst] = arr
case OpObject: case OpObject:
obj := core.NewObject() obj := runtime.NewObject()
var args int var args int
if src1 > 0 { if src1 > 0 {
@@ -213,7 +212,7 @@ loop:
key := reg[i] key := reg[i]
value := reg[i+1] value := reg[i+1]
_ = obj.Set(ctx, core.ToString(key), value) _ = obj.Set(ctx, runtime.ToString(key), value)
} }
reg[dst] = obj reg[dst] = obj
@@ -222,36 +221,36 @@ loop:
prop := reg[src2] prop := reg[src2]
switch getter := prop.(type) { switch getter := prop.(type) {
case core.String: case runtime.String:
switch src := val.(type) { switch src := val.(type) {
case core.Keyed: case runtime.Keyed:
out, err := src.Get(ctx, getter) out, err := src.Get(ctx, getter)
if err == nil { if err == nil {
reg[dst] = out reg[dst] = out
} else if op == OpLoadPropertyOptional { } else if op == OpLoadPropertyOptional {
reg[dst] = core.None reg[dst] = runtime.None
} else { } else {
return nil, err return nil, err
} }
default: default:
if op != OpLoadPropertyOptional { if op != OpLoadPropertyOptional {
return nil, core.TypeError(src, core.TypeMap) return nil, runtime.TypeError(src, runtime.TypeMap)
} }
reg[dst] = core.None reg[dst] = runtime.None
} }
case core.Float, core.Int: case runtime.Float, runtime.Int:
// TODO: Optimize this. Avoid extra type conversion // TODO: Optimize this. Avoid extra type conversion
idx, _ := core.ToInt(ctx, getter) idx, _ := runtime.ToInt(ctx, getter)
switch src := val.(type) { switch src := val.(type) {
case core.Indexed: case runtime.Indexed:
out, err := src.Get(ctx, idx) out, err := src.Get(ctx, idx)
if err == nil { if err == nil {
reg[dst] = out reg[dst] = out
} else if op == OpLoadPropertyOptional { } else if op == OpLoadPropertyOptional {
reg[dst] = core.None reg[dst] = runtime.None
} else { } else {
return nil, err return nil, err
} }
@@ -259,13 +258,25 @@ loop:
reg[dst] = src.Get(ctx, idx) reg[dst] = src.Get(ctx, idx)
default: default:
if op != OpLoadPropertyOptional { if op != OpLoadPropertyOptional {
return nil, core.TypeError(src, core.TypeList) return nil, runtime.TypeError(src, runtime.TypeList)
} }
reg[dst] = core.None reg[dst] = runtime.None
} }
} }
case OpCall, OpProtectedCall: 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 var size int
if src1 > 0 { if src1 > 0 {
@@ -274,24 +285,21 @@ loop:
start := int(src1) start := int(src1)
end := int(src1) + size end := int(src1) + size
args := make([]core.Value, size) args := make([]runtime.Value, size)
// Iterate over registers starting from src1 and up to the src2 // Iterate over registers starting from src1 and up to the src2
for i := start; i < end; i++ { for i := start; i < end; i++ {
args[i-start] = reg[i] args[i-start] = reg[i]
} }
fnName := reg[dst].String()
fn := vm.env.GetFunction(fnName)
out, err := fn(ctx, args...) out, err := fn(ctx, args...)
if err == nil { if err == nil {
reg[dst] = out reg[dst] = out
} else if op == OpProtectedCall { } else if op == OpProtectedCall {
reg[dst] = core.None reg[dst] = runtime.None
} else if catch, ok := tryCatch(vm.pc); ok { } else if catch, ok := tryCatch(vm.pc); ok {
reg[dst] = core.None reg[dst] = runtime.None
if catch[2] > 0 { if catch[2] > 0 {
vm.pc = catch[2] vm.pc = catch[2]
@@ -300,7 +308,7 @@ loop:
return nil, err return nil, err
} }
case OpLength: case OpLength:
val, ok := reg[src1].(core.Measurable) val, ok := reg[src1].(runtime.Measurable)
if ok { if ok {
length, err := val.Length(ctx) length, err := val.Length(ctx)
@@ -315,21 +323,21 @@ loop:
reg[dst] = length reg[dst] = length
} else if _, catch := tryCatch(vm.pc); catch { } else if _, catch := tryCatch(vm.pc); catch {
reg[dst] = core.ZeroInt reg[dst] = runtime.ZeroInt
} else { } else {
return core.None, core.TypeError(reg[src1], return runtime.None, runtime.TypeError(reg[src1],
core.TypeString, runtime.TypeString,
core.TypeList, runtime.TypeList,
core.TypeMap, runtime.TypeMap,
core.TypeBinary, runtime.TypeBinary,
core.TypeMeasurable, runtime.TypeMeasurable,
) )
} }
case OpType: case OpType:
reg[dst] = core.String(core.Reflect(reg[src1])) reg[dst] = runtime.String(runtime.Reflect(reg[src1]))
case OpClose: case OpClose:
val, ok := reg[dst].(io.Closer) val, ok := reg[dst].(io.Closer)
reg[dst] = core.None reg[dst] = runtime.None
if ok { if ok {
err := val.Close() err := val.Close()
@@ -351,13 +359,21 @@ loop:
case OpLoopBegin: case OpLoopBegin:
reg[dst] = internal.NewDataSet(src1 == 1) reg[dst] = internal.NewDataSet(src1 == 1)
case OpLoopEnd: case OpLoopEnd:
ds := reg[src1].(*internal.DataSet) // TODO: Optimize this. Avoid extra type conversion
reg[dst] = ds.ToList() ds, ok := reg[src1].(*internal.DataSet)
if ok {
reg[dst] = ds.ToList()
} else {
// Recover from an error
reg[dst] = runtime.None
}
case OpForLoopPrep: case OpForLoopPrep:
input := reg[src1] input := reg[src1]
switch src := input.(type) { switch src := input.(type) {
case core.Iterable: case runtime.Iterable:
iterator, err := src.Iterate(ctx) iterator, err := src.Iterate(ctx)
if err != nil { if err != nil {
@@ -370,7 +386,7 @@ loop:
// Fall back to an empty iterator // Fall back to an empty iterator
reg[dst] = internal.NoopIter reg[dst] = internal.NoopIter
} else { } else {
return nil, core.TypeError(src, core.TypeIterable) return nil, runtime.TypeError(src, runtime.TypeIterable)
} }
} }
case OpForLoopNext: case OpForLoopNext:
@@ -395,9 +411,9 @@ loop:
iterator := reg[src1].(*internal.Iterator) iterator := reg[src1].(*internal.Iterator)
reg[dst] = iterator.Key() reg[dst] = iterator.Key()
case OpWhileLoopPrep: case OpWhileLoopPrep:
reg[dst] = core.Int(-1) reg[dst] = runtime.Int(-1)
case OpWhileLoopNext: case OpWhileLoopNext:
cond := core.ToBoolean(reg[src1]) cond := runtime.ToBoolean(reg[src1])
if cond { if cond {
reg[dst] = internal.Increment(ctx, reg[dst]) reg[dst] = internal.Increment(ctx, reg[dst])
@@ -435,8 +451,8 @@ loop:
reg[dst] = pair.Key reg[dst] = pair.Key
case OpSortSwap: case OpSortSwap:
ds := reg[dst].(*internal.DataSet) ds := reg[dst].(*internal.DataSet)
i, _ := core.ToInt(ctx, reg[src1]) i, _ := runtime.ToInt(ctx, reg[src1])
j, _ := core.ToInt(ctx, reg[src2]) j, _ := runtime.ToInt(ctx, reg[src2])
ds.Swap(ctx, i, j) ds.Swap(ctx, i, j)
case OpGroupPrep: case OpGroupPrep:
reg[dst] = internal.NewCollector() reg[dst] = internal.NewCollector()
@@ -445,6 +461,20 @@ loop:
key := reg[src1] key := reg[src1]
value := reg[src2] value := reg[src2]
collector.Add(key, value) collector.Add(key, value)
case OpSleep:
dur, err := runtime.ToInt(ctx, reg[dst])
if err != nil {
if _, catch := tryCatch(vm.pc); catch {
continue
} else {
return nil, err
}
}
if err := internal.Sleep(ctx, dur); err != nil {
return nil, err
}
case OpReturn: case OpReturn:
break loop break loop
} }
@@ -475,7 +505,7 @@ func (vm *VM) validateParams(env *Environment) error {
} }
if len(missedParams) > 0 { if len(missedParams) > 0 {
return core.Error(ErrMissedParam, strings.Join(missedParams, ", ")) return runtime.Error(ErrMissedParam, strings.Join(missedParams, ", "))
} }
return nil return nil