mirror of
				https://github.com/MontFerret/ferret.git
				synced 2025-10-30 23:37:40 +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 { | ||||
| 	l := len(t.value) - 1 | ||||
|  | ||||
| 	if l < 0 { | ||||
| 		return None | ||||
| 	} | ||||
|  | ||||
| 	if int(idx) > l { | ||||
| 		return None | ||||
| 	} | ||||
| @@ -151,8 +155,21 @@ func (t *Array) Push(item core.Value) { | ||||
| 	t.value = append(t.value, item) | ||||
| } | ||||
|  | ||||
| func (t *Array) Slice(from, to Int) []core.Value { | ||||
| 	return t.value[from:to] | ||||
| func (t *Array) Slice(from, to Int) *Array { | ||||
| 	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 { | ||||
|   | ||||
| @@ -302,12 +302,12 @@ func TestArray(t *testing.T) { | ||||
|  | ||||
| 			s := arr.Slice(0, 1) | ||||
|  | ||||
| 			So(len(s), ShouldEqual, 1) | ||||
| 			So(s[0].Compare(values.ZeroInt), ShouldEqual, 0) | ||||
| 			So(s.Length(), ShouldEqual, 1) | ||||
| 			So(s.Get(0).Compare(values.ZeroInt), ShouldEqual, 0) | ||||
|  | ||||
| 			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 ( | ||||
| 	"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/html" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/strings" | ||||
| @@ -21,6 +22,7 @@ func NewLib() map[string]core.Function { | ||||
| 	add(types.NewLib()) | ||||
| 	add(strings.NewLib()) | ||||
| 	add(collections.NewLib()) | ||||
| 	add(arrays.NewLib()) | ||||
| 	add(html.NewLib()) | ||||
| 	add(utils.NewLib()) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user