mirror of
https://github.com/MontFerret/ferret.git
synced 2025-06-25 00:37:26 +02:00
Feature/#9 array functions (#57)
* #9 Added 'APPEND' function * #9 Added 'FIRST' function * #9 Added 'FLATTEN' function * #9 Added 'INTERSECTION' function * #9 Added 'LAST' function * #9 Added 'MINUS' function * #9 Added 'NTH' function * #9 Added 'OUTERSECTION' function * #9 Added 'POP' function * #9 Added 'POSITION' function * #9 Added 'PUSH' function * Fixed nil pointer exception in value parser * #9 Added 'REMOVE_NTH' function * #9 Added 'REMOVE_VALUE' function * #9 Added 'REMOVE_VALUES' function * #9 Added 'REVERSE' function * #9 Added 'SHIFT' function * #9 Added 'SLICE' function * Removed meme * #9 Added 'SORTED' function * #9 Added SORTED_UNIQUE function * #9 Added 'UNION' function * #9 Added 'UNION_DISTINCT' function * #9 Added 'UNIQUE' function * #9 Added 'UNSHIFT' function * #9 Made more strict optional arg validation * #9 Fixed linting errors
This commit is contained in:
375
README.md.orig
Normal file
375
README.md.orig
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
# Ferret
|
||||||
|
[](https://travis-ci.com/MontFerret/ferret)
|
||||||
|

|
||||||
|
|
||||||
|
## What is it?
|
||||||
|
```ferret``` is a web scraping system aiming to simplify data extraction from the web for such things like ui testing, machine learning and analytics.
|
||||||
|
Having it's own declarative language, ```ferret``` abstracts away technical details and complexity of the underlying technologies, helping to focus on the data itself.
|
||||||
|
It's extremely portable, extensible and fast.
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
## Show me some code
|
||||||
|
=======
|
||||||
|
## Show me some code
|
||||||
|
>>>>>>> master
|
||||||
|
The following example demonstrates the use of dynamic pages.
|
||||||
|
First of all, we load the main Google Search page, type search criteria into an input box and then click a search button.
|
||||||
|
The click action triggers a redirect, so we wait till its end.
|
||||||
|
Once the page gets loaded, we iterate over all elements in search results and assign output to a variable.
|
||||||
|
The final for loop filters out empty elements that might be because of inaccurate use of selectors.
|
||||||
|
|
||||||
|
```aql
|
||||||
|
LET google = DOCUMENT("https://www.google.com/", true)
|
||||||
|
|
||||||
|
INPUT(google, 'input[name="q"]', "ferret")
|
||||||
|
CLICK(google, 'input[name="btnK"]')
|
||||||
|
|
||||||
|
WAIT_NAVIGATION(google)
|
||||||
|
|
||||||
|
LET result = (
|
||||||
|
FOR result IN ELEMENTS(google, '.g')
|
||||||
|
RETURN {
|
||||||
|
title: ELEMENT(result, 'h3 > a'),
|
||||||
|
description: ELEMENT(result, '.st'),
|
||||||
|
url: ELEMENT(result, 'cite')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
RETURN (
|
||||||
|
FOR page IN result
|
||||||
|
FILTER page.title != NONE
|
||||||
|
RETURN page
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Declarative language
|
||||||
|
* Support of both static and dynamic web pages
|
||||||
|
* Embeddable
|
||||||
|
* Extensible
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
Nowadays data is everything and who owns data - owns the world.
|
||||||
|
I have worked on multiple data-driven projects where data was an essential part of a system and I realized how cumbersome writing tons of scrapers is.
|
||||||
|
After some time looking for a tool that would let me to not write a code, but just express what data I need, decided to come up with my own solution.
|
||||||
|
```ferret``` project is an ambitious initiative trying to bring universal platform for writing scrapers without any hassle.
|
||||||
|
|
||||||
|
## Inspiration
|
||||||
|
FQL (Ferret Query Language) is heavily inspired by [AQL](https://www.arangodb.com/) (ArangoDB Query Language).
|
||||||
|
But due to the domain specifics, there are some differences in how things work.
|
||||||
|
|
||||||
|
## WIP
|
||||||
|
Be aware, the the project is under heavy development. There is no documentation and some things may change in the final release.
|
||||||
|
For query syntax, you may go to [ArangoDB web site](https://docs.arangodb.com/3.3/AQL/index.html) and use AQL docs as docs for FQL - since they are identical.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
* Go >=1.6
|
||||||
|
* GoDep
|
||||||
|
* GNU Make
|
||||||
|
* Chrome or Docker (optional)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/MontFerret/ferret
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use your local copy of Google Chrome / Chromium, but for ease of use it's recommended to run it inside a Docker container:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker pull alpeware/chrome-headless-trunk
|
||||||
|
docker run -d -p=0.0.0.0:9222:9222 --name=chrome-headless -v /tmp/chromedata/:/data alpeware/chrome-headless-trunk
|
||||||
|
```
|
||||||
|
|
||||||
|
But if you want to see what's happening during query execution, just start your Chrome with remote debugging port:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chrome.exe --remote-debugging-port=9222
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
### Browserless mode
|
||||||
|
|
||||||
|
If you want to play with ```fql``` and check its syntax, you can run CLI with the following commands:
|
||||||
|
```
|
||||||
|
ferret
|
||||||
|
```
|
||||||
|
|
||||||
|
```ferret``` will run in REPL mode.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
Welcome to Ferret REPL
|
||||||
|
Please use `Ctrl-D` to exit this program.
|
||||||
|
>%
|
||||||
|
>LET doc = DOCUMENT('https://news.ycombinator.com/')
|
||||||
|
>FOR post IN ELEMENTS(doc, '.storylink')
|
||||||
|
>RETURN post.attributes.href
|
||||||
|
>%
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** symbol ```%``` is used to start and end multi line queries. You also can use heredoc format.
|
||||||
|
|
||||||
|
If you want to execute a query stored in a file, just pass a file name:
|
||||||
|
|
||||||
|
```
|
||||||
|
ferret ./docs/examples/static-page.fql
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
cat ./docs/examples/static-page.fql | ferret
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
ferret < ./docs/examples/static-page.fql
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Browser mode
|
||||||
|
|
||||||
|
By default, ``ferret`` loads HTML pages via http protocol, because it's faster.
|
||||||
|
But nowadays, there are more and more websites rendered with JavaScript, and therefore, this 'old school' approach does not really work.
|
||||||
|
For such cases, you may fetch documents using Chrome or Chromium via Chrome DevTools protocol (aka CDP).
|
||||||
|
First, you need to make sure that you launched Chrome with ```remote-debugging-port=9222``` flag.
|
||||||
|
Second, you need to pass the address to ```ferret``` CLI.
|
||||||
|
|
||||||
|
```
|
||||||
|
ferret --cdp http://127.0.0.1:9222
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE:** By default, ```ferret``` will try to use this local address as a default one, so it makes sense to explicitly pass the parameter only in case of either different port number or remote address.
|
||||||
|
|
||||||
|
Alternatively, you can tell CLI to launch Chrome for you.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ferret --cdp-launch
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE:** Launch command is currently broken on MacOS.
|
||||||
|
|
||||||
|
Once ```ferret``` knows how to communicate with Chrome, you can use a function ```DOCUMENT(url, isDynamic)``` with ```true``` boolean value for dynamic pages:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
Welcome to Ferret REPL
|
||||||
|
Please use `exit` or `Ctrl-D` to exit this program.
|
||||||
|
>%
|
||||||
|
>LET doc = DOCUMENT('https://soundcloud.com/charts/top', true)
|
||||||
|
>WAIT_ELEMENT(doc, '.chartTrack__details', 5000)
|
||||||
|
>LET tracks = ELEMENTS(doc, '.chartTrack__details')
|
||||||
|
>FOR track IN tracks
|
||||||
|
> LET username = ELEMENT(track, '.chartTrack__username')
|
||||||
|
> LET title = ELEMENT(track, '.chartTrack__title')
|
||||||
|
> RETURN {
|
||||||
|
> artist: username.innerText,
|
||||||
|
> track: title.innerText
|
||||||
|
> }
|
||||||
|
>%
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
Welcome to Ferret REPL
|
||||||
|
Please use `exit` or `Ctrl-D` to exit this program.
|
||||||
|
>%
|
||||||
|
>LET doc = DOCUMENT("https://github.com/", true)
|
||||||
|
>LET btn = ELEMENT(doc, ".HeaderMenu a")
|
||||||
|
|
||||||
|
>CLICK(btn)
|
||||||
|
>WAIT_NAVIGATION(doc)
|
||||||
|
>WAIT_ELEMENT(doc, '.IconNav')
|
||||||
|
|
||||||
|
>FOR el IN ELEMENTS(doc, '.IconNav a')
|
||||||
|
> RETURN TRIM(el.innerText)
|
||||||
|
>%
|
||||||
|
```
|
||||||
|
|
||||||
|
### Embedded mode
|
||||||
|
|
||||||
|
```ferret``` is a very modular system and therefore, can be easily be embedded into your Go application.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Topic struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
topics, err := getTopTenTrendingTopics()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, topic := range topics {
|
||||||
|
fmt.Println(fmt.Sprintf("%s: %s %s", topic.Name, topic.Description, topic.Url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTopTenTrendingTopics() ([]*Topic, error) {
|
||||||
|
query := `
|
||||||
|
LET doc = DOCUMENT("https://github.com/topics")
|
||||||
|
|
||||||
|
FOR el IN ELEMENTS(doc, ".py-4.border-bottom")
|
||||||
|
LIMIT 10
|
||||||
|
LET url = ELEMENT(el, "a")
|
||||||
|
LET name = ELEMENT(el, ".f3")
|
||||||
|
LET desc = ELEMENT(el, ".f5")
|
||||||
|
|
||||||
|
RETURN {
|
||||||
|
name: TRIM(name.innerText),
|
||||||
|
description: TRIM(desc.innerText),
|
||||||
|
url: "https://github.com" + url.attributes.href
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
comp := compiler.New()
|
||||||
|
|
||||||
|
program, err := comp.Compile(query)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := program.Run(context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]*Topic, 0, 10)
|
||||||
|
|
||||||
|
err = json.Unmarshal(out, &res)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extensibility
|
||||||
|
|
||||||
|
That said, ```ferret``` is a very modular system which also allows not only embed it, but extend its standard library.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
strs, err := getStrings()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, str := range strs {
|
||||||
|
fmt.Println(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStrings() ([]string, error) {
|
||||||
|
// 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) {
|
||||||
|
// it's just a helper function which helps to validate a number of passed args
|
||||||
|
err := core.ValidateArgs(args, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// it's recommended to return built-in None type, instead of nil
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is another helper functions allowing to do type validation
|
||||||
|
err = core.ValidateType(args[0], core.StringType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cast to built-in string type
|
||||||
|
str := args[0].(values.String)
|
||||||
|
|
||||||
|
return str.Concat(values.NewString("_ferret")).ToUpper(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `
|
||||||
|
FOR el IN ["foo", "bar", "qaz"]
|
||||||
|
// conventionally all functions are registered in upper case
|
||||||
|
RETURN TRANSFORM(el)
|
||||||
|
`
|
||||||
|
|
||||||
|
comp := compiler.New()
|
||||||
|
comp.RegisterFunction("transform", transform)
|
||||||
|
|
||||||
|
program, err := comp.Compile(query)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := program.Run(context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, 3)
|
||||||
|
|
||||||
|
err = json.Unmarshal(out, &res)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On top of that, you can completely turn off standard library, by passing the following option:
|
||||||
|
|
||||||
|
```go
|
||||||
|
comp := compiler.New(compiler.WithoutStdlib())
|
||||||
|
```
|
||||||
|
|
||||||
|
And after that, you can easily provide your own implementation of functions from standard library.
|
||||||
|
|
||||||
|
If you don't need a particular set of functions from standard library, you can turn off the entire ```stdlib``` and register separate packages from that:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
comp := compiler.New(compiler.WithoutStdlib())
|
||||||
|
|
||||||
|
comp.RegisterFunctions(strings.NewLib())
|
||||||
|
}
|
||||||
|
```
|
80
pkg/runtime/collections/unique.go
Normal file
80
pkg/runtime/collections/unique.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UniqueIterator struct {
|
||||||
|
src Iterator
|
||||||
|
hashes map[uint64]bool
|
||||||
|
value core.Value
|
||||||
|
key core.Value
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewUniqueIterator(src Iterator) (*UniqueIterator, error) {
|
||||||
|
if src == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "source")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UniqueIterator{
|
||||||
|
src: src,
|
||||||
|
hashes: make(map[uint64]bool),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *UniqueIterator) HasNext() bool {
|
||||||
|
if !iterator.src.HasNext() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator.doNext()
|
||||||
|
|
||||||
|
if iterator.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !core.IsNil(iterator.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *UniqueIterator) Next() (core.Value, core.Value, error) {
|
||||||
|
return iterator.value, iterator.key, iterator.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *UniqueIterator) doNext() {
|
||||||
|
// reset state
|
||||||
|
iterator.err = nil
|
||||||
|
iterator.value = nil
|
||||||
|
iterator.key = nil
|
||||||
|
|
||||||
|
// iterate over source until we find a non-unique item
|
||||||
|
for iterator.src.HasNext() {
|
||||||
|
val, key, err := iterator.src.Next()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
iterator.err = err
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := val.Hash()
|
||||||
|
|
||||||
|
_, exists := iterator.hashes[h]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator.hashes[h] = true
|
||||||
|
iterator.key = key
|
||||||
|
iterator.value = val
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
89
pkg/runtime/collections/unique_test.go
Normal file
89
pkg/runtime/collections/unique_test.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package collections_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUniqueIterator(t *testing.T) {
|
||||||
|
Convey("Should return only unique items", t, func() {
|
||||||
|
arr := []core.Value{
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := collections.NewUniqueIterator(
|
||||||
|
collections.NewSliceIterator(arr),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res, err := collections.ToArray(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(res.String(), ShouldEqual, `[1,2,3,4,5,6]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return only unique items 2", t, func() {
|
||||||
|
arr := []core.Value{
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := collections.NewUniqueIterator(
|
||||||
|
collections.NewSliceIterator(arr),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res, err := collections.ToArray(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(res.String(), ShouldEqual, `[1]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return only unique items 3", t, func() {
|
||||||
|
arr := []core.Value{
|
||||||
|
values.NewString("a"),
|
||||||
|
values.NewString("b"),
|
||||||
|
values.NewString("c"),
|
||||||
|
values.NewString("d"),
|
||||||
|
values.NewString("e"),
|
||||||
|
values.NewString("a"),
|
||||||
|
values.NewString("b"),
|
||||||
|
values.NewString("f"),
|
||||||
|
values.NewString("d"),
|
||||||
|
values.NewString("e"),
|
||||||
|
values.NewString("f"),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := collections.NewUniqueIterator(
|
||||||
|
collections.NewSliceIterator(arr),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res, err := collections.ToArray(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(res.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
|
||||||
|
})
|
||||||
|
}
|
@ -128,6 +128,10 @@ func (t *Array) ForEach(predicate ArrayPredicate) {
|
|||||||
func (t *Array) Get(idx Int) core.Value {
|
func (t *Array) Get(idx Int) core.Value {
|
||||||
l := len(t.value) - 1
|
l := len(t.value) - 1
|
||||||
|
|
||||||
|
if l < 0 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
if int(idx) > l {
|
if int(idx) > l {
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
@ -151,8 +155,21 @@ func (t *Array) Push(item core.Value) {
|
|||||||
t.value = append(t.value, item)
|
t.value = append(t.value, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Array) Slice(from, to Int) []core.Value {
|
func (t *Array) Slice(from, to Int) *Array {
|
||||||
return t.value[from:to]
|
length := t.Length()
|
||||||
|
|
||||||
|
if from >= length {
|
||||||
|
return NewArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if to > length {
|
||||||
|
to = length
|
||||||
|
}
|
||||||
|
|
||||||
|
result := new(Array)
|
||||||
|
result.value = t.value[from:to]
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Array) IndexOf(item core.Value) Int {
|
func (t *Array) IndexOf(item core.Value) Int {
|
||||||
|
@ -302,12 +302,12 @@ func TestArray(t *testing.T) {
|
|||||||
|
|
||||||
s := arr.Slice(0, 1)
|
s := arr.Slice(0, 1)
|
||||||
|
|
||||||
So(len(s), ShouldEqual, 1)
|
So(s.Length(), ShouldEqual, 1)
|
||||||
So(s[0].Compare(values.ZeroInt), ShouldEqual, 0)
|
So(s.Get(0).Compare(values.ZeroInt), ShouldEqual, 0)
|
||||||
|
|
||||||
s2 := arr.Slice(2, arr.Length())
|
s2 := arr.Slice(2, arr.Length())
|
||||||
|
|
||||||
So(len(s2), ShouldEqual, arr.Length()-2)
|
So(s2.Length(), ShouldEqual, arr.Length()-2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
74
pkg/stdlib/arrays/append.go
Normal file
74
pkg/stdlib/arrays/append.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Appends a new item to an array and returns a new array with a given element.
|
||||||
|
* If ``uniqueOnly`` is set to true, then will add the item only if it's unique.
|
||||||
|
* @param arr (Array) - Target array.
|
||||||
|
* @param item (Value) - Target value to add.
|
||||||
|
* @returns arr (Array) - New array.
|
||||||
|
*/
|
||||||
|
func Append(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 3)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
arg := args[1]
|
||||||
|
unique := values.False
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = core.ValidateType(args[2], core.BooleanType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unique = args[2].(values.Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
next := values.NewArray(int(arr.Length()) + 1)
|
||||||
|
|
||||||
|
if !unique {
|
||||||
|
arr.ForEach(func(item core.Value, idx int) bool {
|
||||||
|
next.Push(item)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
next.Push(arg)
|
||||||
|
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDuplicate := false
|
||||||
|
|
||||||
|
arr.ForEach(func(item core.Value, idx int) bool {
|
||||||
|
next.Push(item)
|
||||||
|
|
||||||
|
if !hasDuplicate {
|
||||||
|
hasDuplicate = item.Compare(arg) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if !hasDuplicate {
|
||||||
|
next.Push(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next, nil
|
||||||
|
}
|
50
pkg/stdlib/arrays/append_test.go
Normal file
50
pkg/stdlib/arrays/append_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Append(context.Background(), arr, values.NewInt(6))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldNotEqual, arr)
|
||||||
|
So(out.(collections.Collection).Length(), ShouldBeGreaterThan, arr.Length())
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should ignore non-unique items", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Append(context.Background(), arr, values.NewInt(5), values.True)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldNotEqual, arr)
|
||||||
|
So(out.(collections.Collection).Length(), ShouldEqual, arr.Length())
|
||||||
|
|
||||||
|
out2, err := arrays.Append(context.Background(), arr, values.NewInt(6), values.True)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out2, ShouldNotEqual, arr)
|
||||||
|
So(out2.(collections.Collection).Length(), ShouldBeGreaterThan, arr.Length())
|
||||||
|
})
|
||||||
|
}
|
30
pkg/stdlib/arrays/first.go
Normal file
30
pkg/stdlib/arrays/first.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a first element from a given array.
|
||||||
|
* @param arr (Array) - Target array.
|
||||||
|
* @returns element (Value) - First element in a given array.
|
||||||
|
*/
|
||||||
|
func First(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
|
||||||
|
return arr.Get(0), nil
|
||||||
|
}
|
35
pkg/stdlib/arrays/first_test.go
Normal file
35
pkg/stdlib/arrays/first_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFirst(t *testing.T) {
|
||||||
|
Convey("Should return a first element form a given array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.First(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldEqual, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return NONE if a given array is empty", t, func() {
|
||||||
|
arr := values.NewArray(0)
|
||||||
|
|
||||||
|
out, err := arrays.First(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldEqual, values.None)
|
||||||
|
})
|
||||||
|
}
|
67
pkg/stdlib/arrays/flatten.go
Normal file
67
pkg/stdlib/arrays/flatten.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn an array of arrays into a flat array.
|
||||||
|
* All array elements in array will be expanded in the result array.
|
||||||
|
* Non-array elements are added as they are.
|
||||||
|
* The function will recurse into sub-arrays up to the specified depth.
|
||||||
|
* Duplicates will not be removed.
|
||||||
|
* @param arr (Array) - Target array.
|
||||||
|
* @param depth (Int, optional) - Depth level.
|
||||||
|
* @returns (Array) - Flat array.
|
||||||
|
*/
|
||||||
|
func Flatten(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 2)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
level := 1
|
||||||
|
|
||||||
|
if len(args) > 1 {
|
||||||
|
err = core.ValidateType(args[1], core.IntType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
level = int(args[1].(values.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLevel := 0
|
||||||
|
result := values.NewArray(int(arr.Length()) * 2)
|
||||||
|
var unwrap func(input *values.Array)
|
||||||
|
|
||||||
|
unwrap = func(input *values.Array) {
|
||||||
|
currentLevel++
|
||||||
|
|
||||||
|
input.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
if value.Type() != core.ArrayType || currentLevel > level {
|
||||||
|
result.Push(value)
|
||||||
|
} else {
|
||||||
|
unwrap(value.(*values.Array))
|
||||||
|
currentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unwrap(arr)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
71
pkg/stdlib/arrays/flatten_test.go
Normal file
71
pkg/stdlib/arrays/flatten_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlatten(t *testing.T) {
|
||||||
|
Convey("Should flatten an array with depth 1", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
values.NewInt(7),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(8),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(9),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Flatten(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4,[5,6],7,8,[9,[10]]]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should flatten an array with depth more than 1", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
values.NewInt(7),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(8),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(9),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Flatten(context.Background(), arr, values.NewInt(2))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4,5,6,7,8,9,[10]]")
|
||||||
|
})
|
||||||
|
}
|
70
pkg/stdlib/arrays/intersection.go
Normal file
70
pkg/stdlib/arrays/intersection.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the intersection of all arrays specified.
|
||||||
|
* The result is an array of values that occur in all arguments.
|
||||||
|
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
|
||||||
|
* @returns (Array) - A single array with only the elements, which exist in all provided arrays.
|
||||||
|
* The element order is random. Duplicates are removed.
|
||||||
|
*/
|
||||||
|
func Intersection(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
return sections(args, len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sections(args []core.Value, count int) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
intersections := make(map[uint64][]core.Value)
|
||||||
|
capacity := len(args)
|
||||||
|
|
||||||
|
for _, i := range args {
|
||||||
|
err := core.ValidateType(i, core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := i.(*values.Array)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
h := value.Hash()
|
||||||
|
|
||||||
|
bucket, exists := intersections[h]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
bucket = make([]core.Value, 0, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket = append(bucket, value)
|
||||||
|
intersections[h] = bucket
|
||||||
|
bucketLen := len(bucket)
|
||||||
|
|
||||||
|
if bucketLen > capacity {
|
||||||
|
capacity = bucketLen
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result := values.NewArray(capacity)
|
||||||
|
required := count
|
||||||
|
|
||||||
|
for _, bucket := range intersections {
|
||||||
|
if len(bucket) == required {
|
||||||
|
result.Push(bucket[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
102
pkg/stdlib/arrays/intersection_test.go
Normal file
102
pkg/stdlib/arrays/intersection_test.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntersection(t *testing.T) {
|
||||||
|
Convey("Should find intersections between 2 arrays", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(7),
|
||||||
|
values.NewInt(8),
|
||||||
|
values.NewInt(9),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Intersection(context.Background(), arr1, arr2)
|
||||||
|
|
||||||
|
check := map[int]bool{
|
||||||
|
4: true,
|
||||||
|
5: true,
|
||||||
|
6: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
arr := out.(*values.Array)
|
||||||
|
|
||||||
|
So(arr.Length(), ShouldEqual, 3)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
_, exists := check[int(value.(values.Int))]
|
||||||
|
|
||||||
|
So(exists, ShouldBeTrue)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should find intersections between more than 2 arrays", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr3 := values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(7),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Intersection(context.Background(), arr1, arr2, arr3)
|
||||||
|
|
||||||
|
check := map[int]bool{
|
||||||
|
3: true,
|
||||||
|
4: true,
|
||||||
|
5: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
arr := out.(*values.Array)
|
||||||
|
|
||||||
|
So(arr.Length(), ShouldEqual, 3)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
_, exists := check[int(value.(values.Int))]
|
||||||
|
|
||||||
|
So(exists, ShouldBeTrue)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
30
pkg/stdlib/arrays/last.go
Normal file
30
pkg/stdlib/arrays/last.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the last element of an array.
|
||||||
|
* @param array (Array) - The target array.
|
||||||
|
* @returns (Value) - Last element of an array.
|
||||||
|
*/
|
||||||
|
func Last(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
|
||||||
|
return arr.Get(arr.Length() - 1), nil
|
||||||
|
}
|
35
pkg/stdlib/arrays/last_test.go
Normal file
35
pkg/stdlib/arrays/last_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLast(t *testing.T) {
|
||||||
|
Convey("Should return a last element form a given array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Last(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldEqual, 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return NONE if a given array is empty", t, func() {
|
||||||
|
arr := values.NewArray(0)
|
||||||
|
|
||||||
|
out, err := arrays.Last(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldEqual, values.None)
|
||||||
|
})
|
||||||
|
}
|
31
pkg/stdlib/arrays/lib.go
Normal file
31
pkg/stdlib/arrays/lib.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import "github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
|
||||||
|
func NewLib() map[string]core.Function {
|
||||||
|
return map[string]core.Function{
|
||||||
|
"APPEND": Append,
|
||||||
|
"FIRST": First,
|
||||||
|
"FLATTEN": Flatten,
|
||||||
|
"INTERSECTION": Intersection,
|
||||||
|
"LAST": Last,
|
||||||
|
"MINUS": Minus,
|
||||||
|
"NTH": Nth,
|
||||||
|
"OUTERSECTION": Outersection,
|
||||||
|
"POP": Pop,
|
||||||
|
"POSITION": Position,
|
||||||
|
"PUSH": Push,
|
||||||
|
"REMOVE_NTH": RemoveNth,
|
||||||
|
"REMOVE_VALUE": RemoveValue,
|
||||||
|
"REMOVE_VALUES": RemoveValues,
|
||||||
|
"REVERSE": Reverse,
|
||||||
|
"SHIFT": Shift,
|
||||||
|
"SLICE": Slice,
|
||||||
|
"SORTED": Sorted,
|
||||||
|
"SORTED_UNIQUE": SortedUnique,
|
||||||
|
"UNION": Union,
|
||||||
|
"UNION_DISTINCT": UnionDistinct,
|
||||||
|
"UNIQUE": Unique,
|
||||||
|
"UNSHIFT": Unshift,
|
||||||
|
}
|
||||||
|
}
|
63
pkg/stdlib/arrays/minus.go
Normal file
63
pkg/stdlib/arrays/minus.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the difference of all arrays specified.
|
||||||
|
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
|
||||||
|
* @returns array (Array) - An array of values that occur in the first array, but not in any of the subsequent arrays.
|
||||||
|
* The order of the result array is undefined and should not be relied on. Duplicates will be removed.
|
||||||
|
*/
|
||||||
|
func Minus(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
intersections := make(map[uint64]core.Value)
|
||||||
|
capacity := values.NewInt(0)
|
||||||
|
|
||||||
|
for idx, i := range args {
|
||||||
|
err := core.ValidateType(i, core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := i.(*values.Array)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, _ int) bool {
|
||||||
|
h := value.Hash()
|
||||||
|
|
||||||
|
// first array, fill out the map
|
||||||
|
if idx == 0 {
|
||||||
|
capacity = arr.Length()
|
||||||
|
intersections[h] = value
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := intersections[h]
|
||||||
|
|
||||||
|
// if it exists in the first array, remove it
|
||||||
|
if exists {
|
||||||
|
delete(intersections, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result := values.NewArray(int(capacity))
|
||||||
|
|
||||||
|
for _, item := range intersections {
|
||||||
|
result.Push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
94
pkg/stdlib/arrays/minus_test.go
Normal file
94
pkg/stdlib/arrays/minus_test.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMinus(t *testing.T) {
|
||||||
|
Convey("Should find differences between 2 arrays", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Minus(context.Background(), arr1, arr2)
|
||||||
|
|
||||||
|
check := map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
arr := out.(*values.Array)
|
||||||
|
|
||||||
|
So(arr.Length(), ShouldEqual, 2)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
_, exists := check[int(value.(values.Int))]
|
||||||
|
|
||||||
|
So(exists, ShouldBeTrue)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should find differences between more than 2 arrays", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(9),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr3 := values.NewArrayWith(
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(7),
|
||||||
|
values.NewInt(8),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Minus(context.Background(), arr1, arr2, arr3)
|
||||||
|
|
||||||
|
check := map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
arr := out.(*values.Array)
|
||||||
|
|
||||||
|
So(arr.Length(), ShouldEqual, 2)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
_, exists := check[int(value.(values.Int))]
|
||||||
|
|
||||||
|
So(exists, ShouldBeTrue)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
40
pkg/stdlib/arrays/nth.go
Normal file
40
pkg/stdlib/arrays/nth.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the element of an array at a given position.
|
||||||
|
* It is the same as anyArray[position] for positive positions, but does not support negative positions.
|
||||||
|
* @param array (Array) - An array with elements of arbitrary type.
|
||||||
|
* @param index (Int) - Position of desired element in array, positions start at 0.
|
||||||
|
* @returns (Value) - The array element at the given position.
|
||||||
|
* If position is negative or beyond the upper bound of the array, then NONE will be returned.
|
||||||
|
*/
|
||||||
|
func Nth(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 2)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[1], core.IntType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
idx := args[1].(values.Int)
|
||||||
|
|
||||||
|
return arr.Get(idx), nil
|
||||||
|
}
|
44
pkg/stdlib/arrays/nth_test.go
Normal file
44
pkg/stdlib/arrays/nth_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNth(t *testing.T) {
|
||||||
|
Convey("Should return item by index", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Nth(context.Background(), arr, values.NewInt(1))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.Compare(values.NewInt(2)), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return None when no value", t, func() {
|
||||||
|
arr := values.NewArrayWith()
|
||||||
|
|
||||||
|
out, err := arrays.Nth(context.Background(), arr, values.NewInt(1))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.Compare(values.None), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return None when passed negative value", t, func() {
|
||||||
|
arr := values.NewArrayWith()
|
||||||
|
|
||||||
|
out, err := arrays.Nth(context.Background(), arr, values.NewInt(-1))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.Compare(values.None), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
}
|
16
pkg/stdlib/arrays/outersection.go
Normal file
16
pkg/stdlib/arrays/outersection.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the values that occur only once across all arrays specified.
|
||||||
|
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
|
||||||
|
* @returns (Array) - A single array with only the elements that exist only once across all provided arrays.
|
||||||
|
* The element order is random.
|
||||||
|
*/
|
||||||
|
func Outersection(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
return sections(args, 1)
|
||||||
|
}
|
88
pkg/stdlib/arrays/outersection_test.go
Normal file
88
pkg/stdlib/arrays/outersection_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOutersection(t *testing.T) {
|
||||||
|
Convey("Should find intersections between 2 arrays", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Outersection(context.Background(), arr1, arr2)
|
||||||
|
|
||||||
|
check := map[int]bool{
|
||||||
|
1: true,
|
||||||
|
4: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
arr := out.(*values.Array)
|
||||||
|
|
||||||
|
So(arr.Length(), ShouldEqual, 2)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
_, exists := check[int(value.(values.Int))]
|
||||||
|
|
||||||
|
So(exists, ShouldBeTrue)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should find intersections between more than 2 arrays", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr3 := values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Outersection(context.Background(), arr1, arr2, arr3)
|
||||||
|
|
||||||
|
check := map[int]bool{
|
||||||
|
1: true,
|
||||||
|
5: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
arr := out.(*values.Array)
|
||||||
|
|
||||||
|
So(arr.Length(), ShouldEqual, 2)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
_, exists := check[int(value.(values.Int))]
|
||||||
|
|
||||||
|
So(exists, ShouldBeTrue)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
44
pkg/stdlib/arrays/pop.go
Normal file
44
pkg/stdlib/arrays/pop.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a new array without last element.
|
||||||
|
* @param array (Array) - Target array.
|
||||||
|
* @returns (Array) - Copy of an array without last element.
|
||||||
|
*/
|
||||||
|
func Pop(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
|
||||||
|
length := int(arr.Length())
|
||||||
|
result := values.NewArray(length)
|
||||||
|
lastIdx := length - 1
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
if idx == lastIdx {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Push(value)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
35
pkg/stdlib/arrays/pop_test.go
Normal file
35
pkg/stdlib/arrays/pop_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPop(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array without last element", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Pop(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return empty array if a given one is empty", t, func() {
|
||||||
|
arr := values.NewArray(0)
|
||||||
|
|
||||||
|
out, err := arrays.Pop(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[]")
|
||||||
|
})
|
||||||
|
}
|
49
pkg/stdlib/arrays/position.go
Normal file
49
pkg/stdlib/arrays/position.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a value indicating whether an element is contained in array. Optionally returns its position.
|
||||||
|
* @param array (Array) - The source array.
|
||||||
|
* @param value (Value) - The target value.
|
||||||
|
* @param returnIndex (Boolean, optional) - Value which indicates whether to return item's position.
|
||||||
|
*/
|
||||||
|
func Position(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 3)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
el := args[1]
|
||||||
|
retIdx := false
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = core.ValidateType(args[2], core.BooleanType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
retIdx = args[2].Compare(values.True) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
position := arr.IndexOf(el)
|
||||||
|
|
||||||
|
if !retIdx {
|
||||||
|
return values.NewBoolean(position > -1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return position, nil
|
||||||
|
}
|
81
pkg/stdlib/arrays/position_test.go
Normal file
81
pkg/stdlib/arrays/position_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPosition(t *testing.T) {
|
||||||
|
Convey("Should return TRUE when a value exists in a given array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Position(context.Background(), arr, values.NewInt(3))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "true")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return FALSE when a value does not exist in a given array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Position(context.Background(), arr, values.NewInt(6))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "false")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return index when a value exists in a given array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Position(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewBoolean(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "2")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return -1 when a value does not exist in a given array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Position(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewBoolean(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "-1")
|
||||||
|
})
|
||||||
|
}
|
61
pkg/stdlib/arrays/push.go
Normal file
61
pkg/stdlib/arrays/push.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new array with appended value.
|
||||||
|
* @param array (Array) - Source array.
|
||||||
|
* @param value (Value) - Target value.
|
||||||
|
* @param unique (Boolean, optional) - Value indicating whether to do uniqueness check.
|
||||||
|
* @returns (Array) - A new array with appended value.
|
||||||
|
*/
|
||||||
|
func Push(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 3)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
value := args[1]
|
||||||
|
uniq := false
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = core.ValidateType(args[2], core.BooleanType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq = args[2].Compare(values.True) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
result := values.NewArray(int(arr.Length() + 1))
|
||||||
|
push := true
|
||||||
|
|
||||||
|
arr.ForEach(func(item core.Value, idx int) bool {
|
||||||
|
if uniq && push {
|
||||||
|
push = !(item.Compare(value) == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Push(item)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if push {
|
||||||
|
result.Push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
55
pkg/stdlib/arrays/push_test.go
Normal file
55
pkg/stdlib/arrays/push_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPush(t *testing.T) {
|
||||||
|
Convey("Should create a new array with a new element in the end", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Push(context.Background(), arr, values.NewInt(6))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not add a new element if not unique when uniqueness check is enabled", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Push(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(6),
|
||||||
|
values.True,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||||
|
|
||||||
|
out2, err := arrays.Push(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(6),
|
||||||
|
values.True,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(out2.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||||
|
})
|
||||||
|
}
|
47
pkg/stdlib/arrays/remove_nth.go
Normal file
47
pkg/stdlib/arrays/remove_nth.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a new array without an element by a given position.
|
||||||
|
* @param array (Array) - Source array.
|
||||||
|
* @param position (Int) - Target element position.
|
||||||
|
* @return (Array) - A new array without an element by a given position.
|
||||||
|
*/
|
||||||
|
func RemoveNth(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 2)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[1], core.IntType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
index := int(args[1].(values.Int))
|
||||||
|
result := values.NewArray(int(arr.Length() - 1))
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
if idx != index {
|
||||||
|
result.Push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
41
pkg/stdlib/arrays/remove_nth_test.go
Normal file
41
pkg/stdlib/arrays/remove_nth_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveNth(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array without an element by its position", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.RemoveNth(context.Background(), arr, values.NewInt(2))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,4,5]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return a copy of an array with all elements when a position is invalid", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.RemoveNth(context.Background(), arr, values.NewInt(6))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4,5]")
|
||||||
|
})
|
||||||
|
}
|
64
pkg/stdlib/arrays/remove_value.go
Normal file
64
pkg/stdlib/arrays/remove_value.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a new array with removed all occurrences of value in a given array.
|
||||||
|
* Optionally with a limit to the number of removals.
|
||||||
|
* @param array (Array) - Source array.
|
||||||
|
* @param value (Value) - Target value.
|
||||||
|
* @param limit (Int, optional) - A limit to the number of removals.
|
||||||
|
* @returns (Array) - A new array with removed all occurrences of value in a given array.
|
||||||
|
*/
|
||||||
|
func RemoveValue(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 3)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
value := args[1]
|
||||||
|
limit := -1
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = core.ValidateType(args[2], core.IntType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
limit = int(args[2].(values.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := values.NewArray(int(arr.Length()))
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
arr.ForEach(func(item core.Value, idx int) bool {
|
||||||
|
remove := item.Compare(value) == 0
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
if counter == limit {
|
||||||
|
result.Push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
counter++
|
||||||
|
} else {
|
||||||
|
result.Push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
48
pkg/stdlib/arrays/remove_value_test.go
Normal file
48
pkg/stdlib/arrays/remove_value_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveValue(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array without given element(s)", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(3),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.RemoveValue(context.Background(), arr, values.NewInt(3))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,4]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return a copy of an array without given element(s) with limit", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(3),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.RemoveValue(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(3),
|
||||||
|
values.Int(2),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,4,5,3]")
|
||||||
|
})
|
||||||
|
}
|
59
pkg/stdlib/arrays/remove_values.go
Normal file
59
pkg/stdlib/arrays/remove_values.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a new array with removed all occurrences of values in a given array.
|
||||||
|
* @param array (Array) - Source array.
|
||||||
|
* @param values (Array) - Target values.
|
||||||
|
* @returns (Array) - A new array with removed all occurrences of values in a given array.
|
||||||
|
*/
|
||||||
|
func RemoveValues(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 2)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[1], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
vals := args[1].(*values.Array)
|
||||||
|
|
||||||
|
result := values.NewArray(int(arr.Length()))
|
||||||
|
lookupTable := make(map[uint64]bool)
|
||||||
|
|
||||||
|
vals.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
lookupTable[value.Hash()] = true
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
h := value.Hash()
|
||||||
|
|
||||||
|
_, exists := lookupTable[h]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
result.Push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
35
pkg/stdlib/arrays/remove_values_test.go
Normal file
35
pkg/stdlib/arrays/remove_values_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveValues(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array without given elements", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.RemoveValues(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,4]")
|
||||||
|
})
|
||||||
|
}
|
36
pkg/stdlib/arrays/reverse.go
Normal file
36
pkg/stdlib/arrays/reverse.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a new array with its elements reversed.
|
||||||
|
* @param array (Array) - Target array.
|
||||||
|
* @returns (Array) - A new array with its elements reversed.
|
||||||
|
*/
|
||||||
|
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
size := int(arr.Length())
|
||||||
|
result := values.NewArray(size)
|
||||||
|
|
||||||
|
for i := size - 1; i >= 0; i-- {
|
||||||
|
result.Push(arr.Get(values.NewInt(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
42
pkg/stdlib/arrays/reverse_test.go
Normal file
42
pkg/stdlib/arrays/reverse_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReverse(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array with reversed elements", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Reverse(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[6,5,4,3,2,1]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an empty array when there no elements in a source one", t, func() {
|
||||||
|
arr := values.NewArray(0)
|
||||||
|
|
||||||
|
out, err := arrays.Reverse(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[]")
|
||||||
|
})
|
||||||
|
}
|
41
pkg/stdlib/arrays/shift.go
Normal file
41
pkg/stdlib/arrays/shift.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a new array without the first element.
|
||||||
|
* @param array (Array) - Target array.
|
||||||
|
* @returns (Array) - Copy of an array without the first element.
|
||||||
|
*/
|
||||||
|
func Shift(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
|
||||||
|
length := int(arr.Length())
|
||||||
|
result := values.NewArray(length)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
if idx != 0 {
|
||||||
|
result.Push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
35
pkg/stdlib/arrays/shift_test.go
Normal file
35
pkg/stdlib/arrays/shift_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShift(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array without the first element", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Shift(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[2,3,4,5]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return empty array if a given one is empty", t, func() {
|
||||||
|
arr := values.NewArray(0)
|
||||||
|
|
||||||
|
out, err := arrays.Shift(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[]")
|
||||||
|
})
|
||||||
|
}
|
50
pkg/stdlib/arrays/slice.go
Normal file
50
pkg/stdlib/arrays/slice.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a new sliced array.
|
||||||
|
* @param array (Array) - Source array.
|
||||||
|
* @param start (Int) - Start position of extraction.
|
||||||
|
* @param length (Int, optional) - Value indicating how many elements to extract.
|
||||||
|
* @returns (Array) - Sliced array.
|
||||||
|
*/
|
||||||
|
func Slice(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 3)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[1], core.IntType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
start := args[1].(values.Int)
|
||||||
|
length := values.NewInt(int(arr.Length()))
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
if args[2].Type() == core.IntType {
|
||||||
|
arg2 := args[2].(values.Int)
|
||||||
|
|
||||||
|
if arg2 > 0 {
|
||||||
|
length = start + args[2].(values.Int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr.Slice(start, length), nil
|
||||||
|
}
|
80
pkg/stdlib/arrays/slice_test.go
Normal file
80
pkg/stdlib/arrays/slice_test.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSlice(t *testing.T) {
|
||||||
|
Convey("Should return a sliced array with a given start position ", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Slice(context.Background(), arr, values.NewInt(3))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[4,5,6]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an empty array when start position is out of bounds", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Slice(context.Background(), arr, values.NewInt(6))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return a sliced array with a given start position and length", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Slice(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(2),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[3,4]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an empty array when length is out of bounds", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Slice(context.Background(), arr, values.NewInt(2), values.NewInt(10))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[3,4,5,6]")
|
||||||
|
})
|
||||||
|
}
|
53
pkg/stdlib/arrays/sorted.go
Normal file
53
pkg/stdlib/arrays/sorted.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sorts all elements in anyArray.
|
||||||
|
* The function will use the default comparison order for FQL value types.
|
||||||
|
* @param array (Array) - Target array.
|
||||||
|
* @returns (Array) - Sorted array.
|
||||||
|
*/
|
||||||
|
func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
|
||||||
|
if arr.Length() == 0 {
|
||||||
|
return values.NewArray(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
||||||
|
return first.Compare(second), nil
|
||||||
|
}, collections.SortDirectionAsc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator, err := collections.NewSortIterator(
|
||||||
|
collections.NewArrayIterator(arr),
|
||||||
|
sorter,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return collections.ToArray(iterator)
|
||||||
|
}
|
52
pkg/stdlib/arrays/sorted_test.go
Normal file
52
pkg/stdlib/arrays/sorted_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSorted(t *testing.T) {
|
||||||
|
Convey("Should sort numbers", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Sorted(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should sort strings", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewString("b"),
|
||||||
|
values.NewString("c"),
|
||||||
|
values.NewString("a"),
|
||||||
|
values.NewString("d"),
|
||||||
|
values.NewString("e"),
|
||||||
|
values.NewString("f"),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Sorted(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return empty array", t, func() {
|
||||||
|
arr := values.NewArrayWith()
|
||||||
|
|
||||||
|
out, err := arrays.Sorted(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, `[]`)
|
||||||
|
})
|
||||||
|
}
|
60
pkg/stdlib/arrays/sorted_unique.go
Normal file
60
pkg/stdlib/arrays/sorted_unique.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sorts all elements in anyArray.
|
||||||
|
* The function will use the default comparison order for FQL value types.
|
||||||
|
* Additionally, the values in the result array will be made unique
|
||||||
|
* @param array (Array) - Target array.
|
||||||
|
* @returns (Array) - Sorted array.
|
||||||
|
*/
|
||||||
|
func SortedUnique(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
|
||||||
|
if arr.Length() == 0 {
|
||||||
|
return values.NewArray(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
||||||
|
return first.Compare(second), nil
|
||||||
|
}, collections.SortDirectionAsc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqIterator, err := collections.NewUniqueIterator(collections.NewArrayIterator(arr))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator, err := collections.NewSortIterator(
|
||||||
|
uniqIterator,
|
||||||
|
sorter,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return collections.ToArray(iterator)
|
||||||
|
}
|
60
pkg/stdlib/arrays/sorted_unique_test.go
Normal file
60
pkg/stdlib/arrays/sorted_unique_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSortedUnique(t *testing.T) {
|
||||||
|
Convey("Should sort numbers", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.SortedUnique(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should sort strings", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewString("e"),
|
||||||
|
values.NewString("b"),
|
||||||
|
values.NewString("a"),
|
||||||
|
values.NewString("c"),
|
||||||
|
values.NewString("a"),
|
||||||
|
values.NewString("d"),
|
||||||
|
values.NewString("f"),
|
||||||
|
values.NewString("d"),
|
||||||
|
values.NewString("e"),
|
||||||
|
values.NewString("f"),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.SortedUnique(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return empty array", t, func() {
|
||||||
|
arr := values.NewArrayWith()
|
||||||
|
|
||||||
|
out, err := arrays.SortedUnique(context.Background(), arr)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, `[]`)
|
||||||
|
})
|
||||||
|
}
|
46
pkg/stdlib/arrays/union.go
Normal file
46
pkg/stdlib/arrays/union.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the union of all passed arrays.
|
||||||
|
* @param arrays (Array, repeated) - List of arrays to combine.
|
||||||
|
* @returns (Array) - All array elements combined in a single array, in any order.
|
||||||
|
*/
|
||||||
|
func Union(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
firstArrLen := args[0].(*values.Array).Length()
|
||||||
|
result := values.NewArray(len(args) * int(firstArrLen))
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
err := core.ValidateType(arg, core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := arg.(*values.Array)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, _ int) bool {
|
||||||
|
result.Push(value)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
50
pkg/stdlib/arrays/union_distinct.go
Normal file
50
pkg/stdlib/arrays/union_distinct.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnionDistinct(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
firstArrLen := args[0].(*values.Array).Length()
|
||||||
|
result := values.NewArray(len(args) * int(firstArrLen))
|
||||||
|
hashes := make(map[uint64]bool)
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
err := core.ValidateType(arg, core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := arg.(*values.Array)
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, _ int) bool {
|
||||||
|
h := value.Hash()
|
||||||
|
|
||||||
|
_, exists := hashes[h]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
hashes[h] = true
|
||||||
|
result.Push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
51
pkg/stdlib/arrays/union_distinct_test.go
Normal file
51
pkg/stdlib/arrays/union_distinct_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnionDistinct(t *testing.T) {
|
||||||
|
Convey("Should union all arrays with unique values", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr3 := values.NewArrayWith(
|
||||||
|
values.NewString("a"),
|
||||||
|
values.NewString("b"),
|
||||||
|
values.NewString("c"),
|
||||||
|
values.NewString("d"),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr4 := values.NewArrayWith(
|
||||||
|
values.NewString("e"),
|
||||||
|
values.NewString("b"),
|
||||||
|
values.NewString("f"),
|
||||||
|
values.NewString("d"),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.UnionDistinct(
|
||||||
|
context.Background(),
|
||||||
|
arr1,
|
||||||
|
arr2,
|
||||||
|
arr3,
|
||||||
|
arr4,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, `[1,2,3,4,5,6,"a","b","c","d","e","f"]`)
|
||||||
|
})
|
||||||
|
}
|
53
pkg/stdlib/arrays/union_test.go
Normal file
53
pkg/stdlib/arrays/union_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the union of distinct values of all passed arrays.
|
||||||
|
* @param arrays (Array, repeated) - List of arrays to combine.
|
||||||
|
* @returns (Array) - All array elements combined in a single array, without duplicates, in any order.
|
||||||
|
*/
|
||||||
|
func TestUnion(t *testing.T) {
|
||||||
|
Convey("Should union all arrays", t, func() {
|
||||||
|
arr1 := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr2 := values.NewArrayWith(
|
||||||
|
values.NewString("a"),
|
||||||
|
values.NewString("b"),
|
||||||
|
values.NewString("c"),
|
||||||
|
values.NewString("d"),
|
||||||
|
)
|
||||||
|
|
||||||
|
arr3 := values.NewArrayWith(
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
),
|
||||||
|
values.NewArrayWith(
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Union(
|
||||||
|
context.Background(),
|
||||||
|
arr1,
|
||||||
|
arr2,
|
||||||
|
arr3,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out.String(), ShouldEqual, `[1,2,3,4,"a","b","c","d",[1,2],[3,4]]`)
|
||||||
|
})
|
||||||
|
}
|
43
pkg/stdlib/arrays/unique.go
Normal file
43
pkg/stdlib/arrays/unique.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns all unique elements from a given array.
|
||||||
|
* @param array (Array) - Target array.
|
||||||
|
* @returns (Array) - New array without duplicates.
|
||||||
|
*/
|
||||||
|
func Unique(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, 1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
|
||||||
|
if arr.Length() == 0 {
|
||||||
|
return values.NewArray(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator, err := collections.NewUniqueIterator(
|
||||||
|
collections.NewArrayIterator(arr),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return collections.ToArray(iterator)
|
||||||
|
}
|
35
pkg/stdlib/arrays/unique_test.go
Normal file
35
pkg/stdlib/arrays/unique_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnique(t *testing.T) {
|
||||||
|
Convey("Should return only unique items", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
values.NewInt(5),
|
||||||
|
values.NewInt(6),
|
||||||
|
)
|
||||||
|
|
||||||
|
res, err := arrays.Unique(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(res.String(), ShouldEqual, `[1,2,3,4,5,6]`)
|
||||||
|
})
|
||||||
|
}
|
80
pkg/stdlib/arrays/unshift.go
Normal file
80
pkg/stdlib/arrays/unshift.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package arrays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepends value to a given array.
|
||||||
|
* @param array (Array) - Target array.
|
||||||
|
* @param value (Value) - Target value to prepend.
|
||||||
|
* @param unique (Boolean, optional) - Optional value indicating whether a value must be unique to be prepended.
|
||||||
|
* Default is false.
|
||||||
|
* @returns (Array) - New array with prepended value.
|
||||||
|
*/
|
||||||
|
func Unshift(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 2, 3)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = core.ValidateType(args[0], core.ArrayType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*values.Array)
|
||||||
|
value := args[1]
|
||||||
|
uniq := values.False
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
err = core.ValidateType(args[2], core.BooleanType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq = args[2].(values.Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := values.NewArray(int(arr.Length() + 1))
|
||||||
|
|
||||||
|
if !uniq {
|
||||||
|
result.Push(value)
|
||||||
|
|
||||||
|
arr.ForEach(func(el core.Value, _ int) bool {
|
||||||
|
result.Push(el)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ok := true
|
||||||
|
|
||||||
|
// let's just hope it's unique
|
||||||
|
// if not, we will terminate the loop and return a copy of an array
|
||||||
|
result.Push(value)
|
||||||
|
|
||||||
|
arr.ForEach(func(el core.Value, idx int) bool {
|
||||||
|
if el.Compare(value) != 0 {
|
||||||
|
result.Push(el)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// not unique
|
||||||
|
ok = false
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// value is not unique, just return a new copy with same elements
|
||||||
|
return arr.Clone(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
59
pkg/stdlib/arrays/unshift_test.go
Normal file
59
pkg/stdlib/arrays/unshift_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package arrays_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnshift(t *testing.T) {
|
||||||
|
Convey("Should return a copy of an array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Unshift(context.Background(), arr, values.NewInt(0))
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldNotEqual, arr)
|
||||||
|
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should ignore non-unique items", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := arrays.Unshift(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(0),
|
||||||
|
values.True,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out, ShouldNotEqual, arr)
|
||||||
|
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
|
||||||
|
|
||||||
|
out2, err := arrays.Unshift(
|
||||||
|
context.Background(),
|
||||||
|
arr,
|
||||||
|
values.NewInt(0),
|
||||||
|
values.True,
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(out2, ShouldNotEqual, arr)
|
||||||
|
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
|
||||||
|
})
|
||||||
|
}
|
@ -2,6 +2,7 @@ package stdlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||||
"github.com/MontFerret/ferret/pkg/stdlib/collections"
|
"github.com/MontFerret/ferret/pkg/stdlib/collections"
|
||||||
"github.com/MontFerret/ferret/pkg/stdlib/html"
|
"github.com/MontFerret/ferret/pkg/stdlib/html"
|
||||||
"github.com/MontFerret/ferret/pkg/stdlib/strings"
|
"github.com/MontFerret/ferret/pkg/stdlib/strings"
|
||||||
@ -21,6 +22,7 @@ func NewLib() map[string]core.Function {
|
|||||||
add(types.NewLib())
|
add(types.NewLib())
|
||||||
add(strings.NewLib())
|
add(strings.NewLib())
|
||||||
add(collections.NewLib())
|
add(collections.NewLib())
|
||||||
|
add(arrays.NewLib())
|
||||||
add(html.NewLib())
|
add(html.NewLib())
|
||||||
add(utils.NewLib())
|
add(utils.NewLib())
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user